diff --git a/package-lock.json b/package-lock.json
index a6a0e6c..2dcefb2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/src/App.tsx b/src/App.tsx
index 6a3ef60..2655692 100644
--- a/src/App.tsx
+++ b/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 (
);
}
diff --git a/src/Components/Phone/button.tsx b/src/Components/Phone/button.tsx
new file mode 100644
index 0000000..2ffb285
--- /dev/null
+++ b/src/Components/Phone/button.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+interface ButtonSettings
+{
+ title: string;
+ onClick: () => void;
+}
+
+export default function Button({title, onClick} : ButtonSettings) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Phone/dial-box.tsx b/src/Components/Phone/dial-box.tsx
new file mode 100644
index 0000000..1b449c9
--- /dev/null
+++ b/src/Components/Phone/dial-box.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+const DialBox = () => {
+ return;
+}
+
+export default DialBox;
\ No newline at end of file
diff --git a/src/Components/Phone/display-device-state.tsx b/src/Components/Phone/display-device-state.tsx
new file mode 100644
index 0000000..1db7f51
--- /dev/null
+++ b/src/Components/Phone/display-device-state.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { DeviceState } from '../../Hooks/voice-state';
+
+export default function DisplayDeviceState({deviceState}: {deviceState: DeviceState})
+{
+ return(
+
+ Device is currently {deviceState}.
+
+ );
+}
\ No newline at end of file
diff --git a/src/Components/Phone/phone.tsx b/src/Components/Phone/phone.tsx
new file mode 100644
index 0000000..2569901
--- /dev/null
+++ b/src/Components/Phone/phone.tsx
@@ -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 (
+
+
+
+ );
+ }
+ else if(voiceState.deviceState === "Ringing")
+ {
+ return (
+
+
+
+ );
+ }
+ else if(voiceState.deviceState === "Connected")
+ {
+ return (
+
+
+ hangup()}/>
+
+ );
+ }
+ else
+ {
+ return (
+
+
+
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/src/Mocks/mock-connection.ts b/src/Mocks/mock-connection.ts
new file mode 100644
index 0000000..267785f
--- /dev/null
+++ b/src/Mocks/mock-connection.ts
@@ -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 = { From: this.from, To: this.to };
+ public customParameters: Map = 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() { }
+}
\ No newline at end of file
diff --git a/src/Mocks/mock-device.ts b/src/Mocks/mock-device.ts
new file mode 100644
index 0000000..0ce766f
--- /dev/null
+++ b/src/Mocks/mock-device.ts
@@ -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 = {};
+ 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 {};
+ }
+
+ public activeConnection(): Connection
+ {
+ return {};
+ }
+
+ 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);
+ }
+}
diff --git a/src/Wrappers/default-config.ts b/src/Wrappers/default-config.ts
index 6362fae..075fe7c 100644
--- a/src/Wrappers/default-config.ts
+++ b/src/Wrappers/default-config.ts
@@ -1 +1 @@
-export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODA5NjA0NTAsImlhdCI6MTU4MDk1Njg1MH0.LOlWmq52LjPLM4ONlMdnUybZf6NDk1Xeye-qjVRI_O4";
+export const voiceToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6InNjb3BlOmNsaWVudDppbmNvbWluZz9jbGllbnROYW1lPXNwZW5jZXIgc2NvcGU6Y2xpZW50Om91dGdvaW5nP2FwcFNpZD1BUGJmNzMyOTg4YjcxYTMzZDFiOGY1ZjY1MmI1NDU4MmQ0JmNsaWVudE5hbWU9c3BlbmNlciIsImlzcyI6IkFDOWQyYzcxOTc0NmIxZTg5MWU2ZjVlZmQwMmE5Yjg0MzAiLCJleHAiOjE1ODExMjcyNzEsImlhdCI6MTU4MTEyMzY3MX0.-78SDqG6a-KVJTOfSPuJB7dLZkJTzDUjojTkJ8oxZ3c";
diff --git a/src/Wrappers/voice.ts b/src/Wrappers/voice.ts
index ad7ae97..2645ec4 100644
--- a/src/Wrappers/voice.ts
+++ b/src/Wrappers/voice.ts
@@ -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();
+ const [connection, setConnection] = useState();
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;
}
\ No newline at end of file