@coventia/softphone 0.4.0 → 0.5.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 (57) hide show
  1. package/dist/{call-C6YpT6uR.d.cts → call-FqcvDg1t.d.cts} +105 -12
  2. package/dist/{call-C6YpT6uR.d.ts → call-FqcvDg1t.d.ts} +105 -12
  3. package/dist/{chunk-EYEDPD7Y.js → chunk-6RR6G35E.js} +92 -17
  4. package/dist/chunk-6RR6G35E.js.map +1 -0
  5. package/dist/{chunk-2ZSOGGPO.js → chunk-FRXHEHQA.js} +15 -2
  6. package/dist/chunk-FRXHEHQA.js.map +1 -0
  7. package/dist/{chunk-UHNLEBBO.js → chunk-I3RSC52R.js} +28 -3
  8. package/dist/chunk-I3RSC52R.js.map +1 -0
  9. package/dist/chunk-M76SAXXM.js +13 -0
  10. package/dist/chunk-M76SAXXM.js.map +1 -0
  11. package/dist/{chunk-7R6LOOAI.js → chunk-N4PLEYFJ.js} +4 -2
  12. package/dist/chunk-N4PLEYFJ.js.map +1 -0
  13. package/dist/element/index.d.ts +142 -6
  14. package/dist/element/index.js +138 -43
  15. package/dist/element/index.js.map +1 -1
  16. package/dist/{engine-DPft99Wj.d.ts → engine-CsNRD7B7.d.ts} +1 -1
  17. package/dist/{engine-CDdaSojx.d.cts → engine-CzOCrfY9.d.cts} +1 -1
  18. package/dist/index.cjs +579 -47
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +5 -5
  21. package/dist/index.d.ts +5 -5
  22. package/dist/index.js +447 -37
  23. package/dist/index.js.map +1 -1
  24. package/dist/react/index.cjs +26 -0
  25. package/dist/react/index.cjs.map +1 -1
  26. package/dist/react/index.d.cts +24 -5
  27. package/dist/react/index.d.ts +24 -5
  28. package/dist/react/index.js +2 -2
  29. package/dist/{softphone-CY5d2WWY.d.cts → softphone-By9sizlV.d.ts} +42 -3
  30. package/dist/{softphone-DXgmTyrX.d.ts → softphone-JZvJdzU3.d.cts} +42 -3
  31. package/dist/{sources-ONYQW733.js → sources-QL2LRZVH.js} +19 -2
  32. package/dist/sources-QL2LRZVH.js.map +1 -0
  33. package/dist/testing/index.cjs +13 -0
  34. package/dist/testing/index.cjs.map +1 -1
  35. package/dist/testing/index.d.cts +2 -2
  36. package/dist/testing/index.d.ts +2 -2
  37. package/dist/testing/index.js +1 -1
  38. package/dist/ui/index.cjs +92 -2
  39. package/dist/ui/index.cjs.map +1 -1
  40. package/dist/ui/index.d.cts +2 -2
  41. package/dist/ui/index.d.ts +2 -2
  42. package/dist/ui/index.js +69 -4
  43. package/dist/ui/index.js.map +1 -1
  44. package/dist/zadarma/index.cjs +111 -14
  45. package/dist/zadarma/index.cjs.map +1 -1
  46. package/dist/zadarma/index.d.cts +5 -6
  47. package/dist/zadarma/index.d.ts +5 -6
  48. package/dist/zadarma/index.js +3 -2
  49. package/dist/zadarma-engine-TIUE7PZ7.js +6 -0
  50. package/dist/{zadarma-engine-L5DEFXUS.js.map → zadarma-engine-TIUE7PZ7.js.map} +1 -1
  51. package/package.json +1 -1
  52. package/dist/chunk-2ZSOGGPO.js.map +0 -1
  53. package/dist/chunk-7R6LOOAI.js.map +0 -1
  54. package/dist/chunk-EYEDPD7Y.js.map +0 -1
  55. package/dist/chunk-UHNLEBBO.js.map +0 -1
  56. package/dist/sources-ONYQW733.js.map +0 -1
  57. package/dist/zadarma-engine-L5DEFXUS.js +0 -5
