Skip to content

Commit 989f71b

Browse files
author
Tim Mendoza
committed
Add create_conversation parameter to token endpoint
1 parent 1a936e8 commit 989f71b

File tree

4 files changed

+113
-43
lines changed

4 files changed

+113
-43
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,21 @@ Each request is verified using a passcode generated at deploy time. Passcodes re
8282

8383
### Token
8484

85-
This endpoint returns a Programmable Video Access token. When `create_room` is true, it will create a room and [Twilio conversation](https://www.twilio.com/docs/conversations/api/conversation-resource) associated with the room. This token is used by the above mentioned Video Apps to connect to a video room and a conversation.
85+
This endpoint returns a Programmable Video Access token. When `create_room` is true, it will create a room, and when `create_conversation` is true, it will create a [Twilio conversation](https://www.twilio.com/docs/conversations/api/conversation-resource) associated with the room. This token is used by the above mentioned Video Apps to connect to a video room and a conversation.
8686

8787
```shell
8888
POST /token
8989
```
9090

9191
#### Parameters
9292

93-
| Name | Type | Description |
94-
| --------------- | --------- | -------------------------------------------------------------------------------------- |
95-
| `passcode` | `string` | **Required**. The application passcode. |
96-
| `user_identity` | `string` | **Required**. The user's identity. |
97-
| `room_name` | `string` | **Required when `create_room` is `true`** A room name that will be used to create a token scoped to connecting to only one room. |
98-
| `create_room` | `boolean` | (default: `true`) When false, a room will not be created when a token is requested. |
93+
| Name | Type | Description |
94+
| --------------------- | --------- | -------------------------------------------------------------------------------------- |
95+
| `passcode` | `string` | **Required**. The application passcode. |
96+
| `user_identity` | `string` | **Required**. The user's identity. |
97+
| `room_name` | `string` | **Required when `create_room` is `true`** A room name that will be used to create a token scoped to connecting to only one room. |
98+
| `create_room` | `boolean` | (default: `true`) When false, a room will not be created when a token is requested. |
99+
| `create_conversation` | `boolean` | (default: `false`) When true, a Twilio Conversation will be created (if it doesn't already exist) and a participant will be added to it when a token is requested. `create_room` must also be `true`. |
99100

100101
#### Success Responses
101102

src/serverless/functions/token.js

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module.exports.handler = async (context, event, callback) => {
1212
const authHandler = require(Runtime.getAssets()['/auth-handler.js'].path);
1313
authHandler(context, event, callback);
1414

15-
const { user_identity, room_name, create_room = true } = event;
15+
const { user_identity, room_name, create_room = true, create_conversation = false } = event;
1616

1717
let response = new Twilio.Response();
1818
response.appendHeader('Content-Type', 'application/json');
@@ -28,6 +28,17 @@ module.exports.handler = async (context, event, callback) => {
2828
return callback(null, response);
2929
}
3030

31+
if (typeof create_conversation !== 'boolean') {
32+
response.setStatusCode(400);
33+
response.setBody({
34+
error: {
35+
message: 'invalid parameter',
36+
explanation: 'A boolean value must be provided for the create_conversation parameter',
37+
},
38+
});
39+
return callback(null, response);
40+
}
41+
3142
if (!user_identity) {
3243
response.setStatusCode(400);
3344
response.setBody({
@@ -74,39 +85,41 @@ module.exports.handler = async (context, event, callback) => {
7485
}
7586
}
7687

77-
try {
78-
// See if conversation already exists
79-
await conversationsClient.conversations(room.sid).fetch();
80-
} catch (e) {
88+
if (create_conversation) {
8189
try {
82-
// If conversation doesn't exist, create it.
83-
await conversationsClient.conversations.create({ uniqueName: room.sid });
90+
// See if conversation already exists
91+
await conversationsClient.conversations(room.sid).fetch();
8492
} catch (e) {
85-
response.setStatusCode(500);
86-
response.setBody({
87-
error: {
88-
message: 'error creating conversation',
89-
explanation: 'Something went wrong when creating a conversation.',
90-
},
91-
});
92-
return callback(null, response);
93+
try {
94+
// If conversation doesn't exist, create it.
95+
await conversationsClient.conversations.create({ uniqueName: room.sid });
96+
} catch (e) {
97+
response.setStatusCode(500);
98+
response.setBody({
99+
error: {
100+
message: 'error creating conversation',
101+
explanation: 'Something went wrong when creating a conversation.',
102+
},
103+
});
104+
return callback(null, response);
105+
}
93106
}
94-
}
95107

96-
try {
97-
// Add participant to conversation
98-
await conversationsClient.conversations(room.sid).participants.create({ identity: user_identity });
99-
} catch (e) {
100-
// Ignore "Participant already exists" error (50433)
101-
if (e.code !== 50433) {
102-
response.setStatusCode(500);
103-
response.setBody({
104-
error: {
105-
message: 'error creating conversation participant',
106-
explanation: 'Something went wrong when creating a conversation participant.',
107-
},
108-
});
109-
return callback(null, response);
108+
try {
109+
// Add participant to conversation
110+
await conversationsClient.conversations(room.sid).participants.create({ identity: user_identity });
111+
} catch (e) {
112+
// Ignore "Participant already exists" error (50433)
113+
if (e.code !== 50433) {
114+
response.setStatusCode(500);
115+
response.setBody({
116+
error: {
117+
message: 'error creating conversation participant',
118+
explanation: 'Something went wrong when creating a conversation participant.',
119+
},
120+
});
121+
return callback(null, response);
122+
}
110123
}
111124
}
112125
}

test/e2e/e2e.test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe('the RTC Twilio-CLI Plugin', () => {
146146
const ROOM_NAME = nanoid();
147147
const { body } = await superagent
148148
.post(`${URL}/token`)
149-
.send({ passcode, room_name: ROOM_NAME, user_identity: 'test user' });
149+
.send({ passcode, room_name: ROOM_NAME, user_identity: 'test user', create_conversation: true });
150150

151151
const conversationServiceSid = jwt.decode(body.token).grants.chat.service_sid;
152152

@@ -189,6 +189,30 @@ describe('the RTC Twilio-CLI Plugin', () => {
189189
}
190190
});
191191

192+
it('should return a video token without creating a conversation when the "create_conversation" flag is false', async () => {
193+
const ROOM_NAME = nanoid();
194+
const { body } = await superagent
195+
.post(`${URL}/token`)
196+
.send({ passcode, room_name: ROOM_NAME, user_identity: 'test user', create_conversation: false });
197+
198+
const conversationServiceSid = jwt.decode(body.token).grants.chat.service_sid;
199+
200+
const room = await twilioClient.video.rooms(ROOM_NAME).fetch();
201+
202+
// Find the deployed conversations service
203+
const deployedConversationsServices = await twilioClient.conversations.services.list();
204+
const deployedConversationsService = deployedConversationsServices.find(
205+
service => (service.sid = conversationServiceSid)
206+
);
207+
208+
const conversationPromise = twilioClient.conversations
209+
.services(deployedConversationsService.sid)
210+
.conversations(room.sid)
211+
.fetch();
212+
213+
expect(conversationPromise).rejects.toEqual(expect.objectContaining({ code: 20404 }));
214+
});
215+
192216
it('should return a 401 error when an incorrect passcode is provided', () => {
193217
superagent
194218
.post(`${URL}/token`)

test/serverless/functions/token.test.js

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('the video-token-server', () => {
5454
it('should create a new room and conversation, then return a valid token', async () => {
5555
await handler(
5656
mockContext,
57-
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user' },
57+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: true },
5858
callback
5959
);
6060

@@ -93,7 +93,7 @@ describe('the video-token-server', () => {
9393

9494
await handler(
9595
mockContext,
96-
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user' },
96+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: true },
9797
callback
9898
);
9999

@@ -114,7 +114,7 @@ describe('the video-token-server', () => {
114114
it('should fetch the existing room and conversation, then return a valid token', async () => {
115115
await handler(
116116
mockContext,
117-
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user' },
117+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: true },
118118
callback
119119
);
120120

@@ -135,7 +135,7 @@ describe('the video-token-server', () => {
135135

136136
await handler(
137137
mockContext,
138-
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user' },
138+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: true },
139139
callback
140140
);
141141

@@ -156,7 +156,7 @@ describe('the video-token-server', () => {
156156

157157
await handler(
158158
mockContext,
159-
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user' },
159+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: true },
160160
callback
161161
);
162162

@@ -192,6 +192,21 @@ describe('the video-token-server', () => {
192192
});
193193
});
194194

195+
it('should return an "invalid parameter" error when the create_conversation parameter is not a boolean', async () => {
196+
await handler(mockContext, { user_identity: 'test identity', create_conversation: 'no thanks' }, callback);
197+
198+
expect(callback).toHaveBeenCalledWith(null, {
199+
body: {
200+
error: {
201+
message: 'invalid parameter',
202+
explanation: 'A boolean value must be provided for the create_conversation parameter',
203+
},
204+
},
205+
headers: { 'Content-Type': 'application/json' },
206+
statusCode: 400,
207+
});
208+
});
209+
195210
it('should return a "missing user_identity" error when the "user_identity" parameter is not supplied', () => {
196211
handler(mockContext, {}, callback);
197212

@@ -306,6 +321,23 @@ describe('the video-token-server', () => {
306321
});
307322
});
308323

324+
it('should return a valid token without creating a conversation when "create_conversation" is false', async () => {
325+
await handler(
326+
mockContext,
327+
{ passcode: '12345612345678', room_name: 'test-room', user_identity: 'test-user', create_conversation: false },
328+
callback
329+
);
330+
331+
expect(mockFns.fetchConversation).not.toHaveBeenCalled();
332+
expect(mockFns.createConversation).not.toHaveBeenCalled();
333+
expect(mockFns.createParticipant).not.toHaveBeenCalled();
334+
expect(callback).toHaveBeenCalledWith(null, {
335+
body: { token: expect.any(String), room_type: 'group' },
336+
headers: { 'Content-Type': 'application/json' },
337+
statusCode: 200,
338+
});
339+
});
340+
309341
it('should return a valid token when passcode when the room already exists', async () => {
310342
mockFns.createRoom.mockImplementation(() => Promise.reject({ code: 53113 }));
311343

0 commit comments

Comments
 (0)