rn-verto-typescript
Version:
Verto Free Switch with React Native Type Script
421 lines (420 loc) • 17.4 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 VertoRTC_1 = __importDefault(require("../webrtc/VertoRTC"));
const utils_1 = require("./utils");
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();
class Call {
constructor(direction, verto, params, mediaHandlers) {
this.direction = direction;
this.verto = verto;
this.params = {
callID: utils_1.generateGUID(),
useVideo: verto.options.useVideo,
useStereo: verto.options.useStereo,
screenShare: false,
useCamera: false,
useMic: verto.options.deviceParams.useMic,
useSpeak: verto.options.deviceParams.useSpeak,
remoteVideo: verto.options.remoteVideo,
remoteAudioId: verto.options.remoteAudioId,
localVideo: verto.options.localVideo,
login: verto.options.login,
videoParams: verto.options.videoParams,
...params,
};
this.mediaHandlers = mediaHandlers;
if (!this.params.screenShare) {
this.params.useCamera = verto.options.deviceParams.useCamera;
}
this.answered = false;
this.lastState = utils_1.ENUM.state.new;
this.state = this.lastState;
this.verto.calls[this.params.callID] = this;
if (this.direction === utils_1.ENUM.direction.inbound) {
if (this.params.display_direction === 'outbound') {
this.params.remote_caller_id_name =
this.params.caller_id_name || 'NOBODY';
this.params.remote_caller_id_number =
this.params.caller_id_number || 'UNKNOWN';
}
else {
this.params.remote_caller_id_name =
this.params.callee_id_name || 'NOBODY';
this.params.remote_caller_id_number =
this.params.callee_id_number || 'UNKNOWN';
}
}
else {
this.params.remote_caller_id_name = 'OUTBOUND CALL';
this.params.remote_caller_id_number = this.params.destination_number;
}
this.bootstrapRealtimeConnection();
if (this.direction === utils_1.ENUM.direction.inbound) {
if (this.params.attach) {
this.answer();
}
else {
// this.ring(); // TODO call ring method
}
}
}
bootstrapRealtimeConnection() {
const callbacks = {
onICESDP: () => {
const { requesting, answering, active } = utils_1.ENUM.state;
if ([requesting, answering, active].includes(this.state)) {
console.log('This ICE SDP should not being received, reload your page!');
return;
}
let firstTime = true;
let newsdp = this.rtc.mediaData.SDP.split('\r\n');
newsdp = newsdp.concat([undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]);
newsdp = newsdp.map((line, index) => {
if (line && line.indexOf('a=rtcp:9 IN IP4 0.0.0.0') === 0) {
if (firstTime) {
const audioCandidates = this.rtc.mediaData.candidateList
.filter(val => val.sdpMid === 'audio')
.map(val => `a=${val.candidate}`);
// audioCandidates.unshift(line);
newsdp.splice(index + 1, 0, audioCandidates[0], audioCandidates[1], audioCandidates[2], audioCandidates[3], audioCandidates[4], audioCandidates[5], audioCandidates[6]);
}
else {
const videoCandidates = this.rtc.mediaData.candidateList
.filter(val => val.sdpMid === 'video')
.map(val => `a=${val.candidate}`);
// videoCandidates.unshift(line);
newsdp.splice(index + 1, 0, videoCandidates[0], videoCandidates[1], videoCandidates[2], videoCandidates[3], videoCandidates[4], videoCandidates[5], videoCandidates[6]);
}
firstTime = false;
}
return line;
});
const isActivelyCalling = this.rtc.type === 'offer';
const options = { sdp: newsdp.join('\r\n') };
if (isActivelyCalling) {
if (this.state === active) {
this.setState(requesting);
this.broadcastMethod(CallActions_enum_1.CallActions.Attach, options);
}
else {
this.setState(requesting);
this.broadcastMethod(CallActions_enum_1.CallActions.Invite, options);
}
}
else {
this.setState(answering);
this.broadcastMethod(this.params.attach ? CallActions_enum_1.CallActions.Attach : CallActions_enum_1.CallActions.Answer, options);
}
},
onPeerStreaming: stream => {
this.verto.options.onPeerStreaming(stream);
},
//(reason: any) => PromiseLike<never>
onPeerStreamingError: (reason) => Promise.reject((error) => {
this.verto.options.onPeerStreamingError(error);
this.hangup({ cause: 'Device or Permission Error' });
return Promise.reject(error);
}),
onNewCall: (call) => {
console.log('onNewCall 1');
if (this.verto && this.verto.callbacks && this.verto.callbacks.onNewCall) {
console.log('onNewCall 2');
this.verto.callbacks.onNewCall(call);
}
}
};
this.rtc = new VertoRTC_1.default({
callbacks,
mediaHandlers: this.mediaHandlers,
localVideo: this.params.screenShare ? null : (this.params.localVideo ? true : false),
useVideo: (this.params.remoteVideo ? true : false),
useAudio: (this.params.remoteAudioId ? true : false),
videoParams: this.params.videoParams || {},
audioParams: this.verto.options.audioParams || {},
iceServers: this.verto.options.iceServers,
screenShare: (this.params.screenShare ? true : false),
useCamera: this.params.useCamera,
useMic: this.params.useMic,
useSpeak: this.params.useSpeak,
verto: this.verto,
userData: null
});
}
broadcastMethod(method, options) {
const { noDialogParams, ...methodParams } = options;
const dialogParams = Object.keys(this.params).reduce((accumulator, currentKey) => {
if (currentKey === 'sdp' &&
method !== CallActions_enum_1.CallActions.Invite &&
method !== CallActions_enum_1.CallActions.Attach) {
return accumulator;
}
if (currentKey === 'callID' && noDialogParams === true) {
return accumulator;
}
return { ...accumulator, [currentKey]: this.params[currentKey] };
}, {});
const handleMethodResponseFn = success => x => this.handleMethodResponse(method, success, x);
this.verto.publish(method, {
...methodParams,
dialogParams,
}, handleMethodResponseFn(true), handleMethodResponseFn(false));
}
setState(state) {
if (this.state === utils_1.ENUM.state.ringing) {
// this.stopRinging(); // TODO set a ringer
}
const checkStateChange = state === utils_1.ENUM.state.purge || utils_1.ENUM.states[this.state.name][state.name];
if (this.state === state || !checkStateChange) {
console.log(`Invalid call state change from ${this.state.name} to ${state.name}. ${this}`);
this.hangup();
return false;
}
this.lastState = this.state;
this.state = state;
this.verto.callbacks.onCallStateChange({
previous: this.lastState,
current: this.state,
});
const speaker = this.params.useSpeak;
const useCustomSpeaker = speaker && speaker !== 'any' && speaker !== 'none';
const isAfterRequesting = this.lastState.val > utils_1.ENUM.state.requesting.val;
const isBeforeHangup = this.lastState.val < utils_1.ENUM.state.hangup.val;
switch (this.state) {
case utils_1.ENUM.state.early:
case utils_1.ENUM.state.active:
if (useCustomSpeaker) {
console.log('Custom speaker not supported, ignoring.');
}
break;
case utils_1.ENUM.state.trying:
// setTimeout(() => {
// if (this.state === ENUM.state.trying) {
// console.log(`Turning off after 3s of trying. ${this}`);
// this.setState(ENUM.state.hangup);
// }
// }, 3000);
break;
case utils_1.ENUM.state.purge:
this.setState(utils_1.ENUM.state.destroy);
break;
case utils_1.ENUM.state.hangup:
if (isAfterRequesting && isBeforeHangup) {
this.broadcastMethod(CallActions_enum_1.CallActions.Bye, {});
}
this.setState(utils_1.ENUM.state.destroy);
break;
case utils_1.ENUM.state.destroy:
delete this.verto.calls[this.params.callID];
if (this.params.screenShare) {
this.rtc.stopPeer();
}
else {
this.rtc.stop();
}
break;
default:
break;
}
return true;
}
handleMethodResponse(method, success, response) {
switch (method) {
case CallActions_enum_1.CallActions.Answer:
case CallActions_enum_1.CallActions.Attach:
if (success) {
this.setState(utils_1.ENUM.state.active);
}
else {
this.hangup();
}
break;
case CallActions_enum_1.CallActions.Invite:
if (success) {
this.setState(utils_1.ENUM.state.trying);
}
else {
this.setState(utils_1.ENUM.state.destroy);
}
break;
case CallActions_enum_1.CallActions.Bye:
this.hangup();
break;
case CallActions_enum_1.CallActions.Modify:
if (response.holdState === 'held' && this.state !== utils_1.ENUM.state.held) {
this.setState(utils_1.ENUM.state.held);
}
if (response.holdState === 'active' &&
this.state !== utils_1.ENUM.state.active) {
this.setState(utils_1.ENUM.state.active);
}
break;
default:
break;
}
}
hangup(params) {
if (params) {
this.cause = params.cause;
}
if (!this.cause) {
this.cause = 'NORMAL_CLEARING';
}
const isNotNew = this.state.val >= utils_1.ENUM.state.new.val;
const didntHangupYet = this.state.val < utils_1.ENUM.state.hangup.val;
if (isNotNew && didntHangupYet) {
this.setState(utils_1.ENUM.state.hangup);
}
// TODO Check => converted to destroy.val from destroy
const didntDestroyYet = this.state.val < utils_1.ENUM.state.destroy.val;
if (didntDestroyYet) {
this.setState(utils_1.ENUM.state.destroy);
}
}
// stopRinging() {
// if (!this.verto.ringer) {
// return;
// }
// this.verto.ringer.getTracks().forEach(ringer => ringer.stop());
// }
// indicateRing() {
// if (!this.verto.ringer) {
// console.log(`Call is ringing, but no ringer set. ${this}`);
// return;
// }
// if (!this.verto.ringer.src && this.verto.options.ringFile) {
// this.verto.ringer.src = this.verto.options.ringFile;
// }
// this.verto.ringer.play();
// BackgroundTimer.setTimeout(() => {
// this.stopRinging();
// if (this.state === ENUM.state.ringing) {
// this.indicateRing();
// } else {
// console.log(`Call stopped ringing, but no ringer set. ${this}`);
// }
// }, this.verto.options.ringSleep);
// }
// ring() {
// this.setState(ENUM.state.ringing);
// this.indicateRing();
// }
sendTouchtone(digit) {
this.broadcastMethod(CallActions_enum_1.CallActions.Info, { dtmf: digit });
}
sendRealTimeText({ code, chars }) {
this.broadcastMethod(CallActions_enum_1.CallActions.Info, {
txt: { code, chars },
noDialogParams: true,
});
}
transferTo(destination) {
this.broadcastMethod(CallActions_enum_1.CallActions.Modify, { action: 'transfer', destination });
}
hold() {
this.broadcastMethod(CallActions_enum_1.CallActions.Modify, { action: 'hold' });
}
unhold() {
this.broadcastMethod(CallActions_enum_1.CallActions.Modify, { action: 'unhold' });
}
toggleHold() {
this.broadcastMethod(CallActions_enum_1.CallActions.Modify, { action: 'toggleHold' });
}
sendMessageTo(to, body) {
this.broadcastMethod(CallActions_enum_1.CallActions.Info, {
msg: { from: this.params.login, to, body },
});
}
answer() {
if (this.answered) {
return;
}
this.rtc.createAnswer(this.params);
this.answered = true;
}
handleAnswer(sdp) {
this.gotAnswer = true;
if (this.state.val >= utils_1.ENUM.state.active.val) {
return;
}
const afterOrAtEarly = this.state.val >= utils_1.ENUM.state.early.val;
if (afterOrAtEarly) {
this.setState(utils_1.ENUM.state.active);
return;
}
const shouldDelayForNow = this.gotEarly;
if (shouldDelayForNow) {
return;
}
this.rtc.answer(sdp, () => {
this.setState(utils_1.ENUM.state.active);
}, error => Promise.reject(error).then(value => {
console.log('Error while answering', value);
this.hangup();
return Promise.reject();
}));
}
getDestinationNumber() {
return this.params.destination_number;
}
getId() {
return this.params.callID;
}
getCallerIdentification({ useCaracterEntities }) {
return [
this.params.remote_caller_id_name,
' ',
useCaracterEntities ? '<' : '<',
this.params.remote_caller_id_number,
useCaracterEntities ? '>' : '>',
].join('');
}
handleInfo(params) {
this.verto.callbacks.onInfo(params);
}
handleDisplay(displayName, displayNumber) {
if (displayName !== undefined) {
this.params.remote_caller_id_name = displayName;
}
if (displayNumber !== undefined) {
this.params.remote_caller_id_number = displayNumber;
}
this.verto.callbacks.onDisplay({
name: displayName,
number: displayNumber,
});
}
handleMedia(sdp) {
if (this.state.val >= utils_1.ENUM.state.early.val) {
return;
}
this.gotEarly = true;
this.rtc.answer(sdp, () => {
this.setState(utils_1.ENUM.state.early);
if (this.gotAnswer) {
this.setState(utils_1.ENUM.state.active);
}
}, reason => Promise.reject(reason).then(error => {
console.log('Error on answering early', error);
this.hangup();
return Promise.reject();
}));
}
toString() {
const { callID: id, destination_number: destination, caller_id_name: callerName, caller_id_number: callerNumber, remote_caller_id_name: calleeName, remote_caller_id_number: calleeNumber, } = this.params;
const attributes = [
{ key: 'id', value: id },
{ key: 'destination', value: destination },
{ key: 'from', value: `${callerName} (${callerNumber})` },
{ key: 'to', value: `${calleeName} (${calleeNumber})` },
]
.map(({ key, value }) => `${key}: "${value}"`)
.join(', ');
return `Call<${attributes}>`;
}
}
exports.default = Call;