• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ringcentral / ringcentral-js-widgets / 9987870733

18 Jul 2024 08:08AM UTC coverage: 63.06% (-0.007%) from 63.067%
9987870733

push

github

web-flow
chore(deps): bump es5-ext from 0.10.53 to 0.10.64 (#1750)

Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.53 to 0.10.64.
- [Release notes](https://github.com/medikoo/es5-ext/releases)
- [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.53...v0.10.64)

---
updated-dependencies:
- dependency-name: es5-ext
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

9783 of 17002 branches covered (57.54%)

Branch coverage included in aggregate %.

16602 of 24839 relevant lines covered (66.84%)

178532.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.4
/packages/ringcentral-integration/modules/ConferenceCall/ConferenceCall.ts
1
import {
2
  action,
3
  computed,
4
  RcModuleV2,
5
  state,
6
  track,
7
} from '@ringcentral-integration/core';
8
import { EventEmitter } from 'events';
9
import { filter, find, is, map, values } from 'ramda';
10

11
import callDirections from '../../enums/callDirections';
12
import calleeTypes from '../../enums/calleeTypes';
13
import { permissionsMessages } from '../../enums/permissionsMessages';
14
import { trackEvents } from '../../enums/trackEvents';
15
import type {
16
  NormalizedSession,
17
  WebphoneSession,
18
} from '../../interfaces/Webphone.interface';
19
import { Module } from '../../lib/di';
20
import { proxify } from '../../lib/proxy/proxify';
21
import { callingModes } from '../CallingSettings';
22
import sessionStatusEnum from '../Webphone/sessionStatus';
23
import { isConferenceSession, isRecording } from '../Webphone/webphoneHelper';
24

25
import type {
26
  Conference,
27
  ConferencesState,
28
  ConferenceState,
29
  Deps,
30
  LastCallInfo,
31
  MergingPair,
32
  Party,
33
  PartyState,
34
} from './ConferenceCall.interface';
35
import { conferenceCallErrors } from './conferenceCallErrors';
36
import {
37
  ascendSortParties,
38
  conferenceCallStatus,
39
  DEFAULT_TIMEOUT,
40
  DEFAULT_TTL,
41
  MAXIMUM_CAPACITY,
42
  mergeEvents,
43
  mergeParty,
44
  partyStatusCode,
45
} from './lib';
46

47
@Module({
48
  name: 'ConferenceCall',
49
  deps: [
50
    'Auth',
51
    'Alert',
52
    'Call',
53
    'CallingSettings',
54
    'ConnectivityMonitor',
55
    'Client',
56
    'AppFeatures',
57
    { dep: 'ContactMatcher', optional: true },
58
    { dep: 'Webphone', optional: true },
59
    { dep: 'AvailabilityMonitor', optional: true },
60
    { dep: 'ConferenceCallOptions', optional: true },
61
  ],
62
})
63
export class ConferenceCall extends RcModuleV2<Deps> {
64
  _eventEmitter = new EventEmitter();
342✔
65
  private _timers: {
66
    [key: string]: number;
67
  } = {};
342✔
68

69
  // @ts-expect-error TS(2564): Property '_fromSessionId' has no initializer and i... Remove this comment to see the full error message
70
  private _fromSessionId: string;
71
  private _ttl: number = DEFAULT_TTL;
342✔
72
  private _timeout: number =
73
    this._deps.conferenceCallOptions?.timeout ?? DEFAULT_TIMEOUT;
342✔
74
  private _capacity: number =
75
    this._deps.conferenceCallOptions?.capacity ?? MAXIMUM_CAPACITY;
342✔
76
  protected _pulling: boolean =
77
    this._deps.conferenceCallOptions?.pulling ?? true;
342✔
78
  // @ts-expect-error TS(2564): Property '_lastCallInfo' has no initializer and is... Remove this comment to see the full error message
79
  private _lastCallInfo: {
80
    calleeType: string;
81
    extraNum: number;
82
    phoneNumber?: string;
83
    status: string;
84
    lastCallContact?: any;
85
    avatarUrl?: string;
86
    name?: string;
87
  };
88

89
  @state
90
  conferences: ConferencesState = {};
342✔
91

92
  @state
93
  conferenceCallStatus = conferenceCallStatus.idle;
342✔
94

95
  @state
96
  mergingPair: MergingPair = {};
342✔
97

98
  @state
99
  // @ts-expect-error TS(2322): Type 'null' is not assignable to type 'string'.
100
  currentConferenceId: string = null;
342✔
101

102
  @state
103
  isMerging = false;
342✔
104

105
  constructor(deps: Deps) {
106
    super({ deps });
342✔
107
  }
108

109
  @action
110
  setIsMerging(state: boolean) {
111
    this.isMerging = state;
335✔
112
  }
113

114
  @action
115
  setCurrentConferenceId(id: string) {
116
    this.currentConferenceId = id;
398✔
117
  }
118

119
  @action
120
  setMergingPair(val: MergingPair) {
121
    this.mergingPair = val;
348✔
122
  }
123

124
  @action
125
  setConferencesState(val: ConferencesState) {
126
    this.conferences = val;
71✔
127
  }
128

129
  @action
130
  toggleConferenceCallStatus() {
131
    if (this.conferenceCallStatus === conferenceCallStatus.idle) {
×
132
      this.conferenceCallStatus = conferenceCallStatus.requesting as any;
×
133
      return;
×
134
    }
135
    this.conferenceCallStatus = conferenceCallStatus.idle as any;
×
136
  }
137

138
  @action
139
  setConferenceCallStatus(val: conferenceCallStatus) {
140
    this.conferenceCallStatus = val;
119✔
141
  }
142

143
  @proxify
144
  async updateAConference(conference: Conference, sessionId: string) {
145
    this.setConferencesState({
35✔
146
      ...this.conferences,
147
      [conference.id]: {
148
        conference,
149
        sessionId,
150
        profiles:
151
          (this.conferences[conference.id] &&
70✔
152
            this.conferences[conference.id].profiles) ||
153
          [],
154
      } as ConferenceState,
155
    });
156
  }
157

158
  bringInParty(
159
    conference: Conference,
160
    sessionId: string,
161
    partyProfile: PartyState,
162
  ) {
163
    this.setConferencesState({
15✔
164
      ...this.conferences,
165
      [conference.id]: {
166
        conference,
167
        sessionId,
168
        profiles: [...this.conferences[conference.id].profiles, partyProfile],
169
      },
170
    });
171
  }
172

173
  @action
174
  removeConference(id: string) {
175
    delete this.conferences[id];
1✔
176
  }
177

178
  isConferenceSession(sessionId: string) {
179
    // only can be used after webphone._onCallStartFunc
180
    let res = !!this.findConferenceWithSession(sessionId);
3,009✔
181

182
    if (this.isMerging && !res) {
3,009✔
183
      const session = find(
165✔
184
        (session) => session.id === sessionId,
227✔
185
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
186
        this._deps.webphone.sessions,
187
      );
188

189
      res = isConferenceSession(session);
165✔
190
    }
191

192
    return res;
3,009✔
193
  }
194

195
  findConferenceWithSession(sessionId: string) {
196
    return find((c) => c.sessionId === sessionId, values(this.conferences));
3,009✔
197
  }
198

199
  @proxify
200
  async updateConferenceStatus(id: string) {
201
    this.setConferenceCallStatus(conferenceCallStatus.requesting);
35✔
202
    try {
35✔
203
      const rawResponse = await this._deps.client.service
35✔
204
        .platform()
205
        .get(`/restapi/v1.0/account/~/telephony/sessions/${id}`);
206
      const response = await rawResponse.json();
28✔
207
      const storedConference = this.conferences[response.id];
28✔
208
      const conference = { ...storedConference.conference };
28✔
209
      conference.parties =
28✔
210
        // if BE session hasn't been updated
211
        conference.parties.length > response.parties.length
28!
212
          ? mergeParty(response.parties, conference.parties)
213
          : response.parties;
214
      const { sessionId } = storedConference;
28✔
215
      this.updateAConference(conference, sessionId);
28✔
216
    } finally {
217
      this.setConferenceCallStatus(conferenceCallStatus.idle);
35✔
218
      // eslint-disable-next-line no-unsafe-finally
219
      return this.conferences[id];
35✔
220
    }
221
  }
222

223
  @proxify
224
  async terminateConference(id: string) {
225
    if (this.conferenceCallStatus === conferenceCallStatus.requesting) {
×
226
      return;
×
227
    }
228
    this.setConferenceCallStatus(conferenceCallStatus.requesting);
×
229
    const conferenceData = this.conferences[id];
×
230
    try {
×
231
      if (!conferenceData) {
×
232
        return;
×
233
      }
234
      if (this._deps.webphone) {
×
235
        this._deps.webphone.hangup(conferenceData.sessionId);
×
236
      }
237
      await this._deps.client.service
×
238
        .platform()
239
        .delete(`/restapi/v1.0/account/~/telephony/sessions/${id}`);
240
      this.removeConference(id);
×
241
    } catch (e: any /** TODO: confirm with instanceof */) {
242
      if (
×
243
        !this._deps.availabilityMonitor ||
×
244
        !(await this._deps.availabilityMonitor.checkIfHAError(e))
245
      ) {
246
        this._deps.alert.warning({
×
247
          message: conferenceCallErrors.terminateConferenceFailed,
248
        });
249
      }
250
    } finally {
251
      this.setConferenceCallStatus(conferenceCallStatus.idle);
×
252
      // eslint-disable-next-line no-unsafe-finally
253
      return conferenceData;
×
254
    }
255
  }
256

257
  @proxify
258
  async bringInToConference(
259
    id: string,
260
    webphoneSession: NormalizedSession,
261
    propagate = false,
×
262
  ) {
263
    const conferenceState = this.conferences[id];
16✔
264
    if (
16✔
265
      !conferenceState ||
80✔
266
      !this.ready ||
267
      !webphoneSession ||
268
      this.isOverload(id) ||
269
      !this._deps.connectivityMonitor.connectivity
270
    ) {
271
      this._deps.alert.danger({
1✔
272
        message: conferenceCallErrors.modeError,
273
        ttl: 0,
274
      });
275
      return null;
1✔
276
    }
277
    const { sessionId } = conferenceState;
15✔
278
    this.setConferenceCallStatus(conferenceCallStatus.requesting);
15✔
279

280
    try {
15✔
281
      const partyProfile = this._getProfile(webphoneSession.id);
15✔
282
      await this._deps.client.service
15✔
283
        .platform()
284
        .post(
285
          `/restapi/v1.0/account/~/telephony/sessions/${id}/parties/bring-in`,
286
          webphoneSession.partyData,
287
        );
288
      const newConference = await this.updateConferenceStatus(id);
15✔
289
      const conference = newConference.conference;
15✔
290

291
      if (partyProfile) {
15!
292
        const conferenceState = this.conferences[id];
15✔
293
        const newParties = ascendSortParties(
15✔
294
          conferenceState.conference.parties,
295
        );
296
        // @ts-expect-error TS(2322): Type 'string | undefined' is not assignable to typ... Remove this comment to see the full error message
297
        (partyProfile as PartyState).id = newParties[newParties.length - 1].id;
15✔
298
        this.bringInParty(conference, sessionId, partyProfile as PartyState);
15✔
299
      }
300
      // else using BE push notification to get the new party data
301
      return id;
15✔
302
    } catch (e: any /** TODO: confirm with instanceof */) {
303
      if (!propagate) {
×
304
        return;
×
305
      }
306
      throw e;
×
307
    } finally {
308
      this.setConferenceCallStatus(conferenceCallStatus.idle);
15✔
309
    }
310
  }
311

312
  @proxify
313
  async removeFromConference(id: string, partyId: string) {
314
    this.setConferenceCallStatus(conferenceCallStatus.requesting);
2✔
315
    try {
2✔
316
      await this._deps.client.service
2✔
317
        .platform()
318
        .delete(
319
          `/restapi/v1.0/account/~/telephony/sessions/${id}/parties/${partyId}`,
320
        );
321
      await this.updateConferenceStatus(id);
2✔
322
    } catch (e: any /** TODO: confirm with instanceof */) {
323
      if (
×
324
        !this._deps.availabilityMonitor ||
×
325
        !(await this._deps.availabilityMonitor.checkIfHAError(e))
326
      ) {
327
        this._deps.alert.warning({
×
328
          message: conferenceCallErrors.removeFromConferenceFailed,
329
        });
330
      }
331
    } finally {
332
      this.setConferenceCallStatus(conferenceCallStatus.idle);
2✔
333
      // eslint-disable-next-line no-unsafe-finally
334
      return this.conferences[id];
2✔
335
    }
336
  }
337

338
  @proxify
339
  async makeConference(propagate = false) {
×
340
    if (!this.ready || !this._deps.connectivityMonitor.connectivity) {
7!
341
      this._deps.alert.danger({
×
342
        message: conferenceCallErrors.modeError,
343
        ttl: 0,
344
      });
345

346
      return null;
×
347
    }
348
    if (!this._checkPermission()) {
7!
349
      // TODO: investigate whether this could potentially show 2 notifications at once
350
      if (!propagate) {
×
351
        this._deps.alert.danger({
×
352
          message: permissionsMessages.insufficientPrivilege,
353
          ttl: 0,
354
        });
355
      }
356

357
      return null;
×
358
    }
359
    if (this._deps.callingSettings.callingMode !== callingModes.webphone) {
7!
360
      if (!propagate) {
×
361
        this._deps.alert.danger({
×
362
          message: conferenceCallErrors.modeError,
363
          ttl: 0,
364
        });
365
      }
366

367
      return null;
×
368
    }
369
    const conference = await this._makeConference(propagate);
7✔
370
    return conference;
7✔
371
  }
372

373
  /**
374
   * Merge calls to (or create) a conference.
375
   * @param {webphone.sessions} webphoneSessions
376
   * FIXME: dynamically construct this function during the construction
377
   * to avoid `webphone` criterias to improve performance ahead of time
378
   */
379
  @proxify
380
  async mergeToConference(webphoneSessions: NormalizedSession[] = []) {
×
381
    webphoneSessions = filter(
11✔
382
      (session) => !this.isConferenceSession(session.id),
20✔
383
      filter((session) => !!session, webphoneSessions),
20✔
384
    );
385

386
    if (!webphoneSessions.length) {
11!
387
      this._deps.alert.warning({
×
388
        message: conferenceCallErrors.bringInFailed,
389
      });
390
      return;
×
391
    }
392

393
    this.setIsMerging(true);
11✔
394

395
    /**
396
     * Because the concurrency behavior of the server,
397
     * we cannot sure the merging process is over when
398
     * the function's procedure has finished.
399
     */
400
    const sipInstances = map(
11✔
401
      (webphoneSession) =>
402
        this._deps.webphone?._sessions.get(webphoneSession.id),
18✔
403
      webphoneSessions,
404
    ).filter((x) => !!x);
18✔
405

406
    /**
407
     * HACK: we need to preserve the merging session in prevent the glitch of
408
     * the call control page.
409
     */
410
    const sessionIds = map((x) => x.id, webphoneSessions);
18✔
411
    this._deps.webphone?.setSessionCaching(sessionIds);
11✔
412

413
    const pSips = map((instance) => {
11✔
414
      const p = new Promise((resolve) => {
18✔
415
        instance!.on('terminated', () => {
18✔
416
          resolve(null);
16✔
417
        });
418
      });
419
      return p;
18✔
420
    }, sipInstances);
421

422
    await Promise.all([
11✔
423
      this._mergeToConference(webphoneSessions),
424
      ...pSips,
425
    ]).then(
426
      () => {
427
        this.setIsMerging(false);
10✔
428
        this.setMergingPair({});
10✔
429
        const conferenceState = Object.values(this.conferences)[0];
10✔
430

431
        this._eventEmitter.emit(mergeEvents.mergeSucceeded, conferenceState);
10✔
432
      },
433
      (e) => {
434
        console.error(e);
×
435
        const conferenceState = Object.values(this.conferences)[0];
×
436

437
        /**
438
         * if create conference successfully but failed to bring-in,
439
         *  then terminate the conference.
440
         */
441
        if (conferenceState && conferenceState.profiles.length < 1) {
×
442
          this.terminateConference(conferenceState.conference.id);
×
443
        }
444
        this._deps.alert.warning({
×
445
          message: conferenceCallErrors.bringInFailed,
446
        });
447
        this.setIsMerging(false);
×
448
      },
449
    );
450

451
    this._deps.webphone?.clearSessionCaching();
10✔
452
  }
453

454
  @proxify
455
  async setMergeParty({
456
    fromSessionId,
457
    toSessionId,
458
  }: {
459
    fromSessionId?: string;
460
    toSessionId?: string;
461
  }) {
462
    if (fromSessionId) {
24✔
463
      this.setMergingPair({ fromSessionId });
7✔
464
      return;
7✔
465
    }
466
    this.setMergingPair({
17✔
467
      ...this.mergingPair,
468
      ...(toSessionId && { toSessionId }),
34✔
469
    });
470
  }
471

472
  @proxify
473
  async closeMergingPair() {
474
    if (!this.mergingPair.fromSessionId) {
62!
475
      return;
62✔
476
    }
477
    this.setMergingPair({});
×
478
  }
479

480
  getOnlinePartyProfiles(id: string): (Party & PartyState)[] {
481
    const conferenceData = this.conferences[id];
71✔
482

483
    if (!conferenceData) {
71!
484
      // @ts-expect-error TS(2322): Type 'null' is not assignable to type '(Party & Pa... Remove this comment to see the full error message
485
      return null;
×
486
    }
487

488
    return ascendSortParties(conferenceData.conference.parties)
71✔
489
      .reduce((accum, party, idx) => {
490
        if (
161✔
491
          party?.status?.code.toLowerCase() !== partyStatusCode.disconnected
492
        ) {
493
          // 0 position is the host
494
          // @ts-expect-error TS(2322): Type 'number' is not assignable to type 'never'.
495
          accum.push({ idx, party });
158✔
496
        }
497
        return accum;
161✔
498
      }, [])
499
      .map(({ idx, party }) => ({
158✔
500
        // @ts-expect-error TS(2698): Spread types may only be created from object types... Remove this comment to see the full error message
501
        ...party,
502
        ...conferenceData.profiles[idx],
503
      }))
504
      .filter((i) => !!i);
158✔
505
  }
506

507
  getOnlineParties(id: string) {
508
    const conferenceData = this.conferences[id];
622✔
509
    if (!conferenceData) {
622!
510
      return null;
×
511
    }
512
    return filter(
622✔
513
      (p) => p?.status?.code?.toLowerCase() !== partyStatusCode.disconnected,
1,897✔
514
      conferenceData.conference.parties,
515
    );
516
  }
517

518
  countOnlineParties(id: string) {
519
    const res = this.getOnlineParties(id);
622✔
520
    // @ts-expect-error TS(2531): Object is possibly 'null'.
521
    return is(Array, res) ? res.length : null;
622!
522
  }
523

524
  isOverload(id: string) {
525
    // @ts-expect-error TS(2531): Object is possibly 'null'.
526
    return this.countOnlineParties(id) >= this._capacity;
622✔
527
  }
528

529
  @proxify
530
  async startPollingConferenceStatus(id: string) {
531
    if (this._timers[id] || !this._pulling) {
18✔
532
      return;
7✔
533
    }
534

535
    await this.updateConferenceStatus(id);
11✔
536
    this._timers[id] = window.setTimeout(async () => {
11✔
537
      await this.updateConferenceStatus(id);
7✔
538
      this.stopPollingConferenceStatus(id);
7✔
539
      if (this.conferences[id]) {
7✔
540
        this.startPollingConferenceStatus(id);
1✔
541
      }
542
    }, this._ttl);
543
  }
544

545
  stopPollingConferenceStatus(id: string) {
546
    clearTimeout(this._timers[id]);
18✔
547
    delete this._timers[id];
18✔
548
  }
549

550
  onMergeSuccess(func: (...args: any[]) => void) {
551
    this._eventEmitter.on(mergeEvents.mergeSucceeded, func);
342✔
552
  }
553

554
  removeMergeSuccess(func: (...args: any[]) => void) {
555
    this._eventEmitter.off(mergeEvents.mergeSucceeded, func);
×
556
  }
557

558
  @proxify
559
  async loadConference(conferenceId: string) {
560
    this.setCurrentConferenceId(conferenceId);
65✔
561
  }
562

563
  override onReset() {
564
    this.resetSuccess();
314✔
565
  }
566

567
  get hasPermission() {
568
    return this._deps.appFeatures.hasConferenceCall;
2,680✔
569
  }
570

571
  private _checkPermission() {
572
    if (!this.hasPermission) {
7!
573
      this._deps.alert.danger({
×
574
        message: permissionsMessages.insufficientPrivilege,
575
        ttl: 0,
576
      });
577
      return false;
×
578
    }
579
    return true;
7✔
580
  }
581

582
  @proxify
583
  private async _hookConference(
584
    conference: Conference,
585
    session: WebphoneSession,
586
  ) {
587
    ['accepted'].forEach((evt) =>
7✔
588
      session.on(evt as any, () =>
7✔
589
        this.startPollingConferenceStatus(conference.id),
7✔
590
      ),
591
    );
592
    ['terminated', 'failed', 'rejected'].forEach((evt) =>
7✔
593
      session.on(evt as any, () => {
21✔
594
        this.setConferenceCallStatus(conferenceCallStatus.idle);
1✔
595
        this.removeConference(conference.id);
1✔
596
        this.stopPollingConferenceStatus(conference.id);
1✔
597
      }),
598
    );
599
  }
600

601
  @proxify
602
  private async _mergeToConference(webphoneSessions: NormalizedSession[] = []) {
×
603
    const conferenceState = Object.values(this.conferences)[0];
17✔
604
    if (conferenceState) {
17✔
605
      const conferenceId = conferenceState.conference.id;
10✔
606
      this.stopPollingConferenceStatus(conferenceId);
10✔
607
      // for the sake of participants ordering, we can't concurrently bring in the participants
608
      for (const webphoneSession of webphoneSessions) {
10✔
609
        await this.bringInToConference(conferenceId, webphoneSession, true);
16✔
610
      }
611
      if (!this.conferences[conferenceId].profiles.length) {
10!
612
        throw new Error(
×
613
          'bring-in operations failed, not all intended parties were brought in',
614
        );
615
      }
616
      this.startPollingConferenceStatus(conferenceId);
10✔
617
      return conferenceId;
10✔
618
    }
619
    // @ts-expect-error TS(2339): Property 'id' does not exist on type 'Conference |... Remove this comment to see the full error message
620
    const { id } = await this.makeConference(true);
7✔
621
    let conferenceAccepted = false;
7✔
622
    await Promise.race([
7✔
623
      new Promise((resolve, reject) => {
624
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
625
        const sipSession = this._deps.webphone._sessions.get(
7✔
626
          this.conferences[id].sessionId,
627
        );
628
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
629
        sipSession.on('accepted', () => {
7✔
630
          conferenceAccepted = true;
7✔
631
          resolve(null);
7✔
632
        });
633
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
634
        sipSession.on('cancel', () => reject(new Error('conferencing cancel')));
7✔
635
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
636
        sipSession.on('failed', () => reject(new Error('conferencing failed')));
7✔
637
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
638
        sipSession.on('rejected', () =>
7✔
639
          reject(new Error('conferencing rejected')),
×
640
        );
641
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
642
        sipSession.on('terminated', () =>
7✔
643
          reject(new Error('conferencing terminated')),
1✔
644
        );
645
      }),
646
      new Promise((resolve, reject) => {
647
        setTimeout(
7✔
648
          () =>
649
            conferenceAccepted
×
650
              ? resolve(null)
651
              : reject(new Error('conferencing timeout')),
652
          this._timeout,
653
        );
654
      }),
655
    ]);
656
    await this._mergeToConference(webphoneSessions);
6✔
657
    return id;
6✔
658
  }
659

660
  @proxify
661
  private async _makeConference(propagate = false) {
×
662
    this.setConferenceCallStatus(conferenceCallStatus.requesting);
7✔
663
    try {
7✔
664
      // TODO: replace with SDK function chaining calls
665
      const rawResponse = await this._deps.client.service
7✔
666
        .platform()
667
        .post('/restapi/v1.0/account/~/telephony/conference', {});
668
      const response = await rawResponse.json();
7✔
669
      const conference = response.session as Conference;
7✔
670
      const phoneNumber = conference.voiceCallToken;
7✔
671
      // whether to mutate the session to mark the conference?
672
      const session = await this._deps.call.call({
7✔
673
        phoneNumber,
674
        isConference: true,
675
      } as any);
676
      if (
7!
677
        typeof session === 'object' &&
14✔
678
        Object.prototype.toString.call(session.on).toLowerCase() ===
679
          '[object function]'
680
      ) {
681
        this._hookConference(conference, session);
7✔
682
        this.updateAConference(conference, session.id);
7✔
683
      }
684
      return conference;
7✔
685
    } catch (e: any /** TODO: confirm with instanceof */) {
686
      console.error(e);
×
687
      if (
×
688
        !propagate ||
×
689
        !this._deps.availabilityMonitor ||
690
        !(await this._deps.availabilityMonitor.checkIfHAError(e))
691
      ) {
692
        this._deps.alert.warning({
×
693
          message: conferenceCallErrors.makeConferenceFailed,
694
        });
695

696
        return null;
×
697
      }
698
      // need to propagate to out side try...catch block
699
      throw e;
×
700
    } finally {
701
      this.setConferenceCallStatus(conferenceCallStatus.idle);
7✔
702
    }
703
  }
704

705
  // get profile the a webphone session
706
  private _getProfile(sessionId: string) {
707
    const session = find(
15✔
708
      (session) => session.id === sessionId,
32✔
709
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
710
      this._deps.webphone.sessions,
711
    );
712

713
    let rcId;
714
    let avatarUrl;
715
    let calleeType = calleeTypes.unknown;
15✔
716
    let partyName =
717
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
718
      session.direction === callDirections.outbound
15!
719
        ? // @ts-expect-error TS(2532): Object is possibly 'undefined'.
720
          session.toUserName
721
        : // @ts-expect-error TS(2532): Object is possibly 'undefined'.
722
          session.fromUserName;
723
    const partyNumber =
724
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
725
      session.direction === callDirections.outbound ? session.to : session.from;
15!
726

727
    // @ts-expect-error TS(2532): Object is possibly 'undefined'.
728
    let matchedContact = session.contactMatch;
15✔
729
    if (!matchedContact && this._deps.contactMatcher) {
15!
730
      const nameMatches = this._deps.contactMatcher.dataMapping[partyNumber];
15✔
731
      if (nameMatches && nameMatches.length) {
15✔
732
        matchedContact = nameMatches[0];
8✔
733
      }
734
    }
735

736
    if (matchedContact) {
15✔
737
      rcId = matchedContact.id;
8✔
738
      avatarUrl = (matchedContact as any).profileImageUrl;
8✔
739
      partyName = (matchedContact as any).name;
8✔
740
      calleeType = calleeTypes.contacts;
8✔
741
    }
742

743
    return {
15✔
744
      rcId,
745
      avatarUrl,
746
      partyName,
747
      partyNumber,
748
      calleeType,
749
    } as Omit<PartyState, 'id'>;
750
  }
751

752
  @proxify
753
  async parseMergingSessions({
754
    sessionId,
755
    sessionIdToMergeWith,
756
  }: {
757
    sessionId: string;
758
    sessionIdToMergeWith?: string;
759
  }) {
760
    const session = find(
13✔
761
      (x) => x.id === sessionId,
18✔
762
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
763
      this._deps.webphone.sessions,
764
    );
765

766
    const sessionToMergeWith = find(
13✔
767
      (x) => x.id === (sessionIdToMergeWith || this.mergingPair.fromSessionId),
22✔
768
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
769
      this._deps.webphone.sessions,
770
    );
771

772
    const webphoneSessions = sessionToMergeWith
13✔
773
      ? [sessionToMergeWith, session]
774
      : [session];
775

776
    for (const session of webphoneSessions) {
13✔
777
      // @ts-expect-error TS(2345): Argument of type 'NormalizedSession | undefined' i... Remove this comment to see the full error message
778
      if (!this.validateCallRecording(session)) {
24✔
779
        return null;
2✔
780
      }
781
    }
782

783
    const conferenceState = Object.values(this.conferences)[0];
11✔
784
    if (conferenceState) {
11✔
785
      const conferenceSession = find(
4✔
786
        (x) => x.id === conferenceState.sessionId,
8✔
787
        // @ts-expect-error TS(2532): Object is possibly 'undefined'.
788
        this._deps.webphone.sessions,
789
      );
790
      // @ts-expect-error TS(2345): Argument of type 'NormalizedSession | undefined' i... Remove this comment to see the full error message
791
      if (!this.validateCallRecording(conferenceSession)) {
4!
792
        return null;
×
793
      }
794
    }
795

796
    return {
11✔
797
      session,
798
      sessionToMergeWith,
799
    };
800
  }
801

802
  @proxify
803
  async mergeSessions({
804
    session,
805
    sessionToMergeWith,
806
  }: {
807
    session: NormalizedSession;
808
    sessionToMergeWith: NormalizedSession;
809
  }) {
810
    this.setMergeParty({
11✔
811
      toSessionId: session.id,
812
    });
813
    const webphoneSessions = sessionToMergeWith
11✔
814
      ? [sessionToMergeWith, session]
815
      : [session];
816
    await this.mergeToConference(webphoneSessions);
11✔
817

818
    const conferenceData = Object.values(this.conferences)[0];
10✔
819
    if (!conferenceData) {
10!
820
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
821
      await this._deps.webphone.resume(session.id);
×
822
      return null;
×
823
    }
824
    const currentConferenceSession = find(
10✔
825
      (x) => x.id === conferenceData.sessionId,
10✔
826
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
827
      this._deps.webphone.sessions,
828
    );
829
    // @ts-expect-error TS(2532): Object is possibly 'undefined'.
830
    const isCurrentConferenceOnHold = currentConferenceSession.isOnHold;
10✔
831

832
    if (isCurrentConferenceOnHold) {
10✔
833
      // @ts-expect-error TS(2532): Object is possibly 'undefined'.
834
      this._deps.webphone.resume(conferenceData.sessionId);
4✔
835
    }
836

837
    return conferenceData;
10✔
838
  }
839

840
  validateCallRecording(session: NormalizedSession) {
841
    if (isRecording(session)) {
61✔
842
      this._deps.alert.warning({
5✔
843
        message: conferenceCallErrors.callIsRecording,
844
      });
845
      return false;
5✔
846
    }
847
    return true;
56✔
848
  }
849

850
  @action
851
  resetSuccess() {
852
    this.setIsMerging(false);
314✔
853
    this.setMergingPair({});
314✔
854
    // @ts-expect-error TS(2345): Argument of type 'null' is not assignable to param... Remove this comment to see the full error message
855
    this.setCurrentConferenceId(null);
314✔
856
    this.conferenceCallStatus = conferenceCallStatus.idle as any;
314✔
857
    this.conferences = {};
314✔
858
  }
859

860
  /*
861
   * User action track dispatchs
862
   * */
863
  @track(trackEvents.clickHangupParticipantList)
864
  participantListClickHangupTrack() {
865
    //
866
  }
867

868
  @track(trackEvents.cancelRemoveRemoveParticipantsModal)
869
  removeParticipantClickCancelTrack() {
870
    //
871
  }
872

873
  @track(trackEvents.clickRemoveRemoveParticipantsModal)
874
  removeParticipantClickRemoveTrack() {
875
    //
876
  }
877

878
  override _shouldInit() {
879
    return this._deps.auth.loggedIn && super._shouldInit();
183,148✔
880
  }
881

882
  override _shouldReset() {
883
    return super._shouldReset() || (this.ready && !this._deps.auth.loggedIn);
182,802✔
884
  }
885

886
  @computed((that: ConferenceCall) => [
2,569✔
887
    // @ts-expect-error TS(2532): Object is possibly 'undefined'.
888
    that._deps.webphone.sessions,
889
    that.mergingPair.fromSessionId,
890
    that.partyProfiles,
891
  ])
892
  get lastCallInfo(): LastCallInfo {
893
    // @ts-expect-error TS(2339): Property 'sessions' does not exist on type 'Webpho... Remove this comment to see the full error message
894
    const { sessions } = this._deps.webphone;
802✔
895
    const {
896
      partyProfiles,
897
      mergingPair: { fromSessionId },
898
    } = this;
802✔
899
    if (!fromSessionId) {
802✔
900
      // @ts-expect-error TS(2322): Type 'null' is not assignable to type '{ calleeTyp... Remove this comment to see the full error message
901
      this._lastCallInfo = null;
721✔
902
      return this._lastCallInfo;
721✔
903
    }
904

905
    let sessionName;
906
    let sessionNumber;
907
    let sessionStatus;
908
    let matchedContact;
909
    const fromSession = sessions.find(
81✔
910
      (session: any) => session.id === fromSessionId,
207✔
911
    );
912
    if (fromSession) {
81!
913
      sessionName =
81✔
914
        fromSession.direction === callDirections.outbound
81!
915
          ? fromSession.toUserName
916
          : fromSession.fromUserName;
917
      sessionNumber =
81✔
918
        fromSession.direction === callDirections.outbound
81!
919
          ? fromSession.to
920
          : fromSession.from;
921
      sessionStatus = fromSession.callStatus;
81✔
922
      matchedContact = fromSession.contactMatch;
81✔
923
      if (!matchedContact && this._deps.contactMatcher) {
81!
924
        const nameMatches =
925
          this._deps.contactMatcher.dataMapping[sessionNumber];
81✔
926
        if (nameMatches && nameMatches.length) {
81✔
927
          matchedContact = nameMatches[0];
35✔
928
        }
929
      }
930
    }
931

932
    let lastCalleeType;
933
    if (fromSession) {
81!
934
      if (matchedContact) {
81✔
935
        lastCalleeType = calleeTypes.contacts;
35✔
936
      } else if (this.isConferenceSession(fromSession.id)) {
46✔
937
        lastCalleeType = calleeTypes.conference;
17✔
938
      } else {
939
        lastCalleeType = calleeTypes.unknown;
29✔
940
      }
941
    } else if (
×
942
      this._fromSessionId === fromSessionId &&
×
943
      this._lastCallInfo &&
944
      this._lastCallInfo.calleeType
945
    ) {
946
      this._lastCallInfo = {
×
947
        ...this._lastCallInfo,
948
        status: sessionStatusEnum.finished,
949
      };
950
      return this._lastCallInfo;
×
951
    } else {
952
      return {
×
953
        calleeType: calleeTypes.unknown,
954
      };
955
    }
956

957
    let partiesAvatarUrls = null;
81✔
958
    if (lastCalleeType === calleeTypes.conference) {
81✔
959
      partiesAvatarUrls = (partyProfiles || []).map(
17!
960
        (profile) => profile.avatarUrl,
34✔
961
      );
962
    }
963
    switch (lastCalleeType) {
81✔
964
      case calleeTypes.conference:
965
        this._lastCallInfo = {
17✔
966
          calleeType: calleeTypes.conference,
967
          // @ts-expect-error TS(2531): Object is possibly 'null'.
968
          avatarUrl: partiesAvatarUrls[0],
969
          // @ts-expect-error TS(2531): Object is possibly 'null'.
970
          extraNum: partiesAvatarUrls.length - 1,
971
          // @ts-expect-error TS(2322): Type 'null' is not assignable to type 'string | un... Remove this comment to see the full error message
972
          name: null,
973
          // @ts-expect-error TS(2322): Type 'null' is not assignable to type 'string | un... Remove this comment to see the full error message
974
          phoneNumber: null,
975
          status: sessionStatus,
976
          lastCallContact: null,
977
        };
978
        break;
17✔
979
      case calleeTypes.contacts:
980
        this._lastCallInfo = {
35✔
981
          calleeType: calleeTypes.contacts,
982
          avatarUrl: (matchedContact as any).profileImageUrl,
983
          name: (matchedContact as any).name,
984
          status: sessionStatus,
985
          phoneNumber: sessionNumber,
986
          extraNum: 0,
987
          lastCallContact: matchedContact,
988
        };
989
        break;
35✔
990
      default:
991
        this._lastCallInfo = {
29✔
992
          calleeType: calleeTypes.unknown,
993
          // @ts-expect-error TS(2322): Type 'null' is not assignable to type 'string | un... Remove this comment to see the full error message
994
          avatarUrl: null,
995
          name: sessionName,
996
          status: sessionStatus,
997
          phoneNumber: sessionNumber,
998
          extraNum: 0,
999
          lastCallContact: null,
1000
        };
1001
    }
1002

1003
    this._fromSessionId = fromSessionId;
81✔
1004
    return this._lastCallInfo;
81✔
1005
  }
1006

1007
  @computed((that: ConferenceCall) => [
2,764✔
1008
    that.currentConferenceId,
1009
    that.conferences,
1010
  ])
1011
  get partyProfiles() {
1012
    const { currentConferenceId, conferences } = this;
229✔
1013
    const conferenceData = conferences && conferences[currentConferenceId];
229✔
1014
    if (!conferenceData) {
229✔
1015
      return [];
158✔
1016
    }
1017
    return this.getOnlinePartyProfiles(currentConferenceId);
71✔
1018
  }
1019
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc