@coventia/softphone 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{call-BTYlT2SU.d.cts → call-VKq1I8yP.d.cts} +158 -5
- package/dist/{call-BTYlT2SU.d.ts → call-VKq1I8yP.d.ts} +158 -5
- package/dist/{chunk-N2N2EQQJ.js → chunk-7R6LOOAI.js} +4 -2
- package/dist/chunk-7R6LOOAI.js.map +1 -0
- package/dist/{chunk-5BGEHMPP.js → chunk-EYEDPD7Y.js} +146 -20
- package/dist/chunk-EYEDPD7Y.js.map +1 -0
- package/dist/{chunk-3V2L6UTK.js → chunk-LMJXX4YM.js} +17 -3
- package/dist/chunk-LMJXX4YM.js.map +1 -0
- package/dist/element/index.d.ts +162 -7
- package/dist/element/index.js +866 -421
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-D9nbeY08.d.cts → engine-BlQBo2Ys.d.cts} +1 -1
- package/dist/{engine-Doa6Brkm.d.ts → engine-C0BOkIcW.d.ts} +1 -1
- package/dist/index.cjs +586 -28
- 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 +335 -8
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +15 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +14 -3
- package/dist/react/index.d.ts +14 -3
- package/dist/react/index.js +2 -2
- package/dist/{softphone-DhuhcKCy.d.cts → softphone-Cyq3hiIM.d.ts} +17 -2
- package/dist/{softphone-DHmhotbf.d.ts → softphone-DlGV12Xp.d.cts} +17 -2
- package/dist/softphone.css +477 -35
- package/dist/{sources-N4BOEDRL.js → sources-IRKNQPWW.js} +108 -5
- package/dist/sources-IRKNQPWW.js.map +1 -0
- package/dist/testing/index.cjs +41 -5
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +8 -2
- package/dist/testing/index.d.ts +8 -2
- package/dist/testing/index.js +41 -5
- package/dist/testing/index.js.map +1 -1
- package/dist/ui/index.cjs +846 -410
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +17 -6
- package/dist/ui/index.d.ts +17 -6
- package/dist/ui/index.js +878 -456
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.cjs +144 -18
- package/dist/zadarma/index.cjs.map +1 -1
- package/dist/zadarma/index.d.cts +2 -2
- package/dist/zadarma/index.d.ts +2 -2
- package/dist/zadarma/index.js +1 -1
- package/dist/zadarma-engine-L5DEFXUS.js +5 -0
- package/dist/{zadarma-engine-DN2SZSQ3.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3V2L6UTK.js.map +0 -1
- package/dist/chunk-5BGEHMPP.js.map +0 -1
- package/dist/chunk-N2N2EQQJ.js.map +0 -1
- package/dist/sources-N4BOEDRL.js.map +0 -1
- package/dist/zadarma-engine-DN2SZSQ3.js +0 -5
|
@@ -117,6 +117,19 @@ interface CoventiaBackendConfig {
|
|
|
117
117
|
* (SPEC §6.6).
|
|
118
118
|
*/
|
|
119
119
|
readonly contacts?: ContactsSource;
|
|
120
|
+
/**
|
|
121
|
+
* Transfer outcomes, overriding Coventia's own polling — for a `MockProvider`-style test double, or
|
|
122
|
+
* a future non-Zadarma provider that computes outcomes differently. Not a per-partner customisation
|
|
123
|
+
* point the way `contacts` is: unlike a contact list, a transfer outcome is not a partner's own
|
|
124
|
+
* data, so there is normally no reason to set this (`docs/specs/transfer-outcome-relay.md`, Phase A).
|
|
125
|
+
*/
|
|
126
|
+
readonly transferOutcomes?: TransferOutcomeSource;
|
|
127
|
+
/**
|
|
128
|
+
* Live agent presence, overriding Coventia's own WebSocket — for a `MockProvider`-style test
|
|
129
|
+
* double, or a host with its own presence transport. Not a per-partner customisation point the way
|
|
130
|
+
* `contacts` is (`docs/specs/transfer-presence-picker.md`, Phase B).
|
|
131
|
+
*/
|
|
132
|
+
readonly presence?: PresenceSource;
|
|
120
133
|
}
|
|
121
134
|
interface SoftphoneConfig {
|
|
122
135
|
readonly tokenSource: TokenSource;
|
|
@@ -261,6 +274,104 @@ interface AgentsDirectory {
|
|
|
261
274
|
readonly me: PhoneAgent | null;
|
|
262
275
|
readonly people: readonly PhoneAgent[];
|
|
263
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* `'connected'` | `'not-connected'` — whether an attended transfer's second ring, after `Completar`,
|
|
279
|
+
* actually reached the target. Kebab-case to match `HangupReason` (`'no-answer'`, `'transferred'`);
|
|
280
|
+
* the backend's own wire value stays `not_connected` — this is the one place that gets translated.
|
|
281
|
+
*/
|
|
282
|
+
type TransferOutcomeStatus = 'connected' | 'not-connected';
|
|
283
|
+
/** One resolved transfer, as `GET /phone/transfers/outcomes` reports it. */
|
|
284
|
+
interface TransferOutcomeEntry {
|
|
285
|
+
/** Opaque, strictly increasing per organization, never re-issued. The cursor for the NEXT poll. */
|
|
286
|
+
readonly id: string;
|
|
287
|
+
/** The extension the call was transferred to. */
|
|
288
|
+
readonly target: string;
|
|
289
|
+
readonly status: TransferOutcomeStatus;
|
|
290
|
+
readonly at: Date;
|
|
291
|
+
}
|
|
292
|
+
interface TransferOutcomesPage {
|
|
293
|
+
/** The newest `id` currently retained, or `'0'` when there are none — pass back as `since`. */
|
|
294
|
+
readonly cursor: string;
|
|
295
|
+
readonly outcomes: readonly TransferOutcomeEntry[];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Poll for transfer outcomes. `since` omitted ⇒ everything currently retained — used for the FIRST
|
|
299
|
+
* poll after starting a watch, which exists only to learn the current cursor (spec §5.3's
|
|
300
|
+
* no-mis-attribution rule: that first poll's own outcomes are discarded, only `cursor` is kept).
|
|
301
|
+
*/
|
|
302
|
+
type TransferOutcomeSource = (params: {
|
|
303
|
+
readonly since?: string;
|
|
304
|
+
}) => Promise<TransferOutcomesPage>;
|
|
305
|
+
/**
|
|
306
|
+
* `SoftphoneEvent.TransferOutcome`'s payload. Unlike `TransferOutcomeEntry`, this never carries `id`
|
|
307
|
+
* (a polling-internal concern) and adds `caller` — the backend never stores a caller number (spec
|
|
308
|
+
* §5.3: "no caller number, nothing about other users"), so this is filled in locally, from the `Call`
|
|
309
|
+
* that was transferred, at the moment an outcome is matched to it.
|
|
310
|
+
*/
|
|
311
|
+
interface TransferOutcomeEvent {
|
|
312
|
+
readonly status: TransferOutcomeStatus;
|
|
313
|
+
readonly target: string;
|
|
314
|
+
/** The original caller's number, for "Llamar de vuelta" — null if it was never known. */
|
|
315
|
+
readonly caller: string | null;
|
|
316
|
+
readonly at: Date;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* `'disponible'` | `'en_llamada'` | `'desconectado'` — Spanish because this is what the picker shows,
|
|
320
|
+
* not an internal code (spec §4.1). `derivePresence` (the backend's own reducer, mirrored nowhere in
|
|
321
|
+
* the SDK — the SDK only ever receives an already-derived state, never `onCall`/`onlineFresh` apart).
|
|
322
|
+
*/
|
|
323
|
+
type PresenceState = 'disponible' | 'en_llamada' | 'desconectado';
|
|
324
|
+
/** One org member with a phone extension, as the picker lists them. */
|
|
325
|
+
interface PresenceEntry {
|
|
326
|
+
readonly userId: string;
|
|
327
|
+
readonly extension: string;
|
|
328
|
+
/** `null` when the backend has no name to offer — the picker falls back to the extension. */
|
|
329
|
+
readonly name: string | null;
|
|
330
|
+
readonly state: PresenceState;
|
|
331
|
+
}
|
|
332
|
+
/** What a live presence connection hands back once open. */
|
|
333
|
+
interface PresenceConnection {
|
|
334
|
+
/**
|
|
335
|
+
* Report this softphone's own on-call-ness, so a colleague transferring TO this agent sees it
|
|
336
|
+
* without a round trip through the webhook path (spec §4.1's `onCall` OR-combine). Calling this
|
|
337
|
+
* before the connection is genuinely open is never required of an implementation — a caller (see
|
|
338
|
+
* `presence-socket.ts`) that reports early is expected to wait for the connection to exist first.
|
|
339
|
+
*/
|
|
340
|
+
readonly reportCallState: (onCall: boolean) => void;
|
|
341
|
+
/**
|
|
342
|
+
* Says this phone is going away ON PURPOSE (the user turned it off, the app is quitting), so the backend
|
|
343
|
+
* drops it at once instead of waiting out the close grace it gives every ordinary close. Optional: a host's
|
|
344
|
+
* own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
|
|
345
|
+
*/
|
|
346
|
+
readonly bye?: () => void;
|
|
347
|
+
/**
|
|
348
|
+
* Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
|
|
349
|
+
* reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
|
|
350
|
+
* ticket), which is the only thing a caller should ever treat as "reconnect now".
|
|
351
|
+
*/
|
|
352
|
+
readonly close: () => void;
|
|
353
|
+
}
|
|
354
|
+
interface PresenceCallbacks {
|
|
355
|
+
/** Once, right after connecting — the full current roster (spec §4.3). */
|
|
356
|
+
readonly onSnapshot: (agents: readonly PresenceEntry[]) => void;
|
|
357
|
+
/** Zero or more times after that — one agent whose state just changed. */
|
|
358
|
+
readonly onDelta: (agent: PresenceEntry) => void;
|
|
359
|
+
/**
|
|
360
|
+
* The connection ended on its own. Never called after the CALLER's own `close()` — see
|
|
361
|
+
* `PresenceConnection.close`'s own note. The only signal a watch layer needs to know it must
|
|
362
|
+
* reconnect, deliberately carrying no reason: a dropped connection and a rejected ticket look
|
|
363
|
+
* identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
|
|
364
|
+
*/
|
|
365
|
+
readonly onClose: () => void;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
|
|
369
|
+
* `wss://…/phone/presence`); a host may override it entirely, or omit it — omitted, or while the
|
|
370
|
+
* connection is down, the picker simply has no live data to show (spec §4.4, success criterion #5:
|
|
371
|
+
* no error surfaces). Unlike `TransferOutcomeSource` (poll, get a page, done), this is push-based and
|
|
372
|
+
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
|
+
*/
|
|
374
|
+
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
264
375
|
|
|
265
376
|
/**
|
|
266
377
|
* The error taxonomy.
|
|
@@ -456,7 +567,23 @@ declare enum SoftphoneEvent {
|
|
|
456
567
|
* `StateChanged`: `state` is reachability, this is whether this tab is even allowed to be
|
|
457
568
|
* reachable at all while another tab holding the same extension stays open.
|
|
458
569
|
*/
|
|
459
|
-
LeaderChanged = "leaderChanged"
|
|
570
|
+
LeaderChanged = "leaderChanged",
|
|
571
|
+
/**
|
|
572
|
+
* `Completar` completed a transfer, and the target's SECOND ring either connected or didn't —
|
|
573
|
+
* `docs/specs/transfer-outcome-relay.md` (Phase A). Fires for BOTH outcomes so a host can react to
|
|
574
|
+
* either; only `'not-connected'` is worth surfacing to the agent (spec §5.5) — roughly a minute
|
|
575
|
+
* after the transfer for a target who never answers, seconds for a decline or the caller hanging up
|
|
576
|
+
* first. Requires a `TransferOutcomeSource` (from `coventia`/`apiBase`, or a host override); never
|
|
577
|
+
* fires at all when none is configured, since there is then nothing to poll.
|
|
578
|
+
*/
|
|
579
|
+
TransferOutcome = "transferOutcome",
|
|
580
|
+
/**
|
|
581
|
+
* `softphone.presence` changed — the live agent-presence list (Phase B,
|
|
582
|
+
* `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
|
|
583
|
+
* `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
|
|
584
|
+
* `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
|
|
585
|
+
*/
|
|
586
|
+
PresenceChanged = "presenceChanged"
|
|
460
587
|
}
|
|
461
588
|
interface SoftphoneEventMap {
|
|
462
589
|
[SoftphoneEvent.StateChanged]: {
|
|
@@ -473,6 +600,10 @@ interface SoftphoneEventMap {
|
|
|
473
600
|
[SoftphoneEvent.LeaderChanged]: {
|
|
474
601
|
readonly isLeader: boolean;
|
|
475
602
|
};
|
|
603
|
+
[SoftphoneEvent.TransferOutcome]: TransferOutcomeEvent;
|
|
604
|
+
[SoftphoneEvent.PresenceChanged]: {
|
|
605
|
+
readonly agents: readonly PresenceEntry[];
|
|
606
|
+
};
|
|
476
607
|
}
|
|
477
608
|
declare enum CallEvent {
|
|
478
609
|
/** Any transition. The catch-all; the specific events below are conveniences. */
|
|
@@ -663,18 +794,40 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
663
794
|
hold(): Promise<void>;
|
|
664
795
|
unhold(): Promise<void>;
|
|
665
796
|
sendDigits(digits: string): void;
|
|
666
|
-
/**
|
|
797
|
+
/**
|
|
798
|
+
* Blind transfer (SIP REFER). Ends this call — but **whether the transfer itself succeeded or
|
|
799
|
+
* failed is unknowable from here**: against Zadarma, a REFER's outcome is not reported back to the
|
|
800
|
+
* browser at all (confirmed on real calls — `docs/specs/transfer-availability.md` §2), so a REFER to
|
|
801
|
+
* an unreachable target ends this call exactly the same way a successful one does. Prefer
|
|
802
|
+
* `startConsult`/`completeTransfer` (below) when the caller must not be put at risk — that path
|
|
803
|
+
* correctly reports busy/no-answer/failed because the consult leg is an ordinary outbound call, not
|
|
804
|
+
* a REFER.
|
|
805
|
+
*/
|
|
667
806
|
transfer(target: string): Promise<void>;
|
|
668
807
|
/**
|
|
669
808
|
* Attended transfer, part 1: call the target while holding this call.
|
|
670
809
|
*
|
|
671
|
-
* Returns the consult leg — itself a Call, with its own state machine and events.
|
|
810
|
+
* Returns the consult leg — itself a Call, with its own state machine and events. Unlike blind
|
|
811
|
+
* `transfer()`, a consult leg that goes unanswered reports a real reason (busy/no-answer/rejected/
|
|
812
|
+
* failed) and never puts this call at risk — see `docs/specs/transfer-availability.md` §5.
|
|
672
813
|
*/
|
|
673
814
|
startConsult(target: string): Promise<Call>;
|
|
674
|
-
/**
|
|
815
|
+
/**
|
|
816
|
+
* Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces)
|
|
817
|
+
*
|
|
818
|
+
* **Confirmed non-functional against Zadarma, on real calls, three times
|
|
819
|
+
* (`docs/specs/transfer-availability.md` §12.3).** Zadarma accepts the REFER…Replaces but never
|
|
820
|
+
* actually bridges the two legs — the consult leg is silently orphaned and the caller is lost, with
|
|
821
|
+
* no error thrown here to signal it. The pattern that DOES work (the same one `/ui`'s own
|
|
822
|
+
* `ConsultScreen` uses): hang up the consult leg, then call `transfer(target)` on THIS call, to the
|
|
823
|
+
* same already-confirmed-answering target — a blind transfer to a target already known to be there
|
|
824
|
+
* works, even though `completeTransfer()` itself does not. Left in the type for hosts against a
|
|
825
|
+
* different (non-Zadarma) SIP provider where REFER…Replaces may work as specified; do not reach for
|
|
826
|
+
* it against Zadarma.
|
|
827
|
+
*/
|
|
675
828
|
completeTransfer(): Promise<void>;
|
|
676
829
|
/** Abandon the consult and resume this call. */
|
|
677
830
|
cancelConsult(): Promise<void>;
|
|
678
831
|
}
|
|
679
832
|
|
|
680
|
-
export { type AgentsDirectory as A, CALL_TRANSITIONS as C, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HangupReason as H, INITIAL_STATES as I, type LogLevel as L, type NonFatalErrorCode as N, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, 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 SoftphoneError as k, type SoftphoneErrorCode as l, SoftphoneEvent as m, type SoftphoneEventMap as n, type SoftphoneState as o, type TokenSource as p, type TypedEmitter as q, canTransition as r, isFatal as s, isTerminal as t, type ContactsSource as u, type HistorySource as v, type
|
|
833
|
+
export { type AgentsDirectory as A, CALL_TRANSITIONS as C, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HangupReason as H, INITIAL_STATES as I, type LogLevel as L, type NonFatalErrorCode as N, type PresenceEntry as P, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, 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 SoftphoneError as k, type SoftphoneErrorCode as l, SoftphoneEvent as m, type SoftphoneEventMap as n, type SoftphoneState as o, type TokenSource as p, type TypedEmitter as q, canTransition as r, isFatal as s, isTerminal as t, type ContactsSource as u, type HistorySource as v, type TransferOutcomesPage as w, type PresenceSource as x, type CallHistoryEntry as y, type ContactsPage as z };
|
|
@@ -117,6 +117,19 @@ interface CoventiaBackendConfig {
|
|
|
117
117
|
* (SPEC §6.6).
|
|
118
118
|
*/
|
|
119
119
|
readonly contacts?: ContactsSource;
|
|
120
|
+
/**
|
|
121
|
+
* Transfer outcomes, overriding Coventia's own polling — for a `MockProvider`-style test double, or
|
|
122
|
+
* a future non-Zadarma provider that computes outcomes differently. Not a per-partner customisation
|
|
123
|
+
* point the way `contacts` is: unlike a contact list, a transfer outcome is not a partner's own
|
|
124
|
+
* data, so there is normally no reason to set this (`docs/specs/transfer-outcome-relay.md`, Phase A).
|
|
125
|
+
*/
|
|
126
|
+
readonly transferOutcomes?: TransferOutcomeSource;
|
|
127
|
+
/**
|
|
128
|
+
* Live agent presence, overriding Coventia's own WebSocket — for a `MockProvider`-style test
|
|
129
|
+
* double, or a host with its own presence transport. Not a per-partner customisation point the way
|
|
130
|
+
* `contacts` is (`docs/specs/transfer-presence-picker.md`, Phase B).
|
|
131
|
+
*/
|
|
132
|
+
readonly presence?: PresenceSource;
|
|
120
133
|
}
|
|
121
134
|
interface SoftphoneConfig {
|
|
122
135
|
readonly tokenSource: TokenSource;
|
|
@@ -261,6 +274,104 @@ interface AgentsDirectory {
|
|
|
261
274
|
readonly me: PhoneAgent | null;
|
|
262
275
|
readonly people: readonly PhoneAgent[];
|
|
263
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* `'connected'` | `'not-connected'` — whether an attended transfer's second ring, after `Completar`,
|
|
279
|
+
* actually reached the target. Kebab-case to match `HangupReason` (`'no-answer'`, `'transferred'`);
|
|
280
|
+
* the backend's own wire value stays `not_connected` — this is the one place that gets translated.
|
|
281
|
+
*/
|
|
282
|
+
type TransferOutcomeStatus = 'connected' | 'not-connected';
|
|
283
|
+
/** One resolved transfer, as `GET /phone/transfers/outcomes` reports it. */
|
|
284
|
+
interface TransferOutcomeEntry {
|
|
285
|
+
/** Opaque, strictly increasing per organization, never re-issued. The cursor for the NEXT poll. */
|
|
286
|
+
readonly id: string;
|
|
287
|
+
/** The extension the call was transferred to. */
|
|
288
|
+
readonly target: string;
|
|
289
|
+
readonly status: TransferOutcomeStatus;
|
|
290
|
+
readonly at: Date;
|
|
291
|
+
}
|
|
292
|
+
interface TransferOutcomesPage {
|
|
293
|
+
/** The newest `id` currently retained, or `'0'` when there are none — pass back as `since`. */
|
|
294
|
+
readonly cursor: string;
|
|
295
|
+
readonly outcomes: readonly TransferOutcomeEntry[];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Poll for transfer outcomes. `since` omitted ⇒ everything currently retained — used for the FIRST
|
|
299
|
+
* poll after starting a watch, which exists only to learn the current cursor (spec §5.3's
|
|
300
|
+
* no-mis-attribution rule: that first poll's own outcomes are discarded, only `cursor` is kept).
|
|
301
|
+
*/
|
|
302
|
+
type TransferOutcomeSource = (params: {
|
|
303
|
+
readonly since?: string;
|
|
304
|
+
}) => Promise<TransferOutcomesPage>;
|
|
305
|
+
/**
|
|
306
|
+
* `SoftphoneEvent.TransferOutcome`'s payload. Unlike `TransferOutcomeEntry`, this never carries `id`
|
|
307
|
+
* (a polling-internal concern) and adds `caller` — the backend never stores a caller number (spec
|
|
308
|
+
* §5.3: "no caller number, nothing about other users"), so this is filled in locally, from the `Call`
|
|
309
|
+
* that was transferred, at the moment an outcome is matched to it.
|
|
310
|
+
*/
|
|
311
|
+
interface TransferOutcomeEvent {
|
|
312
|
+
readonly status: TransferOutcomeStatus;
|
|
313
|
+
readonly target: string;
|
|
314
|
+
/** The original caller's number, for "Llamar de vuelta" — null if it was never known. */
|
|
315
|
+
readonly caller: string | null;
|
|
316
|
+
readonly at: Date;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* `'disponible'` | `'en_llamada'` | `'desconectado'` — Spanish because this is what the picker shows,
|
|
320
|
+
* not an internal code (spec §4.1). `derivePresence` (the backend's own reducer, mirrored nowhere in
|
|
321
|
+
* the SDK — the SDK only ever receives an already-derived state, never `onCall`/`onlineFresh` apart).
|
|
322
|
+
*/
|
|
323
|
+
type PresenceState = 'disponible' | 'en_llamada' | 'desconectado';
|
|
324
|
+
/** One org member with a phone extension, as the picker lists them. */
|
|
325
|
+
interface PresenceEntry {
|
|
326
|
+
readonly userId: string;
|
|
327
|
+
readonly extension: string;
|
|
328
|
+
/** `null` when the backend has no name to offer — the picker falls back to the extension. */
|
|
329
|
+
readonly name: string | null;
|
|
330
|
+
readonly state: PresenceState;
|
|
331
|
+
}
|
|
332
|
+
/** What a live presence connection hands back once open. */
|
|
333
|
+
interface PresenceConnection {
|
|
334
|
+
/**
|
|
335
|
+
* Report this softphone's own on-call-ness, so a colleague transferring TO this agent sees it
|
|
336
|
+
* without a round trip through the webhook path (spec §4.1's `onCall` OR-combine). Calling this
|
|
337
|
+
* before the connection is genuinely open is never required of an implementation — a caller (see
|
|
338
|
+
* `presence-socket.ts`) that reports early is expected to wait for the connection to exist first.
|
|
339
|
+
*/
|
|
340
|
+
readonly reportCallState: (onCall: boolean) => void;
|
|
341
|
+
/**
|
|
342
|
+
* Says this phone is going away ON PURPOSE (the user turned it off, the app is quitting), so the backend
|
|
343
|
+
* drops it at once instead of waiting out the close grace it gives every ordinary close. Optional: a host's
|
|
344
|
+
* own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
|
|
345
|
+
*/
|
|
346
|
+
readonly bye?: () => void;
|
|
347
|
+
/**
|
|
348
|
+
* Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
|
|
349
|
+
* reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
|
|
350
|
+
* ticket), which is the only thing a caller should ever treat as "reconnect now".
|
|
351
|
+
*/
|
|
352
|
+
readonly close: () => void;
|
|
353
|
+
}
|
|
354
|
+
interface PresenceCallbacks {
|
|
355
|
+
/** Once, right after connecting — the full current roster (spec §4.3). */
|
|
356
|
+
readonly onSnapshot: (agents: readonly PresenceEntry[]) => void;
|
|
357
|
+
/** Zero or more times after that — one agent whose state just changed. */
|
|
358
|
+
readonly onDelta: (agent: PresenceEntry) => void;
|
|
359
|
+
/**
|
|
360
|
+
* The connection ended on its own. Never called after the CALLER's own `close()` — see
|
|
361
|
+
* `PresenceConnection.close`'s own note. The only signal a watch layer needs to know it must
|
|
362
|
+
* reconnect, deliberately carrying no reason: a dropped connection and a rejected ticket look
|
|
363
|
+
* identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
|
|
364
|
+
*/
|
|
365
|
+
readonly onClose: () => void;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
|
|
369
|
+
* `wss://…/phone/presence`); a host may override it entirely, or omit it — omitted, or while the
|
|
370
|
+
* connection is down, the picker simply has no live data to show (spec §4.4, success criterion #5:
|
|
371
|
+
* no error surfaces). Unlike `TransferOutcomeSource` (poll, get a page, done), this is push-based and
|
|
372
|
+
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
|
+
*/
|
|
374
|
+
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
264
375
|
|
|
265
376
|
/**
|
|
266
377
|
* The error taxonomy.
|
|
@@ -456,7 +567,23 @@ declare enum SoftphoneEvent {
|
|
|
456
567
|
* `StateChanged`: `state` is reachability, this is whether this tab is even allowed to be
|
|
457
568
|
* reachable at all while another tab holding the same extension stays open.
|
|
458
569
|
*/
|
|
459
|
-
LeaderChanged = "leaderChanged"
|
|
570
|
+
LeaderChanged = "leaderChanged",
|
|
571
|
+
/**
|
|
572
|
+
* `Completar` completed a transfer, and the target's SECOND ring either connected or didn't —
|
|
573
|
+
* `docs/specs/transfer-outcome-relay.md` (Phase A). Fires for BOTH outcomes so a host can react to
|
|
574
|
+
* either; only `'not-connected'` is worth surfacing to the agent (spec §5.5) — roughly a minute
|
|
575
|
+
* after the transfer for a target who never answers, seconds for a decline or the caller hanging up
|
|
576
|
+
* first. Requires a `TransferOutcomeSource` (from `coventia`/`apiBase`, or a host override); never
|
|
577
|
+
* fires at all when none is configured, since there is then nothing to poll.
|
|
578
|
+
*/
|
|
579
|
+
TransferOutcome = "transferOutcome",
|
|
580
|
+
/**
|
|
581
|
+
* `softphone.presence` changed — the live agent-presence list (Phase B,
|
|
582
|
+
* `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
|
|
583
|
+
* `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
|
|
584
|
+
* `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
|
|
585
|
+
*/
|
|
586
|
+
PresenceChanged = "presenceChanged"
|
|
460
587
|
}
|
|
461
588
|
interface SoftphoneEventMap {
|
|
462
589
|
[SoftphoneEvent.StateChanged]: {
|
|
@@ -473,6 +600,10 @@ interface SoftphoneEventMap {
|
|
|
473
600
|
[SoftphoneEvent.LeaderChanged]: {
|
|
474
601
|
readonly isLeader: boolean;
|
|
475
602
|
};
|
|
603
|
+
[SoftphoneEvent.TransferOutcome]: TransferOutcomeEvent;
|
|
604
|
+
[SoftphoneEvent.PresenceChanged]: {
|
|
605
|
+
readonly agents: readonly PresenceEntry[];
|
|
606
|
+
};
|
|
476
607
|
}
|
|
477
608
|
declare enum CallEvent {
|
|
478
609
|
/** Any transition. The catch-all; the specific events below are conveniences. */
|
|
@@ -663,18 +794,40 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
663
794
|
hold(): Promise<void>;
|
|
664
795
|
unhold(): Promise<void>;
|
|
665
796
|
sendDigits(digits: string): void;
|
|
666
|
-
/**
|
|
797
|
+
/**
|
|
798
|
+
* Blind transfer (SIP REFER). Ends this call — but **whether the transfer itself succeeded or
|
|
799
|
+
* failed is unknowable from here**: against Zadarma, a REFER's outcome is not reported back to the
|
|
800
|
+
* browser at all (confirmed on real calls — `docs/specs/transfer-availability.md` §2), so a REFER to
|
|
801
|
+
* an unreachable target ends this call exactly the same way a successful one does. Prefer
|
|
802
|
+
* `startConsult`/`completeTransfer` (below) when the caller must not be put at risk — that path
|
|
803
|
+
* correctly reports busy/no-answer/failed because the consult leg is an ordinary outbound call, not
|
|
804
|
+
* a REFER.
|
|
805
|
+
*/
|
|
667
806
|
transfer(target: string): Promise<void>;
|
|
668
807
|
/**
|
|
669
808
|
* Attended transfer, part 1: call the target while holding this call.
|
|
670
809
|
*
|
|
671
|
-
* Returns the consult leg — itself a Call, with its own state machine and events.
|
|
810
|
+
* Returns the consult leg — itself a Call, with its own state machine and events. Unlike blind
|
|
811
|
+
* `transfer()`, a consult leg that goes unanswered reports a real reason (busy/no-answer/rejected/
|
|
812
|
+
* failed) and never puts this call at risk — see `docs/specs/transfer-availability.md` §5.
|
|
672
813
|
*/
|
|
673
814
|
startConsult(target: string): Promise<Call>;
|
|
674
|
-
/**
|
|
815
|
+
/**
|
|
816
|
+
* Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces)
|
|
817
|
+
*
|
|
818
|
+
* **Confirmed non-functional against Zadarma, on real calls, three times
|
|
819
|
+
* (`docs/specs/transfer-availability.md` §12.3).** Zadarma accepts the REFER…Replaces but never
|
|
820
|
+
* actually bridges the two legs — the consult leg is silently orphaned and the caller is lost, with
|
|
821
|
+
* no error thrown here to signal it. The pattern that DOES work (the same one `/ui`'s own
|
|
822
|
+
* `ConsultScreen` uses): hang up the consult leg, then call `transfer(target)` on THIS call, to the
|
|
823
|
+
* same already-confirmed-answering target — a blind transfer to a target already known to be there
|
|
824
|
+
* works, even though `completeTransfer()` itself does not. Left in the type for hosts against a
|
|
825
|
+
* different (non-Zadarma) SIP provider where REFER…Replaces may work as specified; do not reach for
|
|
826
|
+
* it against Zadarma.
|
|
827
|
+
*/
|
|
675
828
|
completeTransfer(): Promise<void>;
|
|
676
829
|
/** Abandon the consult and resume this call. */
|
|
677
830
|
cancelConsult(): Promise<void>;
|
|
678
831
|
}
|
|
679
832
|
|
|
680
|
-
export { type AgentsDirectory as A, CALL_TRANSITIONS as C, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HangupReason as H, INITIAL_STATES as I, type LogLevel as L, type NonFatalErrorCode as N, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, 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 SoftphoneError as k, type SoftphoneErrorCode as l, SoftphoneEvent as m, type SoftphoneEventMap as n, type SoftphoneState as o, type TokenSource as p, type TypedEmitter as q, canTransition as r, isFatal as s, isTerminal as t, type ContactsSource as u, type HistorySource as v, type
|
|
833
|
+
export { type AgentsDirectory as A, CALL_TRANSITIONS as C, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HangupReason as H, INITIAL_STATES as I, type LogLevel as L, type NonFatalErrorCode as N, type PresenceEntry as P, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, 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 SoftphoneError as k, type SoftphoneErrorCode as l, SoftphoneEvent as m, type SoftphoneEventMap as n, type SoftphoneState as o, type TokenSource as p, type TypedEmitter as q, canTransition as r, isFatal as s, isTerminal as t, type ContactsSource as u, type HistorySource as v, type TransferOutcomesPage as w, type PresenceSource as x, type CallHistoryEntry as y, type ContactsPage as z };
|
|
@@ -7,6 +7,8 @@ var SoftphoneEvent = /* @__PURE__ */ ((SoftphoneEvent2) => {
|
|
|
7
7
|
SoftphoneEvent2["DeviceChange"] = "deviceChange";
|
|
8
8
|
SoftphoneEvent2["Error"] = "error";
|
|
9
9
|
SoftphoneEvent2["LeaderChanged"] = "leaderChanged";
|
|
10
|
+
SoftphoneEvent2["TransferOutcome"] = "transferOutcome";
|
|
11
|
+
SoftphoneEvent2["PresenceChanged"] = "presenceChanged";
|
|
10
12
|
return SoftphoneEvent2;
|
|
11
13
|
})(SoftphoneEvent || {});
|
|
12
14
|
var CallEvent = /* @__PURE__ */ ((CallEvent2) => {
|
|
@@ -25,5 +27,5 @@ var CallEvent = /* @__PURE__ */ ((CallEvent2) => {
|
|
|
25
27
|
})(CallEvent || {});
|
|
26
28
|
|
|
27
29
|
export { CallEvent, SoftphoneEvent };
|
|
28
|
-
//# sourceMappingURL=chunk-
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
30
|
+
//# sourceMappingURL=chunk-7R6LOOAI.js.map
|
|
31
|
+
//# sourceMappingURL=chunk-7R6LOOAI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/events.ts"],"names":["SoftphoneEvent","CallEvent"],"mappings":";AAcO,IAAK,cAAA,qBAAAA,eAAAA,KAAL;AAEL,EAAAA,gBAAA,cAAA,CAAA,GAAe,cAAA;AAEf,EAAAA,gBAAA,cAAA,CAAA,GAAe,cAAA;AAMf,EAAAA,gBAAA,mBAAA,CAAA,GAAoB,mBAAA;AAQpB,EAAAA,gBAAA,iBAAA,CAAA,GAAkB,iBAAA;AAOlB,EAAAA,gBAAA,cAAA,CAAA,GAAe,cAAA;AAEf,EAAAA,gBAAA,OAAA,CAAA,GAAQ,OAAA;AAMR,EAAAA,gBAAA,eAAA,CAAA,GAAgB,eAAA;AAShB,EAAAA,gBAAA,iBAAA,CAAA,GAAkB,iBAAA;AAOlB,EAAAA,gBAAA,iBAAA,CAAA,GAAkB,iBAAA;AAjDR,EAAA,OAAAA,eAAAA;AAAA,CAAA,EAAA,cAAA,IAAA,EAAA;AAgEL,IAAK,SAAA,qBAAAC,UAAAA,KAAL;AAEL,EAAAA,WAAA,cAAA,CAAA,GAAe,cAAA;AAEf,EAAAA,WAAA,UAAA,CAAA,GAAW,UAAA;AAEX,EAAAA,WAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,WAAA,OAAA,CAAA,GAAQ,OAAA;AAER,EAAAA,WAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,WAAA,QAAA,CAAA,GAAS,QAAA;AAET,EAAAA,WAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,WAAA,aAAA,CAAA,GAAc,aAAA;AACd,EAAAA,WAAA,YAAA,CAAA,GAAa,YAAA;AAEb,EAAAA,WAAA,oBAAA,CAAA,GAAqB,oBAAA;AAMrB,EAAAA,WAAA,cAAA,CAAA,GAAe,cAAA;AAtBL,EAAA,OAAAA,UAAAA;AAAA,CAAA,EAAA,SAAA,IAAA,EAAA","file":"chunk-7R6LOOAI.js","sourcesContent":["import type { Call, CallState, HangupReason, HoldOriginator } from './call.js';\r\nimport type { SoftphoneError } from './errors.js';\r\nimport type { PresenceEntry, SoftphoneState, TransferOutcomeEvent } from './types.js';\r\n\r\n/**\r\n * Typed events.\r\n *\r\n * Enums rather than magic strings: autocomplete, and rename-safety. Daily's magic strings are the\r\n * counter-example — a typo in `'participant-joined'` is a silent no-op forever.\r\n *\r\n * Each map narrows the listener payload PER EVENT, which is the thing that would have been painful\r\n * to express in JSDoc and is a large part of why this package is TypeScript (SPEC §2.2).\r\n */\r\n\r\nexport enum SoftphoneEvent {\r\n /** Lifecycle. `online` = reachable, NOT SIP-registered — see SoftphoneState. */\r\n StateChanged = 'stateChanged',\r\n /** An INVITE landed. The Call is `ringing`; answer or reject it. */\r\n IncomingCall = 'incomingCall',\r\n /**\r\n * The active call changed — a call began (inbound OR outbound) or ended. Fires for both\r\n * directions, unlike IncomingCall. This is what a UI binds to in order to track \"the current call\"\r\n * without threading the Call through every component.\r\n */\r\n ActiveCallChanged = 'activeCallChanged',\r\n /**\r\n * The browser refused to play audio (autoplay policy).\r\n *\r\n * This is the single most under-documented failure in the industry and it is how calls get MISSED:\r\n * a blocked ringtone rings silently. Surface a button and call `startAudio()` from its click\r\n * handler. Only Twilio and LiveKit document it properly.\r\n */\r\n PlaybackBlocked = 'playbackBlocked',\r\n /**\r\n * `softphone.audio` changed: a device was plugged/unplugged (`inputs`/`outputs`), or the selected\r\n * input/output changed (`currentInputId`/`currentOutputId`). One event for all of it — re-read\r\n * `softphone.audio` rather than inspecting the payload, which carries nothing (`useDevices` does\r\n * exactly this).\r\n */\r\n DeviceChange = 'deviceChange',\r\n /** Fatal or non-fatal — narrow on `error.fatal`. */\r\n Error = 'error',\r\n /**\r\n * This tab's leadership changed (`multi-tab-leader-election.md`) — a DIFFERENT axis from\r\n * `StateChanged`: `state` is reachability, this is whether this tab is even allowed to be\r\n * reachable at all while another tab holding the same extension stays open.\r\n */\r\n LeaderChanged = 'leaderChanged',\r\n /**\r\n * `Completar` completed a transfer, and the target's SECOND ring either connected or didn't —\r\n * `docs/specs/transfer-outcome-relay.md` (Phase A). Fires for BOTH outcomes so a host can react to\r\n * either; only `'not-connected'` is worth surfacing to the agent (spec §5.5) — roughly a minute\r\n * after the transfer for a target who never answers, seconds for a decline or the caller hanging up\r\n * first. Requires a `TransferOutcomeSource` (from `coventia`/`apiBase`, or a host override); never\r\n * fires at all when none is configured, since there is then nothing to poll.\r\n */\r\n TransferOutcome = 'transferOutcome',\r\n /**\r\n * `softphone.presence` changed — the live agent-presence list (Phase B,\r\n * `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a\r\n * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being\r\n * `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.\r\n */\r\n PresenceChanged = 'presenceChanged',\r\n}\r\n\r\nexport interface SoftphoneEventMap {\r\n [SoftphoneEvent.StateChanged]: { readonly state: SoftphoneState; readonly previous: SoftphoneState };\r\n [SoftphoneEvent.IncomingCall]: Call;\r\n [SoftphoneEvent.ActiveCallChanged]: { readonly call: Call | null };\r\n [SoftphoneEvent.PlaybackBlocked]: void;\r\n [SoftphoneEvent.DeviceChange]: void;\r\n [SoftphoneEvent.Error]: SoftphoneError;\r\n [SoftphoneEvent.LeaderChanged]: { readonly isLeader: boolean };\r\n [SoftphoneEvent.TransferOutcome]: TransferOutcomeEvent;\r\n [SoftphoneEvent.PresenceChanged]: { readonly agents: readonly PresenceEntry[] };\r\n}\r\n\r\nexport enum CallEvent {\r\n /** Any transition. The catch-all; the specific events below are conveniences. */\r\n StateChanged = 'stateChanged',\r\n /** Media is flowing. */\r\n Accepted = 'accepted',\r\n /** Terminal. Always fires exactly once, whatever the reason. */\r\n Ended = 'ended',\r\n Muted = 'muted',\r\n /** `originator: 'remote'` means the OTHER party held US — new capability; today's softphone is blind to it. */\r\n Held = 'held',\r\n Unheld = 'unheld',\r\n /** Media lost; an ICE restart is in flight. The call is NOT over — show a banner. */\r\n Reconnecting = 'reconnecting',\r\n Reconnected = 'reconnected',\r\n DigitsSent = 'digitsSent',\r\n /** The contact name resolved after the call was already ringing. */\r\n RemotePartyUpdated = 'remotePartyUpdated',\r\n /**\r\n * The remote audio stream is available. Attach it to an `<audio>` element and play it — this is\r\n * how the caller is actually HEARD. Core cannot do this itself (it must not touch `document` —\r\n * SPEC §10.2); the UI layer or the host owns the audio element.\r\n */\r\n RemoteStream = 'remoteStream',\r\n}\r\n\r\nexport interface CallEventMap {\r\n [CallEvent.StateChanged]: { readonly state: CallState; readonly previous: CallState };\r\n [CallEvent.Accepted]: void;\r\n [CallEvent.Ended]: { readonly reason: HangupReason; readonly error?: SoftphoneError };\r\n [CallEvent.Muted]: { readonly muted: boolean };\r\n [CallEvent.Held]: { readonly originator: HoldOriginator };\r\n [CallEvent.Unheld]: { readonly originator: HoldOriginator };\r\n [CallEvent.Reconnecting]: void;\r\n [CallEvent.Reconnected]: void;\r\n [CallEvent.DigitsSent]: { readonly digits: string };\r\n [CallEvent.RemotePartyUpdated]: void;\r\n [CallEvent.RemoteStream]: { readonly stream: MediaStream };\r\n}\r\n\r\n/**\r\n * `on()` returns an unsubscribe function.\r\n *\r\n * Deliberate: it makes the cleanup impossible to get wrong in a React effect\r\n * (`useEffect(() => call.on(...), [call])`) and it avoids Vonage's design, where `on()` returns an\r\n * opaque `symbol` you must retain in order to ever call `off()`.\r\n */\r\nexport interface TypedEmitter<M> {\r\n on<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void;\r\n once<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void;\r\n off<E extends keyof M>(event: E, listener: (payload: M[E]) => void): void;\r\n}\r\n"]}
|