@@ -57,8 +57,44 @@ interface CallOptions {
57
57
  * Zadarma registers on-demand, per call, in response to a push (SPEC §3.2); a persistent UA is
58
58
  * ignored by its PBX. Leaking "registered" into the public API would force every future provider —
59
59
  * including our own SIP infrastructure, which registers persistently — to fake it.
60
+ *
61
+ * `standby` means started, and this browser's leader, but ANOTHER softphone of the same extension has
62
+ * the line — a tab in another browser, the desktop app, a partner page (`docs/specs/line-owner.md` §5).
63
+ * Its push socket is closed on purpose, so nothing here registers or rings: `line` says where the calls
64
+ * go, and `takeLine()` — or a `call()` — brings them here. A host that reads `online` as "ready"
65
+ * correctly reads this as not ready.
66
+ */
67
+ type SoftphoneState = 'stopped' | 'starting' | 'online' | 'standby' | 'offline' | 'reconnecting' | 'destroyed';
68
+ /**
69
+ * Where this extension's incoming calls ring, as far as this softphone knows. `here`: it has the line.
70
+ * `elsewhere`: another softphone of the same extension has it, and this one waits in `standby`. `unknown`:
71
+ * nobody has said — no presence source, a backend that predates the line owner, a presence socket that is
72
+ * down — and this softphone listens, exactly as before the line owner existed (§5.1 R7, fail-open).
73
+ */
74
+ type LineState = 'here' | 'elsewhere' | 'unknown';
75
+ /** Who has the line when it is `elsewhere`: the desktop app, or any other softphone (a browser tab). */
76
+ type LineLocation = 'desktop' | 'browser';
77
+ /**
78
+ * The last attempt to bring the line here — `takeLine()`, or a `call()` from `standby`, whoever made it:
79
+ * `claiming` while it is in flight, `refused` when the user is on a call where the line is, `failed` when
80
+ * nothing could decide. `idle` before any attempt, once the line comes here, and after a `stop()`.
60
81
  */
61
- type SoftphoneState = 'stopped' | 'starting' | 'online' | 'offline' | 'reconnecting' | 'destroyed';
82
+ type LineClaim = 'idle' | 'claiming' | 'refused' | 'failed';
83
+ /** The server's word on this softphone's line, as a `PresenceSource` hands it over (`PresenceCallbacks.onLine`). */
84
+ interface LineUpdate {
85
+ readonly state: 'here' | 'elsewhere';
86
+ /** Who has the line when it is `elsewhere`; `null` when it is here, or the server did not say. */
87
+ readonly where: LineLocation | null;
88
+ }
89
+ /** The answer to `PresenceConnection.claimLine()`. */
90
+ interface LineClaimResult {
91
+ readonly ok: boolean;
92
+ /**
93
+ * Why it was refused: `'on_call'` while the user is on a call (the line never moves during one). Any
94
+ * other value means nothing was decided. Absent when `ok`.
95
+ */
96
+ readonly reason?: string;
97
+ }
62
98
  interface ContactsPage {
63
99
  readonly items: readonly Contact[];
64
100
  /**
@@ -210,6 +246,18 @@ interface PresenceConnection {
210
246
  * own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
211
247
  */
212
248
  readonly bye?: () => void;
249
+ /**
250
+ * The line owner (`docs/specs/line-owner.md` §5.2): says which softphone this connection speaks for, so
251
+ * the server can tell it whether it has the line — through `PresenceCallbacks.onLine`, right after this
252
+ * and on every change. Sent as the FIRST message of every connection. Optional: a source without it never
253
+ * takes part, and its softphone listens exactly as before (fail-open), reading its line as `unknown`.
254
+ */
255
+ readonly hello?: (clientId: string) => void;
256
+ /**
257
+ * "Recibir aquí": asks for the line. Answered through `PresenceCallbacks.onClaimResult` — and, when it
258
+ * succeeds, by `onLine` saying `here` first. Optional, like `hello`.
259
+ */
260
+ readonly claimLine?: () => void;
213
261
  /**
214
262
  * Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
215
263
  * reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
@@ -229,6 +277,13 @@ interface PresenceCallbacks {
229
277
  * identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
230
278
  */
231
279
  readonly onClose: () => void;
280
+ /**
281
+ * The line, as the server says it — right after `PresenceConnection.hello`, and on every change after
282
+ * that. Optional, so callbacks written before the line owner existed still type-check.
283
+ */
284
+ readonly onLine?: (update: LineUpdate) => void;
285
+ /** The answer to `PresenceConnection.claimLine()`. Optional, like `onLine`. */
286
+ readonly onClaimResult?: (result: LineClaimResult) => void;
232
287
  }
233
288
  /**
234
289
  * Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
@@ -470,6 +525,16 @@ declare const ERROR_CATALOGUE: {
470
525
  readonly message: "The provider does not support this operation.";
471
526
  readonly solution: "Guard the UI on the operation before offering it.";
472
527
  };
528
+ readonly 'line/claim-refused': {
529
+ readonly fatal: false;
530
+ readonly message: "The line was not moved here: the user is on a call on the softphone that has it.";
531
+ readonly solution: "Nothing to fix. The line never moves during a call; take it again once that call has ended. Incoming calls keep ringing where they ring now.";
532
+ };
533
+ readonly 'line/unavailable': {
534
+ readonly fatal: false;
535
+ readonly message: "The line could not be taken: nothing decided where it is.";
536
+ readonly solution: "The presence connection is down, the backend predates the line owner, or it did not answer in time. Retry in a moment; incoming calls keep ringing where they ring now.";
537
+ };
473
538
  readonly 'provider/provision-failed': {
474
539
  readonly fatal: true;
475
540
  readonly message: "The provider could not issue SIP credentials.";
@@ -556,10 +621,25 @@ declare enum SoftphoneEvent {
556
621
  /**
557
622
  * `softphone.presence` changed — the live agent-presence list (Phase B,
558
623
  * `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
559
- * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
560
- * `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
624
+ * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being a
625
+ * started leader — `online`, or waiting in `standby` for the line; never fires at all otherwise,
626
+ * same reasoning as `TransferOutcome`.
627
+ */
628
+ PresenceChanged = "presenceChanged",
629
+ /**
630
+ * `softphone.line` / `lineWhere` changed (`docs/specs/line-owner.md` §5): this softphone got the line,
631
+ * lost it to another softphone of the same extension, or stopped knowing (`unknown` — it then listens,
632
+ * as before the line owner existed). A third axis beside `StateChanged` and `LeaderChanged`: losing the
633
+ * line is what takes the softphone to `standby`, but not at once while a call is in progress — the line
634
+ * can read `elsewhere` while the state is still `online`, until that call ends.
635
+ */
636
+ LineChanged = "lineChanged",
637
+ /**
638
+ * `softphone.lineClaim` changed: an attempt to bring the line here started, was refused, failed, or ended
639
+ * (the line came here, or the softphone stopped). Kept apart from `LineChanged`, which only ever means the
640
+ * line itself moved.
561
641
  */
562
- PresenceChanged = "presenceChanged"
642
+ LineClaimChanged = "lineClaimChanged"
563
643
  }
