Skip to content
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.6.0

### Enhancements

- This version will print a URL to the terminal where the user can view and edit the deployed token server function in the Twilio console. By default, the token server is editable in the Twilio console, but this can be disabled with the `--no-ui-editable` flag.
- Upgraded @twilio/cli-core from 5.9.0 to 5.9.1
- Upgraded moment from 2.27.0 to 2.28.0
- Upgraded @twilio-labs/serverless-api from 4.0.1 to 4.0.2

## 0.5.0

### Enhancements
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ OPTIONS
--authentication=(passcode) (required) Type of authentication to use
--override Override an existing App deployment
--room-type=(group|group-small|peer-to-peer|go) [default: group] Set room type
--[no-]ui-editable Specifies whether the app's files and variables can be edited in the Twilio console.

DESCRIPTION
This command publishes two components as a Twilio Function: an application token
Expand All @@ -251,6 +252,7 @@ EXAMPLES
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...

# Deploy an application token server with the React app
$ twilio rtc:apps:video:deploy --authentication passcode --app-directory /path/to/app
Expand All @@ -259,6 +261,7 @@ EXAMPLES
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...

# Override an existing app with a fresh deployment
# Please note that this will remove a previously deployed web application if no
Expand All @@ -269,13 +272,15 @@ EXAMPLES
Passcode: yyy yyy yyyy yyyy
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...

# Deploy an application token server with a specific room type
$ twilio rtc:apps:video:deploy --authentication passcode --room-type peer-to-peer
deploying app... done
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: peer-to-peer
Edit your token server at: https://www.twilio.com/console/functions/editor/...
```

## `twilio rtc:apps:video:view`
Expand All @@ -295,6 +300,7 @@ EXAMPLE
Web App URL: https://video-app-1111-dev.twil.io?passcode=xxxxxxxxxxxxxx
Passcode: xxx xxx xxxx xxxx
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...
```

<!-- commandsstop -->
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@twilio-labs/plugin-rtc",
"version": "0.5.1",
"version": "0.6.0",
"description": "A Twilio-CLI plugin for real-time communication apps",
"main": "index.js",
"publishConfig": {
Expand Down
18 changes: 14 additions & 4 deletions src/commands/rtc/apps/video/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ DeployCommand.flags = Object.assign(
required: false,
default: 'group',
}),
'ui-editable': flags.boolean({
required: false,
default: true,
description: "Specifies whether the app's files and variables can be edited in the Twilio console.",
allowNo: true,
}),
},
TwilioClientCommand.flags
);
Expand Down Expand Up @@ -76,15 +82,17 @@ $ twilio rtc:apps:video:deploy --authentication passcode
deploying app... done
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group`,
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...`,
`
# Deploy an application token server with the React app
$ twilio rtc:apps:video:deploy --authentication passcode --app-directory /path/to/app
deploying app... done
Web App URL: https://video-app-xxxx-xxxx-dev.twil.io?passcode=xxxxxxxxxxxxxx
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group`,
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...`,
`
# Override an existing app with a fresh deployment
# Please note that this will remove a previously deployed web application if no
Expand All @@ -94,14 +102,16 @@ Removed app with Passcode: xxx xxx xxxx xxxx
deploying app... done
Passcode: yyy yyy yyyy yyyy
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: group`,
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/...`,
`
# Deploy an application token server with a specific room type
$ twilio rtc:apps:video:deploy --authentication passcode --room-type peer-to-peer
deploying app... done
Passcode: xxx xxx xxxx xxxx
Expires: Mon Mar 09 2020 16:36:23 GMT-0600
Room Type: peer-to-peer`,
Room Type: peer-to-peer
Edit your token server at: https://www.twilio.com/console/functions/editor/...`,
];

module.exports = DeployCommand;
3 changes: 2 additions & 1 deletion src/commands/rtc/apps/video/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ ViewCommand.description = 'View a Programmable Video app';
ViewCommand.examples = [
`$ twilio rtc:apps:video:view
Web App URL: https://video-app-1111-dev.twil.io?passcode=1111111111
Passcode: 1111111111`,
Passcode: 1111111111
Edit your token server at: https://www.twilio.com/console/functions/editor/...`,
];

