Add phone components
Add mocks
This commit is contained in:
67
package-lock.json
generated
67
package-lock.json
generated
@@ -1441,6 +1441,19 @@
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
|
||||
"integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA=="
|
||||
},
|
||||
"@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.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@twilio/voice-errors/-/voice-errors-1.0.1.tgz",
|
||||
"integrity": "sha512-iXzCuiOhNMhrr8DVjRRzI14YwGUIBM83kWSWcDktxmXim0Tz9xoCth4QFAQcMkNL2h9DlfXlob6noH+3h2iA4A=="
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
|
||||
@@ -2445,6 +2458,14 @@
|
||||
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
|
||||
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
|
||||
},
|
||||
"backoff": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
"integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=",
|
||||
"requires": {
|
||||
"precond": "0.2"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@@ -10012,6 +10033,11 @@
|
||||
"uniq": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw="
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@@ -10952,6 +10978,14 @@
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
||||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
|
||||
},
|
||||
"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.3.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
||||
@@ -11083,6 +11117,11 @@
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@@ -12314,6 +12353,29 @@
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"twilio-client": {
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/twilio-client/-/twilio-client-1.9.7.tgz",
|
||||
"integrity": "sha512-od55ewdYRBk7JIxaMQ3Df1So7vSTIGrOmilL047Fb/axSQHG2hENjgvEfY0QYk72tK5s7avoDgG8DshfnHCzjw==",
|
||||
"requires": {
|
||||
"@twilio/audioplayer": "1.0.6",
|
||||
"@twilio/voice-errors": "1.0.1",
|
||||
"backoff": "2.5.0",
|
||||
"rtcpeerconnection-shim": "1.2.8",
|
||||
"ws": "6.1.3",
|
||||
"xmlhttprequest": "1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.3.tgz",
|
||||
"integrity": "sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
@@ -14508,6 +14570,11 @@
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xmlhttprequest": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
|
||||
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
13
src/App.tsx
13
src/App.tsx
@@ -1,22 +1,15 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import Phone from './Components/Phone/phone';
|
||||
import useVoice from './Wrappers/voice';
|
||||
import { voiceToken } from './Wrappers/default-config';
|
||||
|
||||
const App = () => {
|
||||
const { startDevice, answer, hangup, voiceState } = useVoice();
|
||||
|
||||
window.setTimeout(() => startDevice(voiceToken), 5000);
|
||||
|
||||
const voice = useVoice();
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Device is currently {voiceState.deviceState}.
|
||||
</p>
|
||||
</header>
|
||||
<Phone voice = {voice} ></Phone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
15
src/Components/Phone/button.tsx
Normal file
15
src/Components/Phone/button.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ButtonSettings
|
||||
{
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function Button({title, onClick} : ButtonSettings) {
|
||||
return (
|
||||
<button onClick={() => onClick()}>
|
||||
{ title }
|
||||
</button>
|
||||
);
|
||||
}
|
||||
6
src/Components/Phone/dial-box.tsx
Normal file
6
src/Components/Phone/dial-box.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
const DialBox = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
export default DialBox;
|
||||
11
src/Components/Phone/display-device-state.tsx
Normal file
11
src/Components/Phone/display-device-state.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { DeviceState } from '../../Hooks/voice-state';
|
||||
|
||||
export default function DisplayDeviceState({deviceState}: {deviceState: DeviceState})
|
||||
{
|
||||
return(
|
||||
<div>
|
||||
Device is currently {deviceState}.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
src/Components/Phone/phone.tsx
Normal file
46
src/Components/Phone/phone.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import useVoice, { Voice } from '../../Wrappers/voice';
|
||||
import { voiceToken } from '../../Wrappers/default-config';
|
||||
import Button from './button';
|
||||
import DeviceState from './display-device-state';
|
||||
|
||||
export default function Phone({voice:{ startDevice, answer, reject, hangup, dial, voiceState} }: {voice: Voice})
|
||||
{
|
||||
if (voiceState.deviceState === "Offline")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<Button title = "Start Device" onClick = {() => startDevice(voiceToken)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if(voiceState.deviceState === "Ringing")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<Button title = "Answer" onClick = {() => answer()}/>
|
||||
<Button title = "Reject" onClick = {() => reject()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if(voiceState.deviceState === "Connected")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<Button title = "Hang Up" onClick = {() => hangup()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
68
src/Mocks/mock-connection.ts
Normal file
68
src/Mocks/mock-connection.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Connection, ConnectionParameters, ConnectionStatus, ConnectionEvent } from "twilio-client";
|
||||
|
||||
export class MockConnection implements Connection
|
||||
{
|
||||
private from = "+16133713909";
|
||||
private to = "+123456789";
|
||||
|
||||
public parameters: ConnectionParameters = <ConnectionParameters>{ From: this.from, To: this.to };
|
||||
public customParameters: Map<string, string> = new Map();
|
||||
public options: object = {};
|
||||
|
||||
private connectionStatus: ConnectionStatus = "ringing";
|
||||
private readonly handlers = <{[key in ConnectionEvent]: ((mute?: boolean) => void)[]}>{};
|
||||
private muted: boolean = false;
|
||||
|
||||
|
||||
public accept(audioConstraints?: any)
|
||||
{
|
||||
setTimeout(() => this.handlers["accept"].forEach(handler => handler()), 500)
|
||||
}
|
||||
|
||||
public reject()
|
||||
{
|
||||
setTimeout(() => this.handlers["disconnect"].forEach(handler => handler()), 500)
|
||||
}
|
||||
|
||||
public ignore()
|
||||
{
|
||||
setTimeout(() => this.handlers["disconnect"].forEach(handler => handler()), 500)
|
||||
}
|
||||
|
||||
public disconnect()
|
||||
{
|
||||
setTimeout(() => this.handlers["disconnect"].forEach(handler => handler()), 500)
|
||||
}
|
||||
|
||||
public mute(mute: boolean)
|
||||
{
|
||||
this.muted = mute
|
||||
setTimeout(() => this.handlers["mute"].forEach(handler => handler(this.muted)), 500)
|
||||
}
|
||||
|
||||
public isMuted()
|
||||
{
|
||||
return this.muted;
|
||||
}
|
||||
|
||||
public sendDigits(digits: string)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public status(): ConnectionStatus
|
||||
{
|
||||
return this.connectionStatus;
|
||||
}
|
||||
|
||||
public on(event: ConnectionEvent, handler: (...args: any[]) => void)
|
||||
{
|
||||
if (!this.handlers[event])
|
||||
{
|
||||
this.handlers[event] = [];
|
||||
}
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
|
||||
getRemoteStream() { }
|
||||
}
|
||||
51
src/Mocks/mock-device.ts
Normal file
51
src/Mocks/mock-device.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Device, DeviceAudio, SetupParams, Connection, DeviceStatus, DeviceEvent } from "twilio-client";
|
||||
import { MockConnection } from "./mock-connection";
|
||||
|
||||
export class MockDevice implements Device
|
||||
{
|
||||
public audio: DeviceAudio = <DeviceAudio>{};
|
||||
private deviceStatus: DeviceStatus = "offline";
|
||||
private readonly handlers = <{[key in DeviceEvent]: ((connection?: Connection) => void)[]}>{};
|
||||
|
||||
constructor(token: string, params?: SetupParams)
|
||||
{
|
||||
setTimeout(() => this.handlers["ready"].forEach(handler => handler()), 1000);
|
||||
setTimeout(() => this.handlers["incoming"].forEach(handler => handler(new MockConnection())), 5000);
|
||||
}
|
||||
|
||||
public setup(token: string, params?: SetupParams): void {}
|
||||
|
||||
public connect(params: any, audioConstraints?: any): Connection
|
||||
{
|
||||
return <Connection>{};
|
||||
}
|
||||
|
||||
public activeConnection(): Connection
|
||||
{
|
||||
return <Connection>{};
|
||||
}
|
||||
|
||||
public destroy(): void
|
||||
{
|
||||
this.handlers["offline"].forEach(handler => handler());
|
||||
}
|
||||
|
||||
public disconnectAll(): void
|
||||
{
|
||||
this.handlers["disconnect"].forEach(handler => handler());
|
||||
}
|
||||
|
||||
public status()
|
||||
{
|
||||
return this.deviceStatus;
|
||||
}
|
||||
|
||||
public on(event: DeviceEvent, handler: (...args: any[]) => void): void
|
||||
{
|
||||
if (!this.handlers[event])
|
||||
{
|
||||
this.handlers[event] = [];
|
||||
}
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODA5NjA0NTAsImlhdCI6MTU4MDk1Njg1MH0.LOlWmq52LjPLM4ONlMdnUybZf6NDk1Xeye-qjVRI_O4";
|
||||
export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODExMjcyNzEsImlhdCI6MTU4MTEyMzY3MX0.-78SDqG6a-KVJTOfSPuJB7dLZkJTzDUjojTkJ8oxZ3c";
|
||||
|
||||
@@ -1,53 +1,94 @@
|
||||
import { Device, Connection } from "twilio-client";
|
||||
import useVoiceState, { VoiceState } from "../Hooks/voice-state";
|
||||
import { useState } from "react";
|
||||
import { MockDevice } from "../Mocks/mock-device";
|
||||
|
||||
export interface Voice
|
||||
{
|
||||
startDevice(token: string): void;
|
||||
answer(): void;
|
||||
reject(): void;
|
||||
hangup(): void;
|
||||
dial(phoneNumber: string): void;
|
||||
voiceState: VoiceState;
|
||||
}
|
||||
|
||||
export default function useVoice()
|
||||
{
|
||||
const state = useVoiceState();
|
||||
let device: Device;
|
||||
let connection: Connection;
|
||||
console.log("Creating new voice");
|
||||
|
||||
const [device, setDevice] = useState<Device>();
|
||||
const [connection, setConnection] = useState<Connection>();
|
||||
|
||||
const startDevice = (token: string) =>
|
||||
{
|
||||
device = new Device(token);
|
||||
// const device = new Device(token)
|
||||
const device = new MockDevice(token);
|
||||
|
||||
console.log("Creating new device", device);
|
||||
|
||||
device.on("ready", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
device.on("offline", () => state.onDeviceStateUpdate("Offline"));
|
||||
device.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
device.on("cancel", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
|
||||
device.on("disconnect", () =>
|
||||
{
|
||||
setConnection(undefined);
|
||||
state.onDeviceStateUpdate("Disconnected")
|
||||
});
|
||||
|
||||
device.on("cancel", () =>
|
||||
{
|
||||
setConnection(undefined);
|
||||
state.onDeviceStateUpdate("Disconnected")
|
||||
});
|
||||
|
||||
device.on("incoming", (incomingConnection: Connection) =>
|
||||
{
|
||||
state.onDeviceStateUpdate("Ringing", incomingConnection.parameters.From);
|
||||
|
||||
incomingConnection.on("accept", () => state.onDeviceStateUpdate("Connected"));
|
||||
incomingConnection.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
incomingConnection.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
|
||||
connection = incomingConnection;
|
||||
|
||||
setConnection(incomingConnection);
|
||||
});
|
||||
|
||||
setDevice(device);
|
||||
}
|
||||
|
||||
const answer = () =>
|
||||
{
|
||||
connection.accept();
|
||||
connection?.accept();
|
||||
}
|
||||
|
||||
const reject = () =>
|
||||
{
|
||||
connection?.reject();
|
||||
}
|
||||
|
||||
const hangup = () =>
|
||||
{
|
||||
device.disconnectAll();
|
||||
device?.disconnectAll();
|
||||
}
|
||||
|
||||
const dial = (phoneNumber: string) =>
|
||||
{
|
||||
const connection = device?.connect({To: phoneNumber});
|
||||
|
||||
connection?.on("accept", () => state.onDeviceStateUpdate("Connected"));
|
||||
connection?.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
connection?.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
|
||||
|
||||
setConnection(connection);
|
||||
}
|
||||
|
||||
return {
|
||||
startDevice,
|
||||
answer,
|
||||
reject,
|
||||
hangup,
|
||||
dial,
|
||||
voiceState: state.voiceState,
|
||||
}
|
||||
} as Voice;
|
||||
}
|
||||
Reference in New Issue
Block a user