@coventia/softphone 0.2.0 → 0.3.0

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 (47) hide show
  1. package/dist/{call-DUVnDxsz.d.cts → call-CaPDWVk3.d.cts} +50 -1
  2. package/dist/{call-DUVnDxsz.d.ts → call-CaPDWVk3.d.ts} +50 -1
  3. package/dist/{chunk-YWI6EQYS.js → chunk-EYEDPD7Y.js} +108 -15
  4. package/dist/chunk-EYEDPD7Y.js.map +1 -0
  5. package/dist/{chunk-LMJXX4YM.js → chunk-UHNLEBBO.js} +4 -2
  6. package/dist/chunk-UHNLEBBO.js.map +1 -0
  7. package/dist/element/index.d.ts +76 -11
  8. package/dist/element/index.js +1411 -478
  9. package/dist/element/index.js.map +1 -1
  10. package/dist/{engine-EPCKf-Bl.d.ts → engine-BgBsIcEa.d.ts} +1 -1
  11. package/dist/{engine-CDjkrXHY.d.cts → engine-CU2rEo3g.d.cts} +1 -1
  12. package/dist/index.cjs +318 -32
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +5 -5
  15. package/dist/index.d.ts +5 -5
  16. package/dist/index.js +157 -19
  17. package/dist/index.js.map +1 -1
  18. package/dist/react/index.cjs +2 -0
  19. package/dist/react/index.cjs.map +1 -1
  20. package/dist/react/index.d.cts +2 -2
  21. package/dist/react/index.d.ts +2 -2
  22. package/dist/react/index.js +1 -1
  23. package/dist/{softphone-BtdgZWkm.d.ts → softphone-DEt0TtQw.d.ts} +26 -10
  24. package/dist/{softphone-DHG9MTNg.d.cts → softphone-dZMkW8V1.d.cts} +26 -10
  25. package/dist/softphone.css +192 -46
  26. package/dist/{sources-5L2R4HTV.js → sources-M3GYMS6V.js} +58 -3
  27. package/dist/sources-M3GYMS6V.js.map +1 -0
  28. package/dist/testing/index.d.cts +2 -2
  29. package/dist/testing/index.d.ts +2 -2
  30. package/dist/ui/index.cjs +1308 -366
  31. package/dist/ui/index.cjs.map +1 -1
  32. package/dist/ui/index.d.cts +40 -7
  33. package/dist/ui/index.d.ts +40 -7
  34. package/dist/ui/index.js +1308 -369
  35. package/dist/ui/index.js.map +1 -1
  36. package/dist/zadarma/index.cjs +106 -13
  37. package/dist/zadarma/index.cjs.map +1 -1
  38. package/dist/zadarma/index.d.cts +2 -2
  39. package/dist/zadarma/index.d.ts +2 -2
  40. package/dist/zadarma/index.js +1 -1
  41. package/dist/zadarma-engine-L5DEFXUS.js +5 -0
  42. package/dist/{zadarma-engine-4VOPIBXG.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-LMJXX4YM.js.map +0 -1
  45. package/dist/chunk-YWI6EQYS.js.map +0 -1
  46. package/dist/sources-5L2R4HTV.js.map +0 -1
  47. package/dist/zadarma-engine-4VOPIBXG.js +0 -5
@@ -789,6 +789,8 @@ async function loadJsSipFactory() {
789
789
  // src/transports/providers/zadarma.ts
790
790
  var PING = { localMs: 3e4, globalMs: 6e4 };
791
791
  var KEY_REFRESH_BUFFER_MS = 2 * 60 * 60 * 1e3;
792
+ var REBUILD_CONNECT_TIMEOUT_MS = 1e4;
793
+ var REBUILD_RETRY_MS = 3e4;
792
794
  var ZADARMA_QUIRKS = {
793
795
  // Zadarma's registrar REFUSES to register a WebRTC client with any other User-Agent — a clean 401
794
796
  // after a valid digest challenge, indistinguishable from a wrong password (§3.1). The old CRM
@@ -816,6 +818,12 @@ var ZadarmaPushSignal = class {
816
818
  #connected = false;
817
819
  #pingTimers = [];
818
820
  #refreshTimer;
821
+ /** The replacement socket of a rebuild in flight, not yet current. */
822
+ #candidate = null;
823
+ /** Abandons the rebuild in flight (closing its candidate). */
824
+ #abortRebuild = null;
825
+ #rebuilding = false;
826
+ #retryTimer;
819
827
  #unwatchBackground;
820
828
  #closed = false;
821
829
  #listeners = /* @__PURE__ */ new Set();
@@ -831,20 +839,35 @@ var ZadarmaPushSignal = class {
831
839
  if (this.#closed) return;
832
840
  const socket = this.deps.createPushSocket(buildPushUrl(key, sip));
833
841
  this.#socket = socket;
842
+ this.#attach(socket);
843
+ this.#armRefresh(keyExpiresAt);
844
+ }
845
+ /**
846
+ * Wires one socket's events. `this.#socket` is read when an event FIRES, not when it is wired: a socket that
847
+ * has been replaced must stop driving connectivity (our own `close()` of it emits `disconnect`, which would
848
+ * otherwise flip the state to offline right after a perfectly healthy swap). `update` is the exception — it is
849
+ * forwarded from any socket not yet closed, because a push seen twice is harmless (the engine ignores a
850
+ * repeated signal mid-call and reuses a UA that already exists) while one seen by neither is a missed call.
851
+ *
852
+ * With `hooks`, the socket is a rebuild's candidate: its first `connect` or failure goes to the rebuild instead
853
+ * of changing the connection state (it is not current yet).
854
+ */
855
+ #attach(socket, hooks) {
834
856
  socket.on("connect", () => {
857
+ if (hooks && socket === this.#candidate) return hooks.onConnect();
858
+ if (socket !== this.#socket) return;
835
859
  this.#setConnected(true);
836
860
  this.#startPings(socket);
837
861
  });
838
862
  socket.on("update", () => this.#emit());
839
- socket.on("disconnect", () => {
863
+ const lost = () => {
864
+ if (hooks && socket === this.#candidate) return hooks.onFailure();
865
+ if (socket !== this.#socket) return;
840
866
  this.#stopPings();
841
867
  this.#setConnected(false);
842
- });
843
- socket.on("connect_error", () => {
844
- this.#stopPings();
845
- this.#setConnected(false);
846
- });
847
- this.#armRefresh(keyExpiresAt);
868
+ };
869
+ socket.on("disconnect", lost);
870
+ socket.on("connect_error", lost);
848
871
  }
849
872
  /**
850
873
  * Presence pings — **the reason an idle tab keeps receiving calls**.
@@ -883,22 +906,92 @@ var ZadarmaPushSignal = class {
883
906
  if (!Number.isFinite(ms) || ms <= 0) return;
884
907
  this.#refreshTimer = setTimeout(() => void this.#reopen(), ms);
885
908
  }
909
+ /**
910
+ * Replaces the push socket with a fresh one — MAKE-BEFORE-BREAK (presence-client-reporting.md §4.1, T8).
911
+ *
912
+ * Why it must happen at all: Zadarma's gateway decides the extension is alive from the raw Engine.IO ping, which
913
+ * a hidden tab's throttled timers silently stop sending, so while the tab is hidden the socket is rebuilt every
914
+ * 20 s (`push-socket-reliability.md` §5.3), and once before the key expires. Why it must not leave a gap: the
915
+ * old close-then-reopen reported `connected = false` for the ~0.5 s in between, which took the softphone
916
+ * `offline`, tore down the presence socket and left Zadarma with no socket to push an incoming call to.
917
+ *
918
+ * Now the OLD socket stays up and stays current until the NEW one has actually connected; only then do we swap,
919
+ * move the pings and close the old one. A healthy rebuild never changes `connected`. A failed one — the key fetch
920
+ * throws, the socket errors, or it has not connected within `REBUILD_CONNECT_TIMEOUT_MS` — keeps the old socket
921
+ * and retries, instead of leaving the tab disconnected. Zadarma does not evict the older of two sockets for one
922
+ * sip (multi-tab-leader-election.md §1), so the overlap is safe.
923
+ */
886
924
  async #reopen() {
887
- if (this.#closed) return;
888
- this.#stopPings();
889
- this.#socket?.close();
890
- this.#socket = null;
891
- this.#setConnected(false);
925
+ if (this.#closed || this.#rebuilding) return;
926
+ if (!this.#socket) {
927
+ try {
928
+ await this.#open();
929
+ } catch {
930
+ }
931
+ return;
932
+ }
933
+ this.#rebuilding = true;
892
934
  try {
893
- await this.#open();
935
+ const { key, sip, keyExpiresAt } = await this.deps.fetchKey();
936
+ if (this.#closed) return;
937
+ const swapped = await new Promise((resolve) => {
938
+ const socket = this.deps.createPushSocket(buildPushUrl(key, sip));
939
+ this.#candidate = socket;
940
+ let settled = false;
941
+ const finish = (ok) => {
942
+ if (settled) return;
943
+ settled = true;
944
+ clearTimeout(timer);
945
+ this.#abortRebuild = null;
946
+ if (ok && socket.connected) {
947
+ this.#swapIn(socket, keyExpiresAt);
948
+ } else {
949
+ this.#candidate = null;
950
+ socket.close();
951
+ }
952
+ resolve(ok && socket.connected);
953
+ };
954
+ const timer = setTimeout(() => finish(false), REBUILD_CONNECT_TIMEOUT_MS);
955
+ this.#abortRebuild = () => finish(false);
956
+ this.#attach(socket, { onConnect: () => finish(true), onFailure: () => finish(false) });
957
+ });
958
+ if (!swapped) this.#armRetry();
894
959
  } catch {
960
+ this.#armRetry();
961
+ } finally {
962
+ this.#rebuilding = false;
895
963
  }
896
964
  }
965
+ /**
966
+ * The moment of the swap — synchronous, called from the candidate's own `connect` event so nothing can run
967
+ * between "the new socket is up" and "it is the current one". Order matters: `#socket` moves first, so the old
968
+ * socket's `disconnect` (our own `close()` of it, just below) is ignored by `#attach`.
969
+ */
970
+ #swapIn(socket, keyExpiresAt) {
971
+ const previous = this.#socket;
972
+ this.#socket = socket;
973
+ this.#candidate = null;
974
+ clearTimeout(this.#retryTimer);
975
+ this.#retryTimer = void 0;
976
+ this.#stopPings();
977
+ this.#startPings(socket);
978
+ this.#setConnected(true);
979
+ previous?.close();
980
+ this.#armRefresh(keyExpiresAt);
981
+ }
982
+ #armRetry() {
983
+ if (this.#closed) return;
984
+ clearTimeout(this.#retryTimer);
985
+ this.#retryTimer = setTimeout(() => void this.#reopen(), REBUILD_RETRY_MS);
986
+ }
897
987
  disconnect() {
898
988
  this.#closed = true;
899
989
  this.#stopPings();
900
990
  clearTimeout(this.#refreshTimer);
901
991
  this.#refreshTimer = void 0;
992
+ clearTimeout(this.#retryTimer);
993
+ this.#retryTimer = void 0;
994
+ this.#abortRebuild?.();
902
995
  this.#unwatchBackground?.();
903
996
  this.#unwatchBackground = void 0;
904
997
  this.#socket?.close();