module.exports = ViewCommand;
14 changes: 13 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ async function getAppInfo() {

const assets = await appInstance.assets.list();

const functions = await appInstance.functions.list();
const tokenServerFunction = functions.find(fn => fn.friendlyName === 'token');

const passcodeVar = variables.find(v => v.key === 'API_PASSCODE');
const expiryVar = variables.find(v => v.key === 'API_PASSCODE_EXPIRY');
const roomTypeVar = variables.find(v => v.key === 'ROOM_TYPE');
Expand All @@ -96,6 +99,8 @@ async function getAppInfo() {
passcode: fullPasscode,
hasAssets: Boolean(assets.length),
roomType,
environmentSid: environment.sid,
functionSid: tokenServerFunction.sid,
};
}

Expand All @@ -117,6 +122,10 @@ async function displayAppInfo() {
if (appInfo.roomType) {
console.log(`Room Type: ${appInfo.roomType}`);
}

console.log(
`Edit your token server at: https://www.twilio.com/console/functions/editor/${appInfo.sid}/environment/${appInfo.environmentSid}/function/${appInfo.functionSid}`
);
}

async function deploy() {
Expand Down Expand Up @@ -181,7 +190,10 @@ TWILIO_API_SECRET = the secret for the API Key`);
}

try {
await serverlessClient.deployProject(deployOptions);
const { serviceSid } = await serverlessClient.deployProject(deployOptions);
await this.twilioClient.serverless
.services(serviceSid)
.update({ includeCredentials: true, uiEditable: this.flags['ui-editable'] });
cli.action.stop();
} catch (e) {
console.error('Something went wrong', e);
Expand Down
6 changes: 4 additions & 2 deletions test/e2e/e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('the RTC Twilio-CLI Plugin', () => {
await ViewCommand.run([]);
stdout.stop();
expect(stdout.output).toMatch(
/Web App URL: .+\nPasscode: \d{3} \d{3} \d{4} \d{4}\nExpires: .+\nRoom Type: group/
/Web App URL: .+\nPasscode: \d{3} \d{3} \d{4} \d{4}\nExpires: .+\nRoom Type: group\nEdit your token server at: https:\/\/www.twilio.com\/console\/functions\/editor\/ZS\w{32}\/environment\/ZE\w{32}\/function\/ZH\w{32}/
);
});
});
Expand Down Expand Up @@ -248,7 +248,9 @@ describe('the RTC Twilio-CLI Plugin', () => {
stdout.start();
await ViewCommand.run([]);
stdout.stop();
expect(stdout.output).toMatch(/Passcode: \d{3} \d{3} \d{4} \d{4}\nExpires: .+\nRoom Type: go/);
expect(stdout.output).toMatch(
/Passcode: \d{3} \d{3} \d{4} \d{4}\nExpires: .+\nRoom Type: go\nEdit your token server at: https:\/\/www.twilio.com\/console\/functions\/editor\/ZS\w{32}\/environment\/ZE\w{32}\/function\/ZH\w{32}/
);
expect(stdout.output).not.toMatch(/Web App URL:/);
});
});
Expand Down
77 changes: 55 additions & 22 deletions test/helpers/helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { getListOfFunctionsAndAssets } = require('@twilio-labs/serverless-api/dis
const path = require('path');
const { stdout } = require('stdout-stderr');

const mockDeployProject = jest.fn(() => Promise.resolve());
const mockDeployProject = jest.fn(() => Promise.resolve({ serviceSid: 'mockServiceSid' }));

jest.mock('@twilio-labs/serverless-api', () => ({
TwilioServerlessApiClient: function() {
Expand All @@ -38,10 +38,14 @@ jest.mock('@twilio-labs/serverless-api/dist/utils/fs', () => ({
function getMockTwilioInstance(options) {
const mockTwilioClient = {
serverless: {},
username: options.username,
password: options.password,
};

const mockAppInstance = {
assets: { list: () => Promise.resolve(options.hasAssets ? [{}] : []) },
functions: {},
update: jest.fn(() => Promise.resolve()),
};

mockAppInstance.environments = jest.fn(() => ({
Expand All @@ -55,8 +59,9 @@ function getMockTwilioInstance(options) {
},
}));
mockAppInstance.environments.list = () =>
Promise.resolve([{ sid: 'env', domainName: `${APP_NAME}-1234-5678-dev.twil.io` }]);
mockTwilioClient.serverless.services = jest.fn(() => Promise.resolve(mockAppInstance));
Promise.resolve([{ sid: 'environmentSid', domainName: `${APP_NAME}-1234-5678-dev.twil.io` }]);
mockAppInstance.functions.list = () => Promise.resolve([{ sid: 'tokenFunctionSid', friendlyName: 'token' }]);
mockTwilioClient.serverless.services = jest.fn(() => mockAppInstance);
mockTwilioClient.serverless.services.list = () =>
Promise.resolve([
{
Expand Down Expand Up @@ -178,6 +183,8 @@ describe('the getAppInfo function', () => {
});
expect(result).toEqual({
expiry: 'Wed May 20 2020 18:40:00 GMT+0000',
environmentSid: 'environmentSid',
functionSid: 'tokenFunctionSid',
hasAssets: false,
passcode: '12345612345678',
sid: 'appSid',
Expand All @@ -192,6 +199,8 @@ describe('the getAppInfo function', () => {
});
expect(result).toEqual({
expiry: 'Wed May 20 2020 18:40:00 GMT+0000',
environmentSid: 'environmentSid',
functionSid: 'tokenFunctionSid',
hasAssets: true,
passcode: '12345612345678',
sid: 'appSid',
Expand Down Expand Up @@ -220,6 +229,7 @@ describe('the displayAppInfo function', () => {
"Passcode: 123 456 1234 5678
Expires: Wed May 20 2020 18:40:00 GMT+0000
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/appSid/environment/environmentSid/function/tokenFunctionSid
"
`);
});
Expand All @@ -233,6 +243,7 @@ describe('the displayAppInfo function', () => {
Passcode: 123 456 1234 5678
Expires: Wed May 20 2020 18:40:00 GMT+0000
Room Type: group
Edit your token server at: https://www.twilio.com/console/functions/editor/appSid/environment/environmentSid/function/tokenFunctionSid
"
`);
});
Expand All @@ -242,19 +253,16 @@ describe('the displayAppInfo function', () => {
twilioClient: getMockTwilioInstance({ exists: false }),
});
expect(stdout.output).toMatchInlineSnapshot(`
"There is no deployed app
"
`);
"There is no deployed app
"
`);
});
});

describe('the deploy function', () => {
it('should set serviceSid when appInfo exists', async () => {
await deploy.call({
twilioClient: {
username: '',
password: '',
},
twilioClient: getMockTwilioInstance({ username: '', password: '' }),
appInfo: {
sid: '1234',
},
Expand All @@ -267,16 +275,41 @@ describe('the deploy function', () => {

it('should set serviceName when appInfo doesnt exist', async () => {
await deploy.call({
twilioClient: {
username: '',
password: '',
},
twilioClient: getMockTwilioInstance({ username: '', password: '' }),
flags: {},
});
expect(mockDeployProject.mock.calls[0][0].serviceSid).toBe(undefined);
expect(mockDeployProject.mock.calls[0][0].serviceName).toMatch(new RegExp(`${APP_NAME}-(\\d{4})`));
});

it('should set ui-editable to false when the flag is false', async () => {
const mockTwilioClient = getMockTwilioInstance({ username: '', password: '' });
await deploy.call({
twilioClient: mockTwilioClient,
flags: {
'ui-editable': false,
},
});
expect(mockTwilioClient.serverless.services().update).toHaveBeenCalledWith({
includeCredentials: true,
uiEditable: false,
});
});

it('should set ui-editable to true when the flag is true', async () => {
const mockTwilioClient = getMockTwilioInstance({ username: '', password: '' });
await deploy.call({
twilioClient: mockTwilioClient,
flags: {
'ui-editable': true,
},
});
expect(mockTwilioClient.serverless.services().update).toHaveBeenCalledWith({
includeCredentials: true,
uiEditable: true,
});
});

it('should display an error when the API key is not provided', () => {
return expect(
deploy.call({
Expand All @@ -288,17 +321,17 @@ describe('the deploy function', () => {
flags: {},
})
).rejects.toMatchInlineSnapshot(`
[Error: No API Key found.
[Error: No API Key found.

Please login to the Twilio CLI to create an API key:
Please login to the Twilio CLI to create an API key:

twilio login
twilio login

Alternatively, the Twilio CLI can use credentials stored in these environment variables:
Alternatively, the Twilio CLI can use credentials stored in these environment variables:

TWILIO_ACCOUNT_SID = your Account SID from twil.io/console
TWILIO_API_KEY = an API Key created at twil.io/get-api-key
TWILIO_API_SECRET = the secret for the API Key]
`);
TWILIO_ACCOUNT_SID = your Account SID from twil.io/console
TWILIO_API_KEY = an API Key created at twil.io/get-api-key
TWILIO_API_SECRET = the secret for the API Key]
`);
});
});