Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates Voice Client Quickstart to support incoming number #196

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion voice-client-javascript/.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ APP_NAME=voice-client-javascript
# description: Set a password for your app. Users who want to use the admin interface will have to use this password to access it
# format: text
# required: true
ADMIN_PASSWORD=default
ADMIN_PASSWORD=default

# description: The Client username to wire up the examples and incoming number
# format: text
# required: true
DEFAULT_CLIENT_NAME=alice

# description: The Incoming Twilio Number to wire up to your Client
# format: phone_number
# required: true
TWILIO_PHONE_NUMBER=
71 changes: 59 additions & 12 deletions voice-client-javascript/assets/admin/actions.private.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const assets = Runtime.getAssets();
const { urlForSiblingPage } = require(assets["/admin/shared.js"].path);

const DEFAULT_TWILIO_WEBHOOK = "https://demo.twilio.com/welcome/voice/";

class Actions {
constructor(client, options) {
Expand All @@ -15,21 +16,34 @@ class Actions {
env = Object.assign(env, results);
const voiceUrl = `https://${this.options.DOMAIN_NAME}${urlForSiblingPage(
"client-voice-twiml-app",
this.options.PATH,
"..")}`;
this.options.PATH,
".."
)}`;
console.log(
`Wiring up TwiML Application ${env.TWIML_APPLICATION_SID} to the function: ${voiceUrl}`
);
await this.updateTwimlAppVoiceUrl({
twimlApplicationSid: env.TWIML_APPLICATION_SID,
voiceUrl
voiceUrl,
});
console.log("Generating new REST API Key");
results = await this.generateNewKey(this.options);
env = Object.assign(env, results);
const number = await this.chooseLogicalCallerId();
results = await this.setCallerId({ number });
env = Object.assign(env, results);
const incomingNumber = await this.chooseLogicalIncomingNumber(this.options.CHOSEN_TWILIO_NUMBER);
if (incomingNumber !== undefined) {
results = await this.updateIncomingNumber({
sid: incomingNumber.sid,
voiceApplicationSid: env.TWIML_APPLICATION_SID,
});
env = Object.assign(env, results);
results = await this.setCallerId({ number: incomingNumber.phoneNumber });
env = Object.assign(env, results);
} else {
// Did not find an incoming number, so we'll just choose a number
const callerIdNumber = await this.chooseLogicalCallerId();
results = await this.setCallerId({ number: callerIdNumber });
env = Object.assign(env, results);
}
env.INITIALIZED = "voice-client-quickstart";
env.INITIALIZATION_DATE = new Date().toISOString();
return env;
Expand All @@ -38,7 +52,7 @@ class Actions {
async createTwimlApp({ friendlyName }) {
const result = await this.client.applications.create({ friendlyName });
return {
TWIML_APPLICATION_SID: result.sid
TWIML_APPLICATION_SID: result.sid,
};
}

Expand All @@ -52,20 +66,32 @@ class Actions {
const result = await this.client.newKeys.create({ friendlyName });
return {
API_KEY: result.sid,
API_SECRET: result.secret
API_SECRET: result.secret,
};
}

async chooseLogicalIncomingNumber(preferredNumber) {
const incomingNumbers = await this.client.incomingPhoneNumbers.list();
if (preferredNumber !== undefined) {
return incomingNumbers.find(number => number.phoneNumber === preferredNumber);
}
return incomingNumbers.find(
(number) =>
number.voiceUrl === DEFAULT_TWILIO_WEBHOOK || number.voiceUrl === ""
);
}

async chooseLogicalCallerId() {
// This is only needed if we don't get a logical incoming number
const incomingNumbers = await this.client.incomingPhoneNumbers.list({
limit: 1
limit: 1,
});
let number;
if (incomingNumbers.length > 0) {
number = incomingNumbers[0].phoneNumber;
} else {
const outgoingCallerIds = await this.client.outgoingCallerIds.list({
limit: 1
limit: 1,
});
// Required right?
number = outgoingCallerIds[0].phoneNumber;
Expand All @@ -75,13 +101,34 @@ class Actions {

async setCallerId({ number }) {
return {
CALLER_ID: number
CALLER_ID: number,
};
}

async clearCallerId() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this function setCallerId, updateIncomingNumber, and useExistingTwimlApp all async? None of them seem to be making any network/async requests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I call these from the UI, it is done dynamically. As I am a async/await n00b, do you know what happens if I await a non-async function? I was considering asynchronous behavior as part of the "loose Interface" of an Action (a function that does something on your behalf and returns a dictionary of environment variable name to value).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since these aren't truly async functions, if you rewrote the method to be a standard function, then you can also remove the need to call it with await.

The pattern makes sense for chooseLogicalCallerId but just because one of the functions uses the asyc/await behavior, that doesn't mean everything needs to have the same interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry I should've been more clear, I meant it's called generically here:

https://github.com/craigsdennis/function-templates/blob/main/voice-client-javascript/functions/admin/perform-action.js#L24

I can just see what happens if I pop a regular function through...I'm assuming I got burned originally.

return {
CALLER_ID: undefined,
};
}

async updateIncomingNumber({ sid, voiceApplicationSid }) {
const incomingNumber = await this.client.incomingPhoneNumbers(sid).update({
voiceApplicationSid,
});
return {
INCOMING_NUMBER: incomingNumber.phoneNumber,
};
}

async clearIncomingNumber() {
return {
INCOMING_NUMBER: undefined,
};
}

async useExistingTwimlApp({ twimlApplicationSid }) {
return {
TWIML_APPLICATION_SID: twimlApplicationSid
TWIML_APPLICATION_SID: twimlApplicationSid,
};
}
}
Expand Down
10 changes: 7 additions & 3 deletions voice-client-javascript/assets/admin/shared.private.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ async function getEnvironmentVariables(context, environment) {
}

async function getEnvironmentVariable(context, environment, key) {
const client = context.getTwilioClient();
// The list filter method isn't implemented yet.
const envVars = await getEnvironmentVariables(context, environment);
return envVars.find(variable => variable.key === key);
Expand All @@ -78,8 +77,13 @@ async function setEnvironmentVariable(context, environment, key, value, override
if (currentVariable) {
if (currentVariable.value !== value) {
if (override) {
console.log(`Updating ${key}...`);
await currentVariable.update({ value });
if (value === undefined) {
console.log(`Removing ${key}...`);
await currentVariable.remove();
} else {
console.log(`Updating ${key}...`);
await currentVariable.update({ value });
}
return true;
} else {
console.log(`Not overriding existing variable '${key}' which is set to '${currentVariable.value}'`);
Expand Down
Loading