Skip to content

Commit fe37558

Browse files
author
timmydoza
authored
Merge pull request #55 from twilio-labs/AHOYAPPS-946-start-stop-recording
Ahoyapps 946 start stop recording
2 parents 118f3ad + ed99adc commit fe37558

File tree

9 files changed

+344
-136
lines changed

9 files changed

+344
-136
lines changed

src/helpers.js

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,33 @@ async function getAssets(folder) {
4747

4848
const indexHTML = assets.find(asset => asset.name.includes('index.html'));
4949

50-
assets.push({
51-
...indexHTML,
52-
path: '/',
53-
name: '/',
54-
});
55-
assets.push({
56-
...indexHTML,
57-
path: '/login',
58-
name: '/login',
59-
});
50+
const allAssets = assets.concat([
51+
{
52+
...indexHTML,
53+
path: '/',
54+
name: '/',
55+
},
56+
{
57+
...indexHTML,
58+
path: '/login',
59+
name: '/login',
60+
},
61+
]);
62+
63+
return allAssets;
64+
}
65+
66+
function getMiddleware() {
67+
const authHandlerFn = fs.readFileSync(path.join(__dirname, './serverless/middleware/auth.js'));
6068

61-
return assets;
69+
return [
70+
{
71+
name: 'auth-handler',
72+
path: '/auth-handler.js',
73+
content: authHandlerFn,
74+
access: 'private',
75+
},
76+
];
6277
}
6378

