diff --git a/docs/readme-facebook.md b/docs/readme-facebook.md index 2a65358ec..a2a546801 100644 --- a/docs/readme-facebook.md +++ b/docs/readme-facebook.md @@ -21,6 +21,7 @@ Table of Contents * [Silent and No Notifications](#silent-and-no-notifications) * [Messenger code API](#messenger-code-api) * [Attachment upload API](#attachment-upload-api) +* [Handover Protocol](#handover-protocol) * [Running Botkit with an Express server](#use-botkit-for-facebook-messenger-with-an-express-web-server) ## Getting Started @@ -85,6 +86,10 @@ Normal messages will be sent to your bot using the `message_received` event. In | facebook_account_linking | a user has started the account linking | facebook_optin | a user has clicked the [Send-to-Messenger plugin](https://developers.facebook.com/docs/messenger-platform/implementation#send_to_messenger_plugin) | facebook_referral | a user has clicked on a [m.me URL with a referral param](https://developers.facebook.com/docs/messenger-platform/referral-params) +| facebook_app_roles | This callback will occur when a page admin changes the role of your application. +| standby | This callback will occur when a message has been sent to your page, but your application is not the current thread owner. +| facebook_receive_thread_control | This callback will occur when thread ownership for a user has been passed to your application. +| facebook_lose_thread_control | This callback will occur when thread ownership for a user has been taken away from your application. All incoming events will contain the fields `user` and `channel`, both of which represent the Facebook user's ID, and a `timestamp` field. @@ -570,6 +575,44 @@ var taggedMessage = { bot.reply(message, taggedMessage); ``` +## Handover Protocol + +The Messenger Platform handover protocol enables two or more applications to collaborate on the Messenger Platform for a Page. + +View the facebook [documentation](https://developers.facebook.com/docs/messenger-platform/handover-protocol) for more details. + +### Secondary Receivers List + +Allows the Primary Receiver app to retrieve the list of apps that are Secondary Receivers for a page. Only the app with the Primary Receiver role for the page may use this API. + +- To retrieve the list of Secondary Receivers : +```javascript +controller.api.handover.get_secondary_receivers_list('id,name', function (result) { + // result.data = list of Secondary Receivers +}); +``` + +### Take Thread Control + +The Primary Receiver app can take control of a specific thread from a Secondary Receiver app : + +- To thread control : +```javascript +controller.api.handover.take_thread_control('', 'String to pass to pass to the secondary receiver', function (result) { + // result = {"success":true} +}); +``` + +### Pass Thread Control + +To pass the thread control from your app to another app : + +- To pass thread control : +```javascript +controller.api.handover.pass_thread_control('', '', 'String to pass to the secondary receiver app', function (result) { + // result = {"success":true} +}); +``` ## Use BotKit for Facebook Messenger with an Express web server Instead of the web server generated with setupWebserver(), it is possible to use a different web server to receive webhooks, as well as serving web pages. diff --git a/lib/Facebook.js b/lib/Facebook.js index bff34d432..85d81d360 100644 --- a/lib/Facebook.js +++ b/lib/Facebook.js @@ -358,11 +358,18 @@ function Facebookbot(configuration) { // we split these up into multiple message objects for ingestion if (payload.entry) { for (var e = 0; e < payload.entry.length; e++) { - if (payload.entry[e].messaging) { - for (var m = 0; m < payload.entry[e].messaging.length; m++) { - facebook_botkit.ingest(bot, payload.entry[e].messaging[m], res); - } - } + if (payload.entry[e].messaging) { + for (var m = 0; m < payload.entry[e].messaging.length; m++) { + facebook_botkit.ingest(bot, payload.entry[e].messaging[m], res); + } + } + if (payload.entry[e].standby) { + for (var s = 0; s < payload.entry[e].standby.length; s++) { + var standby_message = payload.entry[e].standby[s]; + standby_message.standby = true; + facebook_botkit.ingest(bot, standby_message, res); + } + } } } @@ -458,6 +465,26 @@ function Facebookbot(configuration) { }); + /* Facebook Handover Protocol categorize middleware */ + facebook_botkit.middleware.categorize.use(function threadControl(bot, message, next) { + + if (message.app_roles) { + message.type = 'facebook_app_roles'; + } + if (message.standby) { + message.type = 'standby' + } + if (message.pass_thread_control) { + message.type = 'facebook_receive_thread_control' + } + if (message.take_thread_control) { + message.type = 'facebook_lose_thread_control' + } + + next(); + + }); + facebook_botkit.on('webserver_up', function(webserver) { // Validate that requests come from facebook, and abort on validation errors @@ -816,8 +843,90 @@ function Facebookbot(configuration) { }; - - + var handover = { + get_secondary_receivers_list: function (fields, cb) { + request.get({ + url: 'https://' + api_host + '/v2.6/me/secondary_receivers', + qs: { + fields: typeof(fields) == 'string' ? fields : fields.join(','), + access_token: configuration.access_token + }, + json: true + }, function (err, res, body) { + if (err) { + facebook_botkit.log('Could not get secondary receivers list'); + cb && cb(err); + } else { + if (body.error) { + facebook_botkit.log('ERROR in secondary receivers list: ', body.error.message); + cb && cb(body.error); + } else { + facebook_botkit.debug('Successfully getting secondary receivers list', body); + cb && cb(null, body); + } + } + }); + }, + take_thread_control: function (recipient, metadata, cb) { + var request_body = { + recipient: { + id: recipient + }, + metadata: metadata + }; + request.post({ + url: 'https://' + api_host + '/v2.6/me/take_thread_control', + qs: { + access_token: configuration.access_token + }, + body: request_body, + json: true + }, function (err, res, body) { + if (err) { + facebook_botkit.log('Could not take thread control'); + cb && cb(err); + } else { + if (body.error) { + facebook_botkit.log('ERROR in take thread control API call: ', body.error.message); + cb && cb(body.error); + } else { + facebook_botkit.debug('Successfully taken thread control', body); + cb && cb(null, body); + } + } + }); + }, + pass_thread_control: function (recipient, target, metadata, cb) { + var request_body = { + recipient: { + id: recipient + }, + target_app_id: target, + metadata: metadata + }; + request.post({ + url: 'https://' + api_host + '/v2.6/me/pass_thread_control', + qs: { + access_token: configuration.access_token + }, + body: request_body, + json: true + }, function (err, res, body) { + if (err) { + facebook_botkit.log('Could not pass thread control'); + cb && cb(err); + } else { + if (body.error) { + facebook_botkit.log('ERROR in pass thread control API call: ', body.error.message); + cb && cb(body.error); + } else { + facebook_botkit.debug('Successfully past thread control', body); + cb && cb(null, body); + } + } + }); + } + }; facebook_botkit.api = { @@ -827,12 +936,10 @@ function Facebookbot(configuration) { 'attachment_upload': attachment_upload_api, 'nlp': nlp, 'tags': tags, + 'handover': handover }; - - - // Verifies the SHA1 signature of the raw request payload before bodyParser parses it // Will abort parsing if signature is invalid, and pass a generic error to response function verifyRequest(req, res, buf, encoding) {