ringcentral-call-control
Version:
[](https://coveralls.io/github/ringcentral/ringcentral-call-control-js?branch=master) [ • 15.7 kB
text/typescript
import { EventEmitter } from 'events';
import { SDK as RingCentralSDK } from '@ringcentral/sdk';
import { formatParty } from './formatParty';
import { USER_AGENT } from './userAgent';
export enum Direction {
inbound = 'Inbound',
outbound = 'Outbound',
}
export enum PartyStatusCode {
setup = 'Setup',
proceeding = 'Proceeding',
answered = 'Answered',
disconnected = 'Disconnected',
gone = 'Gone',
parked = 'Parked',
hold = 'Hold',
voicemail = 'Voicemail',
faxReceive = 'FaxReceive',
voicemailScreening = 'VoiceMailScreening',
}
export interface PartyToFrom {
phoneNumber?: string;
name?: string;
extensionId?: string;
extensionNumber?: string;
}
export interface PartyStatus {
code?: PartyStatusCode;
}
export interface Recording {
id: string;
active: boolean;
}
export interface Party {
id: string;
extensionId?: string;
accountId?: string;
direction: Direction;
to: PartyToFrom;
from: PartyToFrom;
status: PartyStatus;
missedCall: boolean;
standAlone: boolean;
muted: boolean;
conferenceRole?: 'Host' | 'Participant';
ringOutRole?: 'Initiator' | 'Target';
ringMeRole?: 'Initiator' | 'Target';
recordings?: Recording[];
}
export interface Origin {
type: 'Call' | 'RingOut' | 'RingMe' | 'Conference' | 'GreetingsRecording' |
'VerificationCall' | 'TestCall';
}
export interface SessionData {
id: string;
extensionId: string;
accountId: string;
parties: Party[];
sessionId: string;
creationTime: string;
voiceCallToken?: string;
origin: Origin;
}
export interface ForwardParams {
phoneNumber?: string;
extensionNumber?: string;
voicemail?: string;
}
export interface TransferParams extends ForwardParams {
parkOrbit?: string;
}
export interface FlipParams {
callFlipId: string;
}
export interface PartyParams {
muted?: boolean;
standAlone?: boolean;
}
export interface RecordParams {
id: string;
active: boolean;
}
export interface SuperviseParams {
mode: 'Listen';
deviceId: string;
extensionNumber: string;
}
export interface BringInParams {
partyId: string;
sessionId: string;
}
export interface AnswerParams {
deviceId: string;
}
export interface BridgeParams {
telephonySessionId: string;
partyId: string;
}
export interface IgnoreParams {
deviceId: string;
}
export enum ReplyWithPattern {
willCallYouBack = 'WillCallYouBack',
callMeBack = 'CallMeBack',
onMyWay = 'OnMyWay',
onTheOtherLine = 'OnTheOtherLine',
willCallYouBackLater = 'WillCallYouBackLater',
callMeBackLater = 'CallMeBackLater',
inAMeeting = 'InAMeeting',
onTheOtherLineNoCall = 'OnTheOtherLineNoCall'
}
export interface ReplyWithPatternParams {
pattern: ReplyWithPattern,
time?: number,
timeUnit?: 'Minute' | 'Hour' | 'Day',
}
export interface ReplyWithTextParams {
replyWithText?: string;
replyWithPattern?: ReplyWithPatternParams,
}
export interface PickUpParams {
deviceId: string;
}
export interface RemovePartyOptions {
keepConferenceAlive?: boolean;
}
function objectEqual(obj1: any, obj2: any) {
let equal = true;
if (!obj1 || !obj2) {
return false;
}
Object.keys(obj2).forEach((key) => {
if (obj2[key] === obj1[key]) {
return;
}
equal = false;
});
Object.keys(obj1).forEach((key) => {
if (obj1[key] === obj2[key]) {
return;
}
equal = false;
});
return equal;
}
function diffParty(oldParty: Party, updatedParty: Party) {
const diffs = [];
updatedParty && Object.keys(updatedParty).forEach((key) => {
if (updatedParty[key] === oldParty[key]) {
return;
}
if (typeof updatedParty[key] !== 'object') {
diffs.push({ key, value: updatedParty[key] });
return;
}
if (objectEqual(updatedParty[key], oldParty[key])) {
return;
}
diffs.push({ key, value: updatedParty[key] });
})
return diffs;
}
function diffParties(oldParties: Party[], updatedParties: Party[]) {
const oldMap = {};
oldParties.forEach((p) => {
oldMap[p.id] = p;
});
const diffs = [];
updatedParties.forEach((updatedParty) => {
if (!oldMap[updatedParty.id]) {
diffs.push({ type: 'new', party: updatedParty });
return;
}
const oldParty = oldMap[updatedParty.id];
const partyDiffs= diffParty(oldParty, updatedParty);
if (partyDiffs.length === 0) {
return;
}
diffs.push({ type: 'update', party: updatedParty, partyDiffs });
});
return diffs;
}
export class Session extends EventEmitter {
private _data: any;
private _sdk: RingCentralSDK;
private _accountLevel: boolean;
private _userAgent: string;
constructor(rawData: SessionData, sdk: RingCentralSDK, accountLevel: boolean, userAgent?: string) {
super();
this._data = { ...rawData };
this._sdk = sdk;
this._accountLevel = !!accountLevel;
this._userAgent = userAgent;
}
public onUpdated(data: SessionData) {
const partiesDiff = diffParties(this.parties, data.parties);
partiesDiff.forEach((diff) => {
if (diff.type === 'new') {
this._data.parties = [].concat(this.parties).concat(diff.party);
this.emit('status', { party: diff.party });
return;
}
if (diff.type === 'update') {
const oldPartyIndex = this.parties.findIndex(p => p.id === diff.party.id);
const parties = this.parties.slice(0);
parties[oldPartyIndex] = {
...parties[oldPartyIndex],
...diff.party,
}
this._data.parties = parties;
diff.partyDiffs.forEach((partyDiff) => {
this.emit(partyDiff.key, { party: diff.party });
});
return;
}
});
}
public restore(data: SessionData) {
this._data = data;
}
get data() {
return this._data || {};
}
get id() {
return this.data.id;
}
get accountId() {
return this.data.accountId;
}
get creationTime() {
return this.data.creationTime;
}
get extensionId() {
return this.data.extensionId;
}
get origin() {
return this.data.origin;
}
get parties() {
return this.data.parties || [];
}
get serverId() {
return this.data.serverId;
}
get sessionId() {
return this.data.sessionId;
}
get party() {
const extensionId = this.data.extensionId;
const accountId = this.data.accountId;
const parties = this.parties.filter(p => {
if (this._accountLevel) {
return p.accountId === accountId;
}
return p.extensionId === extensionId;
});
if (parties.length === 0) {
return;
}
if (parties.length === 1) {
return parties[0];
}
const activeParty = parties.find(p => p.status.code !== PartyStatusCode.disconnected);
if (activeParty) {
return activeParty;
}
return parties[parties.length - 1];
}
get otherParties() {
if (!this.party) {
return this.parties;
}
const partyId = this.party.id;
return this.parties.filter(p => p.id !== partyId);
}
get recordings() {
const party = this.party;
return (party && party.recordings) || [];
}
get voiceCallToken() {
return this.data.voiceCallToken;
}
toJSON() {
return this.data;
}
async reload() {
const response = await this._sdk.platform().get(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}`,
undefined,
this.requestOptions
);
const data = await response.json();
data.extensionId = this.data.extensionId;
data.accountId = this.data.accountId;
data.parties = data.parties.map(p => formatParty(p));
this._data = data;
}
async drop() {
try {
await this._sdk.platform().delete(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}`,
undefined,
this.requestOptions
);
} catch (e) {
if (e && e.response && e.response.status === 404) {
// Force drop session at client side
const disconnectedParty = {
...this.party,
status: {
...this.party.status,
code: PartyStatusCode.disconnected,
},
};
this.saveNewPartyData(disconnectedParty);
this.emit('status', { party: this.party });
return;
}
throw e;
}
}
private saveNewPartyData(rawParty) {
const newParty = formatParty(rawParty);
const newParties = this._data.parties.filter((p) => p.id !== newParty.id);
newParties.push(newParty);
this._data.parties = newParties;
}
async hold() {
const oldParty = this.party;
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${oldParty.id}/hold`,
undefined,
undefined,
this.requestOptions,
);
const newParty = await response.json();
this.saveNewPartyData(newParty);
this.emit('status', { party: this.party });
return this.party;
}
async unhold() {
const oldParty = this.party;
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${oldParty.id}/unhold`,
undefined,
undefined,
this.requestOptions,
);
const newParty = await response.json();
this.saveNewPartyData(newParty);
this.emit('status', { party: this.party });
return this.party;
}
async toVoicemail() {
await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/reject`,
undefined,
undefined,
this.requestOptions,
);
}
async ignore(params: IgnoreParams) {
await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/ignore`,
params,
undefined,
this.requestOptions,
);
}
async answer(params: AnswerParams) {
await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/answer`,
params,
undefined,
this.requestOptions,
);
}
async reply(params: ReplyWithTextParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/reply`,
params,
undefined,
this.requestOptions,
);
const rawParty = await response.json();
this.saveNewPartyData(rawParty);
this.emit('status', { party: this.party });
return this.party;
}
async forward(params: ForwardParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/forward`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async transfer(params: TransferParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/transfer`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async bridge(params: BridgeParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/bridge`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async park() {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/park`,
undefined,
undefined,
this.requestOptions,
);
return response.json();
}
// async pickup(params: PickUpParams) {
// const response = await this._sdk.platform().post(
// `/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/pickup`,
// params,
// );
// return response.json();
// }
// async transferToVoicemail() {
// const result = await this.forward({ voicemail: this._data.extensionId });
// return result;
// }
async flip(params: FlipParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/flip`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async updateParty(params: PartyParams) {
const response = await this._sdk.platform().send({
method: 'PATCH',
url: `/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}`,
query: undefined,
body: params,
userAgent: this.requestOptions.userAgent,
});
const rawParty = await response.json();
this.saveNewPartyData(rawParty);
return rawParty;
}
async mute() {
const result = await this.updateParty({ muted: true });
this.emit('muted', { party: result })
return result;
}
async unmute() {
const result = await this.updateParty({ muted: false });
this.emit('muted', { party: result })
return result;
}
async createRecord() {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/recordings`,
undefined,
undefined,
this.requestOptions,
);
const recording = await response.json();
const recordings = (this.party.recordings || []).filter(r => r.id !== recording.id);
recordings.push(recording);
this.party.recordings = recordings
this.emit('recordings', { party: this.party });
return recording;
}
async updateRecord(params: RecordParams) {
const response = await this._sdk.platform().send({
method: 'PATCH',
url: `/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${this.party.id}/recordings/${params.id}`,
query: undefined,
body: {
active: params.active,
},
userAgent: this.requestOptions.userAgent,
});
const recording = await response.json();
const recordings = (this.party.recordings || []).filter(r => r.id !== recording.id);
recordings.push(recording);
this.party.recordings = recordings
this.emit('recordings', { party: this.party });
return recording;
}
async pauseRecord(recordingId: string) {
const result = await this.updateRecord({ id: recordingId, active: false });
return result;
}
async resumeRecord(recordingId: string) {
const result = await this.updateRecord({ id: recordingId, active: true });
return result;
}
async supervise(params: SuperviseParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/supervise`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async bringInParty(params: BringInParams) {
const response = await this._sdk.platform().post(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/bring-in`,
params,
undefined,
this.requestOptions,
);
return response.json();
}
async removeParty(partyId: string, options?: RemovePartyOptions) {
const requestOptions: {
userAgent: string;
body?: RemovePartyOptions;
} = {
...this.requestOptions,
};
if (options) {
requestOptions.body = options;
}
return await this._sdk.platform().delete(
`/restapi/v1.0/account/~/telephony/sessions/${this._data.id}/parties/${partyId}`,
undefined,
requestOptions,
);
}
get requestOptions() {
return {
userAgent: this._userAgent ? `${this._userAgent} ${USER_AGENT}` : USER_AGENT,
};
}
}