@coventia/softphone 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{call-DUVnDxsz.d.cts → call-VKq1I8yP.d.cts} +6 -0
  2. package/dist/{call-DUVnDxsz.d.ts → call-VKq1I8yP.d.ts} +6 -0
  3. package/dist/{chunk-YWI6EQYS.js → chunk-EYEDPD7Y.js} +108 -15
  4. package/dist/chunk-EYEDPD7Y.js.map +1 -0
  5. package/dist/element/index.d.ts +8 -2
  6. package/dist/element/index.js +334 -249
  7. package/dist/element/index.js.map +1 -1
  8. package/dist/{engine-CDjkrXHY.d.cts → engine-BlQBo2Ys.d.cts} +1 -1
  9. package/dist/{engine-EPCKf-Bl.d.ts → engine-C0BOkIcW.d.ts} +1 -1
  10. package/dist/index.cjs +199 -23
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +5 -5
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.js +90 -11
  15. package/dist/index.js.map +1 -1
  16. package/dist/react/index.d.cts +2 -2
  17. package/dist/react/index.d.ts +2 -2
  18. package/dist/{softphone-BtdgZWkm.d.ts → softphone-Cyq3hiIM.d.ts} +1 -1
  19. package/dist/{softphone-DHG9MTNg.d.cts → softphone-DlGV12Xp.d.cts} +1 -1
  20. package/dist/softphone.css +82 -34
  21. package/dist/{sources-5L2R4HTV.js → sources-IRKNQPWW.js} +6 -2
  22. package/dist/sources-IRKNQPWW.js.map +1 -0
  23. package/dist/testing/index.d.cts +2 -2
  24. package/dist/testing/index.d.ts +2 -2
  25. package/dist/ui/index.cjs +332 -247
  26. package/dist/ui/index.cjs.map +1 -1
  27. package/dist/ui/index.d.cts +17 -6
  28. package/dist/ui/index.d.ts +17 -6
  29. package/dist/ui/index.js +332 -247
  30. package/dist/ui/index.js.map +1 -1
  31. package/dist/zadarma/index.cjs +106 -13
  32. package/dist/zadarma/index.cjs.map +1 -1
  33. package/dist/zadarma/index.d.cts +2 -2
  34. package/dist/zadarma/index.d.ts +2 -2
  35. package/dist/zadarma/index.js +1 -1
  36. package/dist/zadarma-engine-L5DEFXUS.js +5 -0
  37. package/dist/{zadarma-engine-4VOPIBXG.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-YWI6EQYS.js.map +0 -1
  40. package/dist/sources-5L2R4HTV.js.map +0 -1
  41. package/dist/zadarma-engine-4VOPIBXG.js +0 -5
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { S as Softphone } from './softphone-DHG9MTNg.cjs';
2
- export { A as AudioDevices } from './softphone-DHG9MTNg.cjs';
3
- import { S as SoftphoneConfig } from './call-DUVnDxsz.cjs';
4
- export { C as CALL_TRANSITIONS, a as Call, b as CallDirection, c as CallEvent, d as CallEventMap, e as CallOptions, f as CallState, g as Contact, E as ERROR_CATALOGUE, F as FatalErrorCode, h as FatalSoftphoneError, H as HangupReason, i as HoldOriginator, I as INITIAL_STATES, L as LogLevel, N as NonFatalErrorCode, j as NonFatalSoftphoneError, R as RemoteParty, k as SoftphoneError, l as SoftphoneErrorCode, m as SoftphoneEvent, n as SoftphoneEventMap, o as SoftphoneState, T as TokenEndpointOptions, p as TokenSource, q as TypedEmitter, V as VoiceToken, r as canTransition, s as isFatal, t as isTerminal } from './call-DUVnDxsz.cjs';
1
+ import { S as Softphone } from './softphone-DlGV12Xp.cjs';
2
+ export { A as AudioDevices } from './softphone-DlGV12Xp.cjs';
3
+ import { S as SoftphoneConfig } from './call-VKq1I8yP.cjs';
4
+ export { C as CALL_TRANSITIONS, a as Call, b as CallDirection, c as CallEvent, d as CallEventMap, e as CallOptions, f as CallState, g as Contact, E as ERROR_CATALOGUE, F as FatalErrorCode, h as FatalSoftphoneError, H as HangupReason, i as HoldOriginator, I as INITIAL_STATES, L as LogLevel, N as NonFatalErrorCode, j as NonFatalSoftphoneError, R as RemoteParty, k as SoftphoneError, l as SoftphoneErrorCode, m as SoftphoneEvent, n as SoftphoneEventMap, o as SoftphoneState, T as TokenEndpointOptions, p as TokenSource, q as TypedEmitter, V as VoiceToken, r as canTransition, s as isFatal, t as isTerminal } from './call-VKq1I8yP.cjs';
5
5
 
6
6
  /**
7
7
  * Construct a softphone.
@@ -57,6 +57,6 @@ declare function isWebRtcSupported(): boolean;
57
57
  * - `MockProvider` — lives at @coventia/softphone/testing, so it never lands in a production bundle.
58
58
  */
59
59
 
60
- declare const VERSION = "0.2.0";
60
+ declare const VERSION = "0.2.1";
61
61
 
62
62
  export { Softphone, SoftphoneConfig, VERSION, createSoftphone, isBrowser, isWebRtcSupported };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { S as Softphone } from './softphone-BtdgZWkm.js';
2
- export { A as AudioDevices } from './softphone-BtdgZWkm.js';
3
- import { S as SoftphoneConfig } from './call-DUVnDxsz.js';
4
- export { C as CALL_TRANSITIONS, a as Call, b as CallDirection, c as CallEvent, d as CallEventMap, e as CallOptions, f as CallState, g as Contact, E as ERROR_CATALOGUE, F as FatalErrorCode, h as FatalSoftphoneError, H as HangupReason, i as HoldOriginator, I as INITIAL_STATES, L as LogLevel, N as NonFatalErrorCode, j as NonFatalSoftphoneError, R as RemoteParty, k as SoftphoneError, l as SoftphoneErrorCode, m as SoftphoneEvent, n as SoftphoneEventMap, o as SoftphoneState, T as TokenEndpointOptions, p as TokenSource, q as TypedEmitter, V as VoiceToken, r as canTransition, s as isFatal, t as isTerminal } from './call-DUVnDxsz.js';
1
+ import { S as Softphone } from './softphone-Cyq3hiIM.js';
2
+ export { A as AudioDevices } from './softphone-Cyq3hiIM.js';
3
+ import { S as SoftphoneConfig } from './call-VKq1I8yP.js';
4
+ export { C as CALL_TRANSITIONS, a as Call, b as CallDirection, c as CallEvent, d as CallEventMap, e as CallOptions, f as CallState, g as Contact, E as ERROR_CATALOGUE, F as FatalErrorCode, h as FatalSoftphoneError, H as HangupReason, i as HoldOriginator, I as INITIAL_STATES, L as LogLevel, N as NonFatalErrorCode, j as NonFatalSoftphoneError, R as RemoteParty, k as SoftphoneError, l as SoftphoneErrorCode, m as SoftphoneEvent, n as SoftphoneEventMap, o as SoftphoneState, T as TokenEndpointOptions, p as TokenSource, q as TypedEmitter, V as VoiceToken, r as canTransition, s as isFatal, t as isTerminal } from './call-VKq1I8yP.js';
5
5
 
6
6
  /**
7
7
  * Construct a softphone.
@@ -57,6 +57,6 @@ declare function isWebRtcSupported(): boolean;
57
57
  * - `MockProvider` — lives at @coventia/softphone/testing, so it never lands in a production bundle.
58
58
  */
59
59
 
60
- declare const VERSION = "0.2.0";
60
+ declare const VERSION = "0.2.1";
61
61
 
62
62
  export { Softphone, SoftphoneConfig, VERSION, createSoftphone, isBrowser, isWebRtcSupported };
package/dist/index.js CHANGED
@@ -75,6 +75,9 @@ function canTransition(from, to) {
75
75
  function isTerminal(state) {
76
76
  return CALL_TRANSITIONS[state].length === 0;
77
77
  }
78
+ function isOnCallState(state) {
79
+ return state !== "ringing" && !isTerminal(state);
80
+ }
78
81
 
79
82
  // src/core/call-impl.ts
80
83
  var CallImpl = class _CallImpl extends Emitter {
@@ -281,7 +284,7 @@ function coventiaBackend(config, tokenSource, fallbackBaseUrl) {
281
284
  }
282
285
  let pending = null;
283
286
  const load = () => {
284
- pending ??= import('./sources-5L2R4HTV.js').then(
287
+ pending ??= import('./sources-IRKNQPWW.js').then(
285
288
  (m) => m.coventiaSources({ ...config, baseUrl, tokenSource })
286
289
  );
287
290
  return pending;
@@ -426,13 +429,18 @@ function startPresenceSocket(params) {
426
429
  let agents = /* @__PURE__ */ new Map();
427
430
  let lastReportedOnCall = null;
428
431
  let connection = null;
429
- const stop = () => {
432
+ let byeOnArrival = false;
433
+ const stop = (options) => {
430
434
  if (stopped) return;
431
435
  stopped = true;
432
436
  if (reconnectTimer !== null) {
433
437
  cancelTimeout(reconnectTimer);
434
438
  reconnectTimer = null;
435
439
  }
440
+ if (options?.bye) {
441
+ byeOnArrival = true;
442
+ connection?.bye?.();
443
+ }
436
444
  connection?.close();
437
445
  connection = null;
438
446
  };
@@ -471,6 +479,7 @@ function startPresenceSocket(params) {
471
479
  return;
472
480
  }
473
481
  if (stopped) {
482
+ if (byeOnArrival) opened.bye?.();
474
483
  opened.close();
475
484
  return;
476
485
  }
@@ -491,6 +500,7 @@ function startPresenceSocket(params) {
491
500
 
492
501
  // src/core/softphone-impl.ts
493
502
  var liveInstance = false;
503
+ var PRESENCE_TEARDOWN_GRACE_MS = 1e4;
494
504
  var BrowserAudioDevices = class {
495
505
  inputs = [];
496
506
  outputs = [];
@@ -565,6 +575,8 @@ var SoftphoneImpl = class extends Emitter {
565
575
  * rather than by any one call.
566
576
  */
567
577
  #presenceWatch = null;
578
+ /** A pending state-driven teardown of the watch — see `PRESENCE_TEARDOWN_GRACE_MS`. */
579
+ #presenceTeardownTimer = null;
568
580
  #presenceAgents = [];
569
581
  /**
570
582
  * `tabLock` defaults to the real Web Locks implementation — not part of `SoftphoneConfig` (an
@@ -624,6 +636,7 @@ var SoftphoneImpl = class extends Emitter {
624
636
  }
625
637
  async stop() {
626
638
  if (!this.#startedHere) return;
639
+ this.#sayGoodbye();
627
640
  this.#tabLock.release();
628
641
  if (this.#isLeader) {
629
642
  await this.#engine.stop();
@@ -639,8 +652,7 @@ var SoftphoneImpl = class extends Emitter {
639
652
  this.#destroyed = true;
640
653
  for (const stopWatch of this.#activeWatches) stopWatch();
641
654
  this.#activeWatches.clear();
642
- this.#presenceWatch?.stop();
643
- this.#presenceWatch = null;
655
+ this.#sayGoodbye();
644
656
  await this.stop().catch(() => {
645
657
  });
646
658
  this.#setState("destroyed");
@@ -675,11 +687,15 @@ var SoftphoneImpl = class extends Emitter {
675
687
  (target, callerNumber) => this.#onTransfer(target, callerNumber)
676
688
  );
677
689
  this.#activeCall = call;
690
+ const offState = call.on("stateChanged" /* StateChanged */, () => this.#syncCallState());
691
+ this.#syncCallState();
678
692
  const off = call.on("ended" /* Ended */, () => {
679
693
  off();
694
+ offState();
680
695
  if (this.#activeCall === call) {
681
696
  this.#activeCall = null;
682
697
  this.emit("activeCallChanged" /* ActiveCallChanged */, { call: null });
698
+ this.#syncCallState();
683
699
  }
684
700
  });
685
701
  if (this.#resolveContact) {
@@ -699,6 +715,21 @@ var SoftphoneImpl = class extends Emitter {
699
715
  this.emit("stateChanged" /* StateChanged */, { state, previous });
700
716
  this.#syncPresenceWatch();
701
717
  }
718
+ /**
719
+ * Whether this softphone is on a call right now, as presence means it (`isOnCallState`). There is a single
720
+ * active-call slot, and a consult leg is a child of that call, so the parent staying alive covers a consult.
721
+ */
722
+ #isOnCall() {
723
+ return this.#activeCall !== null && isOnCallState(this.#activeCall.state);
724
+ }
725
+ /**
726
+ * Reports the current on-a-call value to the backend. The watch de-duplicates (it only sends on change) and
727
+ * remembers the latest value to replay after a reconnect, so this can be called on every transition without
728
+ * ceremony. A no-op while there is no presence watch (not online, not the leader, no backend configured).
729
+ */
730
+ #syncCallState() {
731
+ this.#presenceWatch?.reportCallState(this.#isOnCall());
732
+ }
702
733
  #setLeader(isLeader) {
703
734
  if (isLeader === this.#isLeader) return;
704
735
  this.#isLeader = isLeader;
@@ -717,9 +748,21 @@ var SoftphoneImpl = class extends Emitter {
717
748
  * This still passes `getIsLeader: () => this.#isLeader` for that narrower, defensive recheck; the
718
749
  * BROAD online/leader gate — whether to be connected AT ALL — is owned entirely here, the same
719
750
  * split `#onTransfer` already has with `startTransferWatch`.
751
+ *
752
+ * One exception to "tear down the moment `online` is lost": a leader that is merely `offline` or
753
+ * `reconnecting` keeps its watch for `PRESENCE_TEARDOWN_GRACE_MS` — a blip shorter than that never touches
754
+ * the presence socket at all. Anything else (stopped, starting, destroyed, or no longer the leader) still
755
+ * tears it down at once: those are not blips, and a follower must never hold a connection.
720
756
  */
721
757
  #syncPresenceWatch() {
722
- const shouldRun = Boolean(this.sources) && this.#state === "online" && this.#isLeader;
758
+ const eligible = Boolean(this.sources) && this.#isLeader;
759
+ const shouldRun = eligible && this.#state === "online";
760
+ const blip = eligible && (this.#state === "offline" || this.#state === "reconnecting");
761
+ if (!blip) this.#cancelPresenceTeardown();
762
+ if (blip && this.#presenceWatch !== null) {
763
+ this.#schedulePresenceTeardown();
764
+ return;
765
+ }
723
766
  if (shouldRun === (this.#presenceWatch !== null)) return;
724
767
  if (shouldRun) {
725
768
  this.#presenceWatch = startPresenceSocket({
@@ -730,13 +773,49 @@ var SoftphoneImpl = class extends Emitter {
730
773
  this.emit("presenceChanged" /* PresenceChanged */, { agents });
731
774
  }
732
775
  });
776
+ this.#syncCallState();
733
777
  } else {
734
- this.#presenceWatch?.stop();
735
- this.#presenceWatch = null;
736
- this.#presenceAgents = [];
737
- this.emit("presenceChanged" /* PresenceChanged */, { agents: [] });
778
+ this.#stopPresence();
738
779
  }
739
780
  }
781
+ /**
782
+ * Starts the grace period before an `offline` / `reconnecting` leader loses its presence socket. Idempotent
783
+ * on purpose: a second notification inside the same blip must NOT push the deadline back.
784
+ */
785
+ #schedulePresenceTeardown() {
786
+ if (this.#presenceTeardownTimer !== null) return;
787
+ this.#presenceTeardownTimer = setTimeout(() => {
788
+ this.#presenceTeardownTimer = null;
789
+ if (this.#state === "online" && this.#isLeader) return;
790
+ this.#stopPresence();
791
+ }, PRESENCE_TEARDOWN_GRACE_MS);
792
+ }
793
+ #cancelPresenceTeardown() {
794
+ if (this.#presenceTeardownTimer === null) return;
795
+ clearTimeout(this.#presenceTeardownTimer);
796
+ this.#presenceTeardownTimer = null;
797
+ }
798
+ /**
799
+ * Stops the presence watch and forgets its roster. A STATE-DRIVEN teardown (the softphone left `online`
800
+ * for good, or lost leadership) passes nothing and says no goodbye; only a deliberate stop passes `bye`.
801
+ * Any pending grace-period teardown is cancelled — there is nothing left for it to do.
802
+ */
803
+ #stopPresence(options) {
804
+ this.#cancelPresenceTeardown();
805
+ if (!this.#presenceWatch) return;
806
+ this.#presenceWatch.stop(options);
807
+ this.#presenceWatch = null;
808
+ this.#presenceAgents = [];
809
+ this.emit("presenceChanged" /* PresenceChanged */, { agents: [] });
810
+ }
811
+ /**
812
+ * A DELIBERATE stop (`stop()` / `destroy()`): say any call is over, then say goodbye, so the backend drops
813
+ * this phone at once instead of waiting out the close grace. Idempotent — a second call finds no watch.
814
+ */
815
+ #sayGoodbye() {
816
+ this.#presenceWatch?.reportCallState(false);
817
+ this.#stopPresence({ bye: true });
818
+ }
740
819
  /**
741
820
  * Phase A (transfer outcome relay, `docs/specs/transfer-outcome-relay.md`) — `CallImpl.transfer()`'s
742
821
  * internal seam calls this. No-ops entirely when no Coventia backend is configured (`sources` is
@@ -811,7 +890,7 @@ function defaultZadarmaEngine(config) {
811
890
  const stateChange = new LazyRelay();
812
891
  const error = new LazyRelay();
813
892
  function load() {
814
- pending ??= import('./zadarma-engine-4VOPIBXG.js').then(({ createZadarmaEngine }) => {
893
+ pending ??= import('./zadarma-engine-L5DEFXUS.js').then(({ createZadarmaEngine }) => {
815
894
  const engine = createZadarmaEngine(config);
816
895
  incoming.attach({ on: (l) => engine.onIncoming(l) });
817
896
  stateChange.attach({ on: (l) => engine.onStateChange(l) });
@@ -871,7 +950,7 @@ function isCallEngine(value) {
871
950
  }
872
951
 
873
952
  // src/index.ts
874
- var VERSION = "0.2.0";
953
+ var VERSION = "0.2.1";
875
954
 
876
955
  export { CALL_TRANSITIONS, INITIAL_STATES, VERSION, canTransition, createSoftphone, isTerminal };
877
956
  //# sourceMappingURL=index.js.map