@coventia/softphone 0.4.0 → 0.5.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 (60) hide show
  1. package/dist/{call-C6YpT6uR.d.cts → call-iGvr2ZnO.d.cts} +130 -13
  2. package/dist/{call-C6YpT6uR.d.ts → call-iGvr2ZnO.d.ts} +130 -13
  3. package/dist/chunk-3UTUWGVG.js +1891 -0
  4. package/dist/chunk-3UTUWGVG.js.map +1 -0
  5. package/dist/chunk-DZRSSZWB.js +39 -0
  6. package/dist/chunk-DZRSSZWB.js.map +1 -0
  7. package/dist/{chunk-2ZSOGGPO.js → chunk-FRXHEHQA.js} +15 -2
  8. package/dist/chunk-FRXHEHQA.js.map +1 -0
  9. package/dist/{chunk-UHNLEBBO.js → chunk-I3RSC52R.js} +28 -3
  10. package/dist/chunk-I3RSC52R.js.map +1 -0
  11. package/dist/chunk-M76SAXXM.js +13 -0
  12. package/dist/chunk-M76SAXXM.js.map +1 -0
  13. package/dist/{chunk-7R6LOOAI.js → chunk-N4PLEYFJ.js} +4 -2
  14. package/dist/chunk-N4PLEYFJ.js.map +1 -0
  15. package/dist/element/index.d.ts +167 -7
  16. package/dist/element/index.js +138 -43
  17. package/dist/element/index.js.map +1 -1
  18. package/dist/{engine-DPft99Wj.d.ts → engine-BKoT_Pj7.d.cts} +10 -1
  19. package/dist/{engine-CDdaSojx.d.cts → engine-CeGh5Yai.d.ts} +10 -1
  20. package/dist/index.cjs +1596 -145
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +5 -5
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +618 -42
  25. package/dist/index.js.map +1 -1
  26. package/dist/react/index.cjs +26 -0
  27. package/dist/react/index.cjs.map +1 -1
  28. package/dist/react/index.d.cts +24 -5
  29. package/dist/react/index.d.ts +24 -5
  30. package/dist/react/index.js +2 -2
  31. package/dist/{softphone-CY5d2WWY.d.cts → softphone-D0ao5W4z.d.ts} +42 -3
  32. package/dist/{softphone-DXgmTyrX.d.ts → softphone-DRoS0jJR.d.cts} +42 -3
  33. package/dist/{sources-ONYQW733.js → sources-LGFJG7W2.js} +27 -38
  34. package/dist/sources-LGFJG7W2.js.map +1 -0
  35. package/dist/testing/index.cjs +13 -0
  36. package/dist/testing/index.cjs.map +1 -1
  37. package/dist/testing/index.d.cts +2 -2
  38. package/dist/testing/index.d.ts +2 -2
  39. package/dist/testing/index.js +1 -1
  40. package/dist/ui/index.cjs +92 -2
  41. package/dist/ui/index.cjs.map +1 -1
  42. package/dist/ui/index.d.cts +2 -2
  43. package/dist/ui/index.d.ts +2 -2
  44. package/dist/ui/index.js +69 -4
  45. package/dist/ui/index.js.map +1 -1
  46. package/dist/zadarma/index.cjs +950 -104
  47. package/dist/zadarma/index.cjs.map +1 -1
  48. package/dist/zadarma/index.d.cts +40 -14
  49. package/dist/zadarma/index.d.ts +40 -14
  50. package/dist/zadarma/index.js +4 -2
  51. package/dist/zadarma-engine-VYIXLBQG.js +7 -0
  52. package/dist/{zadarma-engine-L5DEFXUS.js.map → zadarma-engine-VYIXLBQG.js.map} +1 -1
  53. package/package.json +1 -1
  54. package/dist/chunk-2ZSOGGPO.js.map +0 -1
  55. package/dist/chunk-7R6LOOAI.js.map +0 -1
  56. package/dist/chunk-EYEDPD7Y.js +0 -1071
  57. package/dist/chunk-EYEDPD7Y.js.map +0 -1
  58. package/dist/chunk-UHNLEBBO.js.map +0 -1
  59. package/dist/sources-ONYQW733.js.map +0 -1
  60. package/dist/zadarma-engine-L5DEFXUS.js +0 -5
