-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathapp.js
More file actions
185 lines (160 loc) · 5.16 KB
/
app.js
File metadata and controls
185 lines (160 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// A sample chatbot app that listens to messages posted to a space in IBM
// Watson Workspace and echoes hello messages back to the space
import express from 'express';
import * as request from 'request';
import * as util from 'util';
import * as bparser from 'body-parser';
import { createHmac } from 'crypto';
import * as http from 'http';
import * as https from 'https';
import * as oauth from './oauth';
import * as ssl from './ssl';
import debug from 'debug';
import Promise from 'bluebird';
// Debug log
const log = debug('watsonwork-echo-app');
const post = Promise.promisify(request.post);
// Echoes Watson Work chat messages containing 'hello' or 'hey' back
// to the space they were sent to
export const echo = (appId, token) => async (req, res) => {
// Respond to the Webhook right away, as the response message will
// be sent asynchronously
res.status(201).end();
// Only handle message-created Webhook events, and ignore the app's
// own messages
if(req.body.type !== 'message-created' || req.body.userId === appId)
return;
log('Got a message %o', req.body);
// React to 'hello' or 'hey' keywords in the message and send an echo
// message back to the conversation in the originating space
if(req.body.content
// Tokenize the message text into individual words
.split(/[^A-Za-z0-9]+/)
// Look for the hello and hey words
.filter((word) => /^(hello|hey)$/i.test(word)).length)
// Send the echo message
await send(req.body.spaceId,
util.format(
'Hey %s, did you say %s?',
req.body.userName, req.body.content),
token());
log('Sent message to space %s', req.body.spaceId);
};
// Send an app message to the conversation in a space
const send = async (spaceId, text, tok) => {
let res;
try {
res = await post(
'https://api.watsonwork.ibm.com/v1/spaces/' + spaceId + '/messages', {
headers: {
Authorization: 'Bearer ' + tok
},
json: true,
// An App message can specify a color, a title, markdown text and
// an 'actor' useful to show where the message is coming from
body: {
type: 'appMessage',
version: 1.0,
annotations: [{
type: 'generic',
version: 1.0,
color: '#6CB7FB',
title: 'Echo message',
text: text,
actor: {
name: 'Sample echo app'
}
}]
}
});
// Handle invalid status response code
if (res.statusCode !== 201)
throw new Error(res.statusCode);
// log the valid response and its body
else
log('Send result %d, %o', res.statusCode, res.body);
}
catch(err) {
// log the error and rethrow it
log('Error sending message %o', err);
throw err;
}
return res;
};
// Verify Watson Work request signature
export const verify = (wsecret) => (req, res, buf, encoding) => {
if(req.get('X-OUTBOUND-TOKEN') !==
createHmac('sha256', wsecret).update(buf).digest('hex')) {
log('Invalid request signature');
const err = new Error('Invalid request signature');
err.status = 401;
throw err;
}
};
// Handle Watson Work Webhook challenge requests
export const challenge = (wsecret) => (req, res, next) => {
if(req.body.type === 'verification') {
log('Got Webhook verification challenge %o', req.body);
const body = JSON.stringify({
response: req.body.challenge
});
res.set('X-OUTBOUND-TOKEN',
createHmac('sha256', wsecret).update(body).digest('hex'));
res.type('json').send(body);
return;
}
next();
};
// Create Express Web app
export const webapp = async (appId, secret, wsecret) => {
// Authenticate the app and get an OAuth token
const token = await oauth.run(appId, secret);
// Configure Express route for the app Webhook
return express().post('/echo',
// Verify Watson Work request signature and parse request body
bparser.json({
type: '*/*',
verify: verify(wsecret)
}),
// Handle Watson Work Webhook challenge requests
challenge(wsecret),
// Handle Watson Work messages
echo(appId, token));
};
// App main entry point
const main = async (argv, env) => {
try {
// Create Express Web app
const app = await webapp(
env.ECHO_APP_ID, env.ECHO_APP_SECRET,
env.ECHO_WEBHOOK_SECRET);
if(env.PORT) {
// In a hosting environment like Bluemix for example, HTTPS is
// handled by a reverse proxy in front of the app, just listen
// on the configured HTTP port
log('HTTP server listening on port %d', env.PORT);
http.createServer(app).listen(env.PORT);
}
else {
// Listen on the configured HTTPS port, default to 443
const sslConfig = await ssl.conf(env);
const port = env.SSLPORT || 443;
log('HTTPS server listening on port %D', port);
https.createServer(sslConfig, app).listen(port);
}
}
catch(err) {
throw err;
}
};
// Run main as IIFE (top level await not supported)
(async () => {
if (require.main === module)
try {
await main(process.argv, process.env);
log('App started!');
}
catch(err) {
console.log('Error starting app:', err);
}
})();