diff --git a/app/features/keypad/components/keypad.tsx b/app/features/keypad/components/keypad.tsx index 1c7473b..a56285c 100644 --- a/app/features/keypad/components/keypad.tsx +++ b/app/features/keypad/components/keypad.tsx @@ -1,45 +1,51 @@ import { type FunctionComponent, type PropsWithChildren, useRef } from "react"; -import { usePress } from "@react-aria/interactions"; +import { type PressHookProps, usePress } from "@react-aria/interactions"; + import { useLongPressDigit, usePressDigit } from "~/features/keypad/hooks/atoms"; -const Keypad: FunctionComponent> = ({ children }) => { +type Props = { + onDigitPressProps?: (digit: string) => PressHookProps; + onZeroPressProps?: PressHookProps; +}; + +const Keypad: FunctionComponent> = ({ children, onDigitPressProps, onZeroPressProps }) => { return (
- - + + ABC - + DEF - + GHI - + JKL - + MNO - + PQRS - + TUV - + WXYZ - - - + + + {typeof children !== "undefined" ? {children} : null}
@@ -58,27 +64,32 @@ const DigitLetters: FunctionComponent> = ({ children }) => type DigitProps = { digit: string; + onPressProps: Props["onDigitPressProps"]; }; -const Digit: FunctionComponent> = ({ children, digit }) => { +const Digit: FunctionComponent> = (props) => { const pressDigit = usePressDigit(); const onPressProps = { onPress() { // navigator.vibrate(1); // removed in webkit - pressDigit(digit); + pressDigit(props.digit); }, }; - const { pressProps } = usePress(onPressProps); + const { pressProps } = usePress(props.onPressProps?.(props.digit) ?? onPressProps); return (
- {digit} - {children} + {props.digit} + {props.children}
); }; -const ZeroDigit: FunctionComponent = () => { +type ZeroDigitProps = { + onPressProps: Props["onZeroPressProps"]; +}; + +const ZeroDigit: FunctionComponent = (props) => { const timeoutRef = useRef | null>(null); const pressDigit = usePressDigit(); const longPressDigit = useLongPressDigit(); @@ -96,7 +107,7 @@ const ZeroDigit: FunctionComponent = () => { } }, }; - const { pressProps } = usePress(onPressProps); + const { pressProps } = usePress(props.onPressProps ?? onPressProps); return (
diff --git a/app/features/phone-calls/hooks/use-device.ts b/app/features/phone-calls/hooks/use-device.ts new file mode 100644 index 0000000..c95621f --- /dev/null +++ b/app/features/phone-calls/hooks/use-device.ts @@ -0,0 +1,110 @@ +import { useEffect, useState } from "react"; +import { type TwilioError, Call, Device } from "@twilio/voice-sdk"; +import { useFetcher } from "@remix-run/react"; + +import type { TwilioTokenLoaderData } from "~/features/phone-calls/loaders/twilio-token"; + +export default function useDevice() { + const jwt = useDeviceToken(); + const [device, setDevice] = useState(null); + const [isDeviceReady, setIsDeviceReady] = useState(() => device?.state === Device.State.Registered); + + useEffect(() => { + jwt.refresh(); + }, []); + + useEffect(() => { + if (jwt.token && device?.state === Device.State.Registered && device?.token !== jwt.token) { + device.updateToken(jwt.token); + } + }, [jwt.token, device]); + + useEffect(() => { + if (!jwt.token || device?.state === Device.State.Registered) { + return; + } + + const newDevice = new Device(jwt.token, { + codecPreferences: [Call.Codec.Opus, Call.Codec.PCMU], + sounds: { + [Device.SoundName.Disconnect]: undefined, // TODO + }, + }); + newDevice.register(); // TODO throwing an error + setDevice(newDevice); + }, [device?.state, jwt.token, setDevice]); + + useEffect(() => { + if (!device) { + return; + } + + device.on("registered", onDeviceRegistered); + device.on("unregistered", onDeviceUnregistered); + device.on("error", onDeviceError); + device.on("incoming", onDeviceIncoming); + device.on("tokenWillExpire", onTokenWillExpire); + + return () => { + if (typeof device.off !== "function") { + return; + } + + device.off("registered", onDeviceRegistered); + device.off("unregistered", onDeviceUnregistered); + device.off("error", onDeviceError); + device.off("incoming", onDeviceIncoming); + device.off("tokenWillExpire", onTokenWillExpire); + }; + }, [device]); + + return { + device, + isDeviceReady, + }; + + function onTokenWillExpire() { + jwt.refresh(); + } + + function onDeviceRegistered() { + setIsDeviceReady(true); + } + + function onDeviceUnregistered() { + setIsDeviceReady(false); + } + + function onDeviceError(error: TwilioError.TwilioError, call?: Call) { + console.log("error", error); + // we might have to change this if we instantiate the device on every page to receive calls + setDevice(() => { + // hack to trigger the error boundary + throw error; + }); + } + + function onDeviceIncoming(call: Call) { + // TODO show alert to accept/reject the incoming call /!\ it should persist between screens /!\ prevent making a new call when there is a pending incoming call + console.log("call", call); + console.log("Incoming connection from " + call.parameters.From); + let archEnemyPhoneNumber = "+12093373517"; + + if (call.parameters.From === archEnemyPhoneNumber) { + call.reject(); + console.log("It's your nemesis. Rejected call."); + } else { + // accept the incoming connection and start two-way audio + call.accept(); + } + } +} + +function useDeviceToken() { + const fetcher = useFetcher(); + + return { + token: fetcher.data, + refresh: () => fetcher.load("/outgoing-call/twilio-token"), + }; +} diff --git a/app/features/phone-calls/hooks/use-make-call.ts b/app/features/phone-calls/hooks/use-make-call.ts new file mode 100644 index 0000000..d98be58 --- /dev/null +++ b/app/features/phone-calls/hooks/use-make-call.ts @@ -0,0 +1,100 @@ +import { useCallback, useState } from "react"; +import { useNavigate } from "@remix-run/react"; +import type { Call } from "@twilio/voice-sdk"; + +import useDevice from "./use-device"; + +type Params = { + recipient: string; + onHangUp?: () => void; +}; + +export default function useMakeCall({ recipient, onHangUp }: Params) { + const navigate = useNavigate(); + const [outgoingConnection, setOutgoingConnection] = useState(null); + const [state, setState] = useState("initial"); + const { device, isDeviceReady } = useDevice(); + + const endCall = useCallback( + function endCall() { + outgoingConnection?.off("cancel", endCall); + outgoingConnection?.off("disconnect", endCall); + outgoingConnection?.disconnect(); + + setState("call_ending"); + setTimeout(() => { + setState("call_ended"); + setTimeout(() => navigate("/keypad"), 100); + }, 150); + }, + [outgoingConnection, navigate], + ); + + const makeCall = useCallback( + async function makeCall() { + if (!device || !isDeviceReady) { + console.warn("device is not ready yet, can't make the call"); + return; + } + + if (state !== "initial") { + return; + } + + if (device.isBusy) { + console.error("device is busy, this shouldn't happen"); + return; + } + + setState("calling"); + + const params = { To: recipient }; + const outgoingConnection = await device.connect({ params }); + setOutgoingConnection(outgoingConnection); + + outgoingConnection.on("error", (error) => { + outgoingConnection.off("cancel", endCall); + outgoingConnection.off("disconnect", endCall); + setState(() => { + // hack to trigger the error boundary + throw error; + }); + }); + outgoingConnection.once("accept", (call: Call) => setState("call_in_progress")); + outgoingConnection.on("cancel", endCall); + outgoingConnection.on("disconnect", endCall); + }, + [device, isDeviceReady, recipient, state], + ); + + const sendDigits = useCallback( + function sendDigits(digits: string) { + return outgoingConnection?.sendDigits(digits); + }, + [outgoingConnection], + ); + + const hangUp = useCallback( + function hangUp() { + setState("call_ending"); + outgoingConnection?.disconnect(); + device?.disconnectAll(); + device?.destroy(); + onHangUp?.(); + navigate("/keypad"); + // TODO: outgoingConnection.off is not a function + outgoingConnection?.off("cancel", endCall); + outgoingConnection?.off("disconnect", endCall); + }, + [device, endCall, onHangUp, outgoingConnection, navigate], + ); + + return { + makeCall, + sendDigits, + hangUp, + state, + }; +} + +type State = "initial" | "ready" | "calling" | "call_in_progress" | "call_ending" | "call_ended"; diff --git a/app/features/phone-calls/loaders/twilio-token.ts b/app/features/phone-calls/loaders/twilio-token.ts new file mode 100644 index 0000000..2d885c2 --- /dev/null +++ b/app/features/phone-calls/loaders/twilio-token.ts @@ -0,0 +1,63 @@ +import { type LoaderFunction } from "@remix-run/node"; +import Twilio from "twilio"; + +import { refreshSessionData, requireLoggedIn } from "~/utils/auth.server"; +import { encrypt } from "~/utils/encryption"; +import db from "~/utils/db.server"; +import { commitSession } from "~/utils/session.server"; +import getTwilioClient from "~/utils/twilio.server"; + +export type TwilioTokenLoaderData = string; + +const loader: LoaderFunction = async ({ request }) => { + const { user, organization, twilioAccount } = await requireLoggedIn(request); + if (!twilioAccount || !twilioAccount.twimlAppSid) { + throw new Error("unreachable"); + } + + const twilioClient = getTwilioClient(twilioAccount); + let shouldRefreshSession = false; + let { apiKeySid, apiKeySecret } = twilioAccount; + if (apiKeySid && apiKeySecret) { + try { + await twilioClient.keys.get(apiKeySid).fetch(); + } catch (error: any) { + if (error.code !== 20404) { + throw error; + } + + apiKeySid = null; + apiKeySecret = null; + } + } + if (!apiKeySid || !apiKeySecret) { + shouldRefreshSession = true; + const apiKey = await twilioClient.newKeys.create({ friendlyName: "Shellphone" }); + apiKeySid = apiKey.sid; + apiKeySecret = apiKey.secret; + await db.twilioAccount.update({ + where: { subAccountSid: twilioAccount.subAccountSid }, + data: { apiKeySid: apiKey.sid, apiKeySecret: encrypt(apiKey.secret) }, + }); + } + + const accessToken = new Twilio.jwt.AccessToken(twilioAccount.subAccountSid, apiKeySid, apiKeySecret, { + identity: `${organization.id}__${user.id}`, + ttl: 3600, + }); + const grant = new Twilio.jwt.AccessToken.VoiceGrant({ + outgoingApplicationSid: twilioAccount.twimlAppSid, + incomingAllow: true, + }); + accessToken.addGrant(grant); + + const headers = new Headers({ "Content-Type": "text/plain" }); + if (shouldRefreshSession) { + const { session } = await refreshSessionData(request); + headers.set("Set-Cookie", await commitSession(session)); + } + + return new Response(accessToken.toJwt(), { headers }); +}; + +export default loader; diff --git a/app/routes/__app/outgoing-call.$recipient.tsx b/app/routes/__app/outgoing-call.$recipient.tsx new file mode 100644 index 0000000..1bbe1d7 --- /dev/null +++ b/app/routes/__app/outgoing-call.$recipient.tsx @@ -0,0 +1,80 @@ +import { useCallback } from "react"; +import type { MetaFunction } from "@remix-run/node"; +import { useParams } from "@remix-run/react"; +import { IoCall } from "react-icons/io5"; + +import { getSeoMeta } from "~/utils/seo"; +import { usePhoneNumber, usePressDigit } from "~/features/keypad/hooks/atoms"; +import useDevice from "~/features/phone-calls/hooks/use-device"; +import useMakeCall from "~/features/phone-calls/hooks/use-make-call"; +import Keypad from "~/features/keypad/components/keypad"; + +export const meta: MetaFunction = ({ params }) => { + const recipient = decodeURIComponent(params.recipient ?? ""); + + return { + ...getSeoMeta({ + title: `Calling ${recipient}`, + }), + }; +}; + +export default function OutgoingCallPage() { + const params = useParams<{ recipient: string }>(); + const recipient = decodeURIComponent(params.recipient ?? ""); + const [phoneNumber, setPhoneNumber] = usePhoneNumber(); + const onHangUp = useCallback(() => setPhoneNumber(""), [setPhoneNumber]); + const call = useMakeCall({ recipient, onHangUp }); + const { isDeviceReady } = useDevice(); + const pressDigit = usePressDigit(); + const onDigitPressProps = useCallback( + (digit: string) => ({ + onPress() { + pressDigit(digit); + + call.sendDigits(digit); + }, + }), + [call, pressDigit], + ); + + return ( +
+
+ {recipient} +
+ +
+
{phoneNumber}
+
{translateState(call.state)}
+
+ + + + +
+ ); + + function translateState(state: typeof call.state) { + switch (state) { + case "initial": + case "ready": + return "Connecting..."; + case "calling": + return "Calling..."; + case "call_in_progress": + return "In call"; // TODO display time elapsed + case "call_ending": + return "Call ending..."; + case "call_ended": + return "Call ended"; + } + } +} + +export const handle = { hideFooter: true }; diff --git a/app/routes/__app/outgoing-call.twilio-token.ts b/app/routes/__app/outgoing-call.twilio-token.ts new file mode 100644 index 0000000..fad25cd --- /dev/null +++ b/app/routes/__app/outgoing-call.twilio-token.ts @@ -0,0 +1,3 @@ +import twilioTokenLoader from "~/features/phone-calls/loaders/twilio-token"; + +export const loader = twilioTokenLoader; diff --git a/app/routes/twilio.authorize.ts b/app/routes/twilio.authorize.ts index fa978b4..352c7b5 100644 --- a/app/routes/twilio.authorize.ts +++ b/app/routes/twilio.authorize.ts @@ -1,10 +1,8 @@ import { type LoaderFunction, redirect } from "@remix-run/node"; -import twilio from "twilio"; import { refreshSessionData, requireLoggedIn } from "~/utils/auth.server"; import { commitSession } from "~/utils/session.server"; import db from "~/utils/db.server"; -import serverConfig from "~/config/config.server"; import getTwilioClient from "~/utils/twilio.server"; import fetchPhoneCallsQueue from "~/queues/fetch-phone-calls.server"; import fetchMessagesQueue from "~/queues/fetch-messages.server"; @@ -18,29 +16,27 @@ export const loader: LoaderFunction = async ({ request }) => { throw new Error("unreachable"); } - let twilioClient = twilio(twilioSubAccountSid, serverConfig.twilio.authToken); + let twilioClient = getTwilioClient({ accountSid: twilioSubAccountSid, subAccountSid: twilioSubAccountSid }); const twilioSubAccount = await twilioClient.api.accounts(twilioSubAccountSid).fetch(); const twilioMainAccountSid = twilioSubAccount.ownerAccountSid; const twilioMainAccount = await twilioClient.api.accounts(twilioMainAccountSid).fetch(); console.log("twilioSubAccount", twilioSubAccount); console.log("twilioAccount", twilioMainAccount); + const data = { + subAccountSid: twilioSubAccount.sid, + subAccountAuthToken: encrypt(twilioSubAccount.authToken), + accountSid: twilioMainAccount.sid, + }; + const twilioAccount = await db.twilioAccount.upsert({ where: { organizationId: organization.id }, create: { organization: { connect: { id: organization.id }, }, - subAccountSid: twilioSubAccount.sid, - subAccountAuthToken: encrypt(twilioSubAccount.authToken), - accountSid: twilioMainAccount.sid, - accountAuthToken: encrypt(twilioMainAccount.authToken), - }, - update: { - subAccountSid: twilioSubAccount.sid, - subAccountAuthToken: encrypt(twilioSubAccount.authToken), - accountSid: twilioMainAccount.sid, - accountAuthToken: encrypt(twilioMainAccount.authToken), + ...data, }, + update: data, }); twilioClient = getTwilioClient(twilioAccount); diff --git a/app/routes/webhooks/call.ts b/app/routes/webhooks/call.ts new file mode 100644 index 0000000..61f6473 --- /dev/null +++ b/app/routes/webhooks/call.ts @@ -0,0 +1,136 @@ +import { type ActionFunction } from "@remix-run/node"; +import { type CallInstance } from "twilio/lib/rest/api/v2010/account/call"; +import { badRequest, serverError } from "remix-utils"; +import { Direction, Prisma, SubscriptionStatus } from "@prisma/client"; + +import logger from "~/utils/logger.server"; +import db from "~/utils/db.server"; +import twilio from "twilio"; +import { voiceUrl, translateCallStatus } from "~/utils/twilio.server"; +import { decrypt } from "~/utils/encryption"; + +export const action: ActionFunction = async ({ request }) => { + const twilioSignature = request.headers.get("X-Twilio-Signature") || request.headers.get("x-twilio-signature"); + if (!twilioSignature || Array.isArray(twilioSignature)) { + return badRequest("Invalid header X-Twilio-Signature"); + } + + const body: Body = await request.json(); + const isOutgoingCall = body.From.startsWith("client:"); + if (isOutgoingCall) { + const recipient = body.To; + const organizationId = body.From.slice("client:".length).split("__")[0]; + + try { + const phoneNumber = await db.phoneNumber.findUnique({ + where: { organizationId_isCurrent: { organizationId, isCurrent: true } }, + include: { + organization: { + include: { + subscriptions: { + where: { + OR: [ + { status: { not: SubscriptionStatus.deleted } }, + { + status: SubscriptionStatus.deleted, + cancellationEffectiveDate: { gt: new Date() }, + }, + ], + }, + orderBy: { lastEventTime: Prisma.SortOrder.desc }, + }, + twilioAccount: true, + }, + }, + }, + }); + + if (phoneNumber?.organization.subscriptions.length === 0) { + // decline the outgoing call because + // the organization is on the free plan + return new Response(null, { status: 402 }); + } + + const encryptedAuthToken = phoneNumber?.organization.twilioAccount?.subAccountAuthToken; + const authToken = encryptedAuthToken ? decrypt(encryptedAuthToken) : ""; + if ( + !phoneNumber || + !encryptedAuthToken || + !twilio.validateRequest(authToken, twilioSignature, voiceUrl, body) + ) { + return badRequest("Invalid webhook"); + } + + await db.phoneCall.create({ + data: { + id: body.CallSid, + recipient: body.To, + from: phoneNumber.number, + to: body.To, + status: translateCallStatus(body.CallStatus), + direction: Direction.Outbound, + duration: "0", + phoneNumberId: phoneNumber.id, + }, + }); + + const voiceResponse = new twilio.twiml.VoiceResponse(); + const dial = voiceResponse.dial({ + answerOnBridge: true, + callerId: phoneNumber!.number, + }); + dial.number(recipient); + console.log("twiml voiceResponse", voiceResponse.toString()); + + return new Response(voiceResponse.toString(), { headers: { "Content-Type": "text/xml" } }); + } catch (error: any) { + logger.error(error); + + return serverError(error.message); + } + } +}; + +type OutgoingCallBody = { + AccountSid: string; + ApiVersion: string; + ApplicationSid: string; + CallSid: string; + CallStatus: CallInstance["status"]; + Called: string; + Caller: string; + Direction: `outbound${string}`; + From: string; + To: string; +}; + +type IncomingCallBody = { + AccountSid: string; + ApiVersion: string; + ApplicationSid: string; + CallSid: string; + CallStatus: CallInstance["status"]; + Called: string; + CalledCity: string; + CalledCountry: string; + CalledState: string; + CalledZip: string; + Caller: string; + CallerCity: string; + CallerCountry: string; + CallerState: string; + CallerZip: string; + Direction: "inbound"; + From: string; + FromCity: string; + FromCountry: string; + FromState: string; + FromZip: string; + To: string; + ToCity: string; + ToCountry: string; + ToState: string; + ToZip: string; +}; + +type Body = OutgoingCallBody | IncomingCallBody; diff --git a/app/routes/webhooks/message.ts b/app/routes/webhooks/message.ts index dc99a64..04f1318 100644 --- a/app/routes/webhooks/message.ts +++ b/app/routes/webhooks/message.ts @@ -57,7 +57,7 @@ export const action: ActionFunction = async ({ request }) => { // if multiple organizations have the same number // find the organization currently using that phone number // maybe we shouldn't let that happen by restricting a phone number to one org? - const encryptedAuthToken = phoneNumber.organization.twilioAccount?.accountAuthToken; + const encryptedAuthToken = phoneNumber.organization.twilioAccount?.subAccountAuthToken; const authToken = encryptedAuthToken ? decrypt(encryptedAuthToken) : ""; return twilio.validateRequest(authToken, twilioSignature, smsUrl, body); }); diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index 0260729..ed11caf 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -11,7 +11,7 @@ import { commitSession, destroySession, getSession } from "./session.server"; type SessionTwilioAccount = Pick< TwilioAccount, - "accountSid" | "accountAuthToken" | "subAccountSid" | "subAccountAuthToken" | "twimlAppSid" + "accountSid" | "subAccountSid" | "subAccountAuthToken" | "apiKeySid" | "apiKeySecret" | "twimlAppSid" >; type SessionOrganization = Pick & { role: MembershipRole }; type SessionPhoneNumber = Pick; @@ -106,7 +106,6 @@ export async function authenticate({ headers: request.headers, }); const sessionData = await authenticator.authenticate("email-password", signInRequest, { failureRedirect }); - console.log("sessionKey", authenticator.sessionKey); const session = await getSession(request); session.set(authenticator.sessionKey, sessionData); const redirectTo = successRedirect ?? "/messages"; @@ -181,9 +180,10 @@ async function buildSessionData(id: string): Promise { twilioAccount: { select: { accountSid: true, - accountAuthToken: true, subAccountSid: true, subAccountAuthToken: true, + apiKeySid: true, + apiKeySecret: true, twimlAppSid: true, }, }, diff --git a/app/utils/twilio.server.ts b/app/utils/twilio.server.ts index b701655..3853f49 100644 --- a/app/utils/twilio.server.ts +++ b/app/utils/twilio.server.ts @@ -15,7 +15,7 @@ export default function getTwilioClient({ throw new Error("unreachable"); } - return twilio(subAccountSid, serverConfig.twilio.authToken, { + return twilio(subAccountSid, subAccountAuthToken ?? serverConfig.twilio.authToken, { accountSid, }); } diff --git a/package-lock.json b/package-lock.json index b8d9b41..c489802 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@tailwindcss/forms": "0.5.1", "@tailwindcss/line-clamp": "0.4.0", "@tailwindcss/typography": "0.5.2", + "@twilio/voice-sdk": "2.1.1", "awesome-phonenumber": "3.0.1", "aws-sdk": "2.1134.0", "bullmq": "1.82.0", @@ -3977,6 +3978,59 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@twilio/audioplayer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@twilio/audioplayer/-/audioplayer-1.0.6.tgz", + "integrity": "sha512-c9cjX/ifICgXqShtyAQdVMqfe7odnxougiuRMXBJtn3dZ320mFdt7kmuKedpNnc3ZJ6irOZ9M9MZi9/vuEqHiw==", + "dependencies": { + "babel-runtime": "^6.26.0" + } + }, + "node_modules/@twilio/voice-errors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@twilio/voice-errors/-/voice-errors-1.1.1.tgz", + "integrity": "sha512-3IJzRhgAqsS3uW2PO7crUXEFxuFhggHeLvt/Q4hz7lrTLFChl37hWiImCMIaM5VHiybQi6ECVQsId2X8UdTr2A==", + "dependencies": { + "npm-run-all": "^4.1.5" + } + }, + "node_modules/@twilio/voice-sdk": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@twilio/voice-sdk/-/voice-sdk-2.1.1.tgz", + "integrity": "sha512-k96NDlqp5k4thf5a3o5RqzWAGmkd3H/RMl8KSD0ChdQX0QuAW025p7we+QHyhLCkqG5MRGSBMy5FdQtvj/DOog==", + "dependencies": { + "@twilio/audioplayer": "1.0.6", + "@twilio/voice-errors": "1.1.1", + "backoff": "2.5.0", + "loglevel": "1.6.7", + "rtcpeerconnection-shim": "1.2.8", + "ws": "7.4.6", + "xmlhttprequest": "1.8.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@twilio/voice-sdk/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -5346,6 +5400,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/babel-walk": { "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", @@ -5357,6 +5425,17 @@ "node": ">= 10.0.0" } }, + "node_modules/backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==", + "dependencies": { + "precond": "0.2" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -6998,6 +7077,13 @@ "node": ">=0.10.0" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, "node_modules/core-js-compat": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", @@ -11046,8 +11132,7 @@ "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "node_modules/hsl-regex": { "version": "1.0.0", @@ -12534,8 +12619,7 @@ "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -13046,7 +13130,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -13061,7 +13144,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, "engines": { "node": ">=4" } @@ -13070,7 +13152,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, "engines": { "node": ">=4" } @@ -13360,6 +13441,18 @@ "node": ">=8" } }, + "node_modules/loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-loglevel?utm_medium=referral&utm_source=npm_fund" + } + }, "node_modules/longest-streak": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", @@ -13807,7 +13900,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true, "engines": { "node": ">= 0.10.0" } @@ -15009,8 +15101,7 @@ "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/node-addon-api": { "version": "1.7.2", @@ -15110,7 +15201,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -15122,7 +15212,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, "bin": { "semver": "bin/semver" } @@ -15159,7 +15248,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -15184,7 +15272,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -15196,7 +15283,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -15210,7 +15296,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -15218,14 +15303,12 @@ "node_modules/npm-run-all/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/npm-run-all/node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -15241,7 +15324,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -15250,7 +15332,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -15259,7 +15340,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, "engines": { "node": ">=4" } @@ -15268,7 +15348,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, "bin": { "pidtree": "bin/pidtree.js" }, @@ -15280,7 +15359,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, "bin": { "semver": "bin/semver" } @@ -15289,7 +15367,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -15301,7 +15378,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -15310,7 +15386,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -15322,7 +15397,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -15836,7 +15910,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -16930,6 +17003,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -17619,7 +17700,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -17762,7 +17842,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, "dependencies": { "pify": "^3.0.0" }, @@ -17774,7 +17853,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, "engines": { "node": ">=4" } @@ -18450,6 +18528,18 @@ "resolved": "https://registry.npmjs.org/rootpath/-/rootpath-0.1.2.tgz", "integrity": "sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms=" }, + "node_modules/rtcpeerconnection-shim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.8.tgz", + "integrity": "sha512-5Sx90FGru1sQw9aGOM+kHU4i6mbP8eJPgxliu2X3Syhg8qgDybx8dpDTxUwfJvPnubXFnZeRNl59DWr4AttJKQ==", + "dependencies": { + "sdp": "^2.6.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -18554,6 +18644,11 @@ "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" }, + "node_modules/sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, "node_modules/secure-password": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/secure-password/-/secure-password-4.0.0.tgz", @@ -18798,8 +18893,7 @@ "node_modules/shell-quote": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" }, "node_modules/side-channel": { "version": "1.0.4", @@ -19267,7 +19361,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -19276,14 +19369,12 @@ "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -19292,8 +19383,7 @@ "node_modules/spdx-license-ids": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" }, "node_modules/split": { "version": "0.3.3", @@ -19770,7 +19860,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -21278,7 +21367,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -21821,6 +21909,14 @@ "node": ">=4.0" } }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", @@ -24691,6 +24787,44 @@ "dev": true, "requires": {} }, + "@twilio/audioplayer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@twilio/audioplayer/-/audioplayer-1.0.6.tgz", + "integrity": "sha512-c9cjX/ifICgXqShtyAQdVMqfe7odnxougiuRMXBJtn3dZ320mFdt7kmuKedpNnc3ZJ6irOZ9M9MZi9/vuEqHiw==", + "requires": { + "babel-runtime": "^6.26.0" + } + }, + "@twilio/voice-errors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@twilio/voice-errors/-/voice-errors-1.1.1.tgz", + "integrity": "sha512-3IJzRhgAqsS3uW2PO7crUXEFxuFhggHeLvt/Q4hz7lrTLFChl37hWiImCMIaM5VHiybQi6ECVQsId2X8UdTr2A==", + "requires": { + "npm-run-all": "^4.1.5" + } + }, + "@twilio/voice-sdk": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@twilio/voice-sdk/-/voice-sdk-2.1.1.tgz", + "integrity": "sha512-k96NDlqp5k4thf5a3o5RqzWAGmkd3H/RMl8KSD0ChdQX0QuAW025p7we+QHyhLCkqG5MRGSBMy5FdQtvj/DOog==", + "requires": { + "@twilio/audioplayer": "1.0.6", + "@twilio/voice-errors": "1.1.1", + "backoff": "2.5.0", + "loglevel": "1.6.7", + "rtcpeerconnection-shim": "1.2.8", + "ws": "7.4.6", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} + } + } + }, "@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -25776,6 +25910,22 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "babel-walk": { "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", @@ -25784,6 +25934,14 @@ "@babel/types": "^7.9.6" } }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==", + "requires": { + "precond": "0.2" + } + }, "bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -27053,6 +27211,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, "core-js-compat": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", @@ -30042,8 +30205,7 @@ "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "hsl-regex": { "version": "1.0.0", @@ -31097,8 +31259,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -31509,7 +31670,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -31520,14 +31680,12 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" } } }, @@ -31764,6 +31922,11 @@ } } }, + "loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==" + }, "longest-streak": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", @@ -32106,8 +32269,7 @@ "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, "mensch": { "version": "0.3.4", @@ -32896,8 +33058,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-addon-api": { "version": "1.7.2", @@ -32964,7 +33125,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -32975,8 +33135,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -33000,7 +33159,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -33017,7 +33175,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -33026,7 +33183,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -33037,7 +33193,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -33045,14 +33200,12 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -33064,38 +33217,32 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "pidtree": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==" }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -33103,14 +33250,12 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -33119,7 +33264,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -33502,7 +33646,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -34296,6 +34439,11 @@ } } }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -34842,7 +34990,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, "requires": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -34853,7 +35000,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, "requires": { "pify": "^3.0.0" } @@ -34861,8 +35007,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" } } }, @@ -35501,6 +35646,14 @@ "resolved": "https://registry.npmjs.org/rootpath/-/rootpath-0.1.2.tgz", "integrity": "sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms=" }, + "rtcpeerconnection-shim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.8.tgz", + "integrity": "sha512-5Sx90FGru1sQw9aGOM+kHU4i6mbP8eJPgxliu2X3Syhg8qgDybx8dpDTxUwfJvPnubXFnZeRNl59DWr4AttJKQ==", + "requires": { + "sdp": "^2.6.0" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -35580,6 +35733,11 @@ "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" }, + "sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, "secure-password": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/secure-password/-/secure-password-4.0.0.tgz", @@ -35788,8 +35946,7 @@ "shell-quote": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" }, "side-channel": { "version": "1.0.4", @@ -36182,7 +36339,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -36191,14 +36347,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -36207,8 +36361,7 @@ "spdx-license-ids": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" }, "split": { "version": "0.3.3", @@ -36594,7 +36747,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -37759,7 +37911,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -38144,6 +38295,11 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", diff --git a/package.json b/package.json index c78d604..12799f9 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@tailwindcss/forms": "0.5.1", "@tailwindcss/line-clamp": "0.4.0", "@tailwindcss/typography": "0.5.2", + "@twilio/voice-sdk": "2.1.1", "awesome-phonenumber": "3.0.1", "aws-sdk": "2.1134.0", "bullmq": "1.82.0", diff --git a/prisma/migrations/20220517184134_init/migration.sql b/prisma/migrations/20220517184134_init/migration.sql index 57023d0..ee6c93d 100644 --- a/prisma/migrations/20220517184134_init/migration.sql +++ b/prisma/migrations/20220517184134_init/migration.sql @@ -26,8 +26,9 @@ CREATE TABLE "TwilioAccount" ( "updatedAt" TIMESTAMPTZ(6) NOT NULL, "subAccountAuthToken" TEXT NOT NULL, "accountSid" TEXT NOT NULL, - "accountAuthToken" TEXT NOT NULL, "twimlAppSid" TEXT, + "apiKeySid" TEXT, + "apiKeySecret" TEXT, "organizationId" TEXT NOT NULL, CONSTRAINT "TwilioAccount_pkey" PRIMARY KEY ("subAccountSid") diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 133ba8e..b2bcc3f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,8 +13,9 @@ model TwilioAccount { updatedAt DateTime @updatedAt @db.Timestamptz(6) subAccountAuthToken String accountSid String - accountAuthToken String twimlAppSid String? + apiKeySid String? + apiKeySecret String? organizationId String @unique organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)