rn-verto-typescript
Version:
Verto Free Switch with React Native Type Script
509 lines (508 loc) • 20 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Call_1 = __importDefault(require("./Call"));
const ConferenceManager_1 = __importDefault(require("../conference/ConferenceManager"));
const ConferenceLiveArray_1 = __importDefault(require("../conference/ConferenceLiveArray"));
const utils_1 = require("./utils");
const store_1 = require("../store");
const react_native_background_timer_1 = __importDefault(require("react-native-background-timer"));
const CallActions_enum_1 = require("../enums/CallActions.enum");
react_native_background_timer_1.default.start();
let sessionIDCache;
class VertinhoClient {
constructor(params = store_1.Params, vertoCallbacks, conferenceCallbacks) {
this.params = { ...params };
const defaultCallback = (x) => x;
this.callbacks = {
onClientReady: defaultCallback,
onClientClose: defaultCallback,
onConferenceReady: defaultCallback,
onConferenceDisabled: defaultCallback,
onInfo: defaultCallback,
onDisplay: defaultCallback,
onCallStateChange: defaultCallback,
onPrivateEvent: defaultCallback,
onStreamReady: () => { },
onNewCall: defaultCallback,
...vertoCallbacks,
};
this.conferenceCallbacks = {
onReady: defaultCallback,
onDestroyed: defaultCallback,
onBootstrappedMembers: defaultCallback,
onAddedMember: defaultCallback,
onModifiedMember: defaultCallback,
onRemovedMember: defaultCallback,
onChatMessage: defaultCallback,
onInfo: defaultCallback,
onModeration: defaultCallback,
...conferenceCallbacks,
};
this.webSocket = null;
this.webSocketCallbacks = {};
this.retryingTimer = null;
this.currentWebSocketRequestId = 0;
// this.options = {};
this.calls = new Map();
this.conference = null;
this.connect();
}
connect() {
this.options = {
webSocket: {
login: '',
password: '',
url: '',
},
videoParams: {},
audioParams: {},
loginParams: {},
deviceParams: {},
userVariables: {},
iceServers: false,
ringSleep: 6000,
sessid: null,
onmessage: event => this.handleMessage(event.eventData),
onWebSocketLoginSuccess: () => { },
onWebSocketLoginError: (error) => {
console.log('Error reported by WebSocket login', error);
if (this.callbacks.onError) {
this.callbacks.onError(error);
}
},
onPeerStreaming: () => { },
onPeerStreamingError: (error) => {
console.log('onPeerStreamingError:', error);
if (this.callbacks.onError) {
this.callbacks.onError(error);
}
},
...this.params,
...this.callbacks
};
if (!this.options.deviceParams.useMic) {
this.options.deviceParams.useMic = 'any';
}
if (!this.options.deviceParams.useSpeak) {
this.options.deviceParams.useSpeak = 'any';
}
if (!this.options.blockSessionRecovery) {
if (this.options.sessid) {
this.sessid = this.options.sessid;
}
else {
this.sessid = sessionIDCache || utils_1.generateGUID();
sessionIDCache = this.sessid;
}
}
else {
this.sessid = utils_1.generateGUID();
}
this.calls = new Map();
this.callbacks = this.callbacks || {};
this.webSocketSubscriptions = new Map();
this.connectSocket();
}
connectSocket() {
if (this.retryingTimer) {
clearTimeout(this.retryingTimer);
}
if (this.socketReady()) {
console.log('Tried to connect to socket but already had a ready one');
return;
}
this.authing = false;
if (this.webSocket) {
delete this.webSocket;
}
this.webSocket = new WebSocket(this.options.webSocket.url);
this.webSocket.onmessage = this.onWebSocketMessage.bind(this);
this.webSocket.onclose = (event) => {
console.log('WebSocket closed, attempting to connect again in 10s.', event);
this.callbacks.onClientClose();
this.retryingTimer = react_native_background_timer_1.default.setTimeout(() => {
if (this.webSocket != null)
this.connectSocket();
}, 10000);
};
this.webSocket.onopen = () => {
console.log('WebSocket opened');
if (this.retryingTimer) {
console.log('Successfully WebSocket attempt to reconnect.');
clearTimeout(this.retryingTimer);
}
this.publish('login', {});
};
this.webSocket.onerror = event => {
console.log('onError event:', event);
if (this.callbacks.onError) {
this.callbacks.onError(event);
}
};
}
socketReady() {
if (this.webSocket === null || this.webSocket.readyState > 1) {
return false;
}
return true;
}
purge() {
Object.keys(this.calls).forEach(callId => {
this.calls[callId].setState(utils_1.ENUM.state.purge);
});
this.webSocketSubscriptions = new Map();
}
publish(method, params = {}, onSuccess = (x) => x, onError = (x) => x) {
this.currentWebSocketRequestId += 1;
const request = {
jsonrpc: '2.0',
method,
params: { sessid: this.sessid, ...params },
id: this.currentWebSocketRequestId,
};
const requestStringified = JSON.stringify(request);
if ('id' in request && onSuccess !== undefined) {
this.webSocketCallbacks[request.id] = {
requestStringified,
request,
onSuccess,
onError,
};
}
if (this.webSocket) {
this.webSocket.send(requestStringified);
}
}
handleJSONRPCMessage(message) {
if (message.result) {
const { onSuccess } = this.webSocketCallbacks[message.id];
delete this.webSocketCallbacks[message.id];
onSuccess(message.result, this);
return;
}
if (!message.error) {
if (this.callbacks.onError) {
this.callbacks.onError(message.error);
}
return;
}
if (!this.authing && parseInt(message.error.code, 10) === -32000) {
this.authing = true;
this.publish('login', {
login: this.options.webSocket.login,
passwd: this.options.webSocket.password,
loginParams: this.options.loginParams,
userVariables: this.options.userVariables,
}, () => {
this.authing = false;
delete this.webSocketCallbacks[message.id];
this.options.onWebSocketLoginSuccess();
}, () => {
delete this.webSocketCallbacks[message.id];
this.options.onWebSocketLoginError(message.error);
});
return;
}
const { onError } = this.webSocketCallbacks[message.id];
delete this.webSocketCallbacks[message.id];
onError(message.error, this);
}
onWebSocketMessage(event) {
const message = JSON.parse(event.data);
if (message &&
message.jsonrpc === '2.0' &&
this.webSocketCallbacks[message.id]) {
this.handleJSONRPCMessage(message);
return;
}
if (typeof this.options.onmessage !== 'function') {
return;
}
const fixedEvent = { ...event, eventData: message || {} };
const reply = this.options.onmessage(fixedEvent);
if (typeof reply !== 'object' ||
!fixedEvent.eventData.id ||
!this.webSocket) {
return;
}
this.webSocket.send(JSON.stringify({
jsonrpc: '2.0',
id: fixedEvent.eventData.id,
result: reply,
}));
}
handleMessage(data) {
if (!data || !data.method || !data.params) {
console.log('Invalid WebSocket message', data);
return;
}
console.log('handleMessage data:', data);
if (data.params.eventType === 'channelPvtData') {
this.handleChannelPrivateDataMessage(data);
}
else if (data.params.callID) {
this.handleMessageForCall(data);
}
else {
this.handleMessageForClient(data);
}
}
handleChannelPrivateDataMessage(data) {
const { params: event } = data;
const existingConference = this.conference && { ...this.conference };
if (event.pvtData.action === 'conference-liveArray-join') {
if (existingConference) {
console.log('Ignoring doubled private event of live array join', event);
return;
}
const conference = {
creationEvent: event,
privateEventChannel: event.eventChannel,
memberId: event.pvtData.conferenceMemberID,
role: event.pvtData.role,
manager: new ConferenceManager_1.default(this, {
chat: {
channel: event.pvtData.chatChannel,
handler: this.conferenceCallbacks.onChatMessage,
},
info: {
channel: event.pvtData.infoChannel,
handler: this.conferenceCallbacks.onInfo,
},
mod: event.pvtData.modChannel
? null
: {
channel: event.pvtData.modChannel,
handler: this.conferenceCallbacks.onModeration,
},
}),
liveArray: new ConferenceLiveArray_1.default(this, event.pvtData.laChannel, event.pvtData.laName, {
onBootstrappedMembers: this.conferenceCallbacks
.onBootstrappedMembers,
onAddedMember: this.conferenceCallbacks.onAddedMember,
onModifiedMember: this.conferenceCallbacks.onModifiedMember,
onRemovedMember: this.conferenceCallbacks.onRemovedMember,
}),
};
this.conference = conference;
this.conferenceCallbacks.onReady(conference);
}
else if (event.pvtData.action === 'conference-liveArray-part') {
if (!existingConference) {
console.log('Ignoring event of live array part without conference instance', event);
return;
}
existingConference.manager.destroy();
existingConference.liveArray.destroy();
this.conference = null;
this.conferenceCallbacks.onDestroyed(existingConference);
}
else {
console.log('Not implemented private data message', data);
}
}
handleMessageForClient(data) {
console.log('handleMessageForClient data:', data); // TODO Convert data type
const channel = data.params.eventChannel;
const subscription = channel && this.webSocketSubscriptions[channel];
switch (data.method) {
case CallActions_enum_1.CallActions.Punt:
this.destroy();
break;
case CallActions_enum_1.CallActions.Event:
if (!subscription && channel === this.sessid) {
this.callbacks.onPrivateEvent(data.params);
}
else if (!subscription && channel && this.calls[channel]) {
this.callbacks.onPrivateEvent(data.params);
}
else if (!subscription) {
console.log('Ignoring event for unsubscribed channel', channel, data.params);
}
else if (!subscription || !subscription.ready) {
console.log('Ignoring event for a not ready channel', channel, data.params);
}
else if (subscription.handler) {
subscription.handler(data.params, subscription.userData);
}
else if (this.callbacks.onEvent) {
this.callbacks.onEvent(this, data.params, subscription.userData);
}
else {
console.log('Ignoring event without callback', channel, data.params);
}
break;
case CallActions_enum_1.CallActions.Info:
this.callbacks.onInfo(data.params);
break;
case CallActions_enum_1.CallActions.Ready:
this.callbacks.onClientReady(data.params);
break;
default:
console.log('Ignoring invalid method with no call id', data.method);
break;
}
}
handleMessageForCall(data) {
const existingCall = this.calls[data.params.callID];
if (existingCall) {
switch (data.method) {
case CallActions_enum_1.CallActions.Bye:
existingCall.hangup(data.params);
break;
case CallActions_enum_1.CallActions.Answer:
existingCall.handleAnswer(data.params.sdp);
break;
case CallActions_enum_1.CallActions.Media:
existingCall.handleMedia(data.params.sdp);
break;
case CallActions_enum_1.CallActions.Display:
existingCall.handleDisplay(data.params.display_name, data.params.display_number);
break;
case CallActions_enum_1.CallActions.Info:
existingCall.handleInfo(data.params);
break;
default:
console.log('Ignoring existing call event with invalid method', data.method);
break;
}
}
else if (data.method === CallActions_enum_1.CallActions.Attach ||
data.method === CallActions_enum_1.CallActions.Invite) {
const useVideo = data.params.sdp && data.params.sdp.indexOf('m=video') > 0;
const useStereo = data.params.sdp && data.params.sdp.indexOf('stereo=1') > 0;
const newCall = new Call_1.default(utils_1.ENUM.direction.inbound, this, {
...data.params,
attach: false,
useVideo,
useStereo
});
this.callbacks.onNewCall(newCall);
if (data.method === CallActions_enum_1.CallActions.Attach) {
newCall.setState(utils_1.ENUM.state.recovering);
}
}
else {
console.log('Ignoring call event with invalid method', data.method);
}
}
processReply(method, { subscribedChannels, unauthorizedChannels }) {
if (method !== CallActions_enum_1.CallActions.Subscribe) {
return;
}
Object.keys(subscribedChannels || {}).forEach(channelKey => {
const channel = subscribedChannels[channelKey];
this.setReadySubscription(channel);
});
Object.keys(unauthorizedChannels || {}).forEach(channelKey => {
const channel = unauthorizedChannels[channelKey];
console.log('Unauthorized', channel);
this.setDroppedSubscription(channel);
});
}
// TODO convert channel type
setDroppedSubscription(channel) {
delete this.webSocketSubscriptions[channel];
}
// TODO convert channel type
setReadySubscription(channel) {
const subscription = this.webSocketSubscriptions[channel];
if (subscription) {
subscription.ready = true;
}
}
broadcastMethod(method, params) {
const reply = event => this.processReply(method, event);
this.publish(method, params, reply, reply);
}
broadcast(eventChannel, data) {
this.broadcastMethod(CallActions_enum_1.CallActions.Broadcast, { eventChannel, data });
}
subscribe(eventChannel, params) {
const eventSubscription = {
eventChannel,
handler: params.handler,
userData: params.userData,
ready: false,
};
if (this.webSocketSubscriptions[eventChannel]) {
console.log('Overwriting an already subscribed channel', eventChannel);
}
this.webSocketSubscriptions[eventChannel] = eventSubscription;
this.broadcastMethod(CallActions_enum_1.CallActions.Subscribe, { eventChannel });
return eventSubscription;
}
unsubscribe(eventChannel) {
delete this.webSocketSubscriptions[eventChannel];
this.broadcastMethod(CallActions_enum_1.CallActions.Unsubscribe, { eventChannel });
}
switchCamera(callId, localVideoTrack) {
const call = this.calls && this.calls[callId];
if (call) {
call.rtc.switchCamera(localVideoTrack);
}
}
// private setTrackEnabled(callId: string, stream, audioTrack, enabled): void {
// const call = this.calls && this.calls[callId];
// if (call) {
// call.rtc.setTrackEnabled(stream, audioTrack, enabled);
// }
// }
makeVideoCall({ callerName, ...params }, mediaHandlers) {
if (!callerName) {
console.log('No `callerName` parameter on making video call.');
}
return this.makeCall({ callerName, useVideo: true, ...params }, mediaHandlers);
}
// private makeCall(callParams: any = { to, from, ...otherParams }, mediaHandlers = {}): Call {
makeCall(callParams, mediaHandlers) {
if (!callParams.to || !callParams.from) {
console.log('No `to` or `from` parameters on making call.');
return null;
}
const { callerName = 'Vertinho', ...params } = callParams;
params.destination_number = callParams.to;
params.caller_id_number = callParams.from;
params.caller_id_name = callerName;
if (!this.socketReady()) {
console.log('Socket not ready.');
return null;
}
const call = new Call_1.default(utils_1.ENUM.direction.outbound, this, params, mediaHandlers);
call.rtc.inviteRemotePeerConnection();
return call;
}
destroy() {
if (this.socketReady()) {
this.webSocket.close();
this.purge();
}
else {
console.log('Tried to close a not ready socket while destroying.');
}
if (this.webSocket)
delete this.webSocket;
this.webSocket = null;
if (this.retryingTimer)
clearTimeout(this.retryingTimer);
}
hangup(callId) {
if (callId) {
const call = this.calls[callId];
if (call) {
call.hangup();
}
else {
console.log('Error on hanging up call', callId);
}
return;
}
Object.keys(this.calls).forEach(id => {
this.calls[id].hangup();
});
}
}
exports.default = VertinhoClient;