564
644
  interface SoftphoneEventMap {
565
645
  [SoftphoneEvent.StateChanged]: {
@@ -580,6 +660,13 @@ interface SoftphoneEventMap {
580
660
  [SoftphoneEvent.PresenceChanged]: {
581
661
  readonly agents: readonly PresenceEntry[];
582
662
  };
663
+ [SoftphoneEvent.LineChanged]: {
664
+ readonly line: LineState;
665
+ readonly where: LineLocation | null;
666
+ };
667
+ [SoftphoneEvent.LineClaimChanged]: {
668
+ readonly claim: LineClaim;
669
+ };
583
670
  }
584
671
  declare enum CallEvent {
585
672
  /** Any transition. The catch-all; the specific events below are conveniences. */
@@ -878,6 +965,39 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
878
965
  * "active in another tab" state for that duration (the `/ui` widget already does).
879
966
  */
880
967
  readonly isLeader: boolean;
968
+ /**
969
+ * Where this extension's incoming calls ring (`docs/specs/line-owner.md` §5) — the axis ACROSS browsers,
970
+ * devices and the desktop app that `isLeader` cannot see. Only one softphone of an extension listens:
971
+ * the first one started keeps the line, and the others wait in `standby` until it is free or the user
972
+ * takes it (`takeLine()`).
973
+ *
974
+ * `unknown` until the backend has said, and whenever it cannot (no presence source, a backend that
975
+ * predates the line owner, a presence socket that is down): the softphone then listens, exactly as
976
+ * before this existed — it never leaves nobody listening. Changes fire `SoftphoneEvent.LineChanged`.
977
+ */
978
+ readonly line: LineState;
979
+ /**
980
+ * Who has the line while it is `elsewhere` — the desktop app, or another softphone (a browser tab) —
981
+ * or `null`: the line is here, unknown, or the backend did not say.
982
+ */
983
+ readonly lineWhere: LineLocation | null;
984
+ /**
985
+ * How the last attempt to bring the line here went, whoever made it (`takeLine()`, or a `call()` from
986
+ * `standby`) — what the waiting screen shows under its title, so a refused click-to-call explains itself
987
+ * just like a refused "Recibir aquí". Changes fire `SoftphoneEvent.LineClaimChanged`.
988
+ */
989
+ readonly lineClaim: LineClaim;
990
+ /**
991
+ * "Recibir aquí": takes the line from whichever softphone has it, so incoming calls ring here. Resolves
992
+ * once the line IS here (at once when it already is); the softphone then leaves `standby` and starts
993
+ * listening on its own.
994
+ *
995
+ * Rejects with a non-fatal `SoftphoneError`: `line/claim-refused` while the user is on a call on the
996
+ * softphone that has the line (it never moves during a call — retry once it ends), or `line/unavailable`
997
+ * when nothing could decide (no presence connection, a backend without the line owner, no answer in
998
+ * time). Neither is emitted on `SoftphoneEvent.Error` — this promise is the answer.
999
+ */
1000
+ takeLine(): Promise<void>;
881
1001
  /**
882
1002
  * Start.
883
1003
  *
@@ -890,6 +1010,11 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
890
1010
  stop(): Promise<void>;
891
1011
  /** Idempotent. After this the instance is dead — construct a new one. */
892
1012
  destroy(): Promise<void>;
1013
+ /**
1014
+ * Dial. From `standby` this takes the line first (`takeLine()`), then dials — a call the host starts is
1015
+ * the user asking for the phone here — and rejects with that method's `line/*` error if the line cannot
1016
+ * be taken.
1017
+ */
893
1018
  call(destination: string, options?: CallOptions): Promise<Call>;
894
1019
  /** The live call, or null. The consult leg of an attended transfer is reachable from it. */
895
1020
  readonly activeCall: Call | null;
@@ -904,8 +1029,9 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
904
1029
  /**
905
1030
  * The live agent-presence list (Phase B, `docs/specs/transfer-presence-picker.md`) — every org
906
1031
  * member with a phone extension, whatever their state. Empty when there is no `PresenceSource`
907
- * (no Coventia backend, or a host that omitted one), or while this tab is not `online && isLeader`
908
- * — never an error, just nothing to show (spec §4.4's own success criterion). Updates fire
1032
+ * (no Coventia backend, or a host that omitted one), or while this tab is not a started leader —
1033
+ * `online`, or waiting in `standby` for the line — or its connection is down: never an error, just
1034
+ * nothing to show (spec §4.4's own success criterion). Updates fire
909
1035
  * `SoftphoneEvent.PresenceChanged`; `usePresence()` is the reactive read.
910
1036
  */
911
1037
  readonly presence: readonly PresenceEntry[];
@@ -1053,6 +1179,16 @@ interface SoftphoneElementEventMap {
1053
1179
  'cv-leadership-changed': CustomEvent<{
1054
1180
  readonly isLeader: boolean;
1055
1181
  }>;
1182
+ /**
1183
+ * Where this extension's calls ring changed (`docs/specs/line-owner.md` §5) — the axis across browsers,
1184
+ * devices and the desktop app that `cv-leadership-changed` cannot see. `line: 'elsewhere'` is what puts
1185
+ * the phone in `standby` (`cv-state-changed`), and **the widget already shows its own waiting screen with
1186
+ * "Recibir aquí" for it** — listen here for your own indicator; `el.softphone.takeLine()` takes the line.
1187
+ */
1188
+ 'cv-line-changed': CustomEvent<{
1189
+ readonly line: LineState;
1190
+ readonly where: LineLocation | null;
1191
+ }>;
1056
1192
  /**
1057
1193
  * A transfer's final handoff step resolved — `status` says whether the caller actually reached
1058
1194
  * `target`. Polled, not instant (Phase A, `docs/specs/transfer-outcome-relay.md`); only for that
@@ -6931,7 +6931,7 @@ var require_jsx_runtime = __commonJS({
6931
6931
  });
6932
6932
 
6933
6933
  // src/element/index.ts
6934
- var import_react46 = __toESM(require_react(), 1);
6934
+ var import_react47 = __toESM(require_react(), 1);
6935
6935
  var import_client = __toESM(require_client(), 1);
6936
6936
 
6937
6937
  // src/react/context.tsx
@@ -6950,7 +6950,7 @@ function useSoftphone() {
6950
6950
  }
6951
6951
 
6952
6952
  // src/ui/SoftphoneWidget.tsx
6953
- var import_react44 = __toESM(require_react(), 1);
6953
+ var import_react45 = __toESM(require_react(), 1);
6954
6954
 
6955
6955
  // ../../node_modules/lucide-react/dist/esm/createLucideIcon.js
6956
6956
  var import_react3 = __toESM(require_react());
@@ -7178,6 +7178,19 @@ var Pause = createLucideIcon("Pause", [
7178
7178
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
7179
7179
  ]);
7180
7180
 
7181
+ // ../../node_modules/lucide-react/dist/esm/icons/phone-forwarded.js
7182
+ var PhoneForwarded = createLucideIcon("PhoneForwarded", [
7183
+ ["polyline", { points: "18 2 22 6 18 10", key: "6vjanh" }],
7184
+ ["line", { x1: "14", x2: "22", y1: "6", y2: "6", key: "1jsywh" }],
7185
+ [
7186
+ "path",
7187
+ {
7188
+ d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z",
7189
+ key: "foiqr5"
7190
+ }
7191
+ ]
7192
+ ]);
7193
+
7181
7194
  // ../../node_modules/lucide-react/dist/esm/icons/phone-incoming.js
7182
7195
  var PhoneIncoming = createLucideIcon("PhoneIncoming", [
7183
7196
  ["polyline", { points: "16 2 16 8 22 8", key: "1ygljm" }],
@@ -7349,6 +7362,31 @@ function useIsLeader() {
7349
7362
  (0, import_react4.useCallback)(() => phone.isLeader, [phone])
7350
7363
  );
7351
7364
  }
7365
+ function useLine() {
7366
+ const phone = useSoftphone();
7367
+ const cache = (0, import_react4.useRef)(null);
7368
+ return (0, import_react4.useSyncExternalStore)(
7369
+ (0, import_react4.useCallback)(
7370
+ (cb) => {
7371
+ const offLine = phone.on("lineChanged" /* LineChanged */, () => cb());
7372
+ const offClaim = phone.on("lineClaimChanged" /* LineClaimChanged */, () => cb());
7373
+ return () => {
7374
+ offLine();
7375
+ offClaim();
7376
+ };
7377
+ },
7378
+ [phone]
7379
+ ),
7380
+ (0, import_react4.useCallback)(() => {
7381
+ const { line, lineWhere: where, lineClaim: claim } = phone;
7382
+ const cached = cache.current;
7383
+ if (cached?.line === line && cached.where === where && cached.claim === claim) return cached;
7384
+ const view = { line, where, claim };
7385
+ cache.current = view;
7386
+ return view;
7387
+ }, [phone])
7388
+ );
7389
+ }
7352
7390
  function useActiveCall() {
7353
7391
  const phone = useSoftphone();
7354
7392
  return (0, import_react4.useSyncExternalStore)(
@@ -8787,6 +8825,22 @@ var ERROR_TEXT = {
8787
8825
  // thinks they dropped it will hang up on a client to "fix" it.
8788
8826
  "call/transfer-failed": { title: "No se pudo transferir. Sigues en la llamada" },
8789
8827
  "call/unsupported": { title: "Esta funci\xF3n no est\xE1 disponible" },
8828
+ // ── Line owner ──────────────────────────────────────────────────────────────
8829
+ /*
8830
+ Not call outcomes: they answer "Recibir aquí" on `LineGate`, which has ONE line for them where the
8831
+ explanation was, so it reads the pair as two sentences — `${title}. ${body}`. Split anyway, so the
8832
+ title stands alone wherever there is only room for a headline (a host's own toast), under the same
8833
+ one-line rules as every other non-fatal title. "Allí" because the gate has just said where: the
8834
+ other softphone, the one that has the calls.
8835
+ */
8836
+ "line/claim-refused": {
8837
+ title: "Est\xE1s en una llamada all\xED",
8838
+ body: "Podr\xE1s recibirlas aqu\xED cuando cuelgues."
8839
+ },
8840
+ "line/unavailable": {
8841
+ title: "No se ha podido cambiar",
8842
+ body: "Int\xE9ntalo de nuevo."
8843
+ },
8790
8844
  // ── Provider ────────────────────────────────────────────────────────────────
8791
8845
  "provider/provision-failed": {
8792
8846
  title: "No se pudo activar el tel\xE9fono",
@@ -10929,18 +10983,55 @@ function LeaderGate() {
10929
10983
  ] });
10930
10984
  }
10931
10985
 
10986
+ // src/ui/permission/LineGate.tsx
10987
+ var import_jsx_runtime24 = (
10988
+ /*
10989
+ `part` on all four, like `ErrorGate`: it replaces the whole dialpad, and inside `<coventia-softphone>`
10990
+ a shadow root makes it otherwise unreachable from the host's CSS. `data-claim` is for the light-DOM
10991
+ `/ui` path and for debugging — it cannot cross the shadow boundary either.
10992
+ */
10993
+ __toESM(require_jsx_runtime(), 1)
10994
+ );
10995
+ var BODY = "Las llamadas entran all\xED. Si prefieres recibirlas aqu\xED, pulsa \xABRecibir aqu\xED\xBB.";
10996
+ var sentence = (code) => `${ERROR_TEXT[code].title}. ${ERROR_TEXT[code].body ?? ""}`.trim();
10997
+ function LineGate() {
10998
+ const softphone = useSoftphone();
10999
+ const { where, claim } = useLine();
11000
+ const take = () => {
11001
+ softphone.takeLine().catch(() => {
11002
+ });
11003
+ };
11004
+ const body = claim === "refused" ? sentence("line/claim-refused") : claim === "failed" ? sentence("line/unavailable") : BODY;
11005
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "cv-gate", "data-claim": claim, part: "line-gate", children: [
11006
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "cv-gate-mark", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PhoneForwarded, { size: 22 }) }),
11007
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h3", { className: "cv-gate-title", part: "line-title", children: where === "desktop" ? "Tu tel\xE9fono est\xE1 activo en la app de escritorio" : "Tu tel\xE9fono est\xE1 activo en otro sitio" }),
11008
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "status", part: "line-body", children: body }),
11009
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11010
+ "button",
11011
+ {
11012
+ className: "cv-btn cv-btn--accent",
11013
+ onClick: take,
11014
+ disabled: claim === "claiming",
11015
+ "aria-busy": claim === "claiming",
11016
+ part: "line-claim",
11017
+ children: claim === "claiming" ? "Cambiando\u2026" : "Recibir aqu\xED"
11018
+ }
11019
+ )
11020
+ ] });
11021
+ }
11022
+
10932
11023
  // src/ui/lib/useCallOutcome.ts