6479
async function findApp() {
@@ -80,7 +95,7 @@ async function getAppInfo() {
8095
const assets = await appInstance.assets.list();
8196

8297
const functions = await appInstance.functions.list();
83-
const tokenServerFunction = functions.find(fn => fn.friendlyName === 'token');
98+
const tokenServerFunction = functions.find(fn => fn.friendlyName.includes('token'));
8499

85100
const passcodeVar = variables.find(v => v.key === 'API_PASSCODE');
86101
const expiryVar = variables.find(v => v.key === 'API_PASSCODE_EXPIRY');
@@ -97,7 +112,7 @@ async function getAppInfo() {
97112
expiry: moment(Number(expiry)).toString(),
98113
sid: app.sid,
99114
passcode: fullPasscode,
100-
hasAssets: Boolean(assets.length),
115+
hasWebAssets: Boolean(assets.find(asset => asset.friendlyName.includes('index.html'))),
101116
roomType,
102117
environmentSid: environment.sid,
103118
functionSid: tokenServerFunction.sid,
@@ -112,7 +127,7 @@ async function displayAppInfo() {
112127
return;
113128
}
114129

115-
if (appInfo.hasAssets) {
130+
if (appInfo.hasWebAssets) {
116131
console.log(`Web App URL: ${appInfo.url}`);
117132
}
118133

@@ -130,6 +145,12 @@ async function displayAppInfo() {
130145

131146
async function deploy() {
132147
const assets = this.flags['app-directory'] ? await getAssets(this.flags['app-directory']) : [];
148+
const { functions } = await getListOfFunctionsAndAssets(__dirname, {
149+
functionsFolderNames: ['serverless/functions'],
150+
assetsFolderNames: [],
151+
});
152+
153+
assets.push(...getMiddleware());
133154

134155
if (this.twilioClient.username === this.twilioClient.accountSid) {
135156
// When twilioClient.username equals twilioClient.accountSid, it means that the user
@@ -158,8 +179,6 @@ TWILIO_API_SECRET = the secret for the API Key`);
158179
const pin = getRandomInt(6);
159180
const expiryTime = Date.now() + EXPIRY_PERIOD;
160181

161-
const fn = fs.readFileSync(path.join(__dirname, './video-token-server.js'));
162-
163182
cli.action.start('deploying app');
164183

165184
const deployOptions = {
@@ -170,17 +189,14 @@ TWILIO_API_SECRET = the secret for the API Key`);
170189
API_PASSCODE_EXPIRY: expiryTime,
171190
ROOM_TYPE: this.flags['room-type'],
172191
},
173-
pkgJson: {},
174-
functionsEnv: 'dev',
175-
functions: [
176-
{
177-
name: 'token',
178-
path: '/token',
179-
content: fn,
180-
access: 'public',
192+
pkgJson: {
193+
dependencies: {
194+
twilio: '^3.51.0',
181195
},
182-
],
183-
assets: assets,
196+
},
197+
functionsEnv: 'dev',
198+
functions,
199+
assets,
184200
};
185201

186202
if (this.appInfo && this.appInfo.sid) {
@@ -205,6 +221,7 @@ module.exports = {
205221
displayAppInfo,
206222
findApp,
207223
getAssets,
224+
getMiddleware,
208225
getAppInfo,
209226
getPasscode,
210227
getRandomInt,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* global Twilio Runtime */
2+
'use strict';
3+
4+
// We need to use a newer twilio client than the one provided by context.getTwilioClient(),
5+
// so we require it here. The version is specified in helpers.js in the 'deployOptions' object.
6+
// TODO: replace with context.getTwilioClient() when https://issues.corp.twilio.com/browse/RUN-3731 is complete
7+
const twilio = require('twilio');
8+
9+
module.exports.handler = async (context, event, callback) => {
10+
const authHandler = require(Runtime.getAssets()['/auth-handler.js'].path);
11+
authHandler(context, event, callback);
12+
13+
let response = new Twilio.Response();
14+
response.appendHeader('Content-Type', 'application/json');
15+
16+
const { room_sid, rules } = event;
17+
18+
if (typeof room_sid === 'undefined') {
19+
response.setStatusCode(400);
20+
response.setBody({
21+
error: {
22+
message: 'missing room_sid',
23+
explanation: 'The room_sid parameter is missing.',
24+
},
25+
});
26+
return callback(null, response);
27+
}
28+
29+
if (typeof rules === 'undefined') {
30+
response.setStatusCode(400);
31+
response.setBody({
32+
error: {
33+
message: 'missing rules',
34+
explanation: 'The rules parameter is missing.',
35+
},
36+
});
37+
return callback(null, response);
38+
}
39+
40+
const client = twilio(context.ACCOUNT_SID, context.AUTH_TOKEN);
41+
42+
try {
43+
const recordingRulesResponse = await client.video.rooms(room_sid).recordingRules.update({ rules });
44+
response.setStatusCode(200);
45+
response.setBody(recordingRulesResponse);
46+
} catch (err) {
47+
response.setStatusCode(500);
48+
response.setBody({ error: { message: err.message, code: err.code } });
49+
}
50+
51+
callback(null, response);
52+
};
Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
1-
/* global Twilio */
1+
/* global Twilio Runtime */
22
'use strict';
33

44
const AccessToken = Twilio.jwt.AccessToken;
55
const VideoGrant = AccessToken.VideoGrant;
66
const MAX_ALLOWED_SESSION_DURATION = 14400;
77

88
module.exports.handler = async (context, event, callback) => {
9-
const {
10-
ACCOUNT_SID,
11-
TWILIO_API_KEY_SID,
12-
TWILIO_API_KEY_SECRET,
13-
API_PASSCODE,
14-
API_PASSCODE_EXPIRY,
15-
DOMAIN_NAME,
16-
ROOM_TYPE,
17-
} = context;
9+
const { ACCOUNT_SID, TWILIO_API_KEY_SID, TWILIO_API_KEY_SECRET, ROOM_TYPE } = context;
1810

19-
const { user_identity, room_name, passcode, create_room = true } = event;
20-
const [, appID, serverlessID] = DOMAIN_NAME.match(/-?(\d*)-(\d+)(?:-\w+)?.twil.io$/);
11+
const authHandler = require(Runtime.getAssets()['/auth-handler.js'].path);
12+
authHandler(context, event, callback);
13+
14+
const { user_identity, room_name, create_room = true } = event;
2115

2216
let response = new Twilio.Response();
2317
response.appendHeader('Content-Type', 'application/json');
@@ -33,29 +27,6 @@ module.exports.handler = async (context, event, callback) => {
3327
return callback(null, response);
3428
}
3529

36-
if (Date.now() > API_PASSCODE_EXPIRY) {
37-
response.setStatusCode(401);
38-
response.setBody({
39-
error: {
40-
message: 'passcode expired',
41-
explanation:
42-
'The passcode used to validate application users has expired. Re-deploy the application to refresh the passcode.',
43-
},
44-
});
45-
return callback(null, response);
46-
}
47-
48-
if (API_PASSCODE + appID + serverlessID !== passcode) {
49-
response.setStatusCode(401);
50-
response.setBody({
51-
error: {
52-
message: 'passcode incorrect',
53-
explanation: 'The passcode used to validate application users is incorrect.',
54-
},
55-
});
56-
return callback(null, response);
57-
}
58-
5930
if (!user_identity) {
6031
response.setStatusCode(400);
6132
response.setBody({

src/serverless/middleware/auth.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* global Twilio */
2+
'use strict';
3+
4+
module.exports = async (context, event, callback) => {
5+
const { API_PASSCODE, API_PASSCODE_EXPIRY, DOMAIN_NAME } = context;
6+
7+
const { passcode } = event;
8+
const [, appID, serverlessID] = DOMAIN_NAME.match(/-?(\d*)-(\d+)(?:-\w+)?.twil.io$/);
9+
10+
let response = new Twilio.Response();
11+
response.appendHeader('Content-Type', 'application/json');
12+
13+
if (Date.now() > API_PASSCODE_EXPIRY) {
14+
response.setStatusCode(401);
15+
response.setBody({
16+
error: {
17+
message: 'passcode expired',
18+
explanation:
19+
'The passcode used to validate application users has expired. Re-deploy the application to refresh the passcode.',
20+
},
21+
});
22+
return callback(null, response);
23+
}
24+
25+
if (API_PASSCODE + appID + serverlessID !== passcode) {
26+
response.setStatusCode(401);
27+
response.setBody({
28+
error: {
29+
message: 'passcode incorrect',
30+
explanation: 'The passcode used to validate application users is incorrect.',
31+
},
32+
});
33+
return callback(null, response);
34+
}
35+
};

test/helpers/helpers.test.js

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
getPasscode,
99
getRandomInt,
1010
verifyAppDirectory,
11+
getMiddleware,
1112
} = require('../../src/helpers');
1213
const { getListOfFunctionsAndAssets } = require('@twilio-labs/serverless-api/dist/utils/fs');
1314
const path = require('path');
@@ -43,7 +44,7 @@ function getMockTwilioInstance(options) {
4344
};
4445

4546
const mockAppInstance = {
46-
assets: { list: () => Promise.resolve(options.hasAssets ? [{}] : []) },
47+
assets: { list: () => Promise.resolve(options.hasWebAssets ? [{ friendlyName: 'index.html' }] : []) },
4748
functions: {},
4849
update: jest.fn(() => Promise.resolve()),
4950
};
@@ -114,23 +115,40 @@ describe('the verifyAppDirectory function', () => {
114115

115116
describe('the getAssets function', () => {
116117
it('should add index.html at "/" and "/login" paths', async () => {
117-
expect(await getAssets('mockFolder')).toEqual([
118-
{
119-
name: 'index.html',
120-
path: 'index.html',
121-
content: 'mockHTMLcontent',
122-
},
123-
{
124-
name: '/',
125-
path: '/',
126-
content: 'mockHTMLcontent',
127-
},
128-
{
129-
name: '/login',
130-
path: '/login',
131-
content: 'mockHTMLcontent',
132-
},
133-
]);
118+
expect(await getAssets('mockFolder')).toEqual(
119+
expect.arrayContaining([
120+
{
121+
name: 'index.html',
122+
path: 'index.html',
123+
content: 'mockHTMLcontent',
124+
},
125+
{
126+
name: '/',
127+
path: '/',
128+
content: 'mockHTMLcontent',
129+
},
130+
{
131+
name: '/login',
132+
path: '/login',
133+
content: 'mockHTMLcontent',
134+
},
135+
])
136+
);
137+
});
138+
});
139+
140+
describe('the getMiddleware function', () => {
141+
it('should add the auth-handler.js as a private asset', async () => {
142+
expect(await getMiddleware('mockFolder')).toEqual(
143+
expect.arrayContaining([
144+
{
145+
name: 'auth-handler',
146+
path: '/auth-handler.js',
147+
content: expect.any(Buffer),
148+
access: 'private',
149+
},
150+
])
151+
);
134152
});
135153

136154
it('should use the CWD when provided with a relative path', async () => {
@@ -185,23 +203,23 @@ describe('the getAppInfo function', () => {
185203
expiry: 'Wed May 20 2020 18:40:00 GMT+0000',
186204
environmentSid: 'environmentSid',
187205
functionSid: 'tokenFunctionSid',
188-
hasAssets: false,
206+
hasWebAssets: false,
189207
passcode: '12345612345678',
190208
sid: 'appSid',
191209
url: `https://${APP_NAME}-1234-5678-dev.twil.io?passcode=12345612345678`,
192210
roomType: 'group',
193211
});
194212
});
195213

196-
it('should return the correct information when there are assets', async () => {
214+
it('should return the correct information when there are web assets', async () => {
197215
const result = await getAppInfo.call({
198-
twilioClient: getMockTwilioInstance({ exists: true, hasAssets: true }),
216+
twilioClient: getMockTwilioInstance({ exists: true, hasWebAssets: true }),
199217
});
200218
expect(result).toEqual({
201219
expiry: 'Wed May 20 2020 18:40:00 GMT+0000',
202220
environmentSid: 'environmentSid',
203221
functionSid: 'tokenFunctionSid',
204-
hasAssets: true,
222+
hasWebAssets: true,
205223
passcode: '12345612345678',
206224
sid: 'appSid',
207225
url: `https://${APP_NAME}-1234-5678-dev.twil.io?passcode=12345612345678`,
@@ -236,7 +254,7 @@ describe('the displayAppInfo function', () => {
236254

237255
it('should display the correct information when there are assets', async () => {
238256
await displayAppInfo.call({
239-
twilioClient: getMockTwilioInstance({ exists: true, hasAssets: true }),
257+
twilioClient: getMockTwilioInstance({ exists: true, hasWebAssets: true }),
240258
});
241259
expect(stdout.output).toMatchInlineSnapshot(`
242260
"Web App URL: https://${APP_NAME}-1234-5678-dev.twil.io?passcode=12345612345678

0 commit comments

Comments
 (0)