Add phone components

Add mocks
This commit is contained in:
2020-02-07 22:20:27 -05:00
parent 827978d18d
commit b2fe2ba202
10 changed files with 318 additions and 20 deletions

67
package-lock.json generated
View File

@@ -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",

View File

@@ -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>
);
}

View 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>
);
}

View File

@@ -0,0 +1,6 @@
import React from 'react';
const DialBox = () => {
return;
}
export default DialBox;

View 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>
);
}

View 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>
);
}
}

View 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
View 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);
}
}

View File

@@ -1 +1 @@
export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODA5NjA0NTAsImlhdCI6MTU4MDk1Njg1MH0.LOlWmq52LjPLM4ONlMdnUybZf6NDk1Xeye-qjVRI_O4";
export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODExMjcyNzEsImlhdCI6MTU4MTEyMzY3MX0.-78SDqG6a-KVJTOfSPuJB7dLZkJTzDUjojTkJ8oxZ3c";

View File

@@ -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;
}