@@ -10,16 +10,42 @@ import { Config } from '../config'
10
10
const outgoingTypes = [ 'text' , 'typing' , 'login_prompt' , 'carousel' ]
11
11
type MessengerAction = 'typing_on' | 'typing_off' | 'mark_seen'
12
12
13
- export class MessengerService {
14
- private readonly http = axios . create ( { baseURL : 'https://graph.facebook.com/v2.6/me' } )
13
+ type MountedBot = { pageId : string ; botId : string ; client : MessengerClient }
15
14
16
- private messengerClients : { [ key : string ] : MessengerClient } = { }
17
- private router : Router
15
+ export class MessengerService {
16
+ private readonly http = axios . create ( { baseURL : 'https://graph.facebook.com/v3.2/me' } )
17
+ private mountedBots : MountedBot [ ] = [ ]
18
+ private router : Router & sdk . http . RouterExtension
19
+ private appSecret : string
18
20
19
21
constructor ( private bp : typeof sdk ) { }
20
22
21
- initialize ( ) {
22
- this . router = this . bp . http . createRouterForBot ( 'channel-messenger' , { checkAuthentication : false } )
23
+ async initialize ( ) {
24
+ const config = ( await this . bp . config . getModuleConfig ( 'channel-messenger' ) ) as Config
25
+
26
+ if ( ! config . verifyToken || config . verifyToken . length < 1 ) {
27
+ throw new Error ( 'You need to set a non-empty value for "verifyToken" in the *global* messenger config' )
28
+ }
29
+
30
+ if ( ! config . appSecret || config . appSecret . length < 1 ) {
31
+ throw new Error ( `You need to provide your app's App Secret in the *global* messenger config` )
32
+ }
33
+
34
+ this . appSecret = config . appSecret
35
+
36
+ this . router = this . bp . http . createRouterForBot ( 'channel-messenger' , {
37
+ checkAuthentication : false ,
38
+ enableJsonBodyParser : false // we use our custom json body parser instead, see below
39
+ } )
40
+
41
+ this . router . getPublicPath ( ) . then ( publicPath => {
42
+ if ( publicPath . indexOf ( 'https://' ) !== 0 ) {
43
+ this . bp . logger . warn ( 'Messenger requires HTTPS to be setup to work properly. See EXTERNAL_URL botpress config.' )
44
+ }
45
+
46
+ this . bp . logger . info ( `Messenger Webhook URL is ${ publicPath . replace ( 'BOT_ID' , '___' ) } /webhook` )
47
+ } )
48
+
23
49
this . router . use (
24
50
bodyParser . json ( {
25
51
verify : this . _verifySignature . bind ( this )
@@ -38,66 +64,106 @@ export class MessengerService {
38
64
} )
39
65
}
40
66
41
- getMessengerClient ( botId : string ) : MessengerClient {
42
- if ( this . messengerClients [ botId ] ) {
43
- return this . messengerClients [ botId ]
67
+ async mountBot ( botId : string ) {
68
+ const config = ( await this . bp . config . getModuleConfigForBot ( 'channel-messenger' , botId ) ) as Config
69
+ if ( config . enabled ) {
70
+ if ( ! config . accessToken ) {
71
+ return this . bp . logger
72
+ . forBot ( botId )
73
+ . error ( 'You need to configure an Access Token to enable it. Messenger Channel is disabled for this bot.' )
74
+ }
75
+
76
+ const { data } = await this . http . get ( '/' , { params : { access_token : config . accessToken } } )
77
+
78
+ if ( ! data && ! data . id ) {
79
+ return this . bp . logger
80
+ . forBot ( botId )
81
+ . error (
82
+ 'Could not register bot, are you sure your Access Token is valid? Messenger Channel is disabled for this bot.'
83
+ )
84
+ }
85
+
86
+ const pageId = data . id
87
+ const client = new MessengerClient ( botId , this . bp , this . http )
88
+ this . mountedBots . push ( { botId : botId , client, pageId } )
89
+
90
+ await client . setupGreeting ( )
91
+ await client . setupGetStarted ( )
92
+ await client . setupPersistentMenu ( )
93
+ }
94
+ }
95
+
96
+ async unmountBot ( botId : string ) {
97
+ this . mountedBots = _ . remove ( this . mountedBots , x => x . botId === botId )
98
+ }
99
+
100
+ getMessengerClientByBotId ( botId : string ) : MessengerClient {
101
+ const entry = _ . find ( this . mountedBots , x => x . botId === botId )
102
+
103
+ if ( ! entry ) {
104
+ throw new Error ( `Can't find a MessengerClient for bot "${ botId } "` )
44
105
}
45
106
46
- this . messengerClients [ botId ] = new MessengerClient ( botId , this . bp , this . http )
47
- return this . messengerClients [ botId ]
107
+ return entry . client
48
108
}
49
109
50
110
// See: https://developers.facebook.com/docs/messenger-platform/webhook#security
51
111
private async _verifySignature ( req , res , buffer ) {
52
- const client = this . getMessengerClient ( req . params . botId )
53
- const config = await client . getConfig ( )
54
112
const signatureError = new Error ( "Couldn't validate the request signature." )
55
113
56
114
if ( ! / ^ \/ w e b h o o k / i. test ( req . path ) ) {
57
115
return
58
116
}
59
117
118
+ console . log ( req . path , req . headers , req . body )
119
+
60
120
const signature = req . headers [ 'x-hub-signature' ]
61
- if ( ! signature ) {
121
+ if ( ! signature || ! this . appSecret ) {
62
122
throw signatureError
63
123
} else {
64
124
const [ , hash ] = signature . split ( '=' )
125
+
65
126
const expectedHash = crypto
66
- . createHmac ( 'sha1' , config . verifyToken )
127
+ . createHmac ( 'sha1' , this . appSecret )
67
128
. update ( buffer )
68
129
. digest ( 'hex' )
69
130
70
- if ( hash != expectedHash ) {
131
+ if ( hash !== expectedHash ) {
71
132
throw signatureError
72
133
}
73
134
}
74
135
}
75
136
76
137
private async _handleIncomingMessage ( req , res ) {
77
138
const body = req . body
78
- const botId = req . params . botId
79
- const client = this . getMessengerClient ( botId )
80
139
81
140
if ( body . object !== 'page' ) {
82
- res . sendStatus ( 404 )
83
- return
141
+ //
142
+ } else {
143
+ res . status ( 200 ) . send ( 'EVENT_RECEIVED' )
84
144
}
85
145
86
146
for ( const entry of body . entry ) {
87
- // Will only ever contain one message, so we get index 0
88
- const webhookEvent = entry . messaging [ 0 ]
89
- const senderId = webhookEvent . sender . id
147
+ const pageId = entry . id
148
+ const messages = entry . messaging
149
+
150
+ const bot = _ . find < MountedBot > ( this . mountedBots , { pageId } )
151
+ if ( ! bot ) {
152
+ continue
153
+ }
90
154
91
- await client . sendAction ( senderId , 'mark_seen' )
155
+ for ( const webhookEvent of messages ) {
156
+ const senderId = webhookEvent . sender . id
92
157
93
- if ( webhookEvent . message ) {
94
- await this . _sendEvent ( botId , senderId , webhookEvent . message , { type : 'message' } )
95
- } else if ( webhookEvent . postback ) {
96
- await this . _sendEvent ( botId , senderId , { text : webhookEvent . postback . payload } , { type : 'callback' } )
158
+ await bot . client . sendAction ( senderId , 'mark_seen' )
159
+
160
+ if ( webhookEvent . message ) {
161
+ await this . _sendEvent ( req . BOT_ID , senderId , webhookEvent . message , { type : 'message' } )
162
+ } else if ( webhookEvent . postback ) {
163
+ await this . _sendEvent ( req . BOT_ID , senderId , { text : webhookEvent . postback . payload } , { type : 'callback' } )
164
+ }
97
165
}
98
166
}
99
-
100
- res . status ( 200 ) . send ( 'EVENT_RECEIVED' )
101
167
}
102
168
103
169
private async _sendEvent ( botId : string , senderId : string , message , args : { type : string } ) {
@@ -115,24 +181,19 @@ export class MessengerService {
115
181
}
116
182
117
183
private async _setupWebhook ( req , res ) {
184
+ console . log ( 'setup' , req )
118
185
const mode = req . query [ 'hub.mode' ]
119
186
const token = req . query [ 'hub.verify_token' ]
120
187
const challenge = req . query [ 'hub.challenge' ]
121
- const botId = req . params . botId
122
188
123
- const client = this . getMessengerClient ( botId )
124
- const config = await client . getConfig ( )
189
+ const config = ( await this . bp . config . getModuleConfig ( 'channel-messenger' ) ) as Config
125
190
126
191
if ( mode && token && mode === 'subscribe' && token === config . verifyToken ) {
127
- this . bp . logger . forBot ( botId ) . debug ( 'Webhook Verified. ' )
192
+ this . bp . logger . debug ( 'Webhook Verified' )
128
193
res . status ( 200 ) . send ( challenge )
129
194
} else {
130
195
res . sendStatus ( 403 )
131
196
}
132
-
133
- await client . setupGreeting ( )
134
- await client . setupGetStarted ( )
135
- await client . setupPersistentMenu ( )
136
197
}
137
198
138
199
private async _handleOutgoingEvent ( event : sdk . IO . Event , next : sdk . IO . MiddlewareNextCallback ) {
@@ -141,7 +202,7 @@ export class MessengerService {
141
202
}
142
203
143
204
const messageType = event . type === 'default' ? 'text' : event . type
144
- const messenger = this . getMessengerClient ( event . botId )
205
+ const messenger = this . getMessengerClientByBotId ( event . botId )
145
206
146
207
if ( ! _ . includes ( outgoingTypes , messageType ) ) {
147
208
return next ( new Error ( 'Unsupported event type: ' + event . type ) )
@@ -248,6 +309,6 @@ export class MessengerClient {
248
309
249
310
private async _callEndpoint ( endpoint : string , body ) {
250
311
const config = await this . getConfig ( )
251
- await this . http . post ( endpoint , body , { params : { access_token : config . verifyToken } } )
312
+ await this . http . post ( endpoint , body , { params : { access_token : config . accessToken } } )
252
313
}
253
314
}
0 commit comments