@signalwire/js
Advanced tools
Comparing version 1.1.1 to 1.1.2
@@ -30,2 +30,6 @@ # Changelog | ||
## [1.1.2] - 2019-07-17 | ||
### Fixed | ||
- Fix reconnection logic on Verto client. | ||
## [1.1.1] - 2019-06-26 | ||
@@ -32,0 +36,0 @@ ### Fixed |
import * as log from 'loglevel'; | ||
import Connection from './services/Connection'; | ||
import BaseMessage from '../../common/src/messages/BaseMessage'; | ||
import { BroadcastParams, ISignalWireOptions, SubscribeParams, Constructable } from './util/interfaces'; | ||
import Relay from './relay/Relay'; | ||
import { BroadcastParams, ISignalWireOptions, SubscribeParams } from './util/interfaces'; | ||
export default abstract class BaseSession { | ||
@@ -16,10 +15,9 @@ options: ISignalWireOptions; | ||
expiresAt: number; | ||
relayProtocol: string; | ||
protected connection: Connection; | ||
protected _relayInstances: { | ||
[service: string]: Relay; | ||
}; | ||
protected _jwtAuth: boolean; | ||
protected _reconnectDelay: number; | ||
protected _autoReconnect: boolean; | ||
private _idle; | ||
private _executeQueue; | ||
private _autoReconnect; | ||
constructor(options: ISignalWireOptions); | ||
@@ -39,5 +37,3 @@ readonly __logger: log.Logger; | ||
refreshToken(token: string): Promise<void>; | ||
abstract connect(): Promise<void>; | ||
protected _onSessionConnect?(): void; | ||
protected setup(): void; | ||
connect(): Promise<void>; | ||
protected _handleLoginError(error: any): void; | ||
@@ -50,3 +46,2 @@ protected _onSocketOpen(): Promise<void>; | ||
protected _addSubscription(protocol: string, handler: Function, channel: string): void; | ||
protected _addRelayInstance(service: string, klass: Constructable<Relay>): Relay; | ||
_existsSubscription(protocol: string, channel?: string): boolean; | ||
@@ -58,3 +53,2 @@ private _attachListeners; | ||
private _checkTokenExpiration; | ||
private _destroyRelayInstances; | ||
private _unsubscribeAll; | ||
@@ -61,0 +55,0 @@ static on(eventName: string, callback: any): void; |
@@ -11,4 +11,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import logger from './util/logger'; | ||
import Connection from './services/Connection'; | ||
import Setup from './services/Setup'; | ||
import { deRegister, register, trigger, deRegisterAll } from './services/Handler'; | ||
import { BroadcastHandler } from './services/Broadcast'; | ||
import BroadcastHandler from './services/BroadcastHandler'; | ||
import { ADD, REMOVE, SwEvent, BladeMethod, NOTIFICATION_TYPE } from './util/constants'; | ||
@@ -24,8 +26,9 @@ import { Subscription, Connect, Reauthenticate } from './messages/Blade'; | ||
this.expiresAt = 0; | ||
this.relayProtocol = null; | ||
this.connection = null; | ||
this._relayInstances = {}; | ||
this._jwtAuth = false; | ||
this._reconnectDelay = 5000; | ||
this._autoReconnect = false; | ||
this._idle = false; | ||
this._executeQueue = []; | ||
this._autoReconnect = false; | ||
if (!this.validateOptions()) { | ||
@@ -38,7 +41,6 @@ throw new Error('Invalid init options'); | ||
this._onSocketMessage = this._onSocketMessage.bind(this); | ||
if (this._onSessionConnect) { | ||
this._onSessionConnect = this._onSessionConnect.bind(this); | ||
} | ||
this._handleLoginError = this._handleLoginError.bind(this); | ||
this._checkTokenExpiration = this._checkTokenExpiration.bind(this); | ||
this._attachListeners(); | ||
this.connection = new Connection(this); | ||
} | ||
@@ -98,3 +100,2 @@ get __logger() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this._destroyRelayInstances(); | ||
yield this._unsubscribeAll(); | ||
@@ -136,10 +137,13 @@ this.subscriptions = {}; | ||
} | ||
setup() { | ||
if (this.connection) { | ||
if (this.connection.isAlive) { | ||
connect() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this.connection) { | ||
this.connection = new Connection(this); | ||
} | ||
else if (this.connection.isAlive) { | ||
return; | ||
} | ||
this._removeConnection(); | ||
} | ||
this._attachListeners(); | ||
this._attachListeners(); | ||
this.connection.connect(); | ||
}); | ||
} | ||
@@ -159,2 +163,3 @@ _handleLoginError(error) { | ||
this._autoReconnect = true; | ||
this.relayProtocol = yield Setup(this); | ||
const { sessionid, nodeid, master_nodeid, authorization: { expires_at = null } = {} } = response; | ||
@@ -166,5 +171,5 @@ this.expiresAt = +expires_at || 0; | ||
this.master_nodeid = master_nodeid; | ||
trigger(SwEvent.Connect, null, this.uuid, false); | ||
this._emptyExecuteQueues(); | ||
trigger(SwEvent.Ready, this, this.uuid); | ||
logger.info('Session Ready!'); | ||
} | ||
@@ -180,3 +185,3 @@ }); | ||
if (this._autoReconnect) { | ||
setTimeout(() => this.connect(), 1000); | ||
setTimeout(() => this.connect(), this._reconnectDelay); | ||
} | ||
@@ -191,3 +196,3 @@ } | ||
case BladeMethod.Broadcast: | ||
BroadcastHandler(params); | ||
BroadcastHandler(this, params); | ||
break; | ||
@@ -224,8 +229,2 @@ case BladeMethod.Disconnect: | ||
} | ||
_addRelayInstance(service, klass) { | ||
if (!this._relayInstances.hasOwnProperty(service)) { | ||
this._relayInstances[service] = new klass(this); | ||
} | ||
return this._relayInstances[service]; | ||
} | ||
_existsSubscription(protocol, channel) { | ||
@@ -245,5 +244,2 @@ if (this.subscriptions.hasOwnProperty(protocol)) { | ||
this.on(SwEvent.SocketMessage, this._onSocketMessage); | ||
if (this._onSessionConnect) { | ||
this.on(SwEvent.Connect, this._onSessionConnect); | ||
} | ||
} | ||
@@ -255,5 +251,2 @@ _detachListeners() { | ||
this.off(SwEvent.SocketMessage, this._onSocketMessage); | ||
if (this._onSessionConnect) { | ||
this.off(SwEvent.Connect, this._onSessionConnect); | ||
} | ||
} | ||
@@ -290,9 +283,2 @@ _emptyExecuteQueues() { | ||
} | ||
_destroyRelayInstances() { | ||
Object.keys(this._relayInstances).forEach(service => { | ||
this._relayInstances[service].destroy(); | ||
delete this._relayInstances[service]; | ||
this._relayInstances[service] = null; | ||
}); | ||
} | ||
_unsubscribeAll() { | ||
@@ -299,0 +285,0 @@ const promises = Object.keys(this.subscriptions).map(protocol => { |
import BaseSession from './BaseSession'; | ||
import BaseCall from './webrtc/BaseCall'; | ||
import Call from './webrtc/Call'; | ||
import { ICacheDevices, IAudioSettings, IVideoSettings, BroadcastParams, SubscribeParams } from './util/interfaces'; | ||
import { ICacheDevices, IAudioSettings, IVideoSettings, BroadcastParams, SubscribeParams, CallOptions } from './util/interfaces'; | ||
export default abstract class BrowserSession extends BaseSession { | ||
calls: { | ||
[callId: string]: Call; | ||
[callId: string]: BaseCall; | ||
}; | ||
@@ -11,2 +12,3 @@ private _iceServers; | ||
private _remoteElement; | ||
protected _reconnectDelay: number; | ||
protected _devices: ICacheDevices; | ||
@@ -47,6 +49,6 @@ protected _audioConstraints: boolean | MediaTrackConstraints; | ||
remoteElement: HTMLMediaElement | string | Function; | ||
abstract readonly webRtcProtocol: string; | ||
vertoBroadcast({ nodeId, channel: eventChannel, data }: BroadcastParams): void; | ||
vertoSubscribe({ nodeId, channels: eventChannel, handler }: SubscribeParams): Promise<any>; | ||
vertoUnsubscribe({ nodeId, channels: eventChannel }: SubscribeParams): Promise<any>; | ||
newCall(options: CallOptions): Promise<Call>; | ||
} |
@@ -19,3 +19,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import BaseSession from './BaseSession'; | ||
import Connection from './services/Connection'; | ||
import Call from './webrtc/Call'; | ||
import { registerOnce } from './services/Handler'; | ||
@@ -36,2 +36,3 @@ import { SwEvent, SESSION_ID } from './util/constants'; | ||
this._remoteElement = null; | ||
this._reconnectDelay = 1000; | ||
this._devices = {}; | ||
@@ -44,9 +45,8 @@ this._audioConstraints = true; | ||
const _super = Object.create(null, { | ||
setup: { get: () => super.setup } | ||
connect: { get: () => super.connect } | ||
}); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
_super.setup.call(this); | ||
yield this.refreshDevices(); | ||
this.sessionid = yield localStorage.getItem(SESSION_ID); | ||
this.connection = new Connection(this); | ||
_super.connect.call(this); | ||
}); | ||
@@ -209,3 +209,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
eventChannel = eventChannel.filter(channel => channel && !this._existsSubscription(this.webRtcProtocol, channel)); | ||
eventChannel = eventChannel.filter(channel => channel && !this._existsSubscription(this.relayProtocol, channel)); | ||
if (!eventChannel.length) { | ||
@@ -221,5 +221,5 @@ return; | ||
if (unauthorized.length) { | ||
unauthorized.forEach(channel => this._removeSubscription(this.webRtcProtocol, channel)); | ||
unauthorized.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
} | ||
subscribed.forEach(channel => this._addSubscription(this.webRtcProtocol, handler, channel)); | ||
subscribed.forEach(channel => this._addSubscription(this.relayProtocol, handler, channel)); | ||
return response; | ||
@@ -230,3 +230,3 @@ }); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
eventChannel = eventChannel.filter(channel => channel && this._existsSubscription(this.webRtcProtocol, channel)); | ||
eventChannel = eventChannel.filter(channel => channel && this._existsSubscription(this.relayProtocol, channel)); | ||
if (!eventChannel.length) { | ||
@@ -241,7 +241,18 @@ return; | ||
const { unsubscribed = [], notSubscribed = [] } = destructSubscribeResponse(response); | ||
unsubscribed.forEach(channel => this._removeSubscription(this.webRtcProtocol, channel)); | ||
notSubscribed.forEach(channel => this._removeSubscription(this.webRtcProtocol, channel)); | ||
unsubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
notSubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
return response; | ||
}); | ||
} | ||
newCall(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { destinationNumber = null } = options; | ||
if (!destinationNumber) { | ||
throw new TypeError('destinationNumber is required'); | ||
} | ||
const call = new Call(this, options); | ||
call.invite(); | ||
return call; | ||
}); | ||
} | ||
} |
import BaseSession from '../BaseSession'; | ||
declare abstract class Relay { | ||
export default abstract class Relay { | ||
session: BaseSession; | ||
[x: string]: any; | ||
Ready: Promise<string>; | ||
protocol: string; | ||
protected abstract notificationHandler(notification: any): void; | ||
abstract readonly service: string; | ||
constructor(session: BaseSession); | ||
destroy(): void; | ||
} | ||
export default Relay; |
@@ -1,30 +0,5 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import { Setup } from '../services/Setup'; | ||
import { deRegisterAll } from '../services/Handler'; | ||
class Relay { | ||
export default class Relay { | ||
constructor(session) { | ||
this.session = session; | ||
this.Ready = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
this.protocol = yield Setup(this.session, this.service, this.notificationHandler.bind(this)); | ||
resolve(this.protocol); | ||
} | ||
catch (error) { | ||
console.error(error); | ||
} | ||
})); | ||
} | ||
destroy() { | ||
if (this.protocol) { | ||
deRegisterAll(this.protocol); | ||
} | ||
} | ||
} | ||
export default Relay; |
import logger from '../util/logger'; | ||
import { SwEvent } from '../util/constants'; | ||
import { safeParseJson, checkWebSocketHost } from '../util/helpers'; | ||
import { safeParseJson, checkWebSocketHost, destructResponse } from '../util/helpers'; | ||
import { registerOnce, trigger } from '../services/Handler'; | ||
@@ -31,15 +31,14 @@ import { isFunction } from '../util/helpers'; | ||
} | ||
this.connect(); | ||
} | ||
get connected() { | ||
return this._wsClient.readyState === WS_STATE.OPEN; | ||
return this._wsClient && this._wsClient.readyState === WS_STATE.OPEN; | ||
} | ||
get connecting() { | ||
return this._wsClient.readyState === WS_STATE.CONNECTING; | ||
return this._wsClient && this._wsClient.readyState === WS_STATE.CONNECTING; | ||
} | ||
get closing() { | ||
return this._wsClient.readyState === WS_STATE.CLOSING; | ||
return this._wsClient && this._wsClient.readyState === WS_STATE.CLOSING; | ||
} | ||
get closed() { | ||
return this._wsClient.readyState === WS_STATE.CLOSED; | ||
return this._wsClient && this._wsClient.readyState === WS_STATE.CLOSED; | ||
} | ||
@@ -59,3 +58,6 @@ get isAlive() { | ||
}; | ||
this._wsClient.onclose = (event) => trigger(SwEvent.SocketClose, event, this.session.uuid); | ||
this._wsClient.onclose = (event) => { | ||
this._connected = false; | ||
return trigger(SwEvent.SocketClose, event, this.session.uuid); | ||
}; | ||
this._wsClient.onerror = (event) => trigger(SwEvent.SocketError, event, this.session.uuid); | ||
@@ -85,19 +87,4 @@ this._wsClient.onmessage = (event) => { | ||
registerOnce(request.id, (response) => { | ||
const { result, error } = response; | ||
if (error) { | ||
return reject(error); | ||
} | ||
if (result) { | ||
const { result: { code = null, node_id = null, result: nestedResult = null } = {} } = result; | ||
if (code && code !== '200') { | ||
reject(result); | ||
} | ||
else if (nestedResult) { | ||
nestedResult.node_id = node_id; | ||
resolve(nestedResult); | ||
} | ||
else { | ||
resolve(result); | ||
} | ||
} | ||
const { result, error } = destructResponse(response); | ||
return error ? reject(error) : resolve(result); | ||
}); | ||
@@ -141,3 +128,3 @@ this._setTimer(request.id); | ||
_ping() { | ||
if (!this._wsClient || !this._wsClient.ping) { | ||
if (typeof WebSocket !== 'undefined' && this._wsClient instanceof WebSocket) { | ||
return; | ||
@@ -150,5 +137,6 @@ } | ||
} | ||
const { _beginClose, close } = this._wsClient; | ||
isFunction(_beginClose) ? _beginClose() : close(); | ||
if (this._wsClient) { | ||
isFunction(this._wsClient._beginClose) ? this._wsClient._beginClose() : this._wsClient.close(); | ||
} | ||
} | ||
} |
import BaseSession from '../BaseSession'; | ||
export declare const Setup: (session: BaseSession, service: string, handler?: Function) => Promise<string>; | ||
declare const _default: (session: BaseSession) => Promise<string>; | ||
export default _default; |
@@ -9,2 +9,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import logger from '../util/logger'; | ||
import { Execute } from '../messages/Blade'; | ||
@@ -15,7 +16,6 @@ import { sessionStorage } from '../util/storage/'; | ||
const SETUP_CHANNEL = 'notifications'; | ||
export const Setup = (session, service, handler) => __awaiter(this, void 0, void 0, function* () { | ||
const { project } = session.options; | ||
const params = { service }; | ||
const _key = `${project}-${service}`; | ||
const currentProtocol = yield sessionStorage.getItem(_key); | ||
export default (session) => __awaiter(this, void 0, void 0, function* () { | ||
const params = { service: '' }; | ||
const storageKey = `${session.options.project}-setup`; | ||
const currentProtocol = yield sessionStorage.getItem(storageKey); | ||
if (currentProtocol) { | ||
@@ -25,6 +25,11 @@ params.protocol = currentProtocol; | ||
const be = new Execute({ protocol: SETUP_PROTOCOL, method: SETUP_METHOD, params }); | ||
const { result: { protocol = null } = {} } = yield session.execute(be); | ||
yield session.subscribe({ protocol, channels: [SETUP_CHANNEL], handler }); | ||
yield sessionStorage.setItem(_key, protocol); | ||
const { protocol = null } = yield session.execute(be); | ||
if (protocol) { | ||
yield session.subscribe({ protocol, channels: [SETUP_CHANNEL] }); | ||
yield sessionStorage.setItem(storageKey, protocol); | ||
} | ||
else { | ||
logger.error('Error during setup the session protocol.'); | ||
} | ||
return protocol; | ||
}); |
@@ -15,3 +15,2 @@ export declare enum Netcast { | ||
SpeedTest = "signalwire.internal.speedtest", | ||
Connect = "signalwire.internal.connect", | ||
Ready = "signalwire.ready", | ||
@@ -18,0 +17,0 @@ Error = "signalwire.error", |
@@ -17,3 +17,2 @@ export var Netcast; | ||
SwEvent["SpeedTest"] = "signalwire.internal.speedtest"; | ||
SwEvent["Connect"] = "signalwire.internal.connect"; | ||
SwEvent["Ready"] = "signalwire.ready"; | ||
@@ -20,0 +19,0 @@ SwEvent["Error"] = "signalwire.error"; |
@@ -16,1 +16,4 @@ export declare const objEmpty: (obj: Object) => boolean; | ||
export declare const checkWebSocketHost: (host: string) => string; | ||
export declare const destructResponse: (response: any, nodeId?: string) => { | ||
[key: string]: any; | ||
}; |
@@ -49,1 +49,22 @@ import logger from './logger'; | ||
}; | ||
export const destructResponse = (response, nodeId = null) => { | ||
const { result = {}, error } = response; | ||
if (error) { | ||
return { error }; | ||
} | ||
const { result: nestedResult = null } = result; | ||
if (nestedResult === null) { | ||
if (nodeId !== null) { | ||
result.node_id = nodeId; | ||
} | ||
return { result }; | ||
} | ||
const { code = null, node_id = null, result: vertoResult = null } = nestedResult; | ||
if (code && code !== '200') { | ||
return { error: nestedResult }; | ||
} | ||
if (vertoResult) { | ||
return destructResponse(vertoResult, node_id); | ||
} | ||
return { result: nestedResult }; | ||
}; |
@@ -0,1 +1,2 @@ | ||
import Call from '../relay/calling/Call'; | ||
interface IMessageBase { | ||
@@ -134,6 +135,38 @@ jsonrpc: string; | ||
id: string; | ||
tag?: string; | ||
nodeId: string; | ||
state: string; | ||
prevState: string; | ||
on: Function; | ||
off: Function; | ||
context: string; | ||
peer: Call; | ||
type: string; | ||
to: string; | ||
from: string; | ||
timeout: number; | ||
active: boolean; | ||
failed: boolean; | ||
answered: boolean; | ||
ended: boolean; | ||
busy: boolean; | ||
dial: Function; | ||
hangup: Function; | ||
record: Function; | ||
recordAsync: Function; | ||
answer: Function; | ||
connect: Function; | ||
connectAsync: Function; | ||
play: Function; | ||
playAsync: Function; | ||
playAudio: Function; | ||
playAudioAsync: Function; | ||
playSilence: Function; | ||
playSilenceAsync: Function; | ||
playTTS: Function; | ||
playTTSAsync: Function; | ||
prompt: Function; | ||
promptAsync: Function; | ||
promptAudio: Function; | ||
promptAudioAsync: Function; | ||
promptTTS: Function; | ||
promptTTSAsync: Function; | ||
} | ||
@@ -167,5 +200,2 @@ export interface ICallDevice { | ||
} | ||
export interface Constructable<T> { | ||
new (any: any): T; | ||
} | ||
export interface StringTMap<T> { | ||
@@ -209,3 +239,5 @@ [key: string]: T; | ||
setup?: Function; | ||
ready?: Function; | ||
teardown?: Function; | ||
} | ||
export {}; |
@@ -11,5 +11,6 @@ declare const RTCPeerConnection: (config: RTCConfiguration) => RTCPeerConnection; | ||
declare const unmuteMediaElement: (tag: any) => void; | ||
declare const toggleMuteMediaElement: (tag: any) => void; | ||
declare const setMediaElementSinkId: (tag: any, deviceId: string) => Promise<boolean>; | ||
declare const sdpToJsonHack: (sdp: any) => any; | ||
declare const stopStream: (stream: MediaStream) => void; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, setMediaElementSinkId }; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId }; |
@@ -47,2 +47,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
const toggleMuteMediaElement = (tag) => { | ||
const element = findElementByType(tag); | ||
if (element) { | ||
element.muted = !element.muted; | ||
} | ||
}; | ||
const setMediaElementSinkId = (tag, deviceId) => __awaiter(this, void 0, void 0, function* () { | ||
@@ -68,2 +74,2 @@ const element = findElementByType(tag); | ||
}; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, setMediaElementSinkId }; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId }; |
@@ -1,82 +0,15 @@ | ||
import BrowserSession from '../BrowserSession'; | ||
import Peer from './Peer'; | ||
import { Direction } from '../util/constants'; | ||
import { State } from '../util/constants/call'; | ||
import BaseCall from './BaseCall'; | ||
import { CallOptions } from '../util/interfaces'; | ||
export default class Call { | ||
private session; | ||
id: string; | ||
state: string; | ||
prevState: string; | ||
direction: Direction; | ||
peer: Peer; | ||
options: CallOptions; | ||
cause: string; | ||
causeCode: number; | ||
channels: string[]; | ||
role: string; | ||
export default class Call extends BaseCall { | ||
screenShare: Call; | ||
private _state; | ||
private _prevState; | ||
private gotAnswer; | ||
private gotEarly; | ||
private _lastSerno; | ||
private _targetNodeId; | ||
private _iceTimeout; | ||
private _iceDone; | ||
private _statsInterval; | ||
constructor(session: BrowserSession, opts?: CallOptions); | ||
nodeId: string; | ||
invite(): void; | ||
answer(): void; | ||
hangup(params?: any, execute?: boolean): void; | ||
transfer(destination: string): void; | ||
replace(replaceCallID: string): void; | ||
hold(): any; | ||
unhold(): any; | ||
toggleHold(): any; | ||
dtmf(dtmf: string): void; | ||
message(to: string, body: string): void; | ||
startScreenShare(opts?: CallOptions): Promise<Call>; | ||
stopScreenShare(): void; | ||
readonly screenShareActive: boolean; | ||
audioState: boolean | string; | ||
videoState: boolean | string; | ||
readonly localStream: MediaStream; | ||
readonly remoteStream: MediaStream; | ||
readonly memberChannel: string; | ||
muteAudio(): void; | ||
unmuteAudio(): void; | ||
toggleAudioMute(): void; | ||
setAudioInDevice(deviceId: string): Promise<void>; | ||
muteVideo(): void; | ||
unmuteVideo(): void; | ||
toggleVideoMute(): void; | ||
setVideoDevice(deviceId: string): Promise<void>; | ||
deaf(): void; | ||
undeaf(): void; | ||
toggleDeaf(): void; | ||
setAudioOutDevice(deviceId: string): Promise<boolean>; | ||
setState(state: State): void; | ||
handleMessage(msg: any): void; | ||
handleConferenceUpdate(packet: any, initialPvtData: any): Promise<string>; | ||
_addChannel(channel: string): void; | ||
private _subscribeConferenceChat; | ||
private _subscribeConferenceInfo; | ||
private _confControl; | ||
private _subscribeConferenceModerator; | ||
private _handleChangeHoldStateSuccess; | ||
private _handleChangeHoldStateError; | ||
private _onRemoteSdp; | ||
private _requestAnotherLocalDescription; | ||
private _onIceSdp; | ||
private _registerPeerEvents; | ||
private _checkConferenceSerno; | ||
private _onMediaError; | ||
private _validCallback; | ||
private _dispatchConferenceUpdate; | ||
private _dispatchNotification; | ||
private _execute; | ||
private _init; | ||
private _finalize; | ||
protected _finalize(): void; | ||
private _stats; | ||
} |
@@ -9,114 +9,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import logger from '../util/logger'; | ||
import { Invite, Answer, Attach, Bye, Modify, Info } from '../messages/Verto'; | ||
import Peer from './Peer'; | ||
import { PeerType, VertoMethod, SwEvent, NOTIFICATION_TYPE, Direction } from '../util/constants'; | ||
import { State, DEFAULT_CALL_OPTIONS, ConferenceAction, Role } from '../util/constants/call'; | ||
import { trigger, register, deRegister } from '../services/Handler'; | ||
import { sdpStereoHack, sdpMediaOrderHack, checkSubscribeResponse } from './helpers'; | ||
import { objEmpty, mutateLiveArrayData, isFunction } from '../util/helpers'; | ||
import { attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, getUserMedia, getDisplayMedia, setMediaElementSinkId, muteMediaElement, unmuteMediaElement } from '../util/webrtc'; | ||
import { MCULayoutEventHandler } from './LayoutHandler'; | ||
export default class Call { | ||
constructor(session, opts) { | ||
this.session = session; | ||
this.id = ''; | ||
this.state = State[State.New]; | ||
this.prevState = ''; | ||
this.channels = []; | ||
this.role = Role.Participant; | ||
this._state = State.New; | ||
this._prevState = State.New; | ||
this.gotAnswer = false; | ||
this.gotEarly = false; | ||
this._lastSerno = 0; | ||
this._targetNodeId = null; | ||
this._iceTimeout = null; | ||
this._iceDone = false; | ||
import BaseCall from './BaseCall'; | ||
import { getDisplayMedia, setMediaElementSinkId, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement } from '../util/webrtc'; | ||
export default class Call extends BaseCall { | ||
constructor() { | ||
super(...arguments); | ||
this._statsInterval = null; | ||
this._checkConferenceSerno = (serno) => { | ||
const check = (serno < 0) || (!this._lastSerno || (this._lastSerno && serno === (this._lastSerno + 1))); | ||
if (check && serno >= 0) { | ||
this._lastSerno = serno; | ||
} | ||
return check; | ||
}; | ||
const { iceServers, speaker: speakerId, localElement, remoteElement, mediaConstraints: { audio, video } } = session; | ||
this.options = Object.assign({}, DEFAULT_CALL_OPTIONS, { audio, video, iceServers, localElement, remoteElement, speakerId }, opts); | ||
this._onMediaError = this._onMediaError.bind(this); | ||
this._init(); | ||
} | ||
get nodeId() { | ||
return this._targetNodeId; | ||
} | ||
set nodeId(what) { | ||
this._targetNodeId = what; | ||
} | ||
invite() { | ||
this.direction = Direction.Outbound; | ||
this.peer = new Peer(PeerType.Offer, this.options); | ||
this._registerPeerEvents(); | ||
} | ||
answer() { | ||
this.direction = Direction.Inbound; | ||
this.peer = new Peer(PeerType.Answer, this.options); | ||
this._registerPeerEvents(); | ||
} | ||
hangup(params = {}, execute = true) { | ||
if (this.screenShareActive) { | ||
if (this.screenShare instanceof Call) { | ||
this.screenShare.hangup(params, execute); | ||
} | ||
this.cause = params.cause || 'NORMAL_CLEARING'; | ||
this.causeCode = params.causeCode || 16; | ||
this.setState(State.Hangup); | ||
const _close = () => { | ||
this.peer ? this.peer.instance.close() : null; | ||
this.setState(State.Destroy); | ||
}; | ||
if (execute) { | ||
const bye = new Bye({ sessid: this.session.sessionid, dialogParams: this.options }); | ||
this._execute(bye) | ||
.catch(error => logger.error('verto.bye failed!', error)) | ||
.then(_close.bind(this)); | ||
} | ||
else { | ||
_close(); | ||
} | ||
super.hangup(params, execute); | ||
} | ||
transfer(destination) { | ||
const msg = new Modify({ sessid: this.session.sessionid, action: 'transfer', destination, dialogParams: this.options }); | ||
this._execute(msg); | ||
} | ||
replace(replaceCallID) { | ||
const msg = new Modify({ sessid: this.session.sessionid, action: 'replace', replaceCallID, dialogParams: this.options }); | ||
this._execute(msg); | ||
} | ||
hold() { | ||
const msg = new Modify({ sessid: this.session.sessionid, action: 'hold', dialogParams: this.options }); | ||
return this._execute(msg) | ||
.then(this._handleChangeHoldStateSuccess.bind(this)) | ||
.catch(this._handleChangeHoldStateError.bind(this)); | ||
} | ||
unhold() { | ||
const msg = new Modify({ sessid: this.session.sessionid, action: 'unhold', dialogParams: this.options }); | ||
return this._execute(msg) | ||
.then(this._handleChangeHoldStateSuccess.bind(this)) | ||
.catch(this._handleChangeHoldStateError.bind(this)); | ||
} | ||
toggleHold() { | ||
const msg = new Modify({ sessid: this.session.sessionid, action: 'toggleHold', dialogParams: this.options }); | ||
return this._execute(msg) | ||
.then(this._handleChangeHoldStateSuccess.bind(this)) | ||
.catch(this._handleChangeHoldStateError.bind(this)); | ||
} | ||
dtmf(dtmf) { | ||
const msg = new Info({ sessid: this.session.sessionid, dtmf, dialogParams: this.options }); | ||
this._execute(msg); | ||
} | ||
message(to, body) { | ||
const msg = { from: this.session.options.login, to, body }; | ||
const info = new Info({ sessid: this.session.sessionid, msg, dialogParams: this.options }); | ||
this._execute(info); | ||
} | ||
startScreenShare(opts) { | ||
@@ -140,81 +42,6 @@ return __awaiter(this, void 0, void 0, function* () { | ||
stopScreenShare() { | ||
if (this.screenShareActive) { | ||
if (this.screenShare instanceof Call) { | ||
this.screenShare.hangup(); | ||
} | ||
} | ||
get screenShareActive() { | ||
return this.screenShare && this.screenShare instanceof Call; | ||
} | ||
set audioState(what) { | ||
this.peer.audioState = what; | ||
} | ||
get audioState() { | ||
return this.peer.audioState; | ||
} | ||
set videoState(what) { | ||
this.peer.videoState = what; | ||
} | ||
get videoState() { | ||
return this.peer.videoState; | ||
} | ||
get localStream() { | ||
return this.options.localStream; | ||
} | ||
get remoteStream() { | ||
return this.options.remoteStream; | ||
} | ||
get memberChannel() { | ||
return `conference-member.${this.id}`; | ||
} | ||
muteAudio() { | ||
this.peer.audioState = 'off'; | ||
} | ||
unmuteAudio() { | ||
this.peer.audioState = 'on'; | ||
} | ||
toggleAudioMute() { | ||
this.peer.audioState = 'toggle'; | ||
} | ||
setAudioInDevice(deviceId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { instance } = this.peer; | ||
const sender = instance.getSenders().find(({ track: { kind } }) => kind === 'audio'); | ||
if (sender) { | ||
const newStream = yield getUserMedia({ audio: { deviceId: { exact: deviceId } } }); | ||
const audioTrack = newStream.getAudioTracks()[0]; | ||
sender.replaceTrack(audioTrack); | ||
this.options.micId = deviceId; | ||
const { localStream } = this.options; | ||
localStream.getAudioTracks().forEach(t => t.stop()); | ||
localStream.getVideoTracks().forEach(t => newStream.addTrack(t)); | ||
this.options.localStream = newStream; | ||
} | ||
}); | ||
} | ||
muteVideo() { | ||
this.peer.videoState = 'off'; | ||
} | ||
unmuteVideo() { | ||
this.peer.videoState = 'on'; | ||
} | ||
toggleVideoMute() { | ||
this.peer.videoState = 'toggle'; | ||
} | ||
setVideoDevice(deviceId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { instance } = this.peer; | ||
const sender = instance.getSenders().find(({ track: { kind } }) => kind === 'video'); | ||
if (sender) { | ||
const newStream = yield getUserMedia({ video: { deviceId: { exact: deviceId } } }); | ||
const videoTrack = newStream.getVideoTracks()[0]; | ||
sender.replaceTrack(videoTrack); | ||
const { localElement, localStream } = this.options; | ||
attachMediaStream(localElement, newStream); | ||
this.options.camId = deviceId; | ||
localStream.getAudioTracks().forEach(t => newStream.addTrack(t)); | ||
localStream.getVideoTracks().forEach(t => t.stop()); | ||
this.options.localStream = newStream; | ||
} | ||
}); | ||
} | ||
deaf() { | ||
@@ -226,2 +53,5 @@ muteMediaElement(this.options.remoteElement); | ||
} | ||
toggleDeaf() { | ||
toggleMuteMediaElement(this.options.remoteElement); | ||
} | ||
setAudioOutDevice(deviceId) { | ||
@@ -237,512 +67,5 @@ return __awaiter(this, void 0, void 0, function* () { | ||
} | ||
setState(state) { | ||
this._prevState = this._state; | ||
this._state = state; | ||
this.state = State[this._state].toLowerCase(); | ||
this.prevState = State[this._prevState].toLowerCase(); | ||
logger.info(`Call ${this.id} state change from ${this.prevState} to ${this.state}`); | ||
this._dispatchNotification({ type: NOTIFICATION_TYPE.callUpdate, call: this }); | ||
switch (state) { | ||
case State.Purge: | ||
this.hangup({ cause: 'PURGE', causeCode: '01' }, false); | ||
break; | ||
case State.Active: { | ||
setTimeout(() => { | ||
const { remoteElement, speakerId } = this.options; | ||
if (remoteElement && speakerId) { | ||
setMediaElementSinkId(remoteElement, speakerId); | ||
} | ||
}, 0); | ||
break; | ||
} | ||
case State.Destroy: | ||
this._finalize(); | ||
break; | ||
} | ||
} | ||
handleMessage(msg) { | ||
const { method, params } = msg; | ||
switch (method) { | ||
case VertoMethod.Answer: { | ||
this.gotAnswer = true; | ||
if (this._state >= State.Active) { | ||
return; | ||
} | ||
if (this._state >= State.Early) { | ||
this.setState(State.Active); | ||
} | ||
if (!this.gotEarly) { | ||
this._onRemoteSdp(params.sdp); | ||
} | ||
break; | ||
} | ||
case VertoMethod.Media: { | ||
if (this._state >= State.Early) { | ||
return; | ||
} | ||
this.gotEarly = true; | ||
this._onRemoteSdp(params.sdp); | ||
break; | ||
} | ||
case VertoMethod.Display: | ||
case VertoMethod.Attach: { | ||
const { display_name: displayName, display_number: displayNumber, display_direction } = params; | ||
const displayDirection = display_direction === Direction.Inbound ? Direction.Outbound : Direction.Inbound; | ||
const notification = { type: NOTIFICATION_TYPE[method], call: this, displayName, displayNumber, displayDirection }; | ||
if (!trigger(SwEvent.Notification, notification, this.id)) { | ||
trigger(SwEvent.Notification, notification, this.session.uuid); | ||
} | ||
break; | ||
} | ||
case VertoMethod.Info: | ||
case VertoMethod.Event: { | ||
const notification = Object.assign({}, params, { type: NOTIFICATION_TYPE.generic, call: this }); | ||
if (!trigger(SwEvent.Notification, notification, this.id)) { | ||
trigger(SwEvent.Notification, notification, this.session.uuid); | ||
} | ||
break; | ||
} | ||
case VertoMethod.Bye: | ||
this.hangup(params, false); | ||
break; | ||
} | ||
} | ||
handleConferenceUpdate(packet, initialPvtData) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!this._checkConferenceSerno(packet.wireSerno) && packet.name !== initialPvtData.laName) { | ||
logger.error('ConferenceUpdate invalid wireSerno or packet name:', packet); | ||
return 'INVALID_PACKET'; | ||
} | ||
const { action, data, hashKey: callId = String(this._lastSerno), arrIndex: index } = packet; | ||
switch (action) { | ||
case 'bootObj': { | ||
this._lastSerno = 0; | ||
const { chatID, chatChannel, infoChannel, modChannel, laName, conferenceMemberID, role } = initialPvtData; | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.Join, conferenceName: laName, participantId: Number(conferenceMemberID), role }); | ||
if (chatChannel) { | ||
yield this._subscribeConferenceChat(chatChannel); | ||
} | ||
if (infoChannel) { | ||
yield this._subscribeConferenceInfo(infoChannel); | ||
} | ||
if (modChannel && role === Role.Moderator) { | ||
yield this._subscribeConferenceModerator(modChannel); | ||
} | ||
const participants = []; | ||
for (const i in data) { | ||
participants.push(Object.assign({ callId: data[i][0], index: Number(i) }, mutateLiveArrayData(data[i][1]))); | ||
} | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.Bootstrap, participants }); | ||
break; | ||
} | ||
case 'add': { | ||
this._dispatchConferenceUpdate(Object.assign({ action: ConferenceAction.Add, callId, index }, mutateLiveArrayData(data))); | ||
break; | ||
} | ||
case 'modify': | ||
this._dispatchConferenceUpdate(Object.assign({ action: ConferenceAction.Modify, callId, index }, mutateLiveArrayData(data))); | ||
break; | ||
case 'del': | ||
this._dispatchConferenceUpdate(Object.assign({ action: ConferenceAction.Delete, callId, index }, mutateLiveArrayData(data))); | ||
break; | ||
case 'clear': | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.Clear }); | ||
break; | ||
default: | ||
this._dispatchConferenceUpdate({ action, data, callId, index }); | ||
break; | ||
} | ||
}); | ||
} | ||
_addChannel(channel) { | ||
if (!this.channels.includes(channel)) { | ||
this.channels.push(channel); | ||
} | ||
const protocol = this.session.webRtcProtocol; | ||
if (this.session._existsSubscription(protocol, channel)) { | ||
this.session.subscriptions[protocol][channel] = Object.assign({}, this.session.subscriptions[protocol][channel], { callId: this.id }); | ||
} | ||
} | ||
_subscribeConferenceChat(channel) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const tmp = { | ||
nodeId: this.nodeId, | ||
channels: [channel], | ||
handler: (params) => { | ||
const { direction, from: participantNumber, fromDisplay: participantName, message: messageText, type: messageType } = params.data; | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.ChatMessage, direction, participantNumber, participantName, messageText, messageType, messageId: params.eventSerno }); | ||
} | ||
}; | ||
const response = yield this.session.vertoSubscribe(tmp) | ||
.catch(error => { | ||
logger.error('ConfChat subscription error:', error); | ||
}); | ||
if (checkSubscribeResponse(response, channel)) { | ||
this._addChannel(channel); | ||
Object.defineProperties(this, { | ||
sendChatMessage: { | ||
configurable: true, | ||
value: (message, type) => { | ||
this.session.vertoBroadcast({ nodeId: this.nodeId, channel, data: { action: 'send', message, type } }); | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
_subscribeConferenceInfo(channel) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const tmp = { | ||
nodeId: this.nodeId, | ||
channels: [channel], | ||
handler: (params) => { | ||
const { eventData } = params; | ||
switch (eventData.contentType) { | ||
case 'layout-info': | ||
eventData.callID = this.id; | ||
MCULayoutEventHandler(this.session, eventData); | ||
break; | ||
default: | ||
logger.error('Conference-Info unknown contentType', params); | ||
} | ||
} | ||
}; | ||
const response = yield this.session.vertoSubscribe(tmp) | ||
.catch(error => { | ||
logger.error('ConfInfo subscription error:', error); | ||
}); | ||
if (checkSubscribeResponse(response, channel)) { | ||
this._addChannel(channel); | ||
} | ||
}); | ||
} | ||
_confControl(channel, params = {}) { | ||
const data = Object.assign({ application: 'conf-control', callID: this.id, value: null }, params); | ||
this.session.vertoBroadcast({ nodeId: this.nodeId, channel, data }); | ||
} | ||
_subscribeConferenceModerator(channel) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const _modCommand = (command, memberID = null, value = null) => { | ||
const id = parseInt(memberID) || null; | ||
this._confControl(channel, { command, id, value }); | ||
}; | ||
const _videoRequired = () => { | ||
const { video } = this.options; | ||
if ((typeof video === 'boolean' && !video) || (typeof video === 'object' && objEmpty(video))) { | ||
throw `Conference ${this.id} has no video!`; | ||
} | ||
}; | ||
const tmp = { | ||
nodeId: this.nodeId, | ||
channels: [channel], | ||
handler: (params) => { | ||
const { data } = params; | ||
switch (data['conf-command']) { | ||
case 'list-videoLayouts': | ||
if (data.responseData) { | ||
const tmp = JSON.stringify(data.responseData).replace(/IDS"/g, 'Ids"'); | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.LayoutList, layouts: JSON.parse(tmp) }); | ||
} | ||
break; | ||
default: | ||
this._dispatchConferenceUpdate({ action: ConferenceAction.ModCmdResponse, command: data['conf-command'], response: data.response }); | ||
} | ||
} | ||
}; | ||
const response = yield this.session.vertoSubscribe(tmp) | ||
.catch(error => { | ||
logger.error('ConfMod subscription error:', error); | ||
}); | ||
if (checkSubscribeResponse(response, channel)) { | ||
this.role = Role.Moderator; | ||
this._addChannel(channel); | ||
Object.defineProperties(this, { | ||
listVideoLayouts: { | ||
configurable: true, | ||
value: () => { | ||
_modCommand('list-videoLayouts'); | ||
} | ||
}, | ||
playMedia: { | ||
configurable: true, | ||
value: (file) => { | ||
_modCommand('play', null, file); | ||
} | ||
}, | ||
stopMedia: { | ||
configurable: true, | ||
value: () => { | ||
_modCommand('stop', null, 'all'); | ||
} | ||
}, | ||
deaf: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('deaf', memberID); | ||
} | ||
}, | ||
undeaf: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('undeaf', memberID); | ||
} | ||
}, | ||
startRecord: { | ||
configurable: true, | ||
value: (file) => { | ||
_modCommand('recording', null, ['start', file]); | ||
} | ||
}, | ||
stopRecord: { | ||
configurable: true, | ||
value: () => { | ||
_modCommand('recording', null, ['stop', 'all']); | ||
} | ||
}, | ||
snapshot: { | ||
configurable: true, | ||
value: (file) => { | ||
_videoRequired(); | ||
_modCommand('vid-write-png', null, file); | ||
} | ||
}, | ||
setVideoLayout: { | ||
configurable: true, | ||
value: (layout, canvasID) => { | ||
_videoRequired(); | ||
const value = canvasID ? [layout, canvasID] : layout; | ||
_modCommand('vid-layout', null, value); | ||
} | ||
}, | ||
kick: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('kick', memberID); | ||
} | ||
}, | ||
muteMic: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('tmute', memberID); | ||
} | ||
}, | ||
muteVideo: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_videoRequired(); | ||
_modCommand('tvmute', memberID); | ||
} | ||
}, | ||
presenter: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_videoRequired(); | ||
_modCommand('vid-res-id', memberID, 'presenter'); | ||
} | ||
}, | ||
videoFloor: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_videoRequired(); | ||
_modCommand('vid-floor', memberID, 'force'); | ||
} | ||
}, | ||
banner: { | ||
configurable: true, | ||
value: (memberID, text) => { | ||
_videoRequired(); | ||
_modCommand('vid-banner', memberID, encodeURI(text)); | ||
} | ||
}, | ||
volumeDown: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('volume_out', memberID, 'down'); | ||
} | ||
}, | ||
volumeUp: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('volume_out', memberID, 'up'); | ||
} | ||
}, | ||
gainDown: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('volume_in', memberID, 'down'); | ||
} | ||
}, | ||
gainUp: { | ||
configurable: true, | ||
value: (memberID) => { | ||
_modCommand('volume_in', memberID, 'up'); | ||
} | ||
}, | ||
transfer: { | ||
configurable: true, | ||
value: (memberID, exten) => { | ||
_modCommand('transfer', memberID, exten); | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
_handleChangeHoldStateSuccess(response) { | ||
response.holdState === 'active' ? this.setState(State.Active) : this.setState(State.Held); | ||
return true; | ||
} | ||
_handleChangeHoldStateError(error) { | ||
logger.error(`Failed to ${error.action} on call ${this.id}`); | ||
return false; | ||
} | ||
_onRemoteSdp(remoteSdp) { | ||
let sdp = sdpMediaOrderHack(remoteSdp, this.peer.instance.localDescription.sdp); | ||
if (this.options.useStereo) { | ||
sdp = sdpStereoHack(sdp); | ||
} | ||
const sessionDescr = sdpToJsonHack({ sdp, type: PeerType.Answer }); | ||
this.peer.instance.setRemoteDescription(sessionDescr) | ||
.then(() => { | ||
if (this.gotEarly) { | ||
this.setState(State.Early); | ||
} | ||
if (this.gotAnswer) { | ||
this.setState(State.Active); | ||
} | ||
}) | ||
.catch(error => { | ||
logger.error('Call setRemoteDescription Error: ', error); | ||
this.hangup(); | ||
}); | ||
} | ||
_requestAnotherLocalDescription() { | ||
if (isFunction(this.peer.onSdpReadyTwice)) { | ||
trigger(SwEvent.Error, new Error('SDP without candidates for the second time!'), this.session.uuid); | ||
return; | ||
} | ||
Object.defineProperty(this.peer, 'onSdpReadyTwice', { value: this._onIceSdp.bind(this) }); | ||
this._iceDone = false; | ||
this.peer.startNegotiation(); | ||
} | ||
_onIceSdp(data) { | ||
if (this._iceTimeout) { | ||
clearTimeout(this._iceTimeout); | ||
} | ||
this._iceTimeout = null; | ||
this._iceDone = true; | ||
const { sdp, type } = data; | ||
if (sdp.indexOf('candidate') === -1) { | ||
this._requestAnotherLocalDescription(); | ||
return; | ||
} | ||
let msg = null; | ||
const tmpParams = { sessid: this.session.sessionid, sdp, dialogParams: this.options }; | ||
switch (type) { | ||
case PeerType.Offer: | ||
this.setState(State.Requesting); | ||
msg = new Invite(tmpParams); | ||
break; | ||
case PeerType.Answer: | ||
this.setState(State.Answering); | ||
msg = this.options.attach === true ? new Attach(tmpParams) : new Answer(tmpParams); | ||
break; | ||
default: | ||
logger.error(`${this.id} - Unknown local SDP type:`, data); | ||
return this.hangup({}, false); | ||
} | ||
this._execute(msg) | ||
.then(response => { | ||
const { node_id = null } = response; | ||
this._targetNodeId = node_id; | ||
type === PeerType.Offer ? this.setState(State.Trying) : this.setState(State.Active); | ||
}) | ||
.catch(error => { | ||
logger.error(`${this.id} - Sending ${type} error:`, error); | ||
this.hangup(); | ||
}); | ||
} | ||
_registerPeerEvents() { | ||
const { instance } = this.peer; | ||
this._iceDone = false; | ||
instance.onicecandidate = event => { | ||
if (this._iceDone) { | ||
return; | ||
} | ||
if (this._iceTimeout === null) { | ||
this._iceTimeout = setTimeout(() => this._onIceSdp(instance.localDescription), 1000); | ||
} | ||
if (event.candidate) { | ||
logger.info('IceCandidate:', event.candidate); | ||
} | ||
else { | ||
this._onIceSdp(instance.localDescription); | ||
} | ||
}; | ||
instance.ontrack = event => { | ||
this.options.remoteStream = event.streams[0]; | ||
if (this.options.screenShare === true) { | ||
return; | ||
} | ||
const { remoteElement, remoteStream } = this.options; | ||
attachMediaStream(remoteElement, remoteStream); | ||
}; | ||
} | ||
_onMediaError(error) { | ||
this._dispatchNotification({ type: NOTIFICATION_TYPE.userMediaError, error }); | ||
this.hangup({}, false); | ||
} | ||
_validCallback(name) { | ||
return this.options.hasOwnProperty(name) && isFunction(this.options[name]); | ||
} | ||
_dispatchConferenceUpdate(params) { | ||
this._dispatchNotification(Object.assign({ type: NOTIFICATION_TYPE.conferenceUpdate, call: this }, params)); | ||
} | ||
_dispatchNotification(notification) { | ||
if (this.options.screenShare === true) { | ||
return; | ||
} | ||
if (!trigger(SwEvent.Notification, notification, this.id, false)) { | ||
trigger(SwEvent.Notification, notification, this.session.uuid); | ||
} | ||
} | ||
_execute(msg) { | ||
if (this.nodeId) { | ||
msg.targetNodeId = this.nodeId; | ||
} | ||
return this.session.execute(msg); | ||
} | ||
_init() { | ||
const { id, userVariables, remoteCallerNumber } = this.options; | ||
if (!id) { | ||
this.options.id = uuidv4(); | ||
} | ||
if (!userVariables || objEmpty(userVariables)) { | ||
this.options.userVariables = this.session.options.userVariables || {}; | ||
} | ||
if (!remoteCallerNumber) { | ||
this.options.remoteCallerNumber = this.options.destinationNumber; | ||
} | ||
this.id = this.options.id; | ||
this.session.calls[this.id] = this; | ||
register(SwEvent.MediaError, this._onMediaError, this.id); | ||
if (this._validCallback('onNotification')) { | ||
register(SwEvent.Notification, this.options.onNotification.bind(this), this.id); | ||
} | ||
this.setState(State.New); | ||
logger.info('New Call with Options:', this.options); | ||
} | ||
_finalize() { | ||
const { remoteStream, localStream, remoteElement, localElement } = this.options; | ||
stopStream(remoteStream); | ||
stopStream(localStream); | ||
if (this.options.screenShare !== true) { | ||
detachMediaStream(remoteElement); | ||
detachMediaStream(localElement); | ||
} | ||
this._stats(false); | ||
deRegister(SwEvent.MediaError, null, this.id); | ||
this.peer = null; | ||
this.session.calls[this.id] = null; | ||
delete this.session.calls[this.id]; | ||
super._finalize(); | ||
} | ||
@@ -749,0 +72,0 @@ _stats(what = true) { |
@@ -181,10 +181,5 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
let wrapper = response; | ||
const { result = null } = response; | ||
if (result) { | ||
wrapper = result || {}; | ||
} | ||
Object.keys(tmp).forEach(k => { tmp[k] = wrapper[`${k}Channels`] || []; }); | ||
Object.keys(tmp).forEach(k => { tmp[k] = response[`${k}Channels`] || []; }); | ||
return tmp; | ||
}; | ||
export { getUserMedia, getDevices, scanResolutions, getMediaConstraints, assureDeviceId, removeUnsupportedConstraints, checkDeviceIdConstraints, sdpStereoHack, sdpMediaOrderHack, checkSubscribeResponse, destructSubscribeResponse }; |
@@ -81,3 +81,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
} | ||
const protocol = session.webRtcProtocol; | ||
const protocol = session.relayProtocol; | ||
const firstValue = eventChannel.split('.')[0]; | ||
@@ -127,3 +127,3 @@ if (session._existsSubscription(protocol, eventChannel)) { | ||
const { session } = this; | ||
const protocol = session.webRtcProtocol; | ||
const protocol = session.relayProtocol; | ||
const { action, laChannel, laName, chatChannel, infoChannel, modChannel, conferenceMemberID, role, callID } = pvtData; | ||
@@ -178,3 +178,3 @@ switch (action) { | ||
session.vertoUnsubscribe({ nodeId: this.nodeId, channels }) | ||
.then(({ unsubscribedChannels }) => { | ||
.then(({ unsubscribedChannels = [] }) => { | ||
if (call) { | ||
@@ -181,0 +181,0 @@ call.channels = call.channels.filter(c => !unsubscribedChannels.includes(c)); |
import BrowserSession from '../../common/src/BrowserSession'; | ||
import { CallOptions } from '../../common/src/util/interfaces'; | ||
import BaseMessage from '../../common/src/messages/BaseMessage'; | ||
@@ -7,5 +6,2 @@ export default class SignalWire extends BrowserSession { | ||
execute(message: BaseMessage): any; | ||
newCall(options: CallOptions): Promise<any>; | ||
protected _onSessionConnect(): void; | ||
readonly webRtcProtocol: string; | ||
} |
@@ -1,12 +0,2 @@ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
import BrowserSession from '../../common/src/BrowserSession'; | ||
import WebRTC from '../../common/src/relay/webrtc/WebRTC'; | ||
import logger from '../../common/src/util/logger'; | ||
import { Execute } from '../../common/src/messages/Blade'; | ||
@@ -26,21 +16,6 @@ import BaseRequest from '../../common/src/messages/verto/BaseRequest'; | ||
} | ||
msg = new Execute({ protocol: this.webRtcProtocol, method: 'message', params }); | ||
msg = new Execute({ protocol: this.relayProtocol, method: 'message', params }); | ||
} | ||
return super.execute(msg); | ||
} | ||
newCall(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const call = yield this._relayInstances['webrtc'].newCall(options) | ||
.catch(error => { | ||
logger.error('SignalWire newCall error', error); | ||
}); | ||
return call; | ||
}); | ||
} | ||
_onSessionConnect() { | ||
this._addRelayInstance('webrtc', WebRTC); | ||
} | ||
get webRtcProtocol() { | ||
return this._relayInstances['webrtc'] ? this._relayInstances['webrtc'].protocol : null; | ||
} | ||
} |
import BrowserSession from '../../common/src/BrowserSession'; | ||
import { SubscribeParams, BroadcastParams, CallOptions } from '../../common/src/util/interfaces'; | ||
import Call from '../../common/src/webrtc/Call'; | ||
export declare const VERTO_PROTOCOL = "verto-protocol"; | ||
export default class Verto extends BrowserSession { | ||
relayProtocol: string; | ||
validateOptions(): boolean; | ||
newCall(options: CallOptions): Call; | ||
newCall(options: CallOptions): any; | ||
broadcast(params: BroadcastParams): void; | ||
@@ -13,3 +13,2 @@ subscribe(params: SubscribeParams): Promise<any>; | ||
protected _onSocketMessage(msg: any): void; | ||
readonly webRtcProtocol: string; | ||
} |
@@ -18,2 +18,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
export default class Verto extends BrowserSession { | ||
constructor() { | ||
super(...arguments); | ||
this.relayProtocol = VERTO_PROTOCOL; | ||
} | ||
validateOptions() { | ||
@@ -47,2 +51,3 @@ const { host, login, passwd, password } = this.options; | ||
if (response) { | ||
this._autoReconnect = true; | ||
this.sessionid = response.sessid; | ||
@@ -58,5 +63,2 @@ localStorage.setItem(SESSION_ID, this.sessionid); | ||
} | ||
get webRtcProtocol() { | ||
return VERTO_PROTOCOL; | ||
} | ||
} |
{ | ||
"name": "@signalwire/js", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"description": "Relay SDK for JavaScript to connect to SignalWire.", | ||
@@ -48,3 +48,3 @@ "author": "SignalWire Team <open.source@signalwire.com>", | ||
"acorn": "^6.0.0", | ||
"babel-core": "^6.26.0", | ||
"babel-core": "^6.26.3", | ||
"babel-loader": "^7.1.5", | ||
@@ -57,3 +57,3 @@ "babel-plugin-transform-runtime": "^6.23.0", | ||
"tslint": "^5.17.0", | ||
"typescript": "^3.4.5", | ||
"typescript": "~3.4.3", | ||
"webpack": "^4.33.0", | ||
@@ -60,0 +60,0 @@ "webpack-cli": "^3.3.2", |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
333200
141
5340