@@ -86,10 +86,10 @@ interface TokenEndpointOptions {
86
86
  */
87
87
  interface CoventiaBackendConfig {
88
88
  /**
89
- * e.g. `https://api.coventia.es`. **Optional** — falls back to `SoftphoneConfig.apiBase`, which
90
- * exists precisely so this and the default call engine name the same backend once instead of
91
- * twice. Set it here only when contacts/history genuinely live on a DIFFERENT host than calling
92
- * does, which is not the normal case.
89
+ * Coventia's domain, like `SoftphoneConfig.apiBase`: no path, the SDK builds every route.
90
+ * **Optional** — falls back to `SoftphoneConfig.apiBase`, which exists precisely so this and the
91
+ * default call engine name the same backend once instead of twice. Set it here only when
92
+ * contacts/history genuinely live on a DIFFERENT host than calling does, which is not the normal case.
93
93
  */
94
94
  readonly baseUrl?: string | undefined;
95
95
  /**
@@ -104,7 +104,10 @@ interface CoventiaBackendConfig {
104
104
  * That is the CRM today.
105
105
  */
106
106
  readonly organizationId?: string;
107
- /** `/phone` today; `/api/v1/phone` once the partner API ships (SPEC §6.6). */
107
+ /**
108
+ * Where the phone routes sit under the base URL. Internal: leave it unset (`/phone`). The routes are
109
+ * the SDK's to build, which is why `apiBase` is the domain only.
110
+ */
108
111
  readonly basePath?: string;
109
112
  /**
110
113
  * **The one thing a provider customises**: their own contact list, searched on their own backend.
@@ -134,8 +137,11 @@ interface CoventiaBackendConfig {
134
137
  interface SoftphoneConfig {
135
138
  readonly tokenSource: TokenSource;
136
139
  /**
137
- * The Coventia backend, e.g. `https://api.coventia.es`. **The one field to set** if you want the
138
- * default call engine (see `provider` below) and are not overriding `coventia.baseUrl` separately.
140
+ * Coventia's domain — **only the domain**, e.g. `https://backend.example.com`, with no path: the SDK
141
+ * builds every route itself. Not the Partner API URL your backend calls: that one ends in `/api/v1`,
142
+ * which does not belong here (it is ignored, with a console warning). **The one field to set** if you
143
+ * want the default call engine (see `provider` below) and are not overriding `coventia.baseUrl`
144
+ * separately.
139
145
  *
140
146
  * Named once here rather than requiring it on both `coventia` and the engine you construct: they
141
147
  * are two independent subsystems (contacts/history/caller ID vs. SIP credential provisioning) that
@@ -188,8 +194,62 @@ interface CallOptions {
188
194
  * Zadarma registers on-demand, per call, in response to a push (SPEC §3.2); a persistent UA is
189
195
  * ignored by its PBX. Leaking "registered" into the public API would force every future provider —
190
196
  * including our own SIP infrastructure, which registers persistently — to fake it.
197
+ *
198
+ * `standby` means started, and this browser's leader, but ANOTHER softphone of the same extension has
199
+ * the line — a tab in another browser, the desktop app, a partner page (`docs/specs/line-owner.md` §5).
200
+ * Its push socket is closed on purpose, so nothing here registers or rings: `line` says where the calls
201
+ * go, and `takeLine()` — or a `call()` — brings them here. A host that reads `online` as "ready"
202
+ * correctly reads this as not ready.
203
+ */
204
+ type SoftphoneState = 'stopped' | 'starting' | 'online' | 'standby' | 'offline' | 'reconnecting' | 'destroyed';
205
+ /**
206
+ * Where this extension's incoming calls ring, as far as this softphone knows. `here`: it has the line.
207
+ * `elsewhere`: another softphone of the same extension has it, and this one waits in `standby`. `unknown`:
208
+ * nobody has said — no presence source, a backend that predates the line owner, a presence socket that is
209
+ * down — and this softphone listens, exactly as before the line owner existed (§5.1 R7, fail-open).
191
210
  */
192
- type SoftphoneState = 'stopped' | 'starting' | 'online' | 'offline' | 'reconnecting' | 'destroyed';
211
+ type LineState = 'here' | 'elsewhere' | 'unknown';
212
+ /** Who has the line when it is `elsewhere`: the desktop app, or any other softphone (a browser tab). */
213
+ type LineLocation = 'desktop' | 'browser';
214
+ /**
215
+ * The last attempt to bring the line here — `takeLine()`, or a `call()` from `standby`, whoever made it:
216
+ * `claiming` while it is in flight, `refused` when the user is on a call where the line is, `failed` when
217
+ * nothing could decide. `idle` before any attempt, once the line comes here, and after a `stop()`.
218
+ */
219
+ type LineClaim = 'idle' | 'claiming' | 'refused' | 'failed';
220
+ /** The server's word on this softphone's line, as a `PresenceSource` hands it over (`PresenceCallbacks.onLine`). */
221
+ interface LineUpdate {
222
+ readonly state: 'here' | 'elsewhere';
223
+ /** Who has the line when it is `elsewhere`; `null` when it is here, or the server did not say. */
224
+ readonly where: LineLocation | null;
225
+ }
226
+ /**
227
+ * A call offered to this user's line, as the backend announces it over the presence socket
228
+ * (incoming-call-reliability.md §5): `start` each time the call is offered to the extension (a re-offer of the
229
+ * same call repeats it, same `callId`), `end` when the call is over. `callId` is opaque; `from` is the caller,
230
+ * `null` (or, on an `end`, absent) when the backend did not say.
231
+ *
232
+ * The softphone uses it for one thing: after a decline, to know when the declined call's re-offers stop, so a
233
+ * new call from the same number rings (§4.4). A presence source that never sends it loses nothing but that.
234
+ */
235
+ type CallOffer = {
236
+ readonly state: 'start';
237
+ readonly callId: string;
238
+ readonly from: string | null;
239
+ } | {
240
+ readonly state: 'end';
241
+ readonly callId: string;
242
+ readonly from?: string | null;
243
+ };
244
+ /** The answer to `PresenceConnection.claimLine()`. */
245
+ interface LineClaimResult {
246
+ readonly ok: boolean;
247
+ /**
248
+ * Why it was refused: `'on_call'` while the user is on a call (the line never moves during one). Any
249
+ * other value means nothing was decided. Absent when `ok`.
250
+ */
251
+ readonly reason?: string;
252
+ }
193
253
  interface ContactsPage {
194
254
  readonly items: readonly Contact[];
195
255
  /**
@@ -349,6 +409,18 @@ interface PresenceConnection {
349
409
  * own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
350
410
  */
351
411
  readonly bye?: () => void;
412
+ /**
413
+ * The line owner (`docs/specs/line-owner.md` §5.2): says which softphone this connection speaks for, so
414
+ * the server can tell it whether it has the line — through `PresenceCallbacks.onLine`, right after this
415
+ * and on every change. Sent as the FIRST message of every connection. Optional: a source without it never
416
+ * takes part, and its softphone listens exactly as before (fail-open), reading its line as `unknown`.
417
+ */
418
+ readonly hello?: (clientId: string) => void;
419
+ /**
420
+ * "Recibir aquí": asks for the line. Answered through `PresenceCallbacks.onClaimResult` — and, when it
421
+ * succeeds, by `onLine` saying `here` first. Optional, like `hello`.
422
+ */
423
+ readonly claimLine?: () => void;
352
424
  /**
353
425
  * Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
354
426
  * reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
@@ -368,6 +440,19 @@ interface PresenceCallbacks {
368
440
  * identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
369
441
  */
370
442
  readonly onClose: () => void;
443
+ /**
444
+ * The line, as the server says it — right after `PresenceConnection.hello`, and on every change after
445
+ * that. Optional, so callbacks written before the line owner existed still type-check.
446
+ */
447
+ readonly onLine?: (update: LineUpdate) => void;
448
+ /** The answer to `PresenceConnection.claimLine()`. Optional, like `onLine`. */
449
+ readonly onClaimResult?: (result: LineClaimResult) => void;
450
+ /**
451
+ * A call offered to this line started or ended (`CallOffer`). Sent only to the line's owner. Optional, like
452
+ * `onLine`: callbacks written before it existed still type-check, and a source that never calls it leaves the
453
+ * softphone on its fallback after a decline (incoming-call-reliability.md §4.4).
454
+ */
455
+ readonly onOffer?: (offer: CallOffer) => void;
371
456
  }
372
457
  /**
373
458
  * Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
@@ -598,6 +683,16 @@ declare const ERROR_CATALOGUE: {
598
683
  readonly message: "The provider does not support this operation.";
599
684
  readonly solution: "Guard the UI on the operation before offering it.";
600
685
  };
686
+ readonly 'line/claim-refused': {
687
+ readonly fatal: false;
688
+ readonly message: "The line was not moved here: the user is on a call on the softphone that has it.";
689
+ 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.";
690
+ };
691
+ readonly 'line/unavailable': {
692
+ readonly fatal: false;
693
+ readonly message: "The line could not be taken: nothing decided where it is.";
694
+ 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.";
695
+ };
601
696
  readonly 'provider/provision-failed': {
602
697
  readonly fatal: true;
603
698
  readonly message: "The provider could not issue SIP credentials.";
@@ -686,10 +781,25 @@ declare enum SoftphoneEvent {
686
781
  /**
687
782
  * `softphone.presence` changed — the live agent-presence list (Phase B,
688
783
  * `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
689
- * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
690
- * `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
784
+ * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being a
785
+ * started leader — `online`, or waiting in `standby` for the line; never fires at all otherwise,
786
+ * same reasoning as `TransferOutcome`.
787
+ */
788
+ PresenceChanged = "presenceChanged",
789
+ /**
790
+ * `softphone.line` / `lineWhere` changed (`docs/specs/line-owner.md` §5): this softphone got the line,
791
+ * lost it to another softphone of the same extension, or stopped knowing (`unknown` — it then listens,
792
+ * as before the line owner existed). A third axis beside `StateChanged` and `LeaderChanged`: losing the
793
+ * line is what takes the softphone to `standby`, but not at once while a call is in progress — the line
794
+ * can read `elsewhere` while the state is still `online`, until that call ends.
691
795
  */
692
- PresenceChanged = "presenceChanged"
796
+ LineChanged = "lineChanged",
797
+ /**
798
+ * `softphone.lineClaim` changed: an attempt to bring the line here started, was refused, failed, or ended
799
+ * (the line came here, or the softphone stopped). Kept apart from `LineChanged`, which only ever means the
800
+ * line itself moved.
801
+ */
802
+ LineClaimChanged = "lineClaimChanged"
693
803
  }
694
804
  interface SoftphoneEventMap {
695
805
  [SoftphoneEvent.StateChanged]: {
@@ -710,6 +820,13 @@ interface SoftphoneEventMap {
710
820
  [SoftphoneEvent.PresenceChanged]: {
711
821
  readonly agents: readonly PresenceEntry[];
712
822
  };
823
+ [SoftphoneEvent.LineChanged]: {
824
+ readonly line: LineState;
825
+ readonly where: LineLocation | null;
826
+ };
827
+ [SoftphoneEvent.LineClaimChanged]: {
828
+ readonly claim: LineClaim;
829
+ };
713
830
  }
714
831
  declare enum CallEvent {
715
832
  /** Any transition. The catch-all; the specific events below are conveniences. */
@@ -810,7 +927,7 @@ interface TypedEmitter<M> {
810
927
  *
811
928
  * - **`rejecting`.** Today's engine has one, purely to swallow Zadarma's `return_timeout` retries
812
929
  * for 35 s after a decline (SPEC §3.4). That is a PROVIDER quirk — it belongs in
813
- * `providers/zadarma.ts` as `quirks.rejectGuardMs`, not in the shared state machine. A rejected
930
+ * `providers/zadarma.ts` as `quirks.declinedCallerGuardMs`, not in the shared state machine. A rejected
814
931
  * call is simply `ended`. Our own SIP infrastructure would not need it at all (SPEC §2.3).
815
932
  *
816
933
  * - **A pre-INVITE state.** A Call exists only once an INVITE has landed. The "user answered
@@ -936,4 +1053,4 @@ interface Call extends TypedEmitter<CallEventMap> {
936
1053
  cancelConsult(): Promise<void>;
937
1054
  }
938
1055
 
939
- export { isFatal as A, isTerminal as B, CALL_TRANSITIONS as C, type ContactsSource as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HistorySource as G, type HangupReason as H, INITIAL_STATES as I, type AgentsDirectory as J, type TransferOutcomesPage as K, type LogLevel as L, type PresenceSource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type CallHistoryEntry as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type ContactsPage as U, type VoiceToken as V, type Call as a, type CallDirection as b, CallEvent as c, type CallEventMap as d, type CallOptions as e, type CallState as f, type Contact as g, type FatalSoftphoneError as h, type HoldOriginator as i, type NonFatalSoftphoneError as j, type OutboundNumber as k, type OutboundNumberKind as l, type OutboundNumberSettings as m, type OutboundNumberState as n, type OutboundNumbersSource as o, type RingtoneOption as p, type RingtoneSettings as q, type RingtoneSource as r, type SoftphoneError as s, type SoftphoneErrorCode as t, SoftphoneEvent as u, type SoftphoneEventMap as v, type SoftphoneState as w, type TokenSource as x, type TypedEmitter as y, canTransition as z };
1056
+ export { type TokenSource as A, type TypedEmitter as B, CALL_TRANSITIONS as C, canTransition as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, isFatal as G, type HangupReason as H, INITIAL_STATES as I, isTerminal as J, type ContactsSource as K, type LineClaim as L, type HistorySource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type AgentsDirectory as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type TransferOutcomesPage as U, type VoiceToken as V, type PresenceSource as W, type CallHistoryEntry as X, type ContactsPage as Y, type CallOffer as Z, type Call as a, type CallDirection as b, CallEvent as c, type CallEventMap as d, type CallOptions as e, type CallState as f, type Contact as g, type FatalSoftphoneError as h, type HoldOriginator as i, type LineLocation as j, type LineState as k, type LogLevel as l, type NonFatalSoftphoneError as m, type OutboundNumber as n, type OutboundNumberKind as o, type OutboundNumberSettings as p, type OutboundNumberState as q, type OutboundNumbersSource as r, type RingtoneOption as s, type RingtoneSettings as t, type RingtoneSource as u, type SoftphoneError as v, type SoftphoneErrorCode as w, SoftphoneEvent as x, type SoftphoneEventMap as y, type SoftphoneState as z };
@@ -86,10 +86,10 @@ interface TokenEndpointOptions {
86
86
  */
87
87
  interface CoventiaBackendConfig {
88
88
  /**
89
- * e.g. `https://api.coventia.es`. **Optional** — falls back to `SoftphoneConfig.apiBase`, which
90
- * exists precisely so this and the default call engine name the same backend once instead of
91
- * twice. Set it here only when contacts/history genuinely live on a DIFFERENT host than calling
92
- * does, which is not the normal case.
89
+ * Coventia's domain, like `SoftphoneConfig.apiBase`: no path, the SDK builds every route.
90
+ * **Optional** — falls back to `SoftphoneConfig.apiBase`, which exists precisely so this and the
91
+ * default call engine name the same backend once instead of twice. Set it here only when
92
+ * contacts/history genuinely live on a DIFFERENT host than calling does, which is not the normal case.
93
93
  */
94
94
  readonly baseUrl?: string | undefined;
95
95
  /**
@@ -104,7 +104,10 @@ interface CoventiaBackendConfig {
104
104
  * That is the CRM today.
105
105
  */
106
106
  readonly organizationId?: string;
107
- /** `/phone` today; `/api/v1/phone` once the partner API ships (SPEC §6.6). */
107
+ /**
108
+ * Where the phone routes sit under the base URL. Internal: leave it unset (`/phone`). The routes are
109
+ * the SDK's to build, which is why `apiBase` is the domain only.
110
+ */
108
111
  readonly basePath?: string;
109
112
  /**
110
113
  * **The one thing a provider customises**: their own contact list, searched on their own backend.
@@ -134,8 +137,11 @@ interface CoventiaBackendConfig {
134
137
  interface SoftphoneConfig {
135
138
  readonly tokenSource: TokenSource;
136
139
  /**
137
- * The Coventia backend, e.g. `https://api.coventia.es`. **The one field to set** if you want the
138
- * default call engine (see `provider` below) and are not overriding `coventia.baseUrl` separately.
140
+ * Coventia's domain — **only the domain**, e.g. `https://backend.example.com`, with no path: the SDK
141
+ * builds every route itself. Not the Partner API URL your backend calls: that one ends in `/api/v1`,
142
+ * which does not belong here (it is ignored, with a console warning). **The one field to set** if you
143
+ * want the default call engine (see `provider` below) and are not overriding `coventia.baseUrl`
144
+ * separately.
139
145
  *
140
146
  * Named once here rather than requiring it on both `coventia` and the engine you construct: they
141
147
  * are two independent subsystems (contacts/history/caller ID vs. SIP credential provisioning) that
@@ -188,8 +194,62 @@ interface CallOptions {
188
194
  * Zadarma registers on-demand, per call, in response to a push (SPEC §3.2); a persistent UA is
189
195
  * ignored by its PBX. Leaking "registered" into the public API would force every future provider —
190
196
  * including our own SIP infrastructure, which registers persistently — to fake it.
197
+ *
198
+ * `standby` means started, and this browser's leader, but ANOTHER softphone of the same extension has
199
+ * the line — a tab in another browser, the desktop app, a partner page (`docs/specs/line-owner.md` §5).
200
+ * Its push socket is closed on purpose, so nothing here registers or rings: `line` says where the calls
201
+ * go, and `takeLine()` — or a `call()` — brings them here. A host that reads `online` as "ready"
202
+ * correctly reads this as not ready.
203
+ */
204
+ type SoftphoneState = 'stopped' | 'starting' | 'online' | 'standby' | 'offline' | 'reconnecting' | 'destroyed';
205
+ /**
206
+ * Where this extension's incoming calls ring, as far as this softphone knows. `here`: it has the line.
207
+ * `elsewhere`: another softphone of the same extension has it, and this one waits in `standby`. `unknown`:
208
+ * nobody has said — no presence source, a backend that predates the line owner, a presence socket that is
209
+ * down — and this softphone listens, exactly as before the line owner existed (§5.1 R7, fail-open).
191
210
  */
192
- type SoftphoneState = 'stopped' | 'starting' | 'online' | 'offline' | 'reconnecting' | 'destroyed';
211
+ type LineState = 'here' | 'elsewhere' | 'unknown';
212
+ /** Who has the line when it is `elsewhere`: the desktop app, or any other softphone (a browser tab). */
213
+ type LineLocation = 'desktop' | 'browser';
214
+ /**
215
+ * The last attempt to bring the line here — `takeLine()`, or a `call()` from `standby`, whoever made it:
216
+ * `claiming` while it is in flight, `refused` when the user is on a call where the line is, `failed` when
217
+ * nothing could decide. `idle` before any attempt, once the line comes here, and after a `stop()`.
218
+ */
219
+ type LineClaim = 'idle' | 'claiming' | 'refused' | 'failed';
220
+ /** The server's word on this softphone's line, as a `PresenceSource` hands it over (`PresenceCallbacks.onLine`). */
221
+ interface LineUpdate {
222
+ readonly state: 'here' | 'elsewhere';
223
+ /** Who has the line when it is `elsewhere`; `null` when it is here, or the server did not say. */
224
+ readonly where: LineLocation | null;
225
+ }
226
+ /**
227
+ * A call offered to this user's line, as the backend announces it over the presence socket
228
+ * (incoming-call-reliability.md §5): `start` each time the call is offered to the extension (a re-offer of the
229
+ * same call repeats it, same `callId`), `end` when the call is over. `callId` is opaque; `from` is the caller,
230
+ * `null` (or, on an `end`, absent) when the backend did not say.
231
+ *
232
+ * The softphone uses it for one thing: after a decline, to know when the declined call's re-offers stop, so a
233
+ * new call from the same number rings (§4.4). A presence source that never sends it loses nothing but that.
234
+ */
235
+ type CallOffer = {
236
+ readonly state: 'start';
237
+ readonly callId: string;
238
+ readonly from: string | null;
239
+ } | {
240
+ readonly state: 'end';
241
+ readonly callId: string;
242
+ readonly from?: string | null;
243
+ };
244
+ /** The answer to `PresenceConnection.claimLine()`. */
245
+ interface LineClaimResult {
246
+ readonly ok: boolean;
247
+ /**
248
+ * Why it was refused: `'on_call'` while the user is on a call (the line never moves during one). Any
249
+ * other value means nothing was decided. Absent when `ok`.
250
+ */
251
+ readonly reason?: string;
252
+ }
193
253
  interface ContactsPage {
194
254
  readonly items: readonly Contact[];
195
255
  /**
@@ -349,6 +409,18 @@ interface PresenceConnection {
349
409
  * own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
350
410
  */
351
411
  readonly bye?: () => void;
412
+ /**
413
+ * The line owner (`docs/specs/line-owner.md` §5.2): says which softphone this connection speaks for, so
414
+ * the server can tell it whether it has the line — through `PresenceCallbacks.onLine`, right after this
415
+ * and on every change. Sent as the FIRST message of every connection. Optional: a source without it never
416
+ * takes part, and its softphone listens exactly as before (fail-open), reading its line as `unknown`.
417
+ */
418
+ readonly hello?: (clientId: string) => void;
419
+ /**
420
+ * "Recibir aquí": asks for the line. Answered through `PresenceCallbacks.onClaimResult` — and, when it
421
+ * succeeds, by `onLine` saying `here` first. Optional, like `hello`.
422
+ */
423
+ readonly claimLine?: () => void;
352
424
  /**
353
425
  * Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
354
426
  * reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
@@ -368,6 +440,19 @@ interface PresenceCallbacks {
368
440
  * identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
369
441
  */
370
442
  readonly onClose: () => void;
443
+ /**
444
+ * The line, as the server says it — right after `PresenceConnection.hello`, and on every change after
445
+ * that. Optional, so callbacks written before the line owner existed still type-check.
446
+ */
447
+ readonly onLine?: (update: LineUpdate) => void;
448
+ /** The answer to `PresenceConnection.claimLine()`. Optional, like `onLine`. */
449
+ readonly onClaimResult?: (result: LineClaimResult) => void;
450
+ /**
451
+ * A call offered to this line started or ended (`CallOffer`). Sent only to the line's owner. Optional, like
452
+ * `onLine`: callbacks written before it existed still type-check, and a source that never calls it leaves the
453
+ * softphone on its fallback after a decline (incoming-call-reliability.md §4.4).
454
+ */
455
+ readonly onOffer?: (offer: CallOffer) => void;
371
456
  }
372
457
  /**
373
458
  * Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
@@ -598,6 +683,16 @@ declare const ERROR_CATALOGUE: {
598
683
  readonly message: "The provider does not support this operation.";
599
684
  readonly solution: "Guard the UI on the operation before offering it.";
600
685
  };
686
+ readonly 'line/claim-refused': {
687
+ readonly fatal: false;
688
+ readonly message: "The line was not moved here: the user is on a call on the softphone that has it.";
689
+ 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.";
690
+ };
691
+ readonly 'line/unavailable': {
692
+ readonly fatal: false;
693
+ readonly message: "The line could not be taken: nothing decided where it is.";
694
+ 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.";
695
+ };
601
696
  readonly 'provider/provision-failed': {
602
697
  readonly fatal: true;
603
698
  readonly message: "The provider could not issue SIP credentials.";
@@ -686,10 +781,25 @@ declare enum SoftphoneEvent {
686
781
  /**
687
782
  * `softphone.presence` changed — the live agent-presence list (Phase B,
688
783
  * `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
689
- * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
690
- * `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
784
+ * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being a
785
+ * started leader — `online`, or waiting in `standby` for the line; never fires at all otherwise,
786
+ * same reasoning as `TransferOutcome`.
787
+ */
788
+ PresenceChanged = "presenceChanged",
789
+ /**
790
+ * `softphone.line` / `lineWhere` changed (`docs/specs/line-owner.md` §5): this softphone got the line,
791
+ * lost it to another softphone of the same extension, or stopped knowing (`unknown` — it then listens,
792
+ * as before the line owner existed). A third axis beside `StateChanged` and `LeaderChanged`: losing the
793
+ * line is what takes the softphone to `standby`, but not at once while a call is in progress — the line
794
+ * can read `elsewhere` while the state is still `online`, until that call ends.
691
795
  */
692
- PresenceChanged = "presenceChanged"
796
+ LineChanged = "lineChanged",
797
+ /**
798
+ * `softphone.lineClaim` changed: an attempt to bring the line here started, was refused, failed, or ended
799
+ * (the line came here, or the softphone stopped). Kept apart from `LineChanged`, which only ever means the
800
+ * line itself moved.
801
+ */
802
+ LineClaimChanged = "lineClaimChanged"
693
803
  }
694
804
  interface SoftphoneEventMap {
695
805
  [SoftphoneEvent.StateChanged]: {
@@ -710,6 +820,13 @@ interface SoftphoneEventMap {
710
820
  [SoftphoneEvent.PresenceChanged]: {
711
821
  readonly agents: readonly PresenceEntry[];
712
822
  };
823
+ [SoftphoneEvent.LineChanged]: {
824
+ readonly line: LineState;
825
+ readonly where: LineLocation | null;
826
+ };
827
+ [SoftphoneEvent.LineClaimChanged]: {
828
+ readonly claim: LineClaim;
829
+ };
713
830
  }
714
831
  declare enum CallEvent {
715
832
  /** Any transition. The catch-all; the specific events below are conveniences. */
@@ -810,7 +927,7 @@ interface TypedEmitter<M> {
810
927
  *
811
928
  * - **`rejecting`.** Today's engine has one, purely to swallow Zadarma's `return_timeout` retries
812
929
  * for 35 s after a decline (SPEC §3.4). That is a PROVIDER quirk — it belongs in
813
- * `providers/zadarma.ts` as `quirks.rejectGuardMs`, not in the shared state machine. A rejected
930
+ * `providers/zadarma.ts` as `quirks.declinedCallerGuardMs`, not in the shared state machine. A rejected
814
931
  * call is simply `ended`. Our own SIP infrastructure would not need it at all (SPEC §2.3).
815
932
  *
816
933
  * - **A pre-INVITE state.** A Call exists only once an INVITE has landed. The "user answered
@@ -936,4 +1053,4 @@ interface Call extends TypedEmitter<CallEventMap> {
936
1053
  cancelConsult(): Promise<void>;
937
1054
  }
938
1055
 
939
- export { isFatal as A, isTerminal as B, CALL_TRANSITIONS as C, type ContactsSource as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HistorySource as G, type HangupReason as H, INITIAL_STATES as I, type AgentsDirectory as J, type TransferOutcomesPage as K, type LogLevel as L, type PresenceSource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type CallHistoryEntry as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type ContactsPage as U, type VoiceToken as V, type Call as a, type CallDirection as b, CallEvent as c, type CallEventMap as d, type CallOptions as e, type CallState as f, type Contact as g, type FatalSoftphoneError as h, type HoldOriginator as i, type NonFatalSoftphoneError as j, type OutboundNumber as k, type OutboundNumberKind as l, type OutboundNumberSettings as m, type OutboundNumberState as n, type OutboundNumbersSource as o, type RingtoneOption as p, type RingtoneSettings as q, type RingtoneSource as r, type SoftphoneError as s, type SoftphoneErrorCode as t, SoftphoneEvent as u, type SoftphoneEventMap as v, type SoftphoneState as w, type TokenSource as x, type TypedEmitter as y, canTransition as z };
1056
+ export { type TokenSource as A, type TypedEmitter as B, CALL_TRANSITIONS as C, canTransition as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, isFatal as G, type HangupReason as H, INITIAL_STATES as I, isTerminal as J, type ContactsSource as K, type LineClaim as L, type HistorySource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type AgentsDirectory as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type TransferOutcomesPage as U, type VoiceToken as V, type PresenceSource as W, type CallHistoryEntry as X, type ContactsPage as Y, type CallOffer as Z, type Call as a, type CallDirection as b, CallEvent as c, type CallEventMap as d, type CallOptions as e, type CallState as f, type Contact as g, type FatalSoftphoneError as h, type HoldOriginator as i, type LineLocation as j, type LineState as k, type LogLevel as l, type NonFatalSoftphoneError as m, type OutboundNumber as n, type OutboundNumberKind as o, type OutboundNumberSettings as p, type OutboundNumberState as q, type OutboundNumbersSource as r, type RingtoneOption as s, type RingtoneSettings as t, type RingtoneSource as u, type SoftphoneError as v, type SoftphoneErrorCode as w, SoftphoneEvent as x, type SoftphoneEventMap as y, type SoftphoneState as z };