10933
- var import_react37 = __toESM(require_react(), 1);
11024
+ var import_react38 = __toESM(require_react(), 1);
10934
11025
  var BACKSTOP_MS = 6e3;
10935
11026
  function useCallOutcome() {
10936
11027
  const softphone = useSoftphone();
10937
- const [code, setCode] = (0, import_react37.useState)(null);
10938
- const timer = (0, import_react37.useRef)(void 0);
10939
- const clear = (0, import_react37.useCallback)(() => {
11028
+ const [code, setCode] = (0, import_react38.useState)(null);
11029
+ const timer = (0, import_react38.useRef)(void 0);
11030
+ const clear = (0, import_react38.useCallback)(() => {
10940
11031
  clearTimeout(timer.current);
10941
11032
  setCode(null);
10942
11033
  }, []);
10943
- (0, import_react37.useEffect)(() => {
11034
+ (0, import_react38.useEffect)(() => {
10944
11035
  const off = softphone.on("error" /* Error */, (error) => {
10945
11036
  if (error.fatal) return;
10946
11037
  clearTimeout(timer.current);
@@ -10952,7 +11043,7 @@ function useCallOutcome() {
10952
11043
  clearTimeout(timer.current);
10953
11044
  };
10954
11045
  }, [softphone]);
10955
- (0, import_react37.useEffect)(() => softphone.on("incomingCall" /* IncomingCall */, () => clear()), [softphone, clear]);
11046
+ (0, import_react38.useEffect)(() => softphone.on("incomingCall" /* IncomingCall */, () => clear()), [softphone, clear]);
10956
11047
  return {
10957
11048
  outcome: code === null ? null : { code, text: ERROR_TEXT[code].title },
10958
11049
  clear
@@ -10960,12 +11051,12 @@ function useCallOutcome() {
10960
11051
  }
10961
11052
 
10962
11053
  // src/ui/audio/useCallAudio.ts
10963
- var import_react39 = __toESM(require_react(), 1);
11054
+ var import_react40 = __toESM(require_react(), 1);
10964
11055
  function useCallAudio(enabled) {
10965
11056
  const softphone = useSoftphone();
10966
- const remote = (0, import_react39.useRef)(null);
10967
- const tone = (0, import_react39.useRef)(null);
10968
- (0, import_react39.useEffect)(() => {
11057
+ const remote = (0, import_react40.useRef)(null);
11058
+ const tone = (0, import_react40.useRef)(null);
11059
+ (0, import_react40.useEffect)(() => {
10969
11060
  if (!enabled || typeof document === "undefined") return;
10970
11061
  const prime = () => primeAudio();
10971
11062
  const gestures = ["pointerdown", "keydown"];
@@ -11059,7 +11150,7 @@ function useCallAudio(enabled) {
11059
11150
  }
11060
11151
 
11061
11152
  // src/ui/notices/TransferNotices.tsx
11062
- var import_jsx_runtime24 = __toESM(require_jsx_runtime(), 1);
11153
+ var import_jsx_runtime25 = __toESM(require_jsx_runtime(), 1);
11063
11154
  function TransferNotices({
11064
11155
  notices,
11065
11156
  dismiss
@@ -11071,7 +11162,7 @@ function TransferNotices({
11071
11162
  if (notice.caller) void softphone.call(notice.caller).catch(() => {
11072
11163
  });
11073
11164
  };
11074
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
11165
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
11075
11166
  "div",
11076
11167
  {
11077
11168
  className: "cv-transfer-notices",
@@ -11079,20 +11170,20 @@ function TransferNotices({
11079
11170
  part: "transfer-notices",
11080
11171
  "data-has-notices": notices.length > 0,
11081
11172
  children: [
11082
- notices.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "cv-transfer-notices-count", children: [
11173
+ notices.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "cv-transfer-notices-count", children: [
11083
11174
  notices.length,
11084
11175
  " traspasos sin conectar"
11085
11176
  ] }) : null,
11086
- notices.map((notice) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "cv-transfer-notice", part: "transfer-notice", children: [
11087
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "cv-transfer-notice-mark", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PhoneMissed, { size: 16 }) }),
11088
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "cv-transfer-notice-body", children: [
11089
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "cv-transfer-notice-header", children: [
11090
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "cv-transfer-notice-title", children: [
11177
+ notices.map((notice) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "cv-transfer-notice", part: "transfer-notice", children: [
11178
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "cv-transfer-notice-mark", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PhoneMissed, { size: 16 }) }),
11179
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "cv-transfer-notice-body", children: [
11180
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "cv-transfer-notice-header", children: [
11181
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "cv-transfer-notice-title", children: [
11091
11182
  "El traspaso a ",
11092
11183
  notice.target,
11093
11184
  " no se conect\xF3"
11094
11185
  ] }),
11095
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11186
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
11096
11187
  "button",
11097
11188
  {
11098
11189
  type: "button",
@@ -11100,17 +11191,17 @@ function TransferNotices({
11100
11191
  onClick: () => dismiss(notice.id),
11101
11192
  "aria-label": "Descartar aviso",
11102
11193
  part: "transfer-notice-dismiss",
11103
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(X, { size: 14 })
11194
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(X, { size: 14 })
11104
11195
  }
11105
11196
  )
11106
11197
  ] }),
11107
- notice.caller ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "cv-transfer-notice-subtitle", children: [
11198
+ notice.caller ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: "cv-transfer-notice-subtitle", children: [
11108
11199
  notice.caller,
11109
11200
  " se ha desconectado."
11110
11201
  ] }) : null,
11111
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "cv-transfer-notice-footer", children: [
11112
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "cv-transfer-notice-age", children: formatAge(elapsedSeconds(notice.at, now)) }),
11113
- notice.caller ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
11202
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "cv-transfer-notice-footer", children: [
11203
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "cv-transfer-notice-age", children: formatAge(elapsedSeconds(notice.at, now)) }),
11204
+ notice.caller ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
11114
11205
  "button",
11115
11206
  {
11116
11207
  type: "button",
@@ -11118,7 +11209,7 @@ function TransferNotices({
11118
11209
  onClick: () => callBack(notice),
11119
11210
  part: "transfer-notice-callback",
11120
11211
  children: [
11121
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Phone, { size: 14, "aria-hidden": true }),
11212
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Phone, { size: 14, "aria-hidden": true }),
11122
11213
  "Llamar de vuelta"
11123
11214
  ]
11124
11215
  }
@@ -11132,15 +11223,15 @@ function TransferNotices({
11132
11223
  }
11133
11224
 
11134
11225
  // src/ui/notices/useTransferNotices.ts
11135
- var import_react42 = __toESM(require_react(), 1);
11226
+ var import_react43 = __toESM(require_react(), 1);
11136
11227
  function useTransferNotices() {
11137
11228
  const softphone = useSoftphone();
11138
- const [notices, setNotices] = (0, import_react42.useState)([]);
11139
- const nextId = (0, import_react42.useRef)(0);
11140
- const dismiss = (0, import_react42.useCallback)((id) => {
11229
+ const [notices, setNotices] = (0, import_react43.useState)([]);
11230
+ const nextId = (0, import_react43.useRef)(0);
11231
+ const dismiss = (0, import_react43.useCallback)((id) => {
11141
11232
  setNotices((prev) => prev.filter((n) => n.id !== id));
11142
11233
  }, []);
11143
- (0, import_react42.useEffect)(
11234
+ (0, import_react43.useEffect)(
11144
11235
  () => softphone.on("transferOutcome" /* TransferOutcome */, (payload) => {
11145
11236
  if (payload.status === "not-connected") {
11146
11237
  const id = nextId.current++;
@@ -11153,7 +11244,7 @@ function useTransferNotices() {
11153
11244
  }
11154
11245
 
11155
11246
  // src/ui/SoftphoneWidget.tsx
11156
- var import_jsx_runtime25 = __toESM(require_jsx_runtime(), 1);
11247
+ var import_jsx_runtime26 = __toESM(require_jsx_runtime(), 1);
11157
11248
  function SoftphoneWidget(props) {
11158
11249
  const {
11159
11250
  contacts,
@@ -11169,16 +11260,17 @@ function SoftphoneWidget(props) {
11169
11260
  const fallback = softphone.sources;
11170
11261
  const contactsSource = contacts ?? fallback?.contacts;
11171
11262
  const historySource = history ?? fallback?.history;
11172
- const [tab, setTab] = (0, import_react44.useState)("dialer");
11263
+ const [tab, setTab] = (0, import_react45.useState)("dialer");
11173
11264
  const call = useActiveCall();
11174
11265
  const isLeader = useIsLeader();
11175
11266
  const softphoneState = useSoftphoneState();
11176
11267
  const isFollower = !isLeader && softphoneState === "starting";
11268
+ const isStandby = softphoneState === "standby";
11177
11269
  const mic = useMicPermission();
11178
11270
  useCallAudio(sounds);
11179
11271
  const blocked = requireMic && mic.state !== "ready";
11180
11272
  const fatal = useLastError();
11181
- const screenIsFree = !call && !blocked && !fatal && !isFollower;
11273
+ const screenIsFree = !call && !blocked && !fatal && !isFollower && !isStandby;
11182
11274
  const { outcome, clear: clearOutcome } = useCallOutcome();
11183
11275
  const { notices: transferNotices, dismiss: dismissTransferNotice } = useTransferNotices();
11184
11276
  const retry = () => {
@@ -11198,15 +11290,15 @@ function SoftphoneWidget(props) {
11198
11290
  // optional parts of a phone, so this is never conditionally hidden.
11199
11291
  { id: "settings", icon: Settings, label: "Ajustes", show: true }
11200
11292
  ].filter((t) => t.show);
11201
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
11293
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11202
11294
  "div",
11203
11295
  {
11204
11296
  className: className ? `cv-softphone ${className}` : "cv-softphone",
11205
11297
  ...theme === "system" ? {} : { "data-cv-theme": theme },
11206
11298
  children: [
11207
- screenIsFree ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
11208
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LeaderGate, {}) : blocked ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SettingsTab, {}) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
11209
- screenIsFree ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
11299
+ screenIsFree ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
11300
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LeaderGate, {}) : isStandby && !call ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(LineGate, {}) : blocked ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SettingsTab, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
11301
+ screenIsFree ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11210
11302
  "button",
11211
11303
  {
11212
11304
  role: "tab",
@@ -11214,8 +11306,8 @@ function SoftphoneWidget(props) {
11214
11306
  className: "cv-tab",
11215
11307
  onClick: () => setTab(id),
11216
11308
  children: [
11217
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Icon3, { size: 20 }),
11218
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: label })
11309
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Icon3, { size: 20 }),
11310
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: label })
11219
11311
  ]
11220
11312
  },
11221
11313
  id
@@ -11236,6 +11328,7 @@ var EVENTS = {
11236
11328
  callEnded: "cv-call-ended",
11237
11329
  error: "cv-error",
11238
11330
  leaderChanged: "cv-leadership-changed",
11331
+ lineChanged: "cv-line-changed",
11239
11332
  transferOutcome: "cv-transfer-outcome"
11240
11333
  };
11241
11334
  var CoventiaSoftphoneElement = class extends HTMLElement {
@@ -11355,6 +11448,7 @@ var CoventiaSoftphoneElement = class extends HTMLElement {
11355
11448
  "leaderChanged" /* LeaderChanged */,
11356
11449
  ({ isLeader }) => this.#emit(EVENTS.leaderChanged, { isLeader })
11357
11450
  ),
11451
+ phone.on("lineChanged" /* LineChanged */, ({ line, where }) => this.#emit(EVENTS.lineChanged, { line, where })),
11358
11452
  // Payload is already exactly `TransferOutcomeEvent` — no reshaping needed, unlike the events above.
11359
11453
  phone.on("transferOutcome" /* TransferOutcome */, (payload) => this.#emit(EVENTS.transferOutcome, payload))
11360
11454
  ];
@@ -11403,9 +11497,9 @@ var CoventiaSoftphoneElement = class extends HTMLElement {
11403
11497
  ...this.#config.incomingCallAction ? { incomingCallAction: this.#config.incomingCallAction } : {}
11404
11498
  };
11405
11499
  this.#root.render(
11406
- (0, import_react46.createElement)(
11500
+ (0, import_react47.createElement)(
11407
11501
  SoftphoneProvider,
11408
- { softphone: this.#softphone, children: (0, import_react46.createElement)(SoftphoneWidget, widgetProps) }
11502
+ { softphone: this.#softphone, children: (0, import_react47.createElement)(SoftphoneWidget, widgetProps) }
11409
11503
  )
11410
11504
  );
11411
11505
  }
@@ -11476,6 +11570,7 @@ lucide-react/dist/esm/icons/mic.js:
11476
11570
  lucide-react/dist/esm/icons/monitor-smartphone.js:
11477
11571
  lucide-react/dist/esm/icons/music.js:
11478
11572
  lucide-react/dist/esm/icons/pause.js:
11573
+ lucide-react/dist/esm/icons/phone-forwarded.js:
11479
11574
  lucide-react/dist/esm/icons/phone-incoming.js:
11480
11575
  lucide-react/dist/esm/icons/phone-missed.js:
11481
11576
  lucide-react/dist/esm/icons/phone-off.js: