@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.
- package/dist/{call-C6YpT6uR.d.cts → call-FqcvDg1t.d.cts} +105 -12
- package/dist/{call-C6YpT6uR.d.ts → call-FqcvDg1t.d.ts} +105 -12
- package/dist/{chunk-EYEDPD7Y.js → chunk-6RR6G35E.js} +92 -17
- package/dist/chunk-6RR6G35E.js.map +1 -0
- package/dist/{chunk-2ZSOGGPO.js → chunk-FRXHEHQA.js} +15 -2
- package/dist/chunk-FRXHEHQA.js.map +1 -0
- package/dist/{chunk-UHNLEBBO.js → chunk-I3RSC52R.js} +28 -3
- package/dist/chunk-I3RSC52R.js.map +1 -0
- package/dist/chunk-M76SAXXM.js +13 -0
- package/dist/chunk-M76SAXXM.js.map +1 -0
- package/dist/{chunk-7R6LOOAI.js → chunk-N4PLEYFJ.js} +4 -2
- package/dist/chunk-N4PLEYFJ.js.map +1 -0
- package/dist/element/index.d.ts +142 -6
- package/dist/element/index.js +138 -43
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-DPft99Wj.d.ts → engine-CsNRD7B7.d.ts} +1 -1
- package/dist/{engine-CDdaSojx.d.cts → engine-CzOCrfY9.d.cts} +1 -1
- package/dist/index.cjs +579 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +447 -37
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +26 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +24 -5
- package/dist/react/index.d.ts +24 -5
- package/dist/react/index.js +2 -2
- package/dist/{softphone-CY5d2WWY.d.cts → softphone-By9sizlV.d.ts} +42 -3
- package/dist/{softphone-DXgmTyrX.d.ts → softphone-JZvJdzU3.d.cts} +42 -3
- package/dist/{sources-ONYQW733.js → sources-QL2LRZVH.js} +19 -2
- package/dist/sources-QL2LRZVH.js.map +1 -0
- package/dist/testing/index.cjs +13 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/dist/ui/index.cjs +92 -2
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +2 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +69 -4
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.cjs +111 -14
- package/dist/zadarma/index.cjs.map +1 -1
- package/dist/zadarma/index.d.cts +5 -6
- package/dist/zadarma/index.d.ts +5 -6
- package/dist/zadarma/index.js +3 -2
- package/dist/zadarma-engine-TIUE7PZ7.js +6 -0
- package/dist/{zadarma-engine-L5DEFXUS.js.map → zadarma-engine-TIUE7PZ7.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2ZSOGGPO.js.map +0 -1
- package/dist/chunk-7R6LOOAI.js.map +0 -1
- package/dist/chunk-EYEDPD7Y.js.map +0 -1
- package/dist/chunk-UHNLEBBO.js.map +0 -1
- package/dist/sources-ONYQW733.js.map +0 -1
- package/dist/zadarma-engine-L5DEFXUS.js +0 -5
|
@@ -86,10 +86,10 @@ interface TokenEndpointOptions {
|
|
|
86
86
|
*/
|
|
87
87
|
interface CoventiaBackendConfig {
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
138
|
-
*
|
|
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,44 @@ 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).
|
|
210
|
+
*/
|
|
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()`.
|
|
191
218
|
*/
|
|
192
|
-
type
|
|
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
|
+
/** The answer to `PresenceConnection.claimLine()`. */
|
|
227
|
+
interface LineClaimResult {
|
|
228
|
+
readonly ok: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Why it was refused: `'on_call'` while the user is on a call (the line never moves during one). Any
|
|
231
|
+
* other value means nothing was decided. Absent when `ok`.
|
|
232
|
+
*/
|
|
233
|
+
readonly reason?: string;
|
|
234
|
+
}
|
|
193
235
|
interface ContactsPage {
|
|
194
236
|
readonly items: readonly Contact[];
|
|
195
237
|
/**
|
|
@@ -349,6 +391,18 @@ interface PresenceConnection {
|
|
|
349
391
|
* own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
|
|
350
392
|
*/
|
|
351
393
|
readonly bye?: () => void;
|
|
394
|
+
/**
|
|
395
|
+
* The line owner (`docs/specs/line-owner.md` §5.2): says which softphone this connection speaks for, so
|
|
396
|
+
* the server can tell it whether it has the line — through `PresenceCallbacks.onLine`, right after this
|
|
397
|
+
* and on every change. Sent as the FIRST message of every connection. Optional: a source without it never
|
|
398
|
+
* takes part, and its softphone listens exactly as before (fail-open), reading its line as `unknown`.
|
|
399
|
+
*/
|
|
400
|
+
readonly hello?: (clientId: string) => void;
|
|
401
|
+
/**
|
|
402
|
+
* "Recibir aquí": asks for the line. Answered through `PresenceCallbacks.onClaimResult` — and, when it
|
|
403
|
+
* succeeds, by `onLine` saying `here` first. Optional, like `hello`.
|
|
404
|
+
*/
|
|
405
|
+
readonly claimLine?: () => void;
|
|
352
406
|
/**
|
|
353
407
|
* Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
|
|
354
408
|
* reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
|
|
@@ -368,6 +422,13 @@ interface PresenceCallbacks {
|
|
|
368
422
|
* identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
|
|
369
423
|
*/
|
|
370
424
|
readonly onClose: () => void;
|
|
425
|
+
/**
|
|
426
|
+
* The line, as the server says it — right after `PresenceConnection.hello`, and on every change after
|
|
427
|
+
* that. Optional, so callbacks written before the line owner existed still type-check.
|
|
428
|
+
*/
|
|
429
|
+
readonly onLine?: (update: LineUpdate) => void;
|
|
430
|
+
/** The answer to `PresenceConnection.claimLine()`. Optional, like `onLine`. */
|
|
431
|
+
readonly onClaimResult?: (result: LineClaimResult) => void;
|
|
371
432
|
}
|
|
372
433
|
/**
|
|
373
434
|
* Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
|
|
@@ -598,6 +659,16 @@ declare const ERROR_CATALOGUE: {
|
|
|
598
659
|
readonly message: "The provider does not support this operation.";
|
|
599
660
|
readonly solution: "Guard the UI on the operation before offering it.";
|
|
600
661
|
};
|
|
662
|
+
readonly 'line/claim-refused': {
|
|
663
|
+
readonly fatal: false;
|
|
664
|
+
readonly message: "The line was not moved here: the user is on a call on the softphone that has it.";
|
|
665
|
+
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.";
|
|
666
|
+
};
|
|
667
|
+
readonly 'line/unavailable': {
|
|
668
|
+
readonly fatal: false;
|
|
669
|
+
readonly message: "The line could not be taken: nothing decided where it is.";
|
|
670
|
+
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.";
|
|
671
|
+
};
|
|
601
672
|
readonly 'provider/provision-failed': {
|
|
602
673
|
readonly fatal: true;
|
|
603
674
|
readonly message: "The provider could not issue SIP credentials.";
|
|
@@ -686,10 +757,25 @@ declare enum SoftphoneEvent {
|
|
|
686
757
|
/**
|
|
687
758
|
* `softphone.presence` changed — the live agent-presence list (Phase B,
|
|
688
759
|
* `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
|
|
760
|
+
* `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being a
|
|
761
|
+
* started leader — `online`, or waiting in `standby` for the line; never fires at all otherwise,
|
|
762
|
+
* same reasoning as `TransferOutcome`.
|
|
691
763
|
*/
|
|
692
|
-
PresenceChanged = "presenceChanged"
|
|
764
|
+
PresenceChanged = "presenceChanged",
|
|
765
|
+
/**
|
|
766
|
+
* `softphone.line` / `lineWhere` changed (`docs/specs/line-owner.md` §5): this softphone got the line,
|
|
767
|
+
* lost it to another softphone of the same extension, or stopped knowing (`unknown` — it then listens,
|
|
768
|
+
* as before the line owner existed). A third axis beside `StateChanged` and `LeaderChanged`: losing the
|
|
769
|
+
* line is what takes the softphone to `standby`, but not at once while a call is in progress — the line
|
|
770
|
+
* can read `elsewhere` while the state is still `online`, until that call ends.
|
|
771
|
+
*/
|
|
772
|
+
LineChanged = "lineChanged",
|
|
773
|
+
/**
|
|
774
|
+
* `softphone.lineClaim` changed: an attempt to bring the line here started, was refused, failed, or ended
|
|
775
|
+
* (the line came here, or the softphone stopped). Kept apart from `LineChanged`, which only ever means the
|
|
776
|
+
* line itself moved.
|
|
777
|
+
*/
|
|
778
|
+
LineClaimChanged = "lineClaimChanged"
|
|
693
779
|
}
|
|
694
780
|
interface SoftphoneEventMap {
|
|
695
781
|
[SoftphoneEvent.StateChanged]: {
|
|
@@ -710,6 +796,13 @@ interface SoftphoneEventMap {
|
|
|
710
796
|
[SoftphoneEvent.PresenceChanged]: {
|
|
711
797
|
readonly agents: readonly PresenceEntry[];
|
|
712
798
|
};
|
|
799
|
+
[SoftphoneEvent.LineChanged]: {
|
|
800
|
+
readonly line: LineState;
|
|
801
|
+
readonly where: LineLocation | null;
|
|
802
|
+
};
|
|
803
|
+
[SoftphoneEvent.LineClaimChanged]: {
|
|
804
|
+
readonly claim: LineClaim;
|
|
805
|
+
};
|
|
713
806
|
}
|
|
714
807
|
declare enum CallEvent {
|
|
715
808
|
/** Any transition. The catch-all; the specific events below are conveniences. */
|
|
@@ -936,4 +1029,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
936
1029
|
cancelConsult(): Promise<void>;
|
|
937
1030
|
}
|
|
938
1031
|
|
|
939
|
-
export {
|
|
1032
|
+
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 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
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
138
|
-
*
|
|
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,44 @@ 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).
|
|
210
|
+
*/
|
|
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()`.
|
|
191
218
|
*/
|
|
192
|
-
type
|
|
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
|
+
/** The answer to `PresenceConnection.claimLine()`. */
|
|
227
|
+
interface LineClaimResult {
|
|
228
|
+
readonly ok: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Why it was refused: `'on_call'` while the user is on a call (the line never moves during one). Any
|
|
231
|
+
* other value means nothing was decided. Absent when `ok`.
|
|
232
|
+
*/
|
|
233
|
+
readonly reason?: string;
|
|
234
|
+
}
|
|
193
235
|
interface ContactsPage {
|
|
194
236
|
readonly items: readonly Contact[];
|
|
195
237
|
/**
|
|
@@ -349,6 +391,18 @@ interface PresenceConnection {
|
|
|
349
391
|
* own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
|
|
350
392
|
*/
|
|
351
393
|
readonly bye?: () => void;
|
|
394
|
+
/**
|
|
395
|
+
* The line owner (`docs/specs/line-owner.md` §5.2): says which softphone this connection speaks for, so
|
|
396
|
+
* the server can tell it whether it has the line — through `PresenceCallbacks.onLine`, right after this
|
|
397
|
+
* and on every change. Sent as the FIRST message of every connection. Optional: a source without it never
|
|
398
|
+
* takes part, and its softphone listens exactly as before (fail-open), reading its line as `unknown`.
|
|
399
|
+
*/
|
|
400
|
+
readonly hello?: (clientId: string) => void;
|
|
401
|
+
/**
|
|
402
|
+
* "Recibir aquí": asks for the line. Answered through `PresenceCallbacks.onClaimResult` — and, when it
|
|
403
|
+
* succeeds, by `onLine` saying `here` first. Optional, like `hello`.
|
|
404
|
+
*/
|
|
405
|
+
readonly claimLine?: () => void;
|
|
352
406
|
/**
|
|
353
407
|
* Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
|
|
354
408
|
* reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
|
|
@@ -368,6 +422,13 @@ interface PresenceCallbacks {
|
|
|
368
422
|
* identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
|
|
369
423
|
*/
|
|
370
424
|
readonly onClose: () => void;
|
|
425
|
+
/**
|
|
426
|
+
* The line, as the server says it — right after `PresenceConnection.hello`, and on every change after
|
|
427
|
+
* that. Optional, so callbacks written before the line owner existed still type-check.
|
|
428
|
+
*/
|
|
429
|
+
readonly onLine?: (update: LineUpdate) => void;
|
|
430
|
+
/** The answer to `PresenceConnection.claimLine()`. Optional, like `onLine`. */
|
|
431
|
+
readonly onClaimResult?: (result: LineClaimResult) => void;
|
|
371
432
|
}
|
|
372
433
|
/**
|
|
373
434
|
* Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
|
|
@@ -598,6 +659,16 @@ declare const ERROR_CATALOGUE: {
|
|
|
598
659
|
readonly message: "The provider does not support this operation.";
|
|
599
660
|
readonly solution: "Guard the UI on the operation before offering it.";
|
|
600
661
|
};
|
|
662
|
+
readonly 'line/claim-refused': {
|
|
663
|
+
readonly fatal: false;
|
|
664
|
+
readonly message: "The line was not moved here: the user is on a call on the softphone that has it.";
|
|
665
|
+
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.";
|
|
666
|
+
};
|
|
667
|
+
readonly 'line/unavailable': {
|
|
668
|
+
readonly fatal: false;
|
|
669
|
+
readonly message: "The line could not be taken: nothing decided where it is.";
|
|
670
|
+
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.";
|
|
671
|
+
};
|
|
601
672
|
readonly 'provider/provision-failed': {
|
|
602
673
|
readonly fatal: true;
|
|
603
674
|
readonly message: "The provider could not issue SIP credentials.";
|
|
@@ -686,10 +757,25 @@ declare enum SoftphoneEvent {
|
|
|
686
757
|
/**
|
|
687
758
|
* `softphone.presence` changed — the live agent-presence list (Phase B,
|
|
688
759
|
* `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
|
|
760
|
+
* `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being a
|
|
761
|
+
* started leader — `online`, or waiting in `standby` for the line; never fires at all otherwise,
|
|
762
|
+
* same reasoning as `TransferOutcome`.
|
|
691
763
|
*/
|
|
692
|
-
PresenceChanged = "presenceChanged"
|
|
764
|
+
PresenceChanged = "presenceChanged",
|
|
765
|
+
/**
|
|
766
|
+
* `softphone.line` / `lineWhere` changed (`docs/specs/line-owner.md` §5): this softphone got the line,
|
|
767
|
+
* lost it to another softphone of the same extension, or stopped knowing (`unknown` — it then listens,
|
|
768
|
+
* as before the line owner existed). A third axis beside `StateChanged` and `LeaderChanged`: losing the
|
|
769
|
+
* line is what takes the softphone to `standby`, but not at once while a call is in progress — the line
|
|
770
|
+
* can read `elsewhere` while the state is still `online`, until that call ends.
|
|
771
|
+
*/
|
|
772
|
+
LineChanged = "lineChanged",
|
|
773
|
+
/**
|
|
774
|
+
* `softphone.lineClaim` changed: an attempt to bring the line here started, was refused, failed, or ended
|
|
775
|
+
* (the line came here, or the softphone stopped). Kept apart from `LineChanged`, which only ever means the
|
|
776
|
+
* line itself moved.
|
|
777
|
+
*/
|
|
778
|
+
LineClaimChanged = "lineClaimChanged"
|
|
693
779
|
}
|
|
694
780
|
interface SoftphoneEventMap {
|
|
695
781
|
[SoftphoneEvent.StateChanged]: {
|
|
@@ -710,6 +796,13 @@ interface SoftphoneEventMap {
|
|
|
710
796
|
[SoftphoneEvent.PresenceChanged]: {
|
|
711
797
|
readonly agents: readonly PresenceEntry[];
|
|
712
798
|
};
|
|
799
|
+
[SoftphoneEvent.LineChanged]: {
|
|
800
|
+
readonly line: LineState;
|
|
801
|
+
readonly where: LineLocation | null;
|
|
802
|
+
};
|
|
803
|
+
[SoftphoneEvent.LineClaimChanged]: {
|
|
804
|
+
readonly claim: LineClaim;
|
|
805
|
+
};
|
|
713
806
|
}
|
|
714
807
|
declare enum CallEvent {
|
|
715
808
|
/** Any transition. The catch-all; the specific events below are conveniences. */
|
|
@@ -936,4 +1029,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
936
1029
|
cancelConsult(): Promise<void>;
|
|
937
1030
|
}
|
|
938
1031
|
|
|
939
|
-
export {
|
|
1032
|
+
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 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 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeApiBase } from './chunk-M76SAXXM.js';
|
|
2
|
+
import { createError, isSoftphoneError } from './chunk-FRXHEHQA.js';
|
|
2
3
|
import { cachedTokenSource, invalidateToken } from './chunk-MHOJHG5E.js';
|
|
3
4
|
|
|
4
5
|
// src/transports/sip/media.ts
|
|
@@ -254,9 +255,22 @@ var SipCallEngine = class {
|
|
|
254
255
|
provider;
|
|
255
256
|
factory;
|
|
256
257
|
#state = "stopped";
|
|
258
|
+
/**
|
|
259
|
+
* Which `start()`…`stop()` cycle this is — bumped by `stop()`, which ends one. Everything that awaits
|
|
260
|
+
* and then acts (builds a UA, subscribes to the signal, sets the state) captures it first and gives up
|
|
261
|
+
* if a stop has happened since. Not bumped by `start()`: that would cancel work begun just before it,
|
|
262
|
+
* such as a call placed while the softphone is still starting.
|
|
263
|
+
*
|
|
264
|
+
* Without it a stopped phone came back to life: a provisioning or a signal connect still in flight
|
|
265
|
+
* when `stop()` ran finished afterwards and REGISTERed — and since the PBX routes a call to whichever
|
|
266
|
+
* client registered last, it quietly took calls from the user's other device while its UI said off.
|
|
267
|
+
*/
|
|
268
|
+
#run = 0;
|
|
257
269
|
#ua = null;
|
|
258
270
|
/** In-flight `#createUA()`. THE concurrency guard — see `#ensureUA`. */
|
|
259
271
|
#uaInFlight = null;
|
|
272
|
+
/** Calls waiting for the current UA to register (`#callWhenRegistered`), failed by `#teardownUA`. */
|
|
273
|
+
#awaitingRegistration = /* @__PURE__ */ new Set();
|
|
260
274
|
#domain = "";
|
|
261
275
|
#activeCall = null;
|
|
262
276
|
/**
|
|
@@ -339,14 +353,25 @@ var SipCallEngine = class {
|
|
|
339
353
|
for (const listener of [...this.#errorListeners]) listener(error);
|
|
340
354
|
}
|
|
341
355
|
async start() {
|
|
356
|
+
const run = this.#run;
|
|
342
357
|
this.#setState("starting");
|
|
343
358
|
if (this.provider.registrationMode === "persistent") {
|
|
344
|
-
|
|
359
|
+
try {
|
|
360
|
+
await this.#ensureUA();
|
|
361
|
+
} catch (cause) {
|
|
362
|
+
if (run === this.#run) throw cause;
|
|
363
|
+
}
|
|
364
|
+
if (run !== this.#run) return;
|
|
345
365
|
this.#setState("online");
|
|
346
366
|
} else {
|
|
347
367
|
const signal = this.provider.incomingSignal;
|
|
348
368
|
if (signal) {
|
|
349
|
-
|
|
369
|
+
try {
|
|
370
|
+
await signal.connect();
|
|
371
|
+
} catch (cause) {
|
|
372
|
+
if (run === this.#run) throw cause;
|
|
373
|
+
}
|
|
374
|
+
if (run !== this.#run) return;
|
|
350
375
|
this.#signalUnsub = signal.onSignal(() => void this.#onIncomingSignal());
|
|
351
376
|
this.#signalConnectionUnsub = signal.onConnectionChange?.(
|
|
352
377
|
(connected) => this.#setState(connected ? "online" : "offline")
|
|
@@ -356,6 +381,7 @@ var SipCallEngine = class {
|
|
|
356
381
|
}
|
|
357
382
|
}
|
|
358
383
|
async stop() {
|
|
384
|
+
this.#run += 1;
|
|
359
385
|
this.#clearNoInviteTimer();
|
|
360
386
|
this.#signalUnsub?.();
|
|
361
387
|
this.#signalUnsub = null;
|
|
@@ -374,11 +400,12 @@ var SipCallEngine = class {
|
|
|
374
400
|
async #onIncomingSignal() {
|
|
375
401
|
if (this.now() < this.#rejectGuardUntil) return;
|
|
376
402
|
if (this.#activeCall) return;
|
|
403
|
+
const run = this.#run;
|
|
377
404
|
try {
|
|
378
405
|
await this.#ensureUA();
|
|
379
406
|
this.#armNoInviteTeardown();
|
|
380
407
|
} catch {
|
|
381
|
-
this.#teardownUA();
|
|
408
|
+
if (run === this.#run) this.#teardownUA();
|
|
382
409
|
}
|
|
383
410
|
}
|
|
384
411
|
#armNoInviteTeardown() {
|
|
@@ -440,12 +467,16 @@ var SipCallEngine = class {
|
|
|
440
467
|
*/
|
|
441
468
|
async #ensureUA() {
|
|
442
469
|
if (this.#ua) return this.#ua;
|
|
443
|
-
this.#uaInFlight
|
|
444
|
-
this.#
|
|
445
|
-
|
|
470
|
+
if (!this.#uaInFlight) {
|
|
471
|
+
const inFlight = this.#createUA().finally(() => {
|
|
472
|
+
if (this.#uaInFlight === inFlight) this.#uaInFlight = null;
|
|
473
|
+
});
|
|
474
|
+
this.#uaInFlight = inFlight;
|
|
475
|
+
}
|
|
446
476
|
return this.#uaInFlight;
|
|
447
477
|
}
|
|
448
478
|
async #createUA() {
|
|
479
|
+
const run = this.#run;
|
|
449
480
|
const factory = await this.#resolveFactory();
|
|
450
481
|
let credentials;
|
|
451
482
|
try {
|
|
@@ -455,17 +486,28 @@ var SipCallEngine = class {
|
|
|
455
486
|
else this.#reportError("provider/provision-failed", cause);
|
|
456
487
|
throw cause;
|
|
457
488
|
}
|
|
489
|
+
if (run !== this.#run) throw createError("engine/not-started");
|
|
458
490
|
this.#domain = domainOf(credentials);
|
|
459
491
|
const config = this.#uaConfig(credentials, factory);
|
|
460
492
|
const ua = factory.createUA(config);
|
|
461
|
-
ua.on("newRTCSession", ({ session, originator }) =>
|
|
493
|
+
ua.on("newRTCSession", ({ session, originator }) => {
|
|
494
|
+
if (ua !== this.#ua) {
|
|
495
|
+
if (originator === "remote") session.terminate({ status_code: 486, reason_phrase: "Busy Here" });
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.#onNewSession(session, originator);
|
|
499
|
+
});
|
|
462
500
|
ua.on("disconnected", ({ error }) => {
|
|
501
|
+
if (ua !== this.#ua) return;
|
|
463
502
|
if (!error) return;
|
|
464
503
|
this.#setState("reconnecting");
|
|
465
504
|
this.#reportError("net/transport-failed", error);
|
|
466
505
|
});
|
|
467
|
-
ua.on("connected", () =>
|
|
506
|
+
ua.on("connected", () => {
|
|
507
|
+
if (ua === this.#ua) this.#setState("online");
|
|
508
|
+
});
|
|
468
509
|
ua.on("registrationFailed", (e) => {
|
|
510
|
+
if (run !== this.#run) return;
|
|
469
511
|
const detail = e;
|
|
470
512
|
this.#reportError("net/registration-failed", {
|
|
471
513
|
cause: detail?.cause,
|
|
@@ -473,7 +515,7 @@ var SipCallEngine = class {
|
|
|
473
515
|
domain: this.#domain,
|
|
474
516
|
hint: "A bare 401 with no auth challenge usually means the registrar is in the wrong datacenter (SPEC \xA73.1)."
|
|
475
517
|
});
|
|
476
|
-
this.#teardownUA();
|
|
518
|
+
if (ua === this.#ua) this.#teardownUA();
|
|
477
519
|
void this.#refreshSignal();
|
|
478
520
|
});
|
|
479
521
|
this.#ua = ua;
|
|
@@ -500,6 +542,7 @@ var SipCallEngine = class {
|
|
|
500
542
|
#teardownUA() {
|
|
501
543
|
this.#uaInFlight = null;
|
|
502
544
|
if (!this.#ua) return;
|
|
545
|
+
for (const fail of [...this.#awaitingRegistration]) fail();
|
|
503
546
|
try {
|
|
504
547
|
this.#ua.terminateSessions({ status_code: 487 });
|
|
505
548
|
this.#ua.stop();
|
|
@@ -523,8 +566,27 @@ var SipCallEngine = class {
|
|
|
523
566
|
#callWhenRegistered(ua, target) {
|
|
524
567
|
const options = { mediaConstraints: buildAudioConstraints(this.#inputDeviceId), pcConfig: PC_CONFIG };
|
|
525
568
|
if (ua.isRegistered()) return Promise.resolve(ua.call(target, options));
|
|
526
|
-
|
|
527
|
-
|
|
569
|
+
const run = this.#run;
|
|
570
|
+
return new Promise((resolve, reject) => {
|
|
571
|
+
let settled = false;
|
|
572
|
+
const fail = () => {
|
|
573
|
+
if (settled) return;
|
|
574
|
+
settled = true;
|
|
575
|
+
this.#awaitingRegistration.delete(fail);
|
|
576
|
+
reject(createError(run === this.#run ? "call/failed" : "engine/not-started"));
|
|
577
|
+
};
|
|
578
|
+
this.#awaitingRegistration.add(fail);
|
|
579
|
+
ua.on("registered", () => {
|
|
580
|
+
if (settled) return;
|
|
581
|
+
if (ua !== this.#ua) return fail();
|
|
582
|
+
settled = true;
|
|
583
|
+
this.#awaitingRegistration.delete(fail);
|
|
584
|
+
try {
|
|
585
|
+
resolve(ua.call(target, options));
|
|
586
|
+
} catch (cause) {
|
|
587
|
+
reject(cause);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
528
590
|
});
|
|
529
591
|
}
|
|
530
592
|
#wrapCall(session) {
|
|
@@ -664,6 +726,12 @@ var ZadarmaPushSignal = class {
|
|
|
664
726
|
#retryTimer;
|
|
665
727
|
#unwatchBackground;
|
|
666
728
|
#closed = false;
|
|
729
|
+
/**
|
|
730
|
+
* Bumped by `disconnect()`. An open or a rebuild still awaiting its key when the signal is disconnected
|
|
731
|
+
* must build nothing afterwards — and `#closed` alone cannot say so once a later `connect()` (stop, then
|
|
732
|
+
* start again) has reset it: the late socket was built beside the new one, and nothing ever closed it.
|
|
733
|
+
*/
|
|
734
|
+
#run = 0;
|
|
667
735
|
#listeners = /* @__PURE__ */ new Set();
|
|
668
736
|
#connectionListeners = /* @__PURE__ */ new Set();
|
|
669
737
|
async connect() {
|
|
@@ -673,8 +741,9 @@ var ZadarmaPushSignal = class {
|
|
|
673
741
|
await this.#open();
|
|
674
742
|
}
|
|
675
743
|
async #open() {
|
|
744
|
+
const run = this.#run;
|
|
676
745
|
const { key, sip, keyExpiresAt } = await this.deps.fetchKey();
|
|
677
|
-
if (this.#
|
|
746
|
+
if (run !== this.#run) return;
|
|
678
747
|
const socket = this.deps.createPushSocket(buildPushUrl(key, sip));
|
|
679
748
|
this.#socket = socket;
|
|
680
749
|
this.#attach(socket);
|
|
@@ -769,9 +838,10 @@ var ZadarmaPushSignal = class {
|
|
|
769
838
|
return;
|
|
770
839
|
}
|
|
771
840
|
this.#rebuilding = true;
|
|
841
|
+
const run = this.#run;
|
|
772
842
|
try {
|
|
773
843
|
const { key, sip, keyExpiresAt } = await this.deps.fetchKey();
|
|
774
|
-
if (this.#
|
|
844
|
+
if (run !== this.#run) return;
|
|
775
845
|
const swapped = await new Promise((resolve) => {
|
|
776
846
|
const socket = this.deps.createPushSocket(buildPushUrl(key, sip));
|
|
777
847
|
this.#candidate = socket;
|
|
@@ -824,6 +894,7 @@ var ZadarmaPushSignal = class {
|
|
|
824
894
|
}
|
|
825
895
|
disconnect() {
|
|
826
896
|
this.#closed = true;
|
|
897
|
+
this.#run += 1;
|
|
827
898
|
this.#stopPings();
|
|
828
899
|
clearTimeout(this.#refreshTimer);
|
|
829
900
|
this.#refreshTimer = void 0;
|
|
@@ -1061,11 +1132,15 @@ function makeZadarmaBrowserDeps(config) {
|
|
|
1061
1132
|
// src/transports/zadarma-engine.ts
|
|
1062
1133
|
function createZadarmaEngine(config) {
|
|
1063
1134
|
const provider = new ZadarmaProvider(
|
|
1064
|
-
makeZadarmaBrowserDeps({
|
|
1135
|
+
makeZadarmaBrowserDeps({
|
|
1136
|
+
...config,
|
|
1137
|
+
apiBase: normalizeApiBase(config.apiBase),
|
|
1138
|
+
tokenSource: cachedTokenSource(config.tokenSource)
|
|
1139
|
+
})
|
|
1065
1140
|
);
|
|
1066
1141
|
return new SipCallEngine(provider, loadJsSipFactory);
|
|
1067
1142
|
}
|
|
1068
1143
|
|
|
1069
1144
|
export { ZADARMA_QUIRKS, createZadarmaEngine };
|
|
1070
|
-
//# sourceMappingURL=chunk-
|
|
1071
|
-
//# sourceMappingURL=chunk-
|
|
1145
|
+
//# sourceMappingURL=chunk-6RR6G35E.js.map
|
|
1146
|
+
//# sourceMappingURL=chunk-6RR6G35E.js.map
|