@mqarty/plugin-dev-phone 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,696 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const path_1 = __importDefault(require("path"));
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const open_1 = __importDefault(require("open"));
18
+ const express_1 = __importDefault(require("express"));
19
+ const confirm_1 = __importDefault(require("@inquirer/confirm"));
20
+ const core_1 = require("@oclif/core");
21
+ const create_serverless_util_1 = require("../utils/create-serverless-util");
22
+ const helpers_1 = require("../utils/helpers");
23
+ const phone_number_utils_1 = require("../utils/phone-number-utils");
24
+ const { TwilioClientCommand } = require('@twilio/cli-core').baseCommands;
25
+ const { TwilioCliError } = require('@twilio/cli-core').services.error;
26
+ const WebClientPath = path_1.default.resolve(require.resolve('@mqarty/dev-phone-ui'), '..');
27
+ const { version } = require('../../package.json');
28
+ const AccessToken = require('twilio').jwt.AccessToken;
29
+ const ChatGrant = AccessToken.ChatGrant;
30
+ const VoiceGrant = AccessToken.VoiceGrant;
31
+ const SyncGrant = AccessToken.SyncGrant;
32
+ const CALL_LOG_MAP_NAME = 'CallLog';
33
+ // removes unecessary properties to standardize the twilio phone number
34
+ const reformatTwilioPns = (twilioResponse) => {
35
+ return {
36
+ "phone-numbers": twilioResponse.map(({ phoneNumber, friendlyName, smsUrl, voiceUrl, sid }) => ({ phoneNumber, friendlyName, smsUrl, voiceUrl, sid }))
37
+ };
38
+ };
39
+ const generateRandomPhoneName = () => {
40
+ let rand = Math.random().toString().substring(2, 6);
41
+ return `dev-phone-${rand}`;
42
+ };
43
+ class DevPhoneServer extends TwilioClientCommand {
44
+ constructor(argv, config, secureStorage) {
45
+ super(argv, config, secureStorage);
46
+ this.cliSettings = {};
47
+ this.pns = [];
48
+ this.port = 1337;
49
+ this.jwt = null;
50
+ this.apikey = {};
51
+ this.twimlApp = {};
52
+ this.devPhoneName = generateRandomPhoneName();
53
+ this.voiceUrl = null;
54
+ this.smsUrl = null;
55
+ this.voiceOutboundUrl = null;
56
+ }
57
+ run() {
58
+ const _super = Object.create(null, {
59
+ run: { get: () => super.run }
60
+ });
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ yield _super.run.call(this);
63
+ const props = this.parseProperties() || {};
64
+ yield this.validatePropsAndFlags(props, this.flags);
65
+ console.log(`Hello 👋 I'm your dev-phone and my name is ${this.devPhoneName}\n`);
66
+ // set user agent header on twilio client
67
+ this.twilioClient.userAgentExtensions = [
68
+ `@mqarty/plugin-dev-phone/${version}`,
69
+ `@mqarty/plugin-dev-phone/helper-library`,
70
+ 'serverless-functions'
71
+ ];
72
+ // create API KEY and API SECRET to be generate JWT AccessToken for ChatGrant, VoiceGrant and SyncGrant
73
+ this.apikey = yield this.reuseOrCreateApiKey();
74
+ const isDeletingAll = () => !!this.flags.clear;
75
+ const deleteAll = () => __awaiter(this, void 0, void 0, function* () {
76
+ yield this.destroyAllConversations();
77
+ yield this.destroyAllTwimlApps();
78
+ yield this.destroyAllApiKeys();
79
+ yield this.destroyAllSyncs();
80
+ yield this.destroyAllFunctions();
81
+ yield this.removeAllPhoneWebhooks();
82
+ });
83
+ if (isDeletingAll()) {
84
+ const deleteAllConfirmation = yield (0, confirm_1.default)({
85
+ message: "Do you want to delete all of the dev phone resources on your Twilio account? This may interfere with other instances of the Dev Phone.",
86
+ default: false
87
+ });
88
+ if (deleteAllConfirmation) {
89
+ console.log(`🌐 Deleting all dev-phone resources from your account before starting...`);
90
+ yield deleteAll().finally(() => console.log(`✅ All resources have been deleted.`));
91
+ }
92
+ }
93
+ // create conversation for SMS/web interface
94
+ this.conversation = yield this.createConversation();
95
+ // create Sync for Call History interface
96
+ this.sync = yield this.createSync();
97
+ // create Function to handle inbound-voice, inbound-sms and outbound-voice (voip)
98
+ this.serverless = yield this.createFunction();
99
+ // create TwiML App
100
+ this.twimlApp = yield this.createTwimlApp();
101
+ // create JWT Access Token with ChatGrant, VoiceGrant and SyncGrant
102
+ this.jwt = yield this.createJwt();
103
+ // add webhook config to the phone number, if there is one passed by CLI flag
104
+ // TO-DO return updated phone number and set this.phoneNumber
105
+ const phoneNumberProps = { voiceUrl: this.voiceUrl, smsUrl: this.smsUrl, statusCallback: this.statusCallback };
106
+ this.cliSettings.phoneNumber = yield (0, phone_number_utils_1.updatePhoneWebhooks)(this.cliSettings.phoneNumber, this.twilioClient.incomingPhoneNumbers, phoneNumberProps);
107
+ const onShutdown = () => __awaiter(this, void 0, void 0, function* () {
108
+ yield this.destroyConversations();
109
+ yield this.destroyTwimlApps();
110
+ yield this.destroyApiKeys();
111
+ yield this.destroySyncs();
112
+ yield this.destroyFunction();
113
+ yield (0, phone_number_utils_1.removePhoneWebhooks)(this.cliSettings.phoneNumber, this.twilioClient.incomingPhoneNumbers);
114
+ });
115
+ process.on("SIGTERM", function () {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ console.log("\n👋 Shutting down");
118
+ yield onShutdown().finally(() => process.exit(0));
119
+ });
120
+ });
121
+ process.on('SIGINT', function () {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ console.log("\n👋 Shutting down");
124
+ yield onShutdown().finally(() => process.exit(0));
125
+ });
126
+ });
127
+ const app = (0, express_1.default)();
128
+ // serve assets from the "public" directory
129
+ // __dirname is the path to _this_ file, so ../../public to find index.html
130
+ app.use(express_1.default.static(WebClientPath));
131
+ app.use(express_1.default.json()); // response body writer
132
+ app.get("/ping", (req, res) => {
133
+ res.json({ pong: true });
134
+ console.log('TWILIO', this.twilioClient);
135
+ });
136
+ app.get("/plugin-settings", (req, res) => {
137
+ res.json(Object.assign(Object.assign({}, this.cliSettings), { devPhoneName: this.devPhoneName, conversation: this.conversation }));
138
+ });
139
+ app.get("/phone-numbers", (req, res) => __awaiter(this, void 0, void 0, function* () {
140
+ if (this.pns.length === 0) {
141
+ try {
142
+ const pns = yield this.twilioClient.incomingPhoneNumbers.list();
143
+ this.pns = pns;
144
+ res.json(reformatTwilioPns(pns));
145
+ }
146
+ catch (err) {
147
+ console.error('Phone number API threw an error', err);
148
+ res.status(err.status ? err.status : 400).send({ error: err });
149
+ }
150
+ }
151
+ else {
152
+ res.json(reformatTwilioPns(this.pns));
153
+ }
154
+ }));
155
+ app.post("/send-sms", (req, res) => __awaiter(this, void 0, void 0, function* () {
156
+ const { body, from, to } = req.body;
157
+ try {
158
+ const message = yield this.twilioClient.messages
159
+ .create({
160
+ body,
161
+ from,
162
+ to
163
+ });
164
+ res.json({ result: message });
165
+ }
166
+ catch (err) {
167
+ console.error('SMS API threw an error', err);
168
+ res.status(err.status ? err.status : 400).send({ error: err });
169
+ }
170
+ ;
171
+ }));
172
+ app.all("/choose-phone-number", (req, res) => __awaiter(this, void 0, void 0, function* () {
173
+ try {
174
+ const rawNumbers = yield this.twilioClient.incomingPhoneNumbers
175
+ .list({ phoneNumber: req.body.phoneNumber, limit: 20 });
176
+ const selectedNumber = reformatTwilioPns(rawNumbers)["phone-numbers"];
177
+ // Should only have a single number
178
+ if (selectedNumber.length === 1) {
179
+ yield (0, phone_number_utils_1.removePhoneWebhooks)(this.cliSettings.phoneNumber, this.twilioClient.incomingPhoneNumbers);
180
+ this.cliSettings.phoneNumber = selectedNumber[0];
181
+ this.cliSettings.phoneNumber = yield (0, phone_number_utils_1.updatePhoneWebhooks)(this.cliSettings.phoneNumber, this.twilioClient.incomingPhoneNumbers, { voiceUrl: this.voiceUrl, smsUrl: this.smsUrl, statusCallback: this.statusCallback });
182
+ res.json({
183
+ phoneNumber: this.cliSettings.phoneNumber,
184
+ message: 'Phone number updated!'
185
+ });
186
+ }
187
+ else {
188
+ console.error('Phone number not found!');
189
+ res.status(400).send({
190
+ message: 'Phone number not found!'
191
+ });
192
+ }
193
+ }
194
+ catch (err) {
195
+ console.error(err);
196
+ res.status(400).send(err);
197
+ }
198
+ }));
199
+ app.get("/client-token", (req, res) => __awaiter(this, void 0, void 0, function* () {
200
+ try {
201
+ if (!this.jwt) {
202
+ this.jwt = yield this.createJwt();
203
+ }
204
+ res.json({ token: this.jwt });
205
+ }
206
+ catch (err) {
207
+ res.status(400).send(err);
208
+ }
209
+ }));
210
+ const isHeadless = () => !!this.flags.headless;
211
+ app.listen(this.port, () => {
212
+ console.log(`🚀 Your local webserver is listening on port ${this.port}`);
213
+ if (fs_1.default.existsSync(path_1.default.join(WebClientPath, 'index.html'))) {
214
+ const uiUrl = `http://localhost:${this.port}/`;
215
+ if (isHeadless()) {
216
+ console.log(`🌐 UI is available at ${uiUrl}`);
217
+ }
218
+ else {
219
+ console.log(`🌐 Opening ${uiUrl} your browser`);
220
+ (0, open_1.default)(uiUrl);
221
+ }
222
+ }
223
+ else {
224
+ console.log('Hello friend! Front end files are missing, ie you are developing this pluign.');
225
+ console.log('Run: `cd plugin-dev-phone-client` then `npm start` to run dev front-end');
226
+ console.log('To build the front-end so that the local backend will serve it: ./build-for-release.sh');
227
+ }
228
+ console.log('▶️ Use ctrl-c to stop your dev-phone\n');
229
+ });
230
+ });
231
+ }
232
+ createFunction() {
233
+ return __awaiter(this, void 0, void 0, function* () {
234
+ console.log('💻 Deploying a Functions Service to handle incoming calls and SMS...');
235
+ const deployedFunctions = yield (0, create_serverless_util_1.deployServerless)({
236
+ username: this.twilioClient.username,
237
+ password: this.twilioClient.password,
238
+ env: {
239
+ SYNC_SERVICE_SID: this.sync.sid,
240
+ CONVERSATION_SID: this.conversation.sid,
241
+ CONVERSATION_SERVICE_SID: this.conversation.serviceSid,
242
+ DEV_PHONE_NAME: this.devPhoneName,
243
+ DEV_PHONE_VERSION: version,
244
+ CALL_LOG_MAP_NAME
245
+ },
246
+ onUpdate: (event) => {
247
+ const isBuildStatusPing = event.message.indexOf('Current status: building') > -1;
248
+ const settingEnvVars = event.message.indexOf('environment variables') > -1;
249
+ if (isBuildStatusPing || event.status === 'building') {
250
+ isBuildStatusPing ? process.stdout.write('.') : process.stdout.write(`🛠 ${event.message}`);
251
+ }
252
+ else {
253
+ console.log(`${settingEnvVars ? '\n' : ''}🧑‍💻 ${event.message}`);
254
+ }
255
+ }
256
+ });
257
+ console.log(`✅ I'm using the Serverless Service ${deployedFunctions.serviceSid}\n`);
258
+ this.voiceUrl = `https://${deployedFunctions.domain}/${create_serverless_util_1.constants.INCOMING_CALL_HANDLER}`;
259
+ this.voiceOutboundUrl = `https://${deployedFunctions.domain}/${create_serverless_util_1.constants.OUTBOUND_CALL_HANDLER}`;
260
+ this.smsUrl = `https://${deployedFunctions.domain}/${create_serverless_util_1.constants.INCOMING_MESSAGE_HANDLER}`;
261
+ this.statusCallback = `https://${deployedFunctions.domain}/${create_serverless_util_1.constants.SYNC_CALL_HISTORY}`;
262
+ return deployedFunctions;
263
+ });
264
+ }
265
+ destroyFunction() {
266
+ return __awaiter(this, void 0, void 0, function* () {
267
+ try {
268
+ const functionServices = yield this.twilioClient.serverless.v1.services.list();
269
+ const devPhoneFunctionServices = functionServices.filter((functionServices) => {
270
+ return functionServices.friendlyName !== null && functionServices.friendlyName.startsWith(this.devPhoneName);
271
+ });
272
+ if (devPhoneFunctionServices.length > 0) {
273
+ console.log(`🚮 Removing Serverless Functions for ${this.devPhoneName}`);
274
+ for (const functionService of devPhoneFunctionServices) {
275
+ yield this.twilioClient.serverless.v1.services(functionService.sid)
276
+ .remove();
277
+ }
278
+ }
279
+ }
280
+ catch (err) {
281
+ console.error(err);
282
+ }
283
+ });
284
+ }
285
+ destroyAllFunctions() {
286
+ return __awaiter(this, void 0, void 0, function* () {
287
+ try {
288
+ const functionServices = yield this.twilioClient.serverless.v1.services.list();
289
+ const devPhoneFunctionServices = functionServices.filter((functionServices) => {
290
+ return functionServices.friendlyName !== null && functionServices.friendlyName.startsWith('dev-phone');
291
+ });
292
+ if (devPhoneFunctionServices.length > 0) {
293
+ console.log(`🚮 Removing All Serverless Functions for existing dev phone`);
294
+ for (const functionService of devPhoneFunctionServices) {
295
+ yield this.twilioClient.serverless.v1.services(functionService.sid)
296
+ .remove();
297
+ }
298
+ }
299
+ }
300
+ catch (err) {
301
+ console.error(err);
302
+ }
303
+ });
304
+ }
305
+ validatePropsAndFlags(props, flags) {
306
+ return __awaiter(this, void 0, void 0, function* () {
307
+ // Flags defined below can be validated and used here. Example:
308
+ // https://github.com/twilio/plugin-debugger/blob/main/src/commands/debugger/logs/list.js#L46-L56
309
+ this.cliSettings.forceMode = flags['force'];
310
+ this.port = process.env.TWILIO_DEV_PHONE_PORT || (yield (0, helpers_1.getAvailablePort)());
311
+ if (flags['phone-number']) {
312
+ const phoneNumber = yield flags['phone-number'];
313
+ this.pns = yield this.twilioClient.incomingPhoneNumbers
314
+ .list({ phoneNumber: phoneNumber });
315
+ if (this.pns.length < 1) {
316
+ throw new TwilioCliError(`The phone number ${phoneNumber} is not associated with your Twilio account`);
317
+ }
318
+ const pnConfigAlreadySet = [
319
+ ((0, phone_number_utils_1.isSmsUrlSet)(this.pns[0].smsUrl) ? "SMS webhook URL" : null),
320
+ ((0, phone_number_utils_1.isVoiceUrlSet)(this.pns[0].voiceUrl) ? "Voice webhook URL" : null),
321
+ ].filter(x => x);
322
+ if (pnConfigAlreadySet.length > 0 && !this.cliSettings.forceMode) {
323
+ throw new TwilioCliError(`Cannot use ${phoneNumber} because the following config for that phone number would be overwritten: ` + pnConfigAlreadySet.join(", "));
324
+ }
325
+ this.cliSettings.phoneNumber = reformatTwilioPns(this.pns)["phone-numbers"][0];
326
+ }
327
+ if (flags['port']) {
328
+ const port = yield flags['port'];
329
+ try {
330
+ if ((0, helpers_1.isValidPort)(port)) {
331
+ this.port = parseInt(port);
332
+ }
333
+ else {
334
+ throw new TwilioCliError(`❗️ '${port}' is not a valid port. 😳 I'll try to get set up with ${this.port} instead.`);
335
+ }
336
+ }
337
+ catch (err) {
338
+ console.error(err.message);
339
+ }
340
+ }
341
+ });
342
+ }
343
+ twilioCliIsConfiguredWithApiKey() {
344
+ return this.currentProfile.apiKey.startsWith("SK");
345
+ }
346
+ reuseOrCreateApiKey() {
347
+ return __awaiter(this, void 0, void 0, function* () {
348
+ // We need an API KEY and SECRET to create the Access Token
349
+ // Depending on how the user has provided the CLI with creds
350
+ // we may have one already in this.currentProfile, or we may
351
+ // need to create a new one
352
+ if (this.twilioCliIsConfiguredWithApiKey()) {
353
+ // This case is if the user has _not_ used env vars for
354
+ // their creds. Here we can reuse the api keys and secret
355
+ // that the CLI created when it was installed
356
+ console.log("✅ I'm using your profile API key.\n");
357
+ return {
358
+ sid: this.currentProfile.apiKey,
359
+ secret: this.currentProfile.apiSecret
360
+ };
361
+ }
362
+ else {
363
+ // This case is if the user has started the CLI with
364
+ // $TWILIO_ACCOUNT_SID and $TWILIO_AUTH_TOKEN set in
365
+ // their environment, using their account creds but
366
+ // their API_KEY and SECRET are not properly set.
367
+ // the CLI uses the ACCOUNT_SID into currentProfile.apiKey
368
+ // and we need to generate another key
369
+ console.log("💻 I'm creating a new API Key...");
370
+ yield this.destroyApiKeys();
371
+ try {
372
+ const key = yield this.twilioClient.newKeys.create({ friendlyName: this.devPhoneName });
373
+ const mask = (value) => {
374
+ if (!value)
375
+ return "";
376
+ const last4 = value.slice(-4);
377
+ return `${"*".repeat(value.length - 4)}${last4}`;
378
+ };
379
+ console.log(`✅ I'm using the API Key ${mask(key.sid)}\n`);
380
+ this.currentProfile.apiKey = key.sid;
381
+ this.currentProfile.apiSecret = key.secret;
382
+ return {
383
+ sid: this.currentProfile.apiKey,
384
+ secret: this.currentProfile.apiSecret
385
+ };
386
+ }
387
+ catch (err) {
388
+ console.error(err);
389
+ }
390
+ }
391
+ });
392
+ }
393
+ destroyApiKeys() {
394
+ return __awaiter(this, void 0, void 0, function* () {
395
+ if (this.twilioCliIsConfiguredWithApiKey()) {
396
+ // we never created one
397
+ return;
398
+ }
399
+ else {
400
+ try {
401
+ const keys = yield this.twilioClient.keys.list();
402
+ const devPhoneKeys = keys.filter((key) => {
403
+ return key.friendlyName !== null && key.friendlyName.startsWith(this.devPhoneName);
404
+ });
405
+ if (devPhoneKeys.length > 0) {
406
+ console.log(`🚮 Removing API Keys for ${this.devPhoneName}`);
407
+ for (const key of devPhoneKeys) {
408
+ yield this.twilioClient.keys(key.sid).remove();
409
+ }
410
+ }
411
+ }
412
+ catch (err) {
413
+ console.error(err);
414
+ }
415
+ }
416
+ });
417
+ }
418
+ destroyAllApiKeys() {
419
+ return __awaiter(this, void 0, void 0, function* () {
420
+ if (this.twilioCliIsConfiguredWithApiKey()) {
421
+ // we never created one
422
+ return;
423
+ }
424
+ else {
425
+ try {
426
+ const keys = yield this.twilioClient.keys.list();
427
+ const devPhoneKeys = keys.filter((key) => {
428
+ return key.friendlyName !== null && key.friendlyName.startsWith('dev-phone');
429
+ });
430
+ if (devPhoneKeys.length > 0) {
431
+ console.log(`🚮 Removing All API Keys for existing dev phone`);
432
+ for (const key of devPhoneKeys) {
433
+ yield this.twilioClient.keys(key.sid).remove();
434
+ }
435
+ }
436
+ }
437
+ catch (err) {
438
+ console.error(err);
439
+ }
440
+ }
441
+ });
442
+ }
443
+ createTwimlApp() {
444
+ return __awaiter(this, void 0, void 0, function* () {
445
+ console.log('💻 Creating a new TwiMl App to allow voice calls from your browser...');
446
+ yield this.destroyTwimlApps();
447
+ try {
448
+ const app = yield this.twilioClient.applications
449
+ .create({
450
+ voiceUrl: this.voiceOutboundUrl,
451
+ friendlyName: this.devPhoneName
452
+ });
453
+ console.log(`✅ I'm using the TwiMl App ${app.sid}\n`);
454
+ return app;
455
+ }
456
+ catch (err) {
457
+ console.error(err);
458
+ }
459
+ });
460
+ }
461
+ destroyTwimlApps() {
462
+ return __awaiter(this, void 0, void 0, function* () {
463
+ try {
464
+ const applications = yield this.twilioClient.applications.list();
465
+ const devPhoneApps = applications.filter((twimlApp) => {
466
+ return twimlApp.friendlyName !== null && twimlApp.friendlyName.startsWith(this.devPhoneName);
467
+ });
468
+ if (devPhoneApps.length > 0) {
469
+ console.log(`🚮 Removing TwiML app for ${this.devPhoneName}`);
470
+ for (const twimlApp of devPhoneApps) {
471
+ yield this.twilioClient.applications(twimlApp.sid)
472
+ .remove();
473
+ }
474
+ }
475
+ }
476
+ catch (err) {
477
+ console.error(err);
478
+ }
479
+ });
480
+ }
481
+ destroyAllTwimlApps() {
482
+ return __awaiter(this, void 0, void 0, function* () {
483
+ try {
484
+ const applications = yield this.twilioClient.applications.list();
485
+ const devPhoneApps = applications.filter((twimlApp) => {
486
+ return twimlApp.friendlyName !== null && twimlApp.friendlyName.startsWith('dev-phone');
487
+ });
488
+ if (devPhoneApps.length > 0) {
489
+ console.log(`🚮 Removing All TwiML app for existing dev phone`);
490
+ for (const twimlApp of devPhoneApps) {
491
+ yield this.twilioClient.applications(twimlApp.sid)
492
+ .remove();
493
+ }
494
+ }
495
+ }
496
+ catch (err) {
497
+ console.error(err);
498
+ }
499
+ });
500
+ }
501
+ createJwt() {
502
+ return __awaiter(this, void 0, void 0, function* () {
503
+ const chatGrant = new ChatGrant({
504
+ serviceSid: this.conversation.serviceSid
505
+ });
506
+ const voiceGrant = new VoiceGrant({
507
+ incomingAllow: true,
508
+ outgoingApplicationSid: this.twimlApp.sid
509
+ });
510
+ const syncGrant = new SyncGrant({
511
+ serviceSid: this.sync.sid,
512
+ });
513
+ const token = new AccessToken(this.twilioClient.accountSid, this.apikey.sid, this.apikey.secret, {
514
+ identity: this.devPhoneName,
515
+ ttl: 24 * 60 * 60
516
+ });
517
+ token.addGrant(chatGrant);
518
+ token.addGrant(voiceGrant);
519
+ token.addGrant(syncGrant);
520
+ return token.toJwt();
521
+ });
522
+ }
523
+ createSync() {
524
+ return __awaiter(this, void 0, void 0, function* () {
525
+ console.log('💻 Creating a new sync list for call history...');
526
+ yield this.destroySyncs();
527
+ try {
528
+ const syncService = yield this.twilioClient.sync.v1.services
529
+ .create({ friendlyName: this.devPhoneName });
530
+ console.log(`✅ I'm using the sync service ${syncService.sid}\n`);
531
+ // create 'CallLog' syncMap
532
+ yield this.twilioClient.sync.v1.services(syncService.sid).syncMaps.create({
533
+ uniqueName: CALL_LOG_MAP_NAME,
534
+ });
535
+ return syncService;
536
+ }
537
+ catch (err) {
538
+ console.error(err);
539
+ }
540
+ });
541
+ }
542
+ destroySyncs() {
543
+ return __awaiter(this, void 0, void 0, function* () {
544
+ try {
545
+ const syncServices = yield this.twilioClient.sync.v1.services.list();
546
+ const devPhoneSyncServices = syncServices.filter((syncService) => {
547
+ return syncService.friendlyName !== null && syncService.friendlyName.startsWith(this.devPhoneName);
548
+ });
549
+ if (devPhoneSyncServices.length > 0) {
550
+ console.log(`🚮 Removing Sync Service for ${this.devPhoneName}`);
551
+ for (const syncService of devPhoneSyncServices) {
552
+ yield this.twilioClient.sync.v1.services(syncService.sid)
553
+ .remove();
554
+ }
555
+ }
556
+ }
557
+ catch (err) {
558
+ console.error(err);
559
+ }
560
+ });
561
+ }
562
+ destroyAllSyncs() {
563
+ return __awaiter(this, void 0, void 0, function* () {
564
+ try {
565
+ const syncServices = yield this.twilioClient.sync.v1.services.list();
566
+ const devPhoneSyncServices = syncServices.filter((syncService) => {
567
+ return syncService.friendlyName !== null && syncService.friendlyName.startsWith('dev-phone');
568
+ });
569
+ if (devPhoneSyncServices.length > 0) {
570
+ console.log(`🚮 Removing All Sync Service for existing dev phone`);
571
+ for (const syncService of devPhoneSyncServices) {
572
+ yield this.twilioClient.sync.v1.services(syncService.sid)
573
+ .remove();
574
+ }
575
+ }
576
+ }
577
+ catch (err) {
578
+ console.error(err);
579
+ }
580
+ });
581
+ }
582
+ // Creates a new conversation service, a conversation, and makes the dev phone a participant
583
+ createConversation() {
584
+ return __awaiter(this, void 0, void 0, function* () {
585
+ yield this.destroyConversations();
586
+ console.log('💻 Creating a new conversation...');
587
+ try {
588
+ const service = yield this.twilioClient.conversations.v1.services
589
+ .create({ friendlyName: this.devPhoneName });
590
+ const conversationService = this.twilioClient.conversations.v1.services(service.sid);
591
+ const newConversation = yield conversationService.conversations.create({ friendlyName: this.devPhoneName });
592
+ yield conversationService.conversations(newConversation.sid)
593
+ .participants.create({ identity: this.devPhoneName });
594
+ console.log(`✅ I'm using the conversation ${newConversation.sid} from service ${service.sid}\n`);
595
+ return {
596
+ serviceSid: service.sid,
597
+ sid: newConversation.sid
598
+ };
599
+ }
600
+ catch (err) {
601
+ console.error(err);
602
+ }
603
+ });
604
+ }
605
+ destroyConversations() {
606
+ return __awaiter(this, void 0, void 0, function* () {
607
+ try {
608
+ const convoServices = yield this.twilioClient.conversations.v1.services.list();
609
+ const devPhoneConvoServices = convoServices.filter((convoService) => {
610
+ return convoService.friendlyName !== null && convoService.friendlyName.startsWith(this.devPhoneName);
611
+ });
612
+ if (devPhoneConvoServices.length > 0) {
613
+ console.log(`🚮 Removing Conversation Service for ${this.devPhoneName}`);
614
+ for (const convoService of devPhoneConvoServices) {
615
+ yield this.twilioClient.conversations.v1.services(convoService.sid)
616
+ .remove();
617
+ }
618
+ }
619
+ }
620
+ catch (err) {
621
+ console.error(err);
622
+ }
623
+ });
624
+ }
625
+ destroyAllConversations() {
626
+ return __awaiter(this, void 0, void 0, function* () {
627
+ try {
628
+ const convoServices = yield this.twilioClient.conversations.v1.services.list();
629
+ const devPhoneConvoServices = convoServices.filter((convoService) => {
630
+ return convoService.friendlyName !== null && convoService.friendlyName.startsWith('dev-phone');
631
+ });
632
+ if (devPhoneConvoServices.length > 0) {
633
+ console.log(`🚮 Removing All Conversation Service for existing dev phone`);
634
+ for (const convoService of devPhoneConvoServices) {
635
+ yield this.twilioClient.conversations.v1.services(convoService.sid)
636
+ .remove();
637
+ }
638
+ }
639
+ }
640
+ catch (err) {
641
+ console.error(err);
642
+ }
643
+ });
644
+ }
645
+ removeAllPhoneWebhooks() {
646
+ return __awaiter(this, void 0, void 0, function* () {
647
+ try {
648
+ const pns = yield this.twilioClient.incomingPhoneNumbers.list();
649
+ const numbersDevPhone = pns.filter((pn) => {
650
+ return pn.smsUrl.startsWith('https://dev-phone') && pn.voiceUrl.startsWith('https://dev-phone');
651
+ });
652
+ if (numbersDevPhone.length > 0) {
653
+ console.log(`🚮 Removing All number webhooks for dev phone`);
654
+ for (const pn of numbersDevPhone) {
655
+ yield (0, phone_number_utils_1.removePhoneWebhooks)({
656
+ voiceUrl: '',
657
+ smsUrl: '',
658
+ statusCallback: '',
659
+ phoneNumber: pn.phoneNumber,
660
+ sid: pn.sid,
661
+ }, this.twilioClient.incomingPhoneNumbers);
662
+ }
663
+ }
664
+ }
665
+ catch (err) {
666
+ console.error(err);
667
+ }
668
+ });
669
+ }
670
+ }
671
+ DevPhoneServer.description = `Dev Phone local express server`;
672
+ // Example of how to define flags and properties:
673
+ // https://github.com/twilio/plugin-debugger/blob/main/src/commands/debugger/logs/list.js#L99-L126
674
+ DevPhoneServer.PropertyFlags = {
675
+ "phone-number": core_1.Flags.string({
676
+ description: 'Optional. Associates the Dev Phone with a phone number. Takes a number from the active profile on the Twilio CLI as the parameter.'
677
+ }),
678
+ force: core_1.Flags.boolean({
679
+ char: 'f',
680
+ description: 'Optional. Forces an overwrite of the phone number configuration.',
681
+ dependsOn: ['phone-number']
682
+ }),
683
+ headless: core_1.Flags.boolean({
684
+ description: 'Optional. Prevents the UI from automatically opening in the browser.',
685
+ default: false,
686
+ }),
687
+ clear: core_1.Flags.boolean({
688
+ description: 'Optional. Remove all dev-phone resources from your account before starting the dev-phone.',
689
+ default: false,
690
+ }),
691
+ port: core_1.Flags.string({
692
+ description: 'Optional. Configures the port of the Dev Phone UI. Takes a valid port as a parameter.',
693
+ })
694
+ };
695
+ DevPhoneServer.flags = Object.assign(DevPhoneServer.PropertyFlags, TwilioClientCommand.flags);
696
+ module.exports = DevPhoneServer;