@signalwire/js
Advanced tools
Comparing version 1.3.0-cantina.7 to 1.3.0-cantina.8
@@ -6,2 +6,7 @@ # Changelog | ||
## [Unreleased] | ||
### Removed | ||
- Remove deprecated getters: `devices`, `videoDevices`, `audioInDevices`, `audioOutDevices`. | ||
- Remove deprecated `refreshDevices()` method. Use `getDevices()` instead. | ||
## [1.2.7] - 2020-02-21 | ||
@@ -8,0 +13,0 @@ ### Fixed |
import * as log from 'loglevel'; | ||
import Connection from './services/Connection'; | ||
import BaseMessage from '../../common/src/messages/BaseMessage'; | ||
import { BroadcastParams, ISignalWireOptions, SubscribeParams } from './util/interfaces'; | ||
import { BroadcastParams, ISignalWireOptions, SubscribeParams, IBladeAuthorization } from './util/interfaces'; | ||
export default abstract class BaseSession { | ||
@@ -9,12 +9,9 @@ options: ISignalWireOptions; | ||
sessionid: string; | ||
subscriptions: { | ||
[channel: string]: any; | ||
}; | ||
subscriptions: Map<string, boolean>; | ||
nodeid: string; | ||
master_nodeid: string; | ||
expiresAt: number; | ||
signature: string; | ||
relayProtocol: string; | ||
contexts: string[]; | ||
timeoutErrorCode: number; | ||
authorization: IBladeAuthorization; | ||
protected connection: Connection; | ||
@@ -25,2 +22,3 @@ protected _jwtAuth: boolean; | ||
protected _reconnectTimeout: any; | ||
protected _checkTokenExpirationTimeout: any; | ||
protected _autoReconnect: boolean; | ||
@@ -33,2 +31,7 @@ protected _idle: boolean; | ||
get connected(): boolean; | ||
get signature(): string; | ||
get scopeId(): string; | ||
get resource(): string; | ||
get scopes(): string[]; | ||
get expiresAt(): number; | ||
get expired(): boolean; | ||
@@ -35,0 +38,0 @@ get reconnectDelay(): number; |
@@ -17,3 +17,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { ADD, REMOVE, SwEvent, BladeMethod } from './util/constants'; | ||
import { NOTIFICATION_TYPE } from './webrtc/constants'; | ||
import { Notification } from './webrtc/constants'; | ||
import { Subscription, Connect, Reauthenticate, Ping } from './messages/Blade'; | ||
@@ -28,5 +28,3 @@ import { isFunction, randomInt } from './util/helpers'; | ||
this.sessionid = ''; | ||
this.subscriptions = {}; | ||
this.expiresAt = 0; | ||
this.signature = null; | ||
this.subscriptions = new Map(); | ||
this.relayProtocol = null; | ||
@@ -58,2 +56,17 @@ this.contexts = []; | ||
} | ||
get signature() { | ||
return this.authorization ? this.authorization.signature : null; | ||
} | ||
get scopeId() { | ||
return this.authorization ? this.authorization.scope_id : null; | ||
} | ||
get resource() { | ||
return this.authorization ? this.authorization.resource : null; | ||
} | ||
get scopes() { | ||
return this.authorization ? this.authorization.scopes : []; | ||
} | ||
get expiresAt() { | ||
return this.authorization ? +this.authorization.expires_at : 0; | ||
} | ||
get expired() { | ||
@@ -116,3 +129,3 @@ return this.expiresAt && this.expiresAt <= (Date.now() / 1000); | ||
clearTimeout(this._reconnectTimeout); | ||
this.subscriptions = {}; | ||
this.subscriptions.clear(); | ||
this._autoReconnect = false; | ||
@@ -144,4 +157,3 @@ this.relayProtocol = null; | ||
const response = yield this.execute(br); | ||
const { authorization: { expires_at = null } = {} } = response; | ||
this.expiresAt = +expires_at || 0; | ||
this.authorization = response.authorization || null; | ||
} | ||
@@ -178,5 +190,4 @@ } | ||
this._autoReconnect = true; | ||
const { sessionid, nodeid, master_nodeid, authorization: { expires_at = null, signature = null } = {} } = response; | ||
this.expiresAt = +expires_at || 0; | ||
this.signature = signature; | ||
const { sessionid, nodeid, master_nodeid, authorization } = response; | ||
this.authorization = authorization; | ||
this.relayProtocol = yield Setup(this); | ||
@@ -201,5 +212,6 @@ this._checkTokenExpiration(); | ||
for (const sub in this.subscriptions) { | ||
deRegisterAll(sub); | ||
const protocol = sub.split('|')[0]; | ||
deRegisterAll(protocol); | ||
} | ||
this.subscriptions = {}; | ||
this.subscriptions.clear(); | ||
this.contexts = []; | ||
@@ -209,3 +221,3 @@ if (this.expired) { | ||
this._autoReconnect = false; | ||
this.expiresAt = 0; | ||
this.authorization = null; | ||
} | ||
@@ -232,9 +244,8 @@ if (this._autoReconnect) { | ||
if (channel) { | ||
delete this.subscriptions[protocol][channel]; | ||
deRegister(protocol, null, channel); | ||
} | ||
else { | ||
delete this.subscriptions[protocol]; | ||
deRegisterAll(protocol); | ||
} | ||
this.subscriptions.delete(`${protocol}|${channel}`); | ||
} | ||
@@ -245,17 +256,10 @@ _addSubscription(protocol, handler = null, channel) { | ||
} | ||
if (!this._existsSubscription(protocol)) { | ||
this.subscriptions[protocol] = {}; | ||
} | ||
this.subscriptions[protocol][channel] = {}; | ||
if (isFunction(handler)) { | ||
register(protocol, handler, channel); | ||
} | ||
this.subscriptions.set(`${protocol}|${channel}`, true); | ||
} | ||
_existsSubscription(protocol, channel) { | ||
if (this.subscriptions.hasOwnProperty(protocol)) { | ||
if (!channel || (channel && this.subscriptions[protocol].hasOwnProperty(channel))) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
const sub = `${protocol}|${channel}`; | ||
return this.subscriptions.has(sub); | ||
} | ||
@@ -299,6 +303,7 @@ _attachListeners() { | ||
logger.warn('Your JWT is going to expire. You should refresh it to keep the session live.'); | ||
trigger(SwEvent.Notification, { type: NOTIFICATION_TYPE.refreshToken, session: this }, this.uuid, false); | ||
trigger(SwEvent.Notification, { type: Notification.RefreshToken, session: this }, this.uuid, false); | ||
} | ||
if (!this.expired) { | ||
setTimeout(this._checkTokenExpiration, 30 * 1000); | ||
clearTimeout(this._checkTokenExpirationTimeout); | ||
this._checkTokenExpirationTimeout = setTimeout(this._checkTokenExpiration, 30 * 1000); | ||
} | ||
@@ -305,0 +310,0 @@ } |
import BaseSession from './BaseSession'; | ||
import { ICacheDevices, IAudioSettings, IVideoSettings, BroadcastParams, SubscribeParams } from './util/interfaces'; | ||
import { IWebRTCCall } from './webrtc/interfaces'; | ||
import { IAudioSettings, IVideoSettings, BroadcastParams, SubscribeParams } from './util/interfaces'; | ||
import BaseMessage from './messages/BaseMessage'; | ||
import WebRTCCall from './webrtc/WebRTCCall'; | ||
import { IConferenceInfo } from './webrtc/interfaces'; | ||
import Conference from './webrtc/Conference'; | ||
export default abstract class BrowserSession extends BaseSession { | ||
calls: { | ||
[callId: string]: IWebRTCCall; | ||
[callId: string]: WebRTCCall; | ||
}; | ||
conferences: { | ||
[confUuid: string]: Conference; | ||
}; | ||
channelToCallIds: Map<string, string[]>; | ||
micId: string; | ||
@@ -13,2 +20,3 @@ micLabel: string; | ||
autoRecoverCalls: boolean; | ||
incognito: boolean; | ||
private _iceServers; | ||
@@ -18,10 +26,12 @@ private _localElement; | ||
protected _jwtAuth: boolean; | ||
protected _devices: ICacheDevices; | ||
protected _audioConstraints: boolean | MediaTrackConstraints; | ||
protected _videoConstraints: boolean | MediaTrackConstraints; | ||
protected _speaker: string; | ||
get callIds(): string[]; | ||
addChannelCallIdEntry(channel: string, callId: string): void; | ||
removeChannelCallIdEntry(channel: string, callId: string): void; | ||
get reconnectDelay(): number; | ||
connect(): Promise<void>; | ||
checkPermissions(audio?: boolean, video?: boolean): Promise<boolean>; | ||
logout(): void; | ||
purge(): void; | ||
disconnect(): Promise<void>; | ||
@@ -34,14 +44,6 @@ speedTest(bytes: number): Promise<unknown>; | ||
validateDeviceId(id: string, label: string, kind: MediaDeviceInfo['kind']): Promise<string>; | ||
refreshDevices(): Promise<ICacheDevices>; | ||
get devices(): ICacheDevices; | ||
validateVideoDevice(id: string, label: string): Promise<string>; | ||
validateAudioInDevice(id: string, label: string): Promise<string>; | ||
validateAudioOutDevice(id: string, label: string): Promise<string>; | ||
getDeviceResolutions(deviceId: string): Promise<any[]>; | ||
get videoDevices(): { | ||
[deviceId: string]: MediaDeviceInfo; | ||
}; | ||
get audioInDevices(): { | ||
[deviceId: string]: MediaDeviceInfo; | ||
}; | ||
get audioOutDevices(): { | ||
[deviceId: string]: MediaDeviceInfo; | ||
}; | ||
get mediaConstraints(): { | ||
@@ -65,6 +67,23 @@ audio: boolean | MediaTrackConstraints; | ||
get remoteElement(): HTMLMediaElement | string | Function; | ||
vertoBroadcast({ nodeId, channel: eventChannel, data }: BroadcastParams): void; | ||
vertoSubscribe({ nodeId, channels: eventChannel, handler }: SubscribeParams): Promise<any>; | ||
vertoUnsubscribe({ nodeId, channels: eventChannel }: SubscribeParams): Promise<any>; | ||
vertoBroadcast({ nodeId, channel, data }: BroadcastParams): any; | ||
vertoSubscribe({ nodeId, channels, handler }: SubscribeParams): Promise<any>; | ||
vertoUnsubscribe({ nodeId, channels, handler }: SubscribeParams): Promise<any>; | ||
listConf(): any; | ||
vertoConferenceList(showLayouts?: boolean, showMembers?: boolean): Promise<IConferenceInfo[]>; | ||
vertoLayoutList(options?: { | ||
fullList?: boolean; | ||
}): Promise<{ | ||
id: string; | ||
label: string; | ||
type: string; | ||
reservationIds: string[]; | ||
belongsToAGroup: boolean; | ||
}[]>; | ||
watchVertoConferences: () => Promise<IConferenceInfo[]>; | ||
unwatchVertoConferences: () => Promise<void>; | ||
dispatchConferenceUpdate(params: any): void; | ||
_jsApi(params?: {}): any; | ||
_wrapInExecute(message: BaseMessage): BaseMessage; | ||
execute(message: BaseMessage): any; | ||
protected _onSocketCloseOrError(event: any): void; | ||
} |
@@ -21,12 +21,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import logger from './util/logger'; | ||
import BaseSession from './BaseSession'; | ||
import { registerOnce, trigger } from './services/Handler'; | ||
import { SwEvent, SESSION_ID } from './util/constants'; | ||
import { State, DeviceType } from './webrtc/constants'; | ||
import { getDevices, scanResolutions, removeUnsupportedConstraints, checkDeviceIdConstraints, destructSubscribeResponse, getUserMedia, assureDeviceId } from './webrtc/helpers'; | ||
import { findElementByType } from './util/helpers'; | ||
import { State, DeviceType, Notification } from './webrtc/constants'; | ||
import { removeUnsupportedConstraints, getUserMedia, destructSubscribeResponse, destructConferenceState, mungeLayoutList } from './webrtc/helpers'; | ||
import { getDevices, scanResolutions, checkDeviceIdConstraints, assureDeviceId } from './webrtc/deviceHelpers'; | ||
import BaseRequest from './messages/verto/BaseRequest'; | ||
import { Execute } from './messages/Blade'; | ||
import { Unsubscribe, Subscribe, Broadcast, JSApi } from './messages/Verto'; | ||
import { localStorage } from './util/storage/'; | ||
import { stopStream } from './util/webrtc'; | ||
import laChannelHandler from './webrtc/LaChannelHandler'; | ||
import modChannelHandler from './webrtc/ModChannelHandler'; | ||
import infoChannelHandler from './webrtc/InfoChannelHandler'; | ||
import Conference from './webrtc/Conference'; | ||
export default class BrowserSession extends BaseSession { | ||
@@ -36,3 +41,6 @@ constructor() { | ||
this.calls = {}; | ||
this.conferences = {}; | ||
this.channelToCallIds = new Map(); | ||
this.autoRecoverCalls = true; | ||
this.incognito = false; | ||
this._iceServers = []; | ||
@@ -42,7 +50,84 @@ this._localElement = null; | ||
this._jwtAuth = true; | ||
this._devices = {}; | ||
this._audioConstraints = true; | ||
this._videoConstraints = false; | ||
this._speaker = null; | ||
this.watchVertoConferences = () => __awaiter(this, void 0, void 0, function* () { | ||
this.conferences = {}; | ||
const currentConfList = yield this.vertoConferenceList(); | ||
currentConfList.forEach(row => { | ||
this.conferences[row.uuid] = new Conference(this, row); | ||
}); | ||
const infoChannel = 'conference-info'; | ||
const laChannel = 'conference-liveArray'; | ||
const modChannel = 'conference-mod'; | ||
const result = yield this.vertoSubscribe({ | ||
nodeId: this.nodeid, | ||
channels: [infoChannel, laChannel, modChannel], | ||
}); | ||
const { subscribed = [], alreadySubscribed = [] } = destructSubscribeResponse(result); | ||
const all = subscribed.concat(alreadySubscribed); | ||
if (all.includes(laChannel)) { | ||
this._addSubscription(this.relayProtocol, laChannelHandler.bind(this, this), laChannel); | ||
} | ||
if (all.includes(infoChannel)) { | ||
this.on('signalwire.notification', (event) => { | ||
if (event.type !== 'conferenceUpdate') { | ||
return; | ||
} | ||
switch (event.action) { | ||
case 'clear': | ||
Object.keys(this.conferences).forEach(uuid => { | ||
if (this.conferences[uuid].confName === event.confName) { | ||
delete this.conferences[uuid]; | ||
} | ||
}); | ||
break; | ||
case 'conferenceInfo': | ||
const conferenceState = event.conferenceState; | ||
const { uuid, running } = conferenceState; | ||
if (running) { | ||
this.conferences[uuid] = new Conference(this, conferenceState); | ||
} | ||
else { | ||
delete this.conferences[uuid]; | ||
} | ||
break; | ||
} | ||
}); | ||
this._addSubscription(this.relayProtocol, infoChannelHandler.bind(this, this), infoChannel); | ||
} | ||
if (all.includes(modChannel)) { | ||
this._addSubscription(this.relayProtocol, modChannelHandler.bind(this, this), modChannel); | ||
} | ||
return currentConfList; | ||
}); | ||
this.unwatchVertoConferences = () => __awaiter(this, void 0, void 0, function* () { | ||
this.conferences = {}; | ||
const infoChannel = 'conference-info'; | ||
const laChannel = 'conference-liveArray'; | ||
const modChannel = 'conference-mod'; | ||
const channels = [infoChannel, laChannel, modChannel]; | ||
channels.forEach(channel => { | ||
this._removeSubscription(this.relayProtocol, channel); | ||
}); | ||
yield this.vertoUnsubscribe({ | ||
nodeId: this.nodeid, | ||
channels, | ||
}); | ||
}); | ||
} | ||
get callIds() { | ||
return Object.keys(this.calls); | ||
} | ||
addChannelCallIdEntry(channel, callId) { | ||
const current = this.channelToCallIds.get(channel) || []; | ||
const filtered = current.filter(id => id !== callId); | ||
filtered.push(callId); | ||
this.channelToCallIds.set(channel, filtered); | ||
} | ||
removeChannelCallIdEntry(channel, callId) { | ||
const current = this.channelToCallIds.get(channel) || []; | ||
const filtered = current.filter(id => id !== callId); | ||
this.channelToCallIds.set(channel, filtered); | ||
} | ||
get reconnectDelay() { | ||
@@ -56,3 +141,5 @@ return 1000; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.sessionid = yield localStorage.getItem(SESSION_ID); | ||
if (!this.incognito) { | ||
this.sessionid = yield localStorage.getItem(SESSION_ID); | ||
} | ||
_super.connect.call(this); | ||
@@ -73,4 +160,5 @@ }); | ||
} | ||
logout() { | ||
this.disconnect(); | ||
purge() { | ||
Object.keys(this.calls).forEach(k => this.calls[k].setState(State.Purge)); | ||
this.calls = {}; | ||
} | ||
@@ -82,4 +170,6 @@ disconnect() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
Object.keys(this.calls).forEach(k => this.calls[k].setState(State.Purge)); | ||
yield _super.disconnect.call(this); | ||
const promises = Object.keys(this.calls).map(k => this.calls[k].hangup()); | ||
yield Promise.all(promises); | ||
this.purge(); | ||
return _super.disconnect.call(this); | ||
}); | ||
@@ -138,49 +228,14 @@ } | ||
} | ||
refreshDevices() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
logger.warn('This method has been deprecated. Use getDevices() instead.'); | ||
const cache = {}; | ||
['videoinput', 'audioinput', 'audiooutput'].map((kind) => { | ||
cache[kind] = {}; | ||
Object.defineProperty(cache[kind], 'toArray', { | ||
value: function () { | ||
return Object.keys(this).map(k => this[k]); | ||
} | ||
}); | ||
}); | ||
const devices = yield this.getDevices(); | ||
devices.forEach((t) => { | ||
if (cache.hasOwnProperty(t.kind)) { | ||
cache[t.kind][t.deviceId] = t; | ||
} | ||
}); | ||
this._devices = cache; | ||
return this.devices; | ||
}); | ||
validateVideoDevice(id, label) { | ||
return assureDeviceId(id, label, 'videoinput'); | ||
} | ||
get devices() { | ||
return this._devices || {}; | ||
validateAudioInDevice(id, label) { | ||
return assureDeviceId(id, label, 'audioinput'); | ||
} | ||
validateAudioOutDevice(id, label) { | ||
return assureDeviceId(id, label, 'audiooutput'); | ||
} | ||
getDeviceResolutions(deviceId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
return yield scanResolutions(deviceId); | ||
} | ||
catch (error) { | ||
throw error; | ||
} | ||
}); | ||
return scanResolutions(deviceId); | ||
} | ||
get videoDevices() { | ||
logger.warn('This property has been deprecated. Use getVideoDevices() instead.'); | ||
return this._devices.videoinput || {}; | ||
} | ||
get audioInDevices() { | ||
logger.warn('This property has been deprecated. Use getAudioInDevices() instead.'); | ||
return this._devices.audioinput || {}; | ||
} | ||
get audioOutDevices() { | ||
logger.warn('This property has been deprecated. Use getAudioOutDevices() instead.'); | ||
return this._devices.audiooutput || {}; | ||
} | ||
get mediaConstraints() { | ||
@@ -200,2 +255,4 @@ return { audio: this._audioConstraints, video: this._videoConstraints }; | ||
disableMicrophone() { | ||
this.micId = null; | ||
this.micLabel = null; | ||
this._audioConstraints = false; | ||
@@ -217,2 +274,4 @@ } | ||
disableWebcam() { | ||
this.camId = null; | ||
this.camLabel = null; | ||
this._videoConstraints = false; | ||
@@ -241,3 +300,3 @@ } | ||
set localElement(tag) { | ||
this._localElement = findElementByType(tag); | ||
this._localElement = tag; | ||
} | ||
@@ -248,3 +307,3 @@ get localElement() { | ||
set remoteElement(tag) { | ||
this._remoteElement = findElementByType(tag); | ||
this._remoteElement = tag; | ||
} | ||
@@ -254,48 +313,129 @@ get remoteElement() { | ||
} | ||
vertoBroadcast({ nodeId, channel: eventChannel = '', data }) { | ||
if (!eventChannel) { | ||
throw new Error('Invalid channel for broadcast: ' + eventChannel); | ||
} | ||
const msg = new Broadcast({ sessid: this.sessionid, eventChannel, data }); | ||
vertoBroadcast({ nodeId, channel, data }) { | ||
const msg = new Broadcast({ sessid: this.sessionid, eventChannel: channel, data }); | ||
if (nodeId) { | ||
msg.targetNodeId = nodeId; | ||
} | ||
this.execute(msg).catch(error => error); | ||
return this.execute(msg); | ||
} | ||
vertoSubscribe({ nodeId, channels: eventChannel = [], handler }) { | ||
vertoSubscribe({ nodeId, channels, handler = null }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
eventChannel = eventChannel.filter(channel => channel && !this._existsSubscription(this.relayProtocol, channel)); | ||
if (!eventChannel.length) { | ||
return {}; | ||
} | ||
const msg = new Subscribe({ sessid: this.sessionid, eventChannel }); | ||
const msg = new Subscribe({ sessid: this.sessionid, eventChannel: channels }); | ||
if (nodeId) { | ||
msg.targetNodeId = nodeId; | ||
} | ||
const response = yield this.execute(msg); | ||
const { unauthorized = [], subscribed = [] } = destructSubscribeResponse(response); | ||
if (unauthorized.length) { | ||
unauthorized.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
try { | ||
const response = yield this.execute(msg); | ||
if (handler) { | ||
const { subscribed = [] } = destructSubscribeResponse(response); | ||
subscribed.forEach(channel => this._addSubscription(this.relayProtocol, handler, channel)); | ||
} | ||
return response; | ||
} | ||
subscribed.forEach(channel => this._addSubscription(this.relayProtocol, handler, channel)); | ||
return response; | ||
catch (error) { | ||
throw error; | ||
} | ||
}); | ||
} | ||
vertoUnsubscribe({ nodeId, channels: eventChannel = [] }) { | ||
vertoUnsubscribe({ nodeId, channels, handler = null }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
eventChannel = eventChannel.filter(channel => channel && this._existsSubscription(this.relayProtocol, channel)); | ||
if (!eventChannel.length) { | ||
return {}; | ||
} | ||
const msg = new Unsubscribe({ sessid: this.sessionid, eventChannel }); | ||
const msg = new Unsubscribe({ sessid: this.sessionid, eventChannel: channels }); | ||
if (nodeId) { | ||
msg.targetNodeId = nodeId; | ||
} | ||
const response = yield this.execute(msg); | ||
const { unsubscribed = [], notSubscribed = [] } = destructSubscribeResponse(response); | ||
unsubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
notSubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
return response; | ||
try { | ||
const response = yield this.execute(msg); | ||
if (handler) { | ||
const { unsubscribed = [], notSubscribed = [] } = destructSubscribeResponse(response); | ||
unsubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
notSubscribed.forEach(channel => this._removeSubscription(this.relayProtocol, channel)); | ||
} | ||
return response; | ||
} | ||
catch (error) { | ||
throw error; | ||
} | ||
}); | ||
} | ||
listConf() { | ||
const msg = new Execute({ | ||
protocol: this.relayProtocol, | ||
method: 'conference.list', | ||
params: {} | ||
}); | ||
console.debug('Send confList', msg); | ||
return this.execute(msg); | ||
} | ||
vertoConferenceList(showLayouts = false, showMembers = false) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
const rooms = []; | ||
const response = yield this._jsApi({ | ||
command: 'conference', | ||
data: { | ||
command: 'list', | ||
showLayouts, | ||
showMembers, | ||
}, | ||
}); | ||
response.conferences.forEach((conf) => { | ||
const { conferenceState, members = [], layouts = [] } = conf; | ||
const room = destructConferenceState(conferenceState); | ||
if (members.length) { | ||
room.members = members.filter(({ type }) => type === 'caller') | ||
.map(({ id, uuid, caller_id_number, caller_id_name }) => { | ||
return { | ||
participantId: Number(id).toString(), | ||
callId: uuid, | ||
participantNumber: caller_id_number, | ||
participantName: caller_id_name, | ||
}; | ||
}); | ||
} | ||
if (layouts.length) { | ||
const normal = layouts.filter(({ type }) => type === 'layout'); | ||
const group = layouts.filter(({ type }) => type === 'layoutGroup'); | ||
room.layouts = mungeLayoutList(normal, group); | ||
} | ||
rooms.push(room); | ||
}); | ||
return rooms; | ||
} | ||
catch (error) { | ||
console.error('vertoConferenceList error', error); | ||
return []; | ||
} | ||
}); | ||
} | ||
vertoLayoutList(options = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { fullList = false } = options; | ||
try { | ||
const { layouts: { layouts, groups } } = yield this._jsApi({ | ||
command: 'conference', | ||
data: { | ||
command: 'listLayouts', | ||
}, | ||
}); | ||
const final = mungeLayoutList(layouts, groups) | ||
.map((layout) => { | ||
const { id, type } = layout; | ||
const prefix = type === 'group' ? 'group:' : ''; | ||
return Object.assign(Object.assign({}, layout), { id: `${prefix}${id}` }); | ||
}); | ||
if (fullList) { | ||
return final; | ||
} | ||
return final.filter(layout => !layout.belongsToAGroup); | ||
} | ||
catch (error) { | ||
console.error('vertoLayoutList error', error); | ||
return []; | ||
} | ||
}); | ||
} | ||
dispatchConferenceUpdate(params) { | ||
const notification = Object.assign({ type: Notification.ConferenceUpdate }, params); | ||
trigger(SwEvent.Notification, notification, this.uuid); | ||
} | ||
_jsApi(params = {}) { | ||
@@ -305,2 +445,19 @@ const msg = new JSApi(Object.assign(Object.assign({}, params), { sessid: this.sessionid })); | ||
} | ||
_wrapInExecute(message) { | ||
const params = { | ||
message: message.request, | ||
node_id: message.targetNodeId || undefined | ||
}; | ||
return new Execute({ protocol: this.relayProtocol, method: 'message', params }); | ||
} | ||
execute(message) { | ||
if (message instanceof BaseRequest) { | ||
message = this._wrapInExecute(message); | ||
} | ||
return super.execute(message); | ||
} | ||
_onSocketCloseOrError(event) { | ||
this.purge(); | ||
super._onSocketCloseOrError(event); | ||
} | ||
} |
@@ -33,4 +33,10 @@ import BaseRequest from './verto/BaseRequest'; | ||
declare class JSApi extends BaseRequest { | ||
toString(): string; | ||
toString(): VertoMethod; | ||
} | ||
export { Login, Invite, Answer, Attach, Bye, Modify, Info, Broadcast, Subscribe, Unsubscribe, Result, JSApi, }; | ||
declare class Stats extends BaseRequest { | ||
toString(): VertoMethod; | ||
} | ||
declare class Ping extends BaseRequest { | ||
toString(): VertoMethod; | ||
} | ||
export { Login, Invite, Answer, Attach, Bye, Modify, Info, Broadcast, Subscribe, Unsubscribe, Result, JSApi, Stats, Ping, }; |
@@ -52,5 +52,15 @@ import BaseRequest from './verto/BaseRequest'; | ||
toString() { | ||
return 'jsapi'; | ||
return VertoMethod.JsApi; | ||
} | ||
} | ||
export { Login, Invite, Answer, Attach, Bye, Modify, Info, Broadcast, Subscribe, Unsubscribe, Result, JSApi, }; | ||
class Stats extends BaseRequest { | ||
toString() { | ||
return VertoMethod.Stats; | ||
} | ||
} | ||
class Ping extends BaseRequest { | ||
toString() { | ||
return VertoMethod.Ping; | ||
} | ||
} | ||
export { Login, Invite, Answer, Attach, Bye, Modify, Info, Broadcast, Subscribe, Unsubscribe, Result, JSApi, Stats, Ping, }; |
@@ -12,5 +12,4 @@ import logger from '../util/logger'; | ||
if (event_type === 'webrtc.message') { | ||
const handler = new VertoHandler(session); | ||
handler.nodeId = node_id; | ||
handler.handleMessage(params.params); | ||
params.params.nodeId = node_id; | ||
VertoHandler(session, params.params); | ||
} | ||
@@ -17,0 +16,0 @@ else { |
@@ -1,8 +0,9 @@ | ||
declare const isQueued: (eventName: string, uniqueId?: string) => boolean; | ||
declare const queueLength: (eventName: string, uniqueId?: string) => number; | ||
declare const register: (eventName: string, callback: Function, uniqueId?: string) => void; | ||
declare const registerOnce: (eventName: string, callback: Function, uniqueId?: string) => void; | ||
declare const deRegister: (eventName: string, callback?: Function, uniqueId?: string) => boolean; | ||
declare const trigger: (eventName: string, data: any, uniqueId?: string, globalPropagation?: boolean) => boolean; | ||
declare const deRegisterAll: (eventName: string) => void; | ||
export { trigger, register, registerOnce, deRegister, deRegisterAll, isQueued, queueLength }; | ||
declare const isQueued: (event: string, uniqueId?: string) => boolean; | ||
declare const queueLength: (event: string, uniqueId?: string) => number; | ||
declare const register: (event: string, callback: Function, uniqueId?: string) => void; | ||
declare const registerOnce: (event: string, callback: Function, uniqueId?: string) => void; | ||
declare const deRegister: (event: string, callback?: Function, uniqueId?: string) => boolean; | ||
declare const trigger: (event: string, data: any, uniqueId?: string, globalPropagation?: boolean) => boolean; | ||
declare const deRegisterAll: (event: string) => void; | ||
declare const clearQueue: () => void; | ||
export { trigger, register, registerOnce, deRegister, deRegisterAll, isQueued, queueLength, clearQueue }; |
@@ -1,38 +0,40 @@ | ||
import { objEmpty, isFunction } from '../util/helpers'; | ||
import { isFunction } from '../util/helpers'; | ||
const GLOBAL = 'GLOBAL'; | ||
const queue = {}; | ||
const isQueued = (eventName, uniqueId = GLOBAL) => queue.hasOwnProperty(eventName) && queue[eventName].hasOwnProperty(uniqueId); | ||
const queueLength = (eventName, uniqueId = GLOBAL) => { | ||
if (!isQueued(eventName, uniqueId)) { | ||
return 0; | ||
} | ||
return queue[eventName][uniqueId].length; | ||
window._queue = queue; | ||
const _buildEventName = (event, uniqueId) => `${event}|${uniqueId}`; | ||
const isQueued = (event, uniqueId = GLOBAL) => { | ||
const eventName = _buildEventName(event, uniqueId); | ||
return eventName in queue; | ||
}; | ||
const register = (eventName, callback, uniqueId = GLOBAL) => { | ||
if (!queue.hasOwnProperty(eventName)) { | ||
queue[eventName] = {}; | ||
const queueLength = (event, uniqueId = GLOBAL) => { | ||
const eventName = _buildEventName(event, uniqueId); | ||
return eventName in queue ? queue[eventName].length : 0; | ||
}; | ||
const register = (event, callback, uniqueId = GLOBAL) => { | ||
const eventName = _buildEventName(event, uniqueId); | ||
if (!(eventName in queue)) { | ||
queue[eventName] = []; | ||
} | ||
if (!queue[eventName].hasOwnProperty(uniqueId)) { | ||
queue[eventName][uniqueId] = []; | ||
} | ||
queue[eventName][uniqueId].push(callback); | ||
queue[eventName].push(callback); | ||
}; | ||
const registerOnce = (eventName, callback, uniqueId = GLOBAL) => { | ||
const registerOnce = (event, callback, uniqueId = GLOBAL) => { | ||
const cb = function (data) { | ||
deRegister(eventName, cb, uniqueId); | ||
deRegister(event, cb, uniqueId); | ||
callback(data); | ||
}; | ||
cb.prototype.targetRef = callback; | ||
return register(eventName, cb, uniqueId); | ||
return register(event, cb, uniqueId); | ||
}; | ||
const deRegister = (eventName, callback, uniqueId = GLOBAL) => { | ||
if (!isQueued(eventName, uniqueId)) { | ||
const deRegister = (event, callback, uniqueId = GLOBAL) => { | ||
if (!isQueued(event, uniqueId)) { | ||
return false; | ||
} | ||
const eventName = _buildEventName(event, uniqueId); | ||
if (isFunction(callback)) { | ||
const len = queue[eventName][uniqueId].length; | ||
const len = queue[eventName].length; | ||
for (let i = len - 1; i >= 0; i--) { | ||
const fn = queue[eventName][uniqueId][i]; | ||
const fn = queue[eventName][i]; | ||
if (callback === fn || (fn.prototype && callback === fn.prototype.targetRef)) { | ||
queue[eventName][uniqueId].splice(i, 1); | ||
queue[eventName].splice(i, 1); | ||
} | ||
@@ -42,24 +44,22 @@ } | ||
else { | ||
queue[eventName][uniqueId] = []; | ||
queue[eventName] = []; | ||
} | ||
if (queue[eventName][uniqueId].length === 0) { | ||
delete queue[eventName][uniqueId]; | ||
if (objEmpty(queue[eventName])) { | ||
delete queue[eventName]; | ||
} | ||
if (queue[eventName].length === 0) { | ||
delete queue[eventName]; | ||
} | ||
return true; | ||
}; | ||
const trigger = (eventName, data, uniqueId = GLOBAL, globalPropagation = true) => { | ||
const trigger = (event, data, uniqueId = GLOBAL, globalPropagation = true) => { | ||
const _propagate = globalPropagation && uniqueId !== GLOBAL; | ||
if (!isQueued(eventName, uniqueId)) { | ||
if (!isQueued(event, uniqueId)) { | ||
if (_propagate) { | ||
trigger(eventName, data); | ||
trigger(event, data); | ||
} | ||
return false; | ||
} | ||
const len = queue[eventName][uniqueId].length; | ||
const eventName = _buildEventName(event, uniqueId); | ||
const len = queue[eventName].length; | ||
if (!len) { | ||
if (_propagate) { | ||
trigger(eventName, data); | ||
trigger(event, data); | ||
} | ||
@@ -69,12 +69,16 @@ return false; | ||
for (let i = len - 1; i >= 0; i--) { | ||
queue[eventName][uniqueId][i](data); | ||
queue[eventName][i](data); | ||
} | ||
if (_propagate) { | ||
trigger(eventName, data); | ||
trigger(event, data); | ||
} | ||
return true; | ||
}; | ||
const deRegisterAll = (eventName) => { | ||
delete queue[eventName]; | ||
const deRegisterAll = (event) => { | ||
const eventName = _buildEventName(event, ''); | ||
Object.keys(queue) | ||
.filter(name => name.indexOf(eventName) === 0) | ||
.forEach(event => delete queue[event]); | ||
}; | ||
export { trigger, register, registerOnce, deRegister, deRegisterAll, isQueued, queueLength }; | ||
const clearQueue = () => Object.keys(queue).forEach(event => delete queue[event]); | ||
export { trigger, register, registerOnce, deRegister, deRegisterAll, isQueued, queueLength, clearQueue }; |
@@ -5,2 +5,3 @@ export declare const STORAGE_PREFIX = "@signalwire:"; | ||
export declare const SESSION_ID = "sessId"; | ||
export declare const VERTO_PROTOCOL = "verto-protocol"; | ||
export declare enum SwEvent { | ||
@@ -7,0 +8,0 @@ SocketOpen = "signalwire.socket.open", |
@@ -5,2 +5,3 @@ export const STORAGE_PREFIX = '@signalwire:'; | ||
export const SESSION_ID = 'sessId'; | ||
export const VERTO_PROTOCOL = 'verto-protocol'; | ||
export var SwEvent; | ||
@@ -7,0 +8,0 @@ (function (SwEvent) { |
export declare const deepCopy: (obj: Object) => any; | ||
export declare const objEmpty: (obj: Object) => boolean; | ||
export declare const mutateStorageKey: (key: string) => string; | ||
export declare const mutateLiveArrayData: (data: any) => any; | ||
export declare const mutateLiveArrayData: (data: any) => { | ||
audio?: any; | ||
video?: any; | ||
connectionState?: any; | ||
variables?: any; | ||
participantId: any; | ||
participantNumber: any; | ||
participantName: any; | ||
codec: any; | ||
participantData: any; | ||
}; | ||
export declare const safeParseJson: (value: string) => string | Object; | ||
export declare const isDefined: (variable: any) => boolean; | ||
export declare const isFunction: (variable: any) => boolean; | ||
@@ -14,1 +23,2 @@ export declare const findElementByType: (tag: string | Function | HTMLMediaElement) => HTMLMediaElement; | ||
export declare const randomInt: (min: number, max: number) => number; | ||
export declare const roundToFixed: (value: number, num?: number) => number; |
@@ -10,3 +10,3 @@ import logger from './logger'; | ||
try { | ||
media = JSON.parse(mediaJson.replace(/ID"/g, 'Id"')); | ||
media = JSON.parse(mediaJson); | ||
} | ||
@@ -16,4 +16,3 @@ catch (error) { | ||
} | ||
const result = { participantId: Number(participantId), participantNumber, participantName, codec, media, participantData }; | ||
return result; | ||
return Object.assign({ participantId, participantNumber, participantName, codec, participantData }, media); | ||
}; | ||
@@ -31,3 +30,2 @@ export const safeParseJson = (value) => { | ||
}; | ||
export const isDefined = (variable) => typeof variable !== 'undefined'; | ||
export const isFunction = (variable) => variable instanceof Function || typeof variable === 'function'; | ||
@@ -78,1 +76,4 @@ export const findElementByType = (tag) => { | ||
}; | ||
export const roundToFixed = (value, num = 2) => { | ||
return Number(value.toFixed(num)); | ||
}; |
@@ -34,2 +34,10 @@ interface IMessageBase { | ||
} | ||
export interface IBladeAuthorization { | ||
expires_at: number; | ||
signature: string; | ||
project: string; | ||
scope_id: string; | ||
scopes: string[]; | ||
resource: string; | ||
} | ||
export interface IBladeConnectResult extends IMessageBase { | ||
@@ -40,6 +48,3 @@ sessionid: string; | ||
protocols_uncertified: string[]; | ||
authorization: { | ||
expires_at: number; | ||
signature: string; | ||
}; | ||
authorization: IBladeAuthorization; | ||
} | ||
@@ -79,3 +84,5 @@ export interface IBladeExecuteRequest extends IMessageBase { | ||
password?: string; | ||
userVariables?: Object; | ||
userVariables?: { | ||
[key: string]: any; | ||
}; | ||
} | ||
@@ -82,0 +89,0 @@ export interface SubscribeParams { |
@@ -8,3 +8,6 @@ import log from 'loglevel'; | ||
return function () { | ||
const messages = [datetime(), '-']; | ||
const messages = []; | ||
if (typeof window === 'undefined') { | ||
messages.push(datetime() + ' -'); | ||
} | ||
for (let i = 0; i < arguments.length; i++) { | ||
@@ -11,0 +14,0 @@ messages.push(arguments[i]); |
@@ -5,2 +5,3 @@ declare const RTCPeerConnection: (config: RTCConfiguration) => RTCPeerConnection; | ||
declare const enumerateDevices: () => Promise<MediaDeviceInfo[]>; | ||
declare const enumerateDevicesByKind: (filterByKind?: string) => Promise<MediaDeviceInfo[]>; | ||
declare const getSupportedConstraints: () => MediaTrackSupportedConstraints; | ||
@@ -16,2 +17,5 @@ declare const streamIsValid: (stream: MediaStream) => boolean; | ||
declare const stopStream: (stream: MediaStream) => void; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId }; | ||
declare const getHostname: () => string; | ||
declare const buildVideoElementByTrack: (videoTrack: MediaStreamTrack, streamIds?: string[]) => HTMLVideoElement; | ||
declare const buildAudioElementByTrack: (audioTrack: MediaStreamTrack, streamIds?: string[]) => HTMLAudioElement; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, enumerateDevicesByKind, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId, getHostname, buildVideoElementByTrack, buildAudioElementByTrack, }; |
@@ -11,2 +11,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { findElementByType } from '../helpers'; | ||
import logger from '../logger'; | ||
const RTCPeerConnection = (config) => new window.RTCPeerConnection(config); | ||
@@ -16,2 +17,9 @@ const getUserMedia = (constraints) => navigator.mediaDevices.getUserMedia(constraints); | ||
const enumerateDevices = () => navigator.mediaDevices.enumerateDevices(); | ||
const enumerateDevicesByKind = (filterByKind = null) => __awaiter(void 0, void 0, void 0, function* () { | ||
let devices = yield enumerateDevices().catch(error => []); | ||
if (filterByKind) { | ||
devices = devices.filter(({ kind }) => kind === filterByKind); | ||
} | ||
return devices; | ||
}); | ||
const getSupportedConstraints = () => navigator.mediaDevices.getSupportedConstraints(); | ||
@@ -59,4 +67,9 @@ const streamIsValid = (stream) => stream && stream instanceof MediaStream; | ||
if (element === null) { | ||
logger.info('No HTMLMediaElement to attach the speakerId'); | ||
return false; | ||
} | ||
if (typeof deviceId !== 'string') { | ||
logger.info(`Invalid speaker deviceId: '${deviceId}'`); | ||
return false; | ||
} | ||
try { | ||
@@ -82,2 +95,44 @@ yield element.setSinkId(deviceId); | ||
}; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId }; | ||
const getHostname = () => window.location.hostname; | ||
const buildVideoElementByTrack = (videoTrack, streamIds = []) => { | ||
const video = document.createElement('video'); | ||
video.muted = true; | ||
video.autoplay = true; | ||
video.playsinline = true; | ||
video._streamIds = streamIds; | ||
const mediaStream = new MediaStream([videoTrack]); | ||
video.srcObject = mediaStream; | ||
const onCanPlay = () => console.debug('video can play!'); | ||
const onPlay = () => console.debug('video is now playing...'); | ||
video.addEventListener('play', onPlay); | ||
video.addEventListener('canplay', onCanPlay); | ||
videoTrack.addEventListener('ended', () => { | ||
video.removeEventListener('play', onPlay); | ||
video.removeEventListener('canplay', onCanPlay); | ||
video.srcObject = null; | ||
delete video._streamIds; | ||
video.remove(); | ||
}); | ||
return video; | ||
}; | ||
const buildAudioElementByTrack = (audioTrack, streamIds = []) => { | ||
const audio = new Audio(); | ||
audio.autoplay = true; | ||
audio.playsinline = true; | ||
audio._streamIds = streamIds; | ||
const mediaStream = new MediaStream([audioTrack]); | ||
audio.srcObject = mediaStream; | ||
const onCanPlay = () => console.debug('audio can play!'); | ||
const onPlay = () => console.debug('audio is now playing...'); | ||
audio.addEventListener('play', onPlay); | ||
audio.addEventListener('canplay', onCanPlay); | ||
audioTrack.addEventListener('ended', () => { | ||
audio.removeEventListener('play', onPlay); | ||
audio.removeEventListener('canplay', onCanPlay); | ||
audio.srcObject = null; | ||
delete audio._streamIds; | ||
audio.remove(); | ||
}); | ||
return audio; | ||
}; | ||
export { RTCPeerConnection, getUserMedia, getDisplayMedia, enumerateDevices, enumerateDevicesByKind, getSupportedConstraints, streamIsValid, attachMediaStream, detachMediaStream, sdpToJsonHack, stopStream, muteMediaElement, unmuteMediaElement, toggleMuteMediaElement, setMediaElementSinkId, getHostname, buildVideoElementByTrack, buildAudioElementByTrack, }; |
@@ -1,11 +0,8 @@ | ||
import BaseCall from './BaseCall'; | ||
import WebRTCCall from './WebRTCCall'; | ||
import { CallOptions } from './interfaces'; | ||
export default class Call extends BaseCall { | ||
screenShare: Call; | ||
secondSource: Call; | ||
export default class Call extends WebRTCCall { | ||
private _statsInterval; | ||
hangup(params?: any, execute?: boolean): void; | ||
startScreenShare(opts?: CallOptions): Promise<Call>; | ||
startScreenShare(opts?: CallOptions): Promise<WebRTCCall>; | ||
stopScreenShare(): void; | ||
addSecondSource(opts?: CallOptions): Promise<Call>; | ||
addSecondSource(opts?: CallOptions): Promise<WebRTCCall>; | ||
removeSecondSource(): void; | ||
@@ -12,0 +9,0 @@ setAudioOutDevice(deviceId: string): Promise<boolean>; |
@@ -11,5 +11,5 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import logger from '../util/logger'; | ||
import BaseCall from './BaseCall'; | ||
import WebRTCCall from './WebRTCCall'; | ||
import { getDisplayMedia, setMediaElementSinkId } from '../util/webrtc'; | ||
export default class Call extends BaseCall { | ||
export default class Call extends WebRTCCall { | ||
constructor() { | ||
@@ -19,11 +19,2 @@ super(...arguments); | ||
} | ||
hangup(params = {}, execute = true) { | ||
if (this.screenShare instanceof Call) { | ||
this.screenShare.hangup(params, execute); | ||
} | ||
if (this.secondSource instanceof Call) { | ||
this.secondSource.hangup(params, execute); | ||
} | ||
super.hangup(params, execute); | ||
} | ||
startScreenShare(opts) { | ||
@@ -40,4 +31,6 @@ return __awaiter(this, void 0, void 0, function* () { | ||
}); | ||
const { remoteCallerName, remoteCallerNumber, callerName, callerNumber } = this.options; | ||
const options = Object.assign({ screenShare: true, recoverCall: false, localStream: displayStream, destinationNumber: `${this.extension};screen`, remoteCallerName, remoteCallerNumber: `${remoteCallerNumber};screen`, callerName: `${callerName} (Screen)`, callerNumber: `${callerNumber} (Screen)` }, opts); | ||
const { destinationNumber, remoteCallerName, remoteCallerNumber, callerName, callerNumber } = this.options; | ||
const options = Object.assign({ screenShare: true, localStream: displayStream, destinationNumber, | ||
remoteCallerName, | ||
remoteCallerNumber, callerName: `${callerName} (Screen)`, callerNumber: `${callerNumber} (Screen)` }, opts); | ||
this.screenShare = new Call(this.session, options); | ||
@@ -55,4 +48,6 @@ this.screenShare.invite(); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { remoteCallerName, remoteCallerNumber, callerName, callerNumber } = this.options; | ||
const options = Object.assign({ secondSource: true, recoverCall: false, destinationNumber: `${this.extension};second-source`, remoteCallerName, remoteCallerNumber: `${remoteCallerNumber};second-source`, callerName: `${callerName} (Second Source)`, callerNumber: `${callerNumber} (Second Source)`, localStream: null }, opts); | ||
const { destinationNumber, remoteCallerName, remoteCallerNumber, callerName, callerNumber } = this.options; | ||
const options = Object.assign({ secondSource: true, recoverCall: false, destinationNumber, | ||
remoteCallerName, | ||
remoteCallerNumber, callerName: `${callerName} (Second Source)`, callerNumber: `${callerNumber} (Second Source)`, localStream: null }, opts); | ||
this.secondSource = new Call(this.session, options); | ||
@@ -69,10 +64,5 @@ this.secondSource.invite(); | ||
setAudioOutDevice(deviceId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.options.speakerId = deviceId; | ||
const { remoteElement, speakerId } = this.options; | ||
if (remoteElement && speakerId) { | ||
return setMediaElementSinkId(remoteElement, speakerId); | ||
} | ||
return false; | ||
}); | ||
this.options.speakerId = deviceId; | ||
const { remoteElement, speakerId } = this.options; | ||
return setMediaElementSinkId(remoteElement, speakerId); | ||
} | ||
@@ -79,0 +69,0 @@ _finalize() { |
@@ -1,22 +0,17 @@ | ||
import { ICantinaAuthParams, ICantinaUser } from './interfaces'; | ||
import { ICantinaUser } from './interfaces'; | ||
declare type BootstrapResponse = { | ||
project_id: string; | ||
}; | ||
declare type RefreshResponse = { | ||
project: string; | ||
jwt_token: string; | ||
}; | ||
declare type CheckInviteTokenResponse = { | ||
valid: boolean; | ||
name: string; | ||
config: object; | ||
}; | ||
declare class CantinaAuth { | ||
private params; | ||
baseUrl: string; | ||
hostname: string; | ||
constructor(params?: ICantinaAuthParams); | ||
private _fetch; | ||
userLogin(username: string, password: string): Promise<ICantinaUser>; | ||
guestLogin(name: string, email: string, token: string): Promise<ICantinaUser>; | ||
refresh(): Promise<RefreshResponse>; | ||
checkInviteToken(token: string): Promise<CheckInviteTokenResponse>; | ||
bootstrap(hostname: string): Promise<BootstrapResponse>; | ||
login(username: string, project_id: string): Promise<ICantinaUser>; | ||
refresh(refreshToken?: any): Promise<RefreshResponse>; | ||
logout(): Promise<void>; | ||
} | ||
export default CantinaAuth; |
@@ -19,9 +19,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
class CantinaAuth { | ||
constructor(params = {}) { | ||
this.params = params; | ||
this.baseUrl = 'https://cantina-backend.signalwire.com'; | ||
constructor() { | ||
this.baseUrl = ''; | ||
this._fetch = (url, options) => { | ||
return fetch(url, options).then((response) => __awaiter(this, void 0, void 0, function* () { | ||
const payload = yield response.json(); | ||
if (response.status >= 200 && response.status < 300) { | ||
if (response.status === 204) { | ||
return response; | ||
} | ||
const payload = yield response.json(); | ||
return payload; | ||
@@ -32,3 +34,3 @@ } | ||
const error = new Error(errorMessage); | ||
error.payload = payload; | ||
error.response = response; | ||
return Promise.reject(error); | ||
@@ -38,22 +40,26 @@ } | ||
}; | ||
const { hostname = location.hostname } = params; | ||
this.hostname = hostname; | ||
} | ||
userLogin(username, password) { | ||
bootstrap(hostname) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const response = yield this._fetch(`${this.baseUrl}/login/user`, Object.assign(Object.assign({}, FETCH_OPTIONS), { body: JSON.stringify({ username, password, hostname: this.hostname }) })); | ||
logger.info('userLogin response', response); | ||
const clear = encodeURIComponent(hostname); | ||
const url = `${this.baseUrl}/api/configuration?hostname=${clear}`; | ||
const response = yield this._fetch(url, Object.assign(Object.assign({}, FETCH_OPTIONS), { method: 'GET' })); | ||
logger.info('bootstrap response', response); | ||
return response; | ||
}); | ||
} | ||
guestLogin(name, email, token) { | ||
login(username, project_id) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const response = yield this._fetch(`${this.baseUrl}/login/guest`, Object.assign(Object.assign({}, FETCH_OPTIONS), { body: JSON.stringify({ name, email, token, hostname: this.hostname }) })); | ||
logger.info('guestLogin response', response); | ||
const response = yield this._fetch(`${this.baseUrl}/api/login`, Object.assign(Object.assign({}, FETCH_OPTIONS), { body: JSON.stringify({ username, project_id }) })); | ||
logger.info('userLogin response', response); | ||
return response; | ||
}); | ||
} | ||
refresh() { | ||
refresh(refreshToken = null) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const response = yield this._fetch(`${this.baseUrl}/refresh`, Object.assign(Object.assign({}, FETCH_OPTIONS), { method: 'PUT', body: JSON.stringify({ hostname: this.hostname }) })); | ||
const options = Object.assign(Object.assign({}, FETCH_OPTIONS), { method: 'PUT' }); | ||
if (refreshToken) { | ||
options.body = JSON.stringify({ refresh_token: refreshToken }); | ||
} | ||
const response = yield this._fetch(`${this.baseUrl}/api/refresh`, options); | ||
logger.info('refresh response', response); | ||
@@ -63,6 +69,6 @@ return response; | ||
} | ||
checkInviteToken(token) { | ||
logout() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const response = yield this._fetch(`${this.baseUrl}/check-token`, Object.assign(Object.assign({}, FETCH_OPTIONS), { body: JSON.stringify({ token, hostname: this.hostname }) })); | ||
logger.info('checkInviteToken response', response); | ||
const response = yield this._fetch(`${this.baseUrl}/api/logout`, Object.assign(Object.assign({}, FETCH_OPTIONS), { method: 'PUT' })); | ||
logger.info('logout response', response); | ||
return response; | ||
@@ -69,0 +75,0 @@ }); |
@@ -26,15 +26,20 @@ import { CallOptions } from './interfaces'; | ||
MediaParams = "verto.mediaParams", | ||
Prompt = "verto.prompt" | ||
Prompt = "verto.prompt", | ||
JsApi = "jsapi", | ||
Stats = "verto.stats", | ||
Ping = "verto.ping", | ||
Announce = "verto.announce" | ||
} | ||
export declare const NOTIFICATION_TYPE: { | ||
generic: string; | ||
prompt: string; | ||
"verto.display": string; | ||
"verto.attach": string; | ||
conferenceUpdate: string; | ||
callUpdate: string; | ||
vertoClientReady: string; | ||
userMediaError: string; | ||
refreshToken: string; | ||
}; | ||
export declare enum Notification { | ||
Generic = "event", | ||
ParticipantData = "participantData", | ||
ConferenceUpdate = "conferenceUpdate", | ||
CallUpdate = "callUpdate", | ||
VertoClientReady = "vertoClientReady", | ||
UserMediaError = "userMediaError", | ||
RefreshToken = "refreshToken", | ||
Prompt = "prompt", | ||
Announce = "announce", | ||
DeviceUpdated = "deviceUpdated" | ||
} | ||
export declare const DEFAULT_CALL_OPTIONS: CallOptions; | ||
@@ -51,5 +56,5 @@ export declare enum State { | ||
Held = 8, | ||
Purge = 9, | ||
Hangup = 10, | ||
Destroy = 11 | ||
Hangup = 9, | ||
Destroy = 10, | ||
Purge = 11 | ||
} | ||
@@ -73,4 +78,5 @@ export declare enum Role { | ||
LayoutList = "layoutList", | ||
ModCmdResponse = "modCommandResponse", | ||
ConferenceInfo = "conferenceInfo", | ||
ModCmdResponse = "modCommandResponse" | ||
CaptionInfo = "captionInfo" | ||
} | ||
@@ -77,0 +83,0 @@ export declare enum DeviceType { |
@@ -29,14 +29,20 @@ export var PeerType; | ||
VertoMethod["Prompt"] = "verto.prompt"; | ||
VertoMethod["JsApi"] = "jsapi"; | ||
VertoMethod["Stats"] = "verto.stats"; | ||
VertoMethod["Ping"] = "verto.ping"; | ||
VertoMethod["Announce"] = "verto.announce"; | ||
})(VertoMethod || (VertoMethod = {})); | ||
export const NOTIFICATION_TYPE = { | ||
generic: 'event', | ||
prompt: 'prompt', | ||
[VertoMethod.Display]: 'participantData', | ||
[VertoMethod.Attach]: 'participantData', | ||
conferenceUpdate: 'conferenceUpdate', | ||
callUpdate: 'callUpdate', | ||
vertoClientReady: 'vertoClientReady', | ||
userMediaError: 'userMediaError', | ||
refreshToken: 'refreshToken', | ||
}; | ||
export var Notification; | ||
(function (Notification) { | ||
Notification["Generic"] = "event"; | ||
Notification["ParticipantData"] = "participantData"; | ||
Notification["ConferenceUpdate"] = "conferenceUpdate"; | ||
Notification["CallUpdate"] = "callUpdate"; | ||
Notification["VertoClientReady"] = "vertoClientReady"; | ||
Notification["UserMediaError"] = "userMediaError"; | ||
Notification["RefreshToken"] = "refreshToken"; | ||
Notification["Prompt"] = "prompt"; | ||
Notification["Announce"] = "announce"; | ||
Notification["DeviceUpdated"] = "deviceUpdated"; | ||
})(Notification || (Notification = {})); | ||
export const DEFAULT_CALL_OPTIONS = { | ||
@@ -55,2 +61,4 @@ destinationNumber: '', | ||
userVariables: {}, | ||
requestTimeout: 10 * 1000, | ||
experimental: false, | ||
}; | ||
@@ -68,5 +76,5 @@ export var State; | ||
State[State["Held"] = 8] = "Held"; | ||
State[State["Purge"] = 9] = "Purge"; | ||
State[State["Hangup"] = 10] = "Hangup"; | ||
State[State["Destroy"] = 11] = "Destroy"; | ||
State[State["Hangup"] = 9] = "Hangup"; | ||
State[State["Destroy"] = 10] = "Destroy"; | ||
State[State["Purge"] = 11] = "Purge"; | ||
})(State || (State = {})); | ||
@@ -92,4 +100,5 @@ export var Role; | ||
ConferenceAction["LayoutList"] = "layoutList"; | ||
ConferenceAction["ModCmdResponse"] = "modCommandResponse"; | ||
ConferenceAction["ConferenceInfo"] = "conferenceInfo"; | ||
ConferenceAction["ModCmdResponse"] = "modCommandResponse"; | ||
ConferenceAction["CaptionInfo"] = "captionInfo"; | ||
})(ConferenceAction || (ConferenceAction = {})); | ||
@@ -96,0 +105,0 @@ export var DeviceType; |
@@ -1,12 +0,5 @@ | ||
import { CallOptions } from './interfaces'; | ||
declare const getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>; | ||
declare const getDevices: (kind?: string, fullList?: boolean) => Promise<MediaDeviceInfo[]>; | ||
declare const scanResolutions: (deviceId: string) => Promise<any[]>; | ||
declare const getMediaConstraints: (options: CallOptions) => Promise<MediaStreamConstraints>; | ||
declare const assureDeviceId: (id: string, label: string, kind: MediaDeviceKind) => Promise<string>; | ||
declare const removeUnsupportedConstraints: (constraints: MediaTrackConstraints) => void; | ||
declare const checkDeviceIdConstraints: (id: string, label: string, kind: MediaDeviceKind, constraints: MediaTrackConstraints) => Promise<MediaTrackConstraints>; | ||
declare const sdpStereoHack: (sdp: string) => string; | ||
declare const sdpMediaOrderHack: (answer: string, localOffer: string) => string; | ||
declare const checkSubscribeResponse: (response: any, channel: string) => boolean; | ||
import { CallOptions, IVertoCanvasInfo, ICanvasInfo, IConferenceInfo, ILayout, IVertoLayout } from './interfaces'; | ||
export declare const getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>; | ||
export declare const removeUnsupportedConstraints: (constraints: MediaTrackConstraints) => void; | ||
export declare const getMediaConstraints: (options: CallOptions) => Promise<MediaStreamConstraints>; | ||
declare type DestructuredResult = { | ||
@@ -19,10 +12,15 @@ subscribed: string[]; | ||
}; | ||
declare const destructSubscribeResponse: (response: any) => DestructuredResult; | ||
declare const enableAudioTracks: (stream: MediaStream) => void; | ||
declare const disableAudioTracks: (stream: MediaStream) => void; | ||
declare const toggleAudioTracks: (stream: MediaStream) => void; | ||
declare const enableVideoTracks: (stream: MediaStream) => void; | ||
declare const disableVideoTracks: (stream: MediaStream) => void; | ||
declare const toggleVideoTracks: (stream: MediaStream) => void; | ||
declare const sdpBitrateHack: (sdp: string, max: number, min: number, start: number) => string; | ||
export { getUserMedia, getDevices, scanResolutions, getMediaConstraints, assureDeviceId, removeUnsupportedConstraints, checkDeviceIdConstraints, sdpStereoHack, sdpMediaOrderHack, sdpBitrateHack, checkSubscribeResponse, destructSubscribeResponse, enableAudioTracks, disableAudioTracks, toggleAudioTracks, enableVideoTracks, disableVideoTracks, toggleVideoTracks, }; | ||
export declare const destructSubscribeResponse: (response: any) => DestructuredResult; | ||
export declare const enableAudioTracks: (stream: MediaStream) => void; | ||
export declare const disableAudioTracks: (stream: MediaStream) => void; | ||
export declare const toggleAudioTracks: (stream: MediaStream) => void; | ||
export declare const enableVideoTracks: (stream: MediaStream) => void; | ||
export declare const disableVideoTracks: (stream: MediaStream) => void; | ||
export declare const toggleVideoTracks: (stream: MediaStream) => void; | ||
export declare const mutateCanvasInfoData: (canvasInfo: IVertoCanvasInfo) => ICanvasInfo; | ||
export declare const checkIsDirectCall: ({ variables }: { | ||
variables: any; | ||
}) => boolean; | ||
export declare const destructConferenceState: (confState: any) => IConferenceInfo; | ||
export declare const mungeLayoutList: (layouts: IVertoLayout[], layoutGroups: IVertoLayout[]) => ILayout[]; | ||
export {}; |
@@ -10,7 +10,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
import logger from '../util/logger'; | ||
import * as WebRTC from '../util/webrtc'; | ||
import { isDefined } from '../util/helpers'; | ||
import { roundToFixed } from '../util/helpers'; | ||
import { assureDeviceId } from './deviceHelpers'; | ||
import { DeviceType } from './constants'; | ||
const getUserMedia = (constraints) => __awaiter(void 0, void 0, void 0, function* () { | ||
export const getUserMedia = (constraints) => __awaiter(void 0, void 0, void 0, function* () { | ||
logger.info('RTCService.getUserMedia', constraints); | ||
@@ -29,54 +41,11 @@ const { audio, video } = constraints; | ||
}); | ||
const _constraintsByKind = (kind = null) => { | ||
return { | ||
audio: !kind || kind === DeviceType.AudioIn, | ||
video: !kind || kind === DeviceType.Video | ||
}; | ||
}; | ||
const getDevices = (kind = null, fullList = false) => __awaiter(void 0, void 0, void 0, function* () { | ||
let devices = yield WebRTC.enumerateDevices().catch(error => []); | ||
if (kind) { | ||
devices = devices.filter((d) => d.kind === kind); | ||
} | ||
const valid = devices.length && devices.every((d) => (d.deviceId && d.label)); | ||
if (!valid) { | ||
const stream = yield WebRTC.getUserMedia(_constraintsByKind(kind)); | ||
WebRTC.stopStream(stream); | ||
return getDevices(kind); | ||
} | ||
if (fullList === true) { | ||
return devices; | ||
} | ||
const found = []; | ||
devices = devices.filter(({ kind, groupId }) => { | ||
if (!groupId) { | ||
return true; | ||
export const removeUnsupportedConstraints = (constraints) => { | ||
const supported = WebRTC.getSupportedConstraints(); | ||
Object.keys(constraints).map(key => { | ||
if (!supported.hasOwnProperty(key) || constraints[key] === null || constraints[key] === undefined) { | ||
delete constraints[key]; | ||
} | ||
const key = `${kind}-${groupId}`; | ||
if (!found.includes(key)) { | ||
found.push(key); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return devices; | ||
}); | ||
const resolutionList = [[320, 240], [640, 360], [640, 480], [1280, 720], [1920, 1080]]; | ||
const scanResolutions = (deviceId) => __awaiter(void 0, void 0, void 0, function* () { | ||
const supported = []; | ||
const stream = yield getUserMedia({ video: { deviceId: { exact: deviceId } } }); | ||
const videoTrack = stream.getVideoTracks()[0]; | ||
for (let i = 0; i < resolutionList.length; i++) { | ||
const [width, height] = resolutionList[i]; | ||
const success = yield videoTrack.applyConstraints({ width: { exact: width }, height: { exact: height } }) | ||
.then(() => true) | ||
.catch(() => false); | ||
if (success) { | ||
supported.push({ resolution: `${width}x${height}`, width, height }); | ||
} | ||
} | ||
WebRTC.stopStream(stream); | ||
return supported; | ||
}); | ||
const getMediaConstraints = (options) => __awaiter(void 0, void 0, void 0, function* () { | ||
}; | ||
export const getMediaConstraints = (options) => __awaiter(void 0, void 0, void 0, function* () { | ||
let { audio = true, micId } = options; | ||
@@ -106,81 +75,3 @@ const { micLabel = '' } = options; | ||
}); | ||
const assureDeviceId = (id, label, kind) => __awaiter(void 0, void 0, void 0, function* () { | ||
const devices = yield getDevices(kind, true); | ||
for (let i = 0; i < devices.length; i++) { | ||
const { deviceId, label: deviceLabel } = devices[i]; | ||
if (id === deviceId || label === deviceLabel) { | ||
return deviceId; | ||
} | ||
} | ||
return null; | ||
}); | ||
const removeUnsupportedConstraints = (constraints) => { | ||
const supported = WebRTC.getSupportedConstraints(); | ||
Object.keys(constraints).map(key => { | ||
if (!supported.hasOwnProperty(key) || constraints[key] === null || constraints[key] === undefined) { | ||
delete constraints[key]; | ||
} | ||
}); | ||
}; | ||
const checkDeviceIdConstraints = (id, label, kind, constraints) => __awaiter(void 0, void 0, void 0, function* () { | ||
const { deviceId } = constraints; | ||
if (!isDefined(deviceId) && (id || label)) { | ||
const deviceId = yield assureDeviceId(id, label, kind).catch(error => null); | ||
if (deviceId) { | ||
constraints.deviceId = { exact: deviceId }; | ||
} | ||
} | ||
return constraints; | ||
}); | ||
const sdpStereoHack = (sdp) => { | ||
const endOfLine = '\r\n'; | ||
const sdpLines = sdp.split(endOfLine); | ||
const opusIndex = sdpLines.findIndex(s => /^a=rtpmap/.test(s) && /opus\/48000/.test(s)); | ||
if (opusIndex < 0) { | ||
return sdp; | ||
} | ||
const getCodecPayloadType = (line) => { | ||
const pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); | ||
const result = line.match(pattern); | ||
return result && result.length == 2 ? result[1] : null; | ||
}; | ||
const opusPayload = getCodecPayloadType(sdpLines[opusIndex]); | ||
const pattern = new RegExp(`a=fmtp:${opusPayload}`); | ||
const fmtpLineIndex = sdpLines.findIndex(s => pattern.test(s)); | ||
if (fmtpLineIndex >= 0) { | ||
if (!/stereo=1;/.test(sdpLines[fmtpLineIndex])) { | ||
sdpLines[fmtpLineIndex] += '; stereo=1; sprop-stereo=1'; | ||
} | ||
} | ||
else { | ||
sdpLines[opusIndex] += `${endOfLine}a=fmtp:${opusPayload} stereo=1; sprop-stereo=1`; | ||
} | ||
return sdpLines.join(endOfLine); | ||
}; | ||
const _isAudioLine = (line) => /^m=audio/.test(line); | ||
const _isVideoLine = (line) => /^m=video/.test(line); | ||
const sdpMediaOrderHack = (answer, localOffer) => { | ||
const endOfLine = '\r\n'; | ||
const offerLines = localOffer.split(endOfLine); | ||
const offerAudioIndex = offerLines.findIndex(_isAudioLine); | ||
const offerVideoIndex = offerLines.findIndex(_isVideoLine); | ||
if (offerAudioIndex < offerVideoIndex) { | ||
return answer; | ||
} | ||
const answerLines = answer.split(endOfLine); | ||
const answerAudioIndex = answerLines.findIndex(_isAudioLine); | ||
const answerVideoIndex = answerLines.findIndex(_isVideoLine); | ||
const audioLines = answerLines.slice(answerAudioIndex, answerVideoIndex); | ||
const videoLines = answerLines.slice(answerVideoIndex, (answerLines.length - 1)); | ||
const beginLines = answerLines.slice(0, answerAudioIndex); | ||
return [...beginLines, ...videoLines, ...audioLines, ''].join(endOfLine); | ||
}; | ||
const checkSubscribeResponse = (response, channel) => { | ||
if (!response) { | ||
return false; | ||
} | ||
const { subscribed, alreadySubscribed } = destructSubscribeResponse(response); | ||
return subscribed.includes(channel) || alreadySubscribed.includes(channel); | ||
}; | ||
const destructSubscribeResponse = (response) => { | ||
export const destructSubscribeResponse = (response) => { | ||
const tmp = { | ||
@@ -196,20 +87,2 @@ subscribed: [], | ||
}; | ||
const enableAudioTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'audio', true); | ||
}; | ||
const disableAudioTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'audio', false); | ||
}; | ||
const toggleAudioTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'audio', null); | ||
}; | ||
const enableVideoTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'video', true); | ||
}; | ||
const disableVideoTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'video', false); | ||
}; | ||
const toggleVideoTracks = (stream) => { | ||
_updateMediaStreamTracks(stream, 'video', null); | ||
}; | ||
const _updateMediaStreamTracks = (stream, kind = null, enabled = null) => { | ||
@@ -219,21 +92,7 @@ if (!WebRTC.streamIsValid(stream)) { | ||
} | ||
let tracks = []; | ||
switch (kind) { | ||
case 'audio': | ||
tracks = stream.getAudioTracks(); | ||
break; | ||
case 'video': | ||
tracks = stream.getVideoTracks(); | ||
break; | ||
default: | ||
tracks = stream.getTracks(); | ||
break; | ||
} | ||
tracks.forEach((track) => { | ||
const _updateTrack = (track) => { | ||
switch (enabled) { | ||
case 'on': | ||
case true: | ||
track.enabled = true; | ||
break; | ||
case 'off': | ||
case false: | ||
@@ -246,17 +105,104 @@ track.enabled = false; | ||
} | ||
}); | ||
}; | ||
switch (kind) { | ||
case 'audio': | ||
return stream.getAudioTracks().forEach(_updateTrack); | ||
case 'video': | ||
return stream.getVideoTracks().forEach(_updateTrack); | ||
default: | ||
return stream.getTracks().forEach(_updateTrack); | ||
} | ||
}; | ||
const sdpBitrateHack = (sdp, max, min, start) => { | ||
const endOfLine = '\r\n'; | ||
const lines = sdp.split(endOfLine); | ||
lines.forEach((line, i) => { | ||
if (/^a=fmtp:\d*/.test(line)) { | ||
lines[i] += `;x-google-max-bitrate=${max};x-google-min-bitrate=${min};x-google-start-bitrate=${start}`; | ||
} | ||
else if (/^a=mid:(1|video)/.test(line)) { | ||
lines[i] += `\r\nb=AS:${max}`; | ||
} | ||
export const enableAudioTracks = (stream) => _updateMediaStreamTracks(stream, 'audio', true); | ||
export const disableAudioTracks = (stream) => _updateMediaStreamTracks(stream, 'audio', false); | ||
export const toggleAudioTracks = (stream) => _updateMediaStreamTracks(stream, 'audio', null); | ||
export const enableVideoTracks = (stream) => _updateMediaStreamTracks(stream, 'video', true); | ||
export const disableVideoTracks = (stream) => _updateMediaStreamTracks(stream, 'video', false); | ||
export const toggleVideoTracks = (stream) => _updateMediaStreamTracks(stream, 'video', null); | ||
export const mutateCanvasInfoData = (canvasInfo) => { | ||
const { canvasID, layoutFloorID, scale, canvasLayouts } = canvasInfo, rest = __rest(canvasInfo, ["canvasID", "layoutFloorID", "scale", "canvasLayouts"]); | ||
const layouts = []; | ||
let layoutOverlap = false; | ||
for (let i = 0; i < canvasLayouts.length; i++) { | ||
const layout = canvasLayouts[i]; | ||
const { memberID, audioPOS, xPOS, yPOS } = layout, rest = __rest(layout, ["memberID", "audioPOS", "xPOS", "yPOS"]); | ||
layoutOverlap = layoutOverlap || layout.overlap === 1; | ||
layouts.push(Object.assign({ startX: `${roundToFixed((layout.x / scale) * 100)}%`, startY: `${roundToFixed((layout.y / scale) * 100)}%`, percentageWidth: `${roundToFixed((layout.scale / scale) * 100)}%`, percentageHeight: `${roundToFixed((layout.hscale / scale) * 100)}%`, participantId: String(memberID), audioPos: audioPOS, xPos: xPOS, yPos: yPOS }, rest)); | ||
} | ||
return Object.assign(Object.assign({}, rest), { canvasId: canvasID, layoutFloorId: layoutFloorID, scale, canvasLayouts: layouts, layoutOverlap }); | ||
}; | ||
export const checkIsDirectCall = ({ variables }) => { | ||
return typeof variables === 'object' && 'verto_svar_direct_call' in variables; | ||
}; | ||
export const destructConferenceState = (confState) => { | ||
const { variables = {}, flags = {} } = confState; | ||
const suffix = `${confState.md5}@${confState.domain}`; | ||
return { | ||
uuid: confState.uuid, | ||
md5: confState.md5, | ||
domain: confState.domain, | ||
running: Boolean(confState.running), | ||
laChannel: `conference-liveArray.${suffix}`, | ||
infoChannel: `conference-info.${suffix}`, | ||
modChannel: `conference-mod.${suffix}`, | ||
confName: confState.name, | ||
numMembers: Number(confState.members) || 0, | ||
isPrivate: variables.is_private === 'true', | ||
mohPlaying: Boolean(confState.mohPlaying), | ||
filesPlaying: Boolean(confState.filesPlaying), | ||
filesPlayingName: confState.filesPlayingName || null, | ||
asyncFilesPlaying: Boolean(confState.asyncFilesPlaying), | ||
asyncFilesPlayingName: confState.asyncFilesPlayingName || null, | ||
asyncFilesPlayingPaused: Boolean(confState.asyncFilesPlayingPaused), | ||
asyncFilesPlayingVolume: Number(confState.asyncFilesPlayingVolume) || null, | ||
filesSeekable: Boolean(confState.filesSeekable), | ||
asyncFilesSeekable: Boolean(confState.asyncFilesSeekable), | ||
performerDelay: confState.performerDelay, | ||
volAudience: confState['vol-audience'], | ||
filesFullScreen: Boolean(confState.filesFullScreen), | ||
silentMode: flags['silent-mode'] || false, | ||
meetingMode: flags['meeting-mode'] || false, | ||
vidMuteHide: flags['vid-mute-hide'] || false, | ||
personalCanvas: Boolean(flags.personalCanvas), | ||
personalCanvasTP: flags.personalCanvasTP || null, | ||
locked: Boolean(flags.locked), | ||
recording: Boolean(flags.recording), | ||
liveMusic: Boolean(flags.liveMusic), | ||
publicClipeeze: variables.public_clipeeze === 'true', | ||
confQuality: variables.conf_quality, | ||
accessPin: variables.access_pin || null, | ||
moderatorPin: variables.moderator_pin || null, | ||
speakerHighlight: variables.speaker_highlight === 'true', | ||
disableIntercom: variables.disable_intercom === true, | ||
lastSnapshot: variables.lastSnapshot, | ||
lastLayoutGroup: variables.lastLayoutGroup, | ||
lastLayout: variables.lastLayout, | ||
}; | ||
}; | ||
const _layoutReducer = (result, layout) => { | ||
const { type, name, displayName, resIDS = [] } = layout; | ||
const label = displayName || name.replace(/[-_]/g, ' '); | ||
return result.concat({ id: name, label, type, reservationIds: resIDS, belongsToAGroup: false }); | ||
}; | ||
function _layoutCompare(prev, next) { | ||
const prevLabel = prev.label.toLowerCase(); | ||
const nextLabel = next.label.toLowerCase(); | ||
if (prevLabel > nextLabel) { | ||
return 1; | ||
} | ||
else if (prevLabel < nextLabel) { | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
export const mungeLayoutList = (layouts, layoutGroups) => { | ||
const layoutsPartOfGroup = layoutGroups.reduce((cumulative, layout) => { | ||
return cumulative.concat(layout.groupLayouts || []); | ||
}, []); | ||
const normalList = layouts.reduce(_layoutReducer, []); | ||
normalList.forEach((layout) => { | ||
layout.belongsToAGroup = layoutsPartOfGroup.includes(layout.id); | ||
}); | ||
return lines.join(endOfLine); | ||
const groupList = layoutGroups.reduce(_layoutReducer, []); | ||
return groupList.concat(normalList).sort(_layoutCompare); | ||
}; | ||
export { getUserMedia, getDevices, scanResolutions, getMediaConstraints, assureDeviceId, removeUnsupportedConstraints, checkDeviceIdConstraints, sdpStereoHack, sdpMediaOrderHack, sdpBitrateHack, checkSubscribeResponse, destructSubscribeResponse, enableAudioTracks, disableAudioTracks, toggleAudioTracks, enableVideoTracks, disableVideoTracks, toggleVideoTracks, }; |
@@ -23,3 +23,5 @@ export interface CallOptions { | ||
speakerId?: string; | ||
userVariables?: Object; | ||
userVariables?: { | ||
[key: string]: any; | ||
}; | ||
screenShare?: boolean; | ||
@@ -32,52 +34,12 @@ secondSource?: boolean; | ||
googleStartBitrate?: number; | ||
negotiateAudio?: boolean; | ||
negotiateVideo?: boolean; | ||
sfu?: boolean; | ||
simulcast?: boolean; | ||
msStreamsNumber?: number; | ||
requestTimeout?: number; | ||
shakenCheck?: string; | ||
shakenResult?: string; | ||
experimental?: boolean; | ||
} | ||
export interface IWebRTCCall { | ||
id: string; | ||
state: string; | ||
prevState: string; | ||
direction: string; | ||
options: CallOptions; | ||
cause: string; | ||
causeCode: number; | ||
channels: string[]; | ||
role: string; | ||
extension: string; | ||
localStream: MediaStream; | ||
remoteStream: MediaStream; | ||
isMainCall: boolean; | ||
invite: () => void; | ||
answer: () => void; | ||
hangup: (params: any, execute: boolean) => void; | ||
transfer: (destination: string) => void; | ||
replace: (replaceCallID: string) => void; | ||
hold: () => void; | ||
unhold: () => void; | ||
toggleHold: () => void; | ||
dtmf: (dtmf: string) => void; | ||
message: (to: string, body: string) => void; | ||
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; | ||
setState: (state: any) => void; | ||
destroy: () => void; | ||
handleMessage: (msg: any) => void; | ||
_addChannel: (laChannel: any) => void; | ||
handleConferenceUpdate: (packet: any, pvtData: any) => Promise<string>; | ||
startScreenShare?: (opts?: object) => Promise<IWebRTCCall>; | ||
stopScreenShare?: () => void; | ||
setAudioOutDevice?: (deviceId: string) => Promise<boolean>; | ||
switchCamera?: () => void; | ||
setSpeakerPhone?: (flag: boolean) => void; | ||
} | ||
export interface ICantinaAuthParams { | ||
hostname?: string; | ||
} | ||
export interface ICantinaUser { | ||
@@ -94,1 +56,145 @@ first_name: string; | ||
} | ||
export interface VertoPvtData { | ||
callID: string; | ||
nodeId?: string; | ||
action: string; | ||
laChannel: string; | ||
laName: string; | ||
role: string; | ||
chatID: string; | ||
conferenceMemberID: number; | ||
canvasCount: string; | ||
modChannel: string; | ||
chatChannel: string; | ||
infoChannel: string; | ||
} | ||
export interface IVertoCanvasInfo { | ||
canvasID: number; | ||
totalLayers: number; | ||
layersUsed: number; | ||
layoutFloorID: number; | ||
layoutName: string; | ||
canvasLayouts: IVertoCanvasLayout[]; | ||
scale: number; | ||
} | ||
export interface IVertoCanvasLayout { | ||
x: number; | ||
y: number; | ||
scale: number; | ||
hscale: number; | ||
zoom: number; | ||
border: number; | ||
floor: number; | ||
overlap: number; | ||
screenWidth: number; | ||
screenHeight: number; | ||
xPOS: number; | ||
yPOS: number; | ||
audioPOS: string; | ||
memberID: number; | ||
} | ||
export interface ICanvasInfo { | ||
canvasId: number; | ||
totalLayers: number; | ||
layersUsed: number; | ||
layoutFloorId: number; | ||
layoutName: string; | ||
canvasLayouts: ICanvasLayout[]; | ||
scale: number; | ||
layoutOverlap: boolean; | ||
} | ||
export interface ICanvasLayout { | ||
x: number; | ||
y: number; | ||
startX: string; | ||
startY: string; | ||
percentageWidth: string; | ||
percentageHeight: string; | ||
scale: number; | ||
hscale: number; | ||
zoom: number; | ||
border: number; | ||
floor: number; | ||
overlap: number; | ||
screenWidth: number; | ||
screenHeight: number; | ||
xPos: number; | ||
yPos: number; | ||
audioPos: string; | ||
participantId: string; | ||
} | ||
export interface IHangupParams { | ||
code?: string; | ||
cause?: string; | ||
redirectDestination?: any; | ||
} | ||
export interface ICallParticipant { | ||
id: string; | ||
role: string; | ||
layer: ICanvasLayout; | ||
layerIndex: number; | ||
isLayerBehind: boolean; | ||
} | ||
export interface IConferenceInfoMember { | ||
participantId: string; | ||
callId: string; | ||
participantNumber: string; | ||
participantName: string; | ||
} | ||
export interface IConferenceInfo { | ||
uuid: string; | ||
md5: string; | ||
domain: string; | ||
running: boolean; | ||
laChannel: string; | ||
infoChannel: string; | ||
modChannel: string; | ||
confName: string; | ||
numMembers: number; | ||
isPrivate: boolean; | ||
mohPlaying: boolean; | ||
filesPlaying: boolean; | ||
filesPlayingName: string; | ||
asyncFilesPlaying: boolean; | ||
asyncFilesPlayingName: string; | ||
asyncFilesPlayingPaused: boolean; | ||
asyncFilesPlayingVolume: number; | ||
filesSeekable: boolean; | ||
asyncFilesSeekable: boolean; | ||
performerDelay: number; | ||
volAudience: number; | ||
filesFullScreen: boolean; | ||
silentMode: boolean; | ||
meetingMode: boolean; | ||
locked: boolean; | ||
recording: boolean; | ||
personalCanvas: boolean; | ||
personalCanvasTP: number; | ||
liveMusic: boolean; | ||
vidMuteHide: boolean; | ||
publicClipeeze: boolean; | ||
confQuality: string; | ||
accessPin: string; | ||
moderatorPin: string; | ||
speakerHighlight: boolean; | ||
disableIntercom: boolean; | ||
lastSnapshot: string; | ||
lastLayoutGroup: string; | ||
lastLayout: string; | ||
members?: IConferenceInfoMember[]; | ||
layouts?: any; | ||
} | ||
export interface ILayout { | ||
id: string; | ||
label: string; | ||
type: string; | ||
reservationIds: string[]; | ||
belongsToAGroup: boolean; | ||
} | ||
export interface IVertoLayout { | ||
name: string; | ||
displayName?: string; | ||
type: string; | ||
resIDS: string[]; | ||
groupLayouts?: string[]; | ||
} |
import BrowserSession from '../BrowserSession'; | ||
declare class VertoHandler { | ||
session: BrowserSession; | ||
nodeId: string; | ||
constructor(session: BrowserSession); | ||
private _ack; | ||
handleMessage(msg: any): Promise<void>; | ||
private _retrieveCallId; | ||
private _handlePvtEvent; | ||
private _handleSessionEvent; | ||
} | ||
export default VertoHandler; | ||
declare const _default: (session: BrowserSession, msg: any) => any; | ||
export default _default; |
@@ -12,200 +12,146 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import Call from './Call'; | ||
import { checkSubscribeResponse } from './helpers'; | ||
import { Result } from '../messages/Verto'; | ||
import { SwEvent } from '../util/constants'; | ||
import { VertoMethod, NOTIFICATION_TYPE } from './constants'; | ||
import { trigger, deRegister } from '../services/Handler'; | ||
import { State, ConferenceAction } from './constants'; | ||
import { MCULayoutEventHandler } from './LayoutHandler'; | ||
class VertoHandler { | ||
constructor(session) { | ||
this.session = session; | ||
import { VertoMethod, Notification } from './constants'; | ||
import { trigger } from '../services/Handler'; | ||
import { State } from './constants'; | ||
import { checkIsDirectCall } from './helpers'; | ||
const _handlePvtEvent = (session, pvtData) => __awaiter(void 0, void 0, void 0, function* () { | ||
const { action, callID } = pvtData; | ||
if (!callID || !session.calls[callID]) { | ||
return logger.debug('Verto pvtData with invalid or unknown callID.', pvtData); | ||
} | ||
_ack(id, method) { | ||
const msg = new Result(id, method); | ||
if (this.nodeId) { | ||
msg.targetNodeId = this.nodeId; | ||
const call = session.calls[callID]; | ||
if (!call.isMainCall) { | ||
return logger.debug('Verto pvtData on screenShare or secondSource legs.', pvtData); | ||
} | ||
switch (action) { | ||
case 'conference-liveArray-join': | ||
yield call.conferenceJoinHandler(pvtData); | ||
break; | ||
case 'conference-liveArray-part': | ||
yield call.conferencePartHandler(pvtData); | ||
break; | ||
} | ||
}); | ||
const _handleSessionEvent = (session, eventData) => { | ||
const { contentType, callID } = eventData; | ||
if (!callID || !session.calls.hasOwnProperty(callID)) { | ||
return logger.debug('Unhandled session event:', eventData); | ||
} | ||
const call = session.calls[callID]; | ||
switch (contentType) { | ||
case 'layout-info': | ||
case 'layer-info': | ||
call.updateLayouts(eventData); | ||
break; | ||
case 'logo-info': | ||
call.updateLogo(eventData); | ||
break; | ||
case 'caption-info': | ||
call.handleCaptionInfo(eventData); | ||
break; | ||
} | ||
}; | ||
const _buildCall = (session, params, attach, nodeId) => { | ||
const call = new Call(session, { | ||
id: params.callID, | ||
remoteSdp: params.sdp, | ||
destinationNumber: params.callee_id_number, | ||
remoteCallerName: params.caller_id_name, | ||
remoteCallerNumber: params.caller_id_number, | ||
callerName: params.callee_id_name, | ||
callerNumber: params.callee_id_number, | ||
attach, | ||
secondSource: /;second-source$/.test(params.callee_id_number), | ||
screenShare: /;screen$/.test(params.callee_id_number), | ||
shakenCheck: params.shaken_check || '', | ||
shakenResult: params.shaken_result || '', | ||
}); | ||
const hasAudioLine = params.sdp.indexOf('m=audio') !== -1; | ||
if (!hasAudioLine) { | ||
call.options.audio = false; | ||
call.options.micId = null; | ||
call.options.micLabel = null; | ||
} | ||
const hasVideoLine = params.sdp.indexOf('m=video') !== -1; | ||
if (!hasVideoLine) { | ||
call.options.video = false; | ||
call.options.camId = null; | ||
call.options.camLabel = null; | ||
} | ||
call.nodeId = nodeId; | ||
call.isDirect = checkIsDirectCall(params); | ||
return call; | ||
}; | ||
export default (session, msg) => { | ||
const { id, method, nodeId, params } = msg; | ||
const { callID, eventChannel, eventType } = params; | ||
if (eventType === 'channelPvtData') { | ||
params.pvtData.nodeId = nodeId; | ||
return _handlePvtEvent(session, params.pvtData); | ||
} | ||
if (eventChannel === session.sessionid) { | ||
return _handleSessionEvent(session, params.eventData); | ||
} | ||
if (callID && session.calls.hasOwnProperty(callID)) { | ||
trigger(callID, params, method); | ||
if (method !== VertoMethod.Attach) { | ||
const msg = new Result(id, method); | ||
msg.targetNodeId = nodeId; | ||
session.execute(msg); | ||
} | ||
this.session.execute(msg); | ||
return; | ||
} | ||
handleMessage(msg) { | ||
const { session } = this; | ||
const { id, method, params } = msg; | ||
const { callID, eventChannel, eventType } = params; | ||
const attach = method === VertoMethod.Attach; | ||
if (eventType === 'channelPvtData') { | ||
return this._handlePvtEvent(params.pvtData); | ||
const attach = method === VertoMethod.Attach; | ||
switch (method) { | ||
case VertoMethod.Ping: | ||
const msg = new Result(id, method); | ||
msg.targetNodeId = nodeId; | ||
return session.execute(msg); | ||
case VertoMethod.Punt: | ||
session.purge(); | ||
return session.disconnect(); | ||
case VertoMethod.Invite: { | ||
const call = _buildCall(session, params, attach, nodeId); | ||
call.setState(State.Ringing); | ||
const msg = new Result(id, method); | ||
msg.targetNodeId = nodeId; | ||
return session.execute(msg); | ||
} | ||
if (callID && session.calls.hasOwnProperty(callID)) { | ||
if (attach) { | ||
session.calls[callID].hangup({}, false); | ||
} | ||
else { | ||
session.calls[callID].handleMessage(msg); | ||
this._ack(id, method); | ||
case VertoMethod.Attach: { | ||
const call = _buildCall(session, params, attach, nodeId); | ||
return trigger(call.id, params, method); | ||
} | ||
case VertoMethod.Event: | ||
case 'webrtc.event': { | ||
const { subscribedChannel } = params; | ||
if (subscribedChannel && trigger(session.relayProtocol, params, subscribedChannel)) { | ||
return; | ||
} | ||
} | ||
const _buildCall = () => { | ||
const call = new Call(session, { | ||
id: callID, | ||
remoteSdp: params.sdp, | ||
destinationNumber: params.callee_id_number, | ||
remoteCallerName: params.caller_id_name, | ||
remoteCallerNumber: params.caller_id_number, | ||
callerName: params.callee_id_name, | ||
callerNumber: params.callee_id_number, | ||
attach, | ||
secondSource: /;second-source$/.test(params.callee_id_number), | ||
screenShare: /;screen$/.test(params.callee_id_number), | ||
}); | ||
call.nodeId = this.nodeId; | ||
return call; | ||
}; | ||
switch (method) { | ||
case VertoMethod.Punt: | ||
session.disconnect(); | ||
break; | ||
case VertoMethod.Invite: { | ||
const call = _buildCall(); | ||
call.setState(State.Ringing); | ||
this._ack(id, method); | ||
break; | ||
} | ||
case VertoMethod.Attach: { | ||
const call = _buildCall(); | ||
if (this.session.autoRecoverCalls) { | ||
call.answer(); | ||
} | ||
else { | ||
call.setState(State.Recovering); | ||
} | ||
call.handleMessage(msg); | ||
break; | ||
} | ||
case VertoMethod.Event: | ||
case 'webrtc.event': | ||
if (!eventChannel) { | ||
logger.error('Verto received an unknown event:', params); | ||
if (eventChannel) { | ||
const channelType = eventChannel.split('.')[0]; | ||
const global = trigger(session.relayProtocol, params, channelType); | ||
const specific = trigger(session.relayProtocol, params, eventChannel); | ||
if (global || specific) { | ||
return; | ||
} | ||
const protocol = session.relayProtocol; | ||
const firstValue = eventChannel.split('.')[0]; | ||
if (session._existsSubscription(protocol, eventChannel)) { | ||
trigger(protocol, params, eventChannel); | ||
} | ||
else if (eventChannel === session.sessionid) { | ||
this._handleSessionEvent(params.eventData); | ||
} | ||
else if (session._existsSubscription(protocol, firstValue)) { | ||
trigger(protocol, params, firstValue); | ||
} | ||
else if (session.calls.hasOwnProperty(eventChannel)) { | ||
session.calls[eventChannel].handleMessage(msg); | ||
} | ||
else { | ||
trigger(SwEvent.Notification, params, session.uuid); | ||
} | ||
break; | ||
case VertoMethod.Info: | ||
params.type = NOTIFICATION_TYPE.generic; | ||
trigger(SwEvent.Notification, params, session.uuid); | ||
break; | ||
case VertoMethod.ClientReady: | ||
params.type = NOTIFICATION_TYPE.vertoClientReady; | ||
trigger(SwEvent.Notification, params, session.uuid); | ||
break; | ||
default: | ||
logger.warn('Verto message unknown method:', msg); | ||
} | ||
} | ||
_retrieveCallId(packet, laChannel) { | ||
const callIds = Object.keys(this.session.calls); | ||
if (packet.action === 'bootObj') { | ||
const me = packet.data.find((pr) => callIds.includes(pr[0])); | ||
if (me instanceof Array) { | ||
return me[0]; | ||
} | ||
params.type = Notification.Generic; | ||
return trigger(SwEvent.Notification, params, session.uuid); | ||
} | ||
else { | ||
return callIds.find((id) => this.session.calls[id].channels.includes(laChannel)); | ||
} | ||
case VertoMethod.Info: | ||
params.type = Notification.Generic; | ||
return trigger(SwEvent.Notification, params, session.uuid); | ||
case VertoMethod.ClientReady: | ||
params.type = Notification.VertoClientReady; | ||
return trigger(SwEvent.Notification, params, session.uuid); | ||
case VertoMethod.Announce: | ||
params.type = Notification.Announce; | ||
return trigger(SwEvent.Notification, params, session.uuid); | ||
default: | ||
logger.debug('Unknown Verto method:', method, params); | ||
params.type = method; | ||
return trigger(SwEvent.Notification, params, session.uuid); | ||
} | ||
_handlePvtEvent(pvtData) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { session } = this; | ||
const protocol = session.relayProtocol; | ||
const { action, laChannel, laName, chatChannel, infoChannel, modChannel, conferenceMemberID, role, callID } = pvtData; | ||
switch (action) { | ||
case 'conference-liveArray-join': { | ||
const _liveArrayBootstrap = () => { | ||
session.vertoBroadcast({ nodeId: this.nodeId, channel: laChannel, data: { liveArray: { command: 'bootstrap', context: laChannel, name: laName } } }); | ||
}; | ||
const tmp = { | ||
nodeId: this.nodeId, | ||
channels: [laChannel], | ||
handler: ({ data: packet }) => { | ||
const id = callID || this._retrieveCallId(packet, laChannel); | ||
if (id && session.calls.hasOwnProperty(id)) { | ||
const call = session.calls[id]; | ||
call._addChannel(laChannel); | ||
call.extension = laName; | ||
call.handleConferenceUpdate(packet, pvtData) | ||
.then(error => { | ||
if (error === 'INVALID_PACKET') { | ||
_liveArrayBootstrap(); | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
const result = yield session.vertoSubscribe(tmp) | ||
.catch(error => { | ||
logger.error('liveArray subscription error:', error); | ||
}); | ||
if (checkSubscribeResponse(result, laChannel)) { | ||
_liveArrayBootstrap(); | ||
} | ||
break; | ||
} | ||
case 'conference-liveArray-part': { | ||
const call = session.calls[callID] || null; | ||
if (call) { | ||
const notification = { type: NOTIFICATION_TYPE.conferenceUpdate, action: ConferenceAction.Leave, call, conferenceName: laName, participantId: Number(conferenceMemberID), role }; | ||
if (!trigger(SwEvent.Notification, notification, call.id, false)) { | ||
trigger(SwEvent.Notification, notification, session.uuid); | ||
} | ||
deRegister(SwEvent.Notification, null, call.id); | ||
if (call.isMainCall === false) { | ||
return call.destroy(); | ||
} | ||
} | ||
const channels = [laChannel, chatChannel, infoChannel, modChannel]; | ||
const { unsubscribedChannels = [] } = yield session.vertoUnsubscribe({ nodeId: this.nodeId, channels }) | ||
.catch(error => { | ||
logger.error('liveArray unsubscribe error:', error); | ||
}); | ||
if (call) { | ||
call.channels = call.channels.filter(c => !unsubscribedChannels.includes(c)); | ||
call.destroy(); | ||
} | ||
break; | ||
} | ||
} | ||
}); | ||
} | ||
_handleSessionEvent(eventData) { | ||
switch (eventData.contentType) { | ||
case 'layout-info': | ||
case 'layer-info': | ||
MCULayoutEventHandler(this.session, eventData); | ||
break; | ||
case 'logo-info': { | ||
const notification = { type: NOTIFICATION_TYPE.conferenceUpdate, action: ConferenceAction.LogoInfo, logo: eventData.logoURL }; | ||
trigger(SwEvent.Notification, notification, this.session.uuid); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
export default VertoHandler; | ||
}; |
import Relay from './src/SignalWire'; | ||
import Verto from './src/Verto'; | ||
import CantinaAuth from '../common/src/webrtc/CantinaAuth'; | ||
export declare const VERSION = "1.3.0-cantina.7"; | ||
export declare const VERSION = "1.3.0-cantina.8"; | ||
export { Relay, Verto, CantinaAuth }; | ||
export * from '../common/src/webrtc/deviceHelpers'; | ||
export * from '../common/src/util/interfaces'; | ||
export * from '../common/src/webrtc/interfaces'; |
@@ -5,4 +5,5 @@ import Relay from './src/SignalWire'; | ||
import CantinaAuth from '../common/src/webrtc/CantinaAuth'; | ||
export const VERSION = '1.3.0-cantina.7'; | ||
export const VERSION = '1.3.0-cantina.8'; | ||
setAgentName(`JavaScript SDK/${VERSION}`); | ||
export { Relay, Verto, CantinaAuth }; | ||
export * from '../common/src/webrtc/deviceHelpers'; |
import BrowserSession from '../../common/src/BrowserSession'; | ||
import BaseMessage from '../../common/src/messages/BaseMessage'; | ||
import { CallOptions } from '../../common/src/webrtc/interfaces'; | ||
import Call from '../../common/src/webrtc/Call'; | ||
export default class SignalWire extends BrowserSession { | ||
execute(message: BaseMessage): any; | ||
newCall(options: CallOptions): Promise<Call>; | ||
} |
@@ -11,17 +11,4 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import BrowserSession from '../../common/src/BrowserSession'; | ||
import { Execute } from '../../common/src/messages/Blade'; | ||
import BaseRequest from '../../common/src/messages/verto/BaseRequest'; | ||
import Call from '../../common/src/webrtc/Call'; | ||
export default class SignalWire extends BrowserSession { | ||
execute(message) { | ||
let msg = message; | ||
if (message instanceof BaseRequest) { | ||
const params = { message: message.request }; | ||
if (message.targetNodeId) { | ||
params.node_id = message.targetNodeId; | ||
} | ||
msg = new Execute({ protocol: this.relayProtocol, method: 'message', params }); | ||
} | ||
return super.execute(msg); | ||
} | ||
newCall(options) { | ||
@@ -28,0 +15,0 @@ return __awaiter(this, void 0, void 0, function* () { |
@@ -5,13 +5,18 @@ import BrowserSession from '../../common/src/BrowserSession'; | ||
import Call from '../../common/src/webrtc/Call'; | ||
export declare const VERTO_PROTOCOL = "verto-protocol"; | ||
import BaseMessage from '../../common/src/messages/BaseMessage'; | ||
export default class Verto extends BrowserSession { | ||
relayProtocol: string; | ||
timeoutErrorCode: number; | ||
loginResponse: any; | ||
moderator: boolean; | ||
superuser: boolean; | ||
validateOptions(): boolean; | ||
newCall(options: CallOptions): Call; | ||
broadcast(params: BroadcastParams): void; | ||
broadcast(params: BroadcastParams): any; | ||
subscribe(params: SubscribeParams): Promise<any>; | ||
unsubscribe(params: SubscribeParams): Promise<any>; | ||
ping(): any; | ||
_wrapInExecute(message: BaseMessage): BaseMessage; | ||
protected _onSocketOpen(): Promise<void>; | ||
protected _onSocketMessage(msg: any): void; | ||
} |
@@ -11,9 +11,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import BrowserSession from '../../common/src/BrowserSession'; | ||
import { Login } from '../../common/src/messages/Verto'; | ||
import { Login, Ping } from '../../common/src/messages/Verto'; | ||
import Call from '../../common/src/webrtc/Call'; | ||
import { SwEvent, SESSION_ID } from '../../common/src/util/constants'; | ||
import { SwEvent, SESSION_ID, VERTO_PROTOCOL } from '../../common/src/util/constants'; | ||
import { trigger } from '../../common/src/services/Handler'; | ||
import { localStorage } from '../../common/src/util/storage/'; | ||
import VertoHandler from '../../common/src/webrtc/VertoHandler'; | ||
export const VERTO_PROTOCOL = 'verto-protocol'; | ||
export default class Verto extends BrowserSession { | ||
@@ -24,2 +23,5 @@ constructor() { | ||
this.timeoutErrorCode = -329990; | ||
this.loginResponse = {}; | ||
this.moderator = false; | ||
this.superuser = false; | ||
} | ||
@@ -31,5 +33,2 @@ validateOptions() { | ||
newCall(options) { | ||
if (this._idle || !this.connected) { | ||
throw new Error('Client not connected'); | ||
} | ||
const { destinationNumber = null } = options; | ||
@@ -52,2 +51,9 @@ if (!destinationNumber) { | ||
} | ||
ping() { | ||
const msg = new Ping({ serno: Date.now() }); | ||
return this.execute(msg); | ||
} | ||
_wrapInExecute(message) { | ||
return message; | ||
} | ||
_onSocketOpen() { | ||
@@ -57,6 +63,2 @@ return __awaiter(this, void 0, void 0, function* () { | ||
const { login, password, passwd, userVariables } = this.options; | ||
if (this.sessionid) { | ||
const sessidLogin = new Login(undefined, undefined, this.sessionid, undefined); | ||
yield this.execute(sessidLogin).catch(() => null); | ||
} | ||
const msg = new Login(login, (password || passwd), this.sessionid, userVariables); | ||
@@ -66,4 +68,9 @@ const response = yield this.execute(msg).catch(this._handleLoginError); | ||
this._autoReconnect = true; | ||
this.loginResponse = response; | ||
this.moderator = response.moderator || false; | ||
this.superuser = response.superuser || false; | ||
this.sessionid = response.sessid; | ||
localStorage.setItem(SESSION_ID, this.sessionid); | ||
if (!this.incognito) { | ||
localStorage.setItem(SESSION_ID, this.sessionid); | ||
} | ||
trigger(SwEvent.Ready, this, this.uuid); | ||
@@ -74,5 +81,4 @@ } | ||
_onSocketMessage(msg) { | ||
const handler = new VertoHandler(this); | ||
handler.handleMessage(msg); | ||
VertoHandler(this, msg); | ||
} | ||
} |
{ | ||
"name": "@signalwire/js", | ||
"version": "1.3.0-cantina.7", | ||
"version": "1.3.0-cantina.8", | ||
"description": "Relay SDK for JavaScript to connect to SignalWire.", | ||
@@ -21,3 +21,3 @@ "author": "SignalWire Team <open.source@signalwire.com>", | ||
"tslint": "tslint -p tsconfig.json", | ||
"test": "jest --forceExit --detectOpenHandles", | ||
"test": "jest --no-cache --forceExit --detectOpenHandles", | ||
"test:watch": "npm run test -- --watchAll", | ||
@@ -61,4 +61,4 @@ "validate": "npm i && npm run tslint && npm run test && npm run clean-build", | ||
"webpack-cli": "^3.3.11", | ||
"webpack-dev-server": "^3.10.3" | ||
"webpack-dev-server": "^3.11.0" | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
376075
87
5412
4