Repository: Yoctol/bottender
Pull request #319 (2018-07-26 02:02:43)

Messenger: add context.isThreadOwner()
When `appId` is provided to `MessengerBot`

```js
new MessengerBot({
  appId: '...',
});
```

support `context.isThreadOwner` to check the thread owner:

```js
await context.isThreadOwner(); // true | false
```


Patch:
diff --git a/.flowconfig b/.flowconfig
index 0abfdb4c..b9a87c15 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -13,7 +13,7 @@ suppress_type=$FlowFixMe
 suppress_type=$FixMe
 suppress_type=$FlowExpectedError
 
-suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)
-suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)?:? #[0-9]+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
 suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
 suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
diff --git a/src/bot/MessengerBot.js b/src/bot/MessengerBot.js
index 90cc0780..c91ef74d 100644
--- a/src/bot/MessengerBot.js
+++ b/src/bot/MessengerBot.js
@@ -8,6 +8,7 @@ import MessengerConnector from './MessengerConnector';
 export default class MessengerBot extends Bot {
   constructor({
     accessToken,
+    appId,
     appSecret,
     sessionStore,
     sync,
@@ -16,6 +17,7 @@ export default class MessengerBot extends Bot {
     batchConfig,
   }: {
     accessToken: string,
+    appId?: string,
     appSecret: string,
     sessionStore: SessionStore,
     sync?: boolean,
@@ -25,6 +27,7 @@ export default class MessengerBot extends Bot {
   }) {
     const connector = new MessengerConnector({
       accessToken,
+      appId,
       appSecret,
       mapPageToAccessToken,
       verifyToken,
diff --git a/src/bot/MessengerConnector.js b/src/bot/MessengerConnector.js
index 28fc6878..26ff11e7 100644
--- a/src/bot/MessengerConnector.js
+++ b/src/bot/MessengerConnector.js
@@ -76,6 +76,7 @@ type MessengerRequestBody =
 
 type ConstructorOptions = {|
   accessToken?: string,
+  appId?: string,
   appSecret?: string,
   client?: MessengerClient,
   mapPageToAccessToken?: (pageId: string) => Promise<string>,
@@ -87,6 +88,8 @@ export default class MessengerConnector
   implements Connector<MessengerRequestBody> {
   _client: MessengerClient;
 
+  _appId: string;
+
   _appSecret: string;
 
   _mapPageToAccessToken: ?(pageId: string) => ?Promise<string>;
@@ -99,6 +102,7 @@ export default class MessengerConnector
 
   constructor({
     accessToken,
+    appId,
     appSecret,
     client,
     mapPageToAccessToken,
@@ -112,6 +116,7 @@ export default class MessengerConnector
         appSecret,
       });
 
+    this._appId = appId || '';
     this._appSecret = appSecret || '';
 
     this._mapPageToAccessToken = mapPageToAccessToken;
@@ -319,6 +324,7 @@ export default class MessengerConnector
       client: this._client,
       customAccessToken,
       batchQueue: this._batchQueue,
+      appId: this._appId,
     });
   }
 
diff --git a/src/context/MessengerContext.js b/src/context/MessengerContext.js
index 900ff975..c11d5b8e 100644
--- a/src/context/MessengerContext.js
+++ b/src/context/MessengerContext.js
@@ -2,6 +2,7 @@
 
 import sleep from 'delay';
 import warning from 'warning';
+import invariant from 'invariant';
 import { MessengerClient, MessengerBatch } from 'messaging-api-messenger';
 
 import { type Session } from '../session/Session';
@@ -11,6 +12,7 @@ import MessengerEvent from './MessengerEvent';
 import { type PlatformContext } from './PlatformContext';
 
 type Options = {|
+  appId: ?string,
   client: MessengerClient,
   event: MessengerEvent,
   session: ?Session,
@@ -21,6 +23,8 @@ type Options = {|
 |};
 
 class MessengerContext extends Context implements PlatformContext {
+  _appId: ?string;
+
   _client: MessengerClient = this._client;
 
   _event: MessengerEvent = this._event;
@@ -32,6 +36,7 @@ class MessengerContext extends Context implements PlatformContext {
   _batchQueue: ?Object;
 
   constructor({
+    appId,
     client,
     event,
     session,
@@ -43,6 +48,7 @@ class MessengerContext extends Context implements PlatformContext {
     super({ client, event, session, initialState, requestContext });
     this._customAccessToken = customAccessToken;
     this._batchQueue = batchQueue;
+    this._appId = appId;
   }
 
   /**
@@ -377,6 +383,17 @@ class MessengerContext extends Context implements PlatformContext {
     return this._callClientMethod('getThreadOwner', args);
   }
 
+  async isThreadOwner(): Promise<boolean> {
+    invariant(
+      this._appId,
+      'isThreadOwner: must provide appId to use this feature'
+    );
+    const { app_id: appId } = await this.getThreadOwner();
+
+    // $FlowIssue - do not support assert: https://github.com/facebook/flow/issues/34
+    return `${appId}` === `${this._appId}`;
+  }
+
   /**
    * Targeting Broadcast Messages
    *


Test patch:
diff --git a/src/context/__tests__/MessengerContext.spec.js b/src/context/__tests__/MessengerContext.spec.js
index d3e8621f..20c262c5 100644
--- a/src/context/__tests__/MessengerContext.spec.js
+++ b/src/context/__tests__/MessengerContext.spec.js
@@ -20,6 +20,8 @@ afterEach(() => {
   jest.useFakeTimers();
 });
 
+const APP_ID = '1234567890';
+
 const _rawEvent = {
   sender: { id: '1423587017700273' },
   recipient: { id: '404217156637689' },
@@ -46,6 +48,7 @@ const setup = (
 ) => {
   const client = MessengerClient.connect();
   const args = {
+    appId: APP_ID,
     client,
     event: new MessengerEvent(rawEvent),
     session,
@@ -364,6 +367,24 @@ describe('#getThreadOwner', () => {
   });
 });
 
+describe('#isThreadOwner', () => {
+  it('should return true when bot is not the thread owner', async () => {
+    const { context, client } = setup();
+
+    client.getThreadOwner.mockResolvedValue({ app_id: APP_ID });
+
+    expect(await context.isThreadOwner()).toBe(true);
+  });
+
+  it('should return false when bot is not the thread owner', async () => {
+    const { context, client } = setup();
+
+    client.getThreadOwner.mockResolvedValue({ app_id: '54367890123' });
+
+    expect(await context.isThreadOwner()).toBe(false);
+  });
+});
+
 describe('#associateLabel', () => {
   it('should call api to associate label to the user', async () => {
     const { context, client, session } = setup();