@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.
Files changed (54) hide show
  1. package/dist/{call-BTYlT2SU.d.cts → call-VKq1I8yP.d.cts} +158 -5
  2. package/dist/{call-BTYlT2SU.d.ts → call-VKq1I8yP.d.ts} +158 -5
  3. package/dist/{chunk-N2N2EQQJ.js → chunk-7R6LOOAI.js} +4 -2
  4. package/dist/chunk-7R6LOOAI.js.map +1 -0
  5. package/dist/{chunk-5BGEHMPP.js → chunk-EYEDPD7Y.js} +146 -20
  6. package/dist/chunk-EYEDPD7Y.js.map +1 -0
  7. package/dist/{chunk-3V2L6UTK.js → chunk-LMJXX4YM.js} +17 -3
  8. package/dist/chunk-LMJXX4YM.js.map +1 -0
  9. package/dist/element/index.d.ts +162 -7
  10. package/dist/element/index.js +866 -421
  11. package/dist/element/index.js.map +1 -1
  12. package/dist/{engine-D9nbeY08.d.cts → engine-BlQBo2Ys.d.cts} +1 -1
  13. package/dist/{engine-Doa6Brkm.d.ts → engine-C0BOkIcW.d.ts} +1 -1
  14. package/dist/index.cjs +586 -28
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +5 -5
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +335 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/react/index.cjs +15 -0
  21. package/dist/react/index.cjs.map +1 -1
  22. package/dist/react/index.d.cts +14 -3
  23. package/dist/react/index.d.ts +14 -3
  24. package/dist/react/index.js +2 -2
  25. package/dist/{softphone-DhuhcKCy.d.cts → softphone-Cyq3hiIM.d.ts} +17 -2
  26. package/dist/{softphone-DHmhotbf.d.ts → softphone-DlGV12Xp.d.cts} +17 -2
  27. package/dist/softphone.css +477 -35
  28. package/dist/{sources-N4BOEDRL.js → sources-IRKNQPWW.js} +108 -5
  29. package/dist/sources-IRKNQPWW.js.map +1 -0
  30. package/dist/testing/index.cjs +41 -5
  31. package/dist/testing/index.cjs.map +1 -1
  32. package/dist/testing/index.d.cts +8 -2
  33. package/dist/testing/index.d.ts +8 -2
  34. package/dist/testing/index.js +41 -5
  35. package/dist/testing/index.js.map +1 -1
  36. package/dist/ui/index.cjs +846 -410
  37. package/dist/ui/index.cjs.map +1 -1
  38. package/dist/ui/index.d.cts +17 -6
  39. package/dist/ui/index.d.ts +17 -6
  40. package/dist/ui/index.js +878 -456
  41. package/dist/ui/index.js.map +1 -1
  42. package/dist/zadarma/index.cjs +144 -18
  43. package/dist/zadarma/index.cjs.map +1 -1
  44. package/dist/zadarma/index.d.cts +2 -2
  45. package/dist/zadarma/index.d.ts +2 -2
  46. package/dist/zadarma/index.js +1 -1
  47. package/dist/zadarma-engine-L5DEFXUS.js +5 -0
  48. package/dist/{zadarma-engine-DN2SZSQ3.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
  49. package/package.json +1 -1
  50. package/dist/chunk-3V2L6UTK.js.map +0 -1
  51. package/dist/chunk-5BGEHMPP.js.map +0 -1
  52. package/dist/chunk-N2N2EQQJ.js.map +0 -1
  53. package/dist/sources-N4BOEDRL.js.map +0 -1
  54. package/dist/zadarma-engine-DN2SZSQ3.js +0 -5
@@ -143,14 +143,111 @@ interface AgentsDirectory {
143
143
  readonly me: PhoneAgent | null;
144
144
  readonly people: readonly PhoneAgent[];
145
145
  }
146
+ /**
147
+ * `'connected'` | `'not-connected'` — whether an attended transfer's second ring, after `Completar`,
148
+ * actually reached the target. Kebab-case to match `HangupReason` (`'no-answer'`, `'transferred'`);
149
+ * the backend's own wire value stays `not_connected` — this is the one place that gets translated.
150
+ */
151
+ type TransferOutcomeStatus = 'connected' | 'not-connected';
152
+ /** One resolved transfer, as `GET /phone/transfers/outcomes` reports it. */
153
+ interface TransferOutcomeEntry {
154
+ /** Opaque, strictly increasing per organization, never re-issued. The cursor for the NEXT poll. */
155
+ readonly id: string;
156
+ /** The extension the call was transferred to. */
157
+ readonly target: string;
158
+ readonly status: TransferOutcomeStatus;
159
+ readonly at: Date;
160
+ }
161
+ interface TransferOutcomesPage {
162
+ /** The newest `id` currently retained, or `'0'` when there are none — pass back as `since`. */
163
+ readonly cursor: string;
164
+ readonly outcomes: readonly TransferOutcomeEntry[];
165
+ }
166
+ /**
167
+ * `SoftphoneEvent.TransferOutcome`'s payload. Unlike `TransferOutcomeEntry`, this never carries `id`
168
+ * (a polling-internal concern) and adds `caller` — the backend never stores a caller number (spec
169
+ * §5.3: "no caller number, nothing about other users"), so this is filled in locally, from the `Call`
170
+ * that was transferred, at the moment an outcome is matched to it.
171
+ */
172
+ interface TransferOutcomeEvent {
173
+ readonly status: TransferOutcomeStatus;
174
+ readonly target: string;
175
+ /** The original caller's number, for "Llamar de vuelta" — null if it was never known. */
176
+ readonly caller: string | null;
177
+ readonly at: Date;
178
+ }
179
+ /**
180
+ * `'disponible'` | `'en_llamada'` | `'desconectado'` — Spanish because this is what the picker shows,
181
+ * not an internal code (spec §4.1). `derivePresence` (the backend's own reducer, mirrored nowhere in
182
+ * the SDK — the SDK only ever receives an already-derived state, never `onCall`/`onlineFresh` apart).
183
+ */
184
+ type PresenceState = 'disponible' | 'en_llamada' | 'desconectado';
185
+ /** One org member with a phone extension, as the picker lists them. */
186
+ interface PresenceEntry {
187
+ readonly userId: string;
188
+ readonly extension: string;
189
+ /** `null` when the backend has no name to offer — the picker falls back to the extension. */
190
+ readonly name: string | null;
191
+ readonly state: PresenceState;
192
+ }
193
+ /** What a live presence connection hands back once open. */
194
+ interface PresenceConnection {
195
+ /**
196
+ * Report this softphone's own on-call-ness, so a colleague transferring TO this agent sees it
197
+ * without a round trip through the webhook path (spec §4.1's `onCall` OR-combine). Calling this
198
+ * before the connection is genuinely open is never required of an implementation — a caller (see
199
+ * `presence-socket.ts`) that reports early is expected to wait for the connection to exist first.
200
+ */
201
+ readonly reportCallState: (onCall: boolean) => void;
202
+ /**
203
+ * Says this phone is going away ON PURPOSE (the user turned it off, the app is quitting), so the backend
204
+ * drops it at once instead of waiting out the close grace it gives every ordinary close. Optional: a host's
205
+ * own source that has no such message simply omits it and gets the grace. Sent just before `close()`.
206
+ */
207
+ readonly bye?: () => void;
208
+ /**
209
+ * Ends this connection deliberately. Never causes the SAME callbacks' `onClose` to fire — that is
210
+ * reserved for the connection ending on its OWN (a network drop, a server-side close, a rejected
211
+ * ticket), which is the only thing a caller should ever treat as "reconnect now".
212
+ */
213
+ readonly close: () => void;
214
+ }
215
+ interface PresenceCallbacks {
216
+ /** Once, right after connecting — the full current roster (spec §4.3). */
217
+ readonly onSnapshot: (agents: readonly PresenceEntry[]) => void;
218
+ /** Zero or more times after that — one agent whose state just changed. */
219
+ readonly onDelta: (agent: PresenceEntry) => void;
220
+ /**
221
+ * The connection ended on its own. Never called after the CALLER's own `close()` — see
222
+ * `PresenceConnection.close`'s own note. The only signal a watch layer needs to know it must
223
+ * reconnect, deliberately carrying no reason: a dropped connection and a rejected ticket look
224
+ * identical from here, and both get the same treatment (spec §4.4 — quiet retry, nothing surfaced).
225
+ */
226
+ readonly onClose: () => void;
227
+ }
228
+ /**
229
+ * Opens one live presence connection. Coventia-backed by default (mints a one-time ticket, opens
230
+ * `wss://…/phone/presence`); a host may override it entirely, or omit it — omitted, or while the
231
+ * connection is down, the picker simply has no live data to show (spec §4.4, success criterion #5:
232
+ * no error surfaces). Unlike `TransferOutcomeSource` (poll, get a page, done), this is push-based and
233
+ * long-lived — the returned promise resolves once, when the connection opens, not once per update.
234
+ */
235
+ type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
146
236
 
147
- /** Contacts, history, caller ID and the agents directory, all from one config. */
237
+ /** Contacts, history, caller ID, the agents directory, transfer outcomes and presence, all from one
238
+ * config. */
148
239
  interface BackendSources {
149
240
  readonly contacts: ContactsSource;
150
241
  readonly history: HistorySource;
151
242
  readonly resolveContact: (number: string) => Promise<Contact | null>;
152
243
  /** The Historial tab's "filter by person" directory. Always Coventia's own org membership. */
153
244
  readonly listAgents: () => Promise<AgentsDirectory>;
245
+ /** Phase A (transfer outcome relay) — polled by the transfer watch, never by UI directly. */
246
+ readonly transferOutcomes: (params: {
247
+ readonly since?: string;
248
+ }) => Promise<TransferOutcomesPage>;
249
+ /** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */
250
+ readonly presence: PresenceSource;
154
251
  }
155
252
 
156
253
  /**
@@ -345,7 +442,23 @@ declare enum SoftphoneEvent {
345
442
  * `StateChanged`: `state` is reachability, this is whether this tab is even allowed to be
346
443
  * reachable at all while another tab holding the same extension stays open.
347
444
  */
348
- LeaderChanged = "leaderChanged"
445
+ LeaderChanged = "leaderChanged",
446
+ /**
447
+ * `Completar` completed a transfer, and the target's SECOND ring either connected or didn't —
448
+ * `docs/specs/transfer-outcome-relay.md` (Phase A). Fires for BOTH outcomes so a host can react to
449
+ * either; only `'not-connected'` is worth surfacing to the agent (spec §5.5) — roughly a minute
450
+ * after the transfer for a target who never answers, seconds for a decline or the caller hanging up
451
+ * first. Requires a `TransferOutcomeSource` (from `coventia`/`apiBase`, or a host override); never
452
+ * fires at all when none is configured, since there is then nothing to poll.
453
+ */
454
+ TransferOutcome = "transferOutcome",
455
+ /**
456
+ * `softphone.presence` changed — the live agent-presence list (Phase B,
457
+ * `docs/specs/transfer-presence-picker.md`) gained a snapshot or an updated entry. Requires a
458
+ * `PresenceSource` (from `coventia`/`apiBase`, or a host override) AND this tab currently being
459
+ * `online && isLeader`; never fires at all otherwise, same reasoning as `TransferOutcome`.
460
+ */
461
+ PresenceChanged = "presenceChanged"
349
462
  }
350
463
  interface SoftphoneEventMap {
351
464
  [SoftphoneEvent.StateChanged]: {
@@ -362,6 +475,10 @@ interface SoftphoneEventMap {
362
475
  [SoftphoneEvent.LeaderChanged]: {
363
476
  readonly isLeader: boolean;
364
477
  };
478
+ [SoftphoneEvent.TransferOutcome]: TransferOutcomeEvent;
479
+ [SoftphoneEvent.PresenceChanged]: {
480
+ readonly agents: readonly PresenceEntry[];
481
+ };
365
482
  }
366
483
  declare enum CallEvent {
367
484
  /** Any transition. The catch-all; the specific events below are conveniences. */
@@ -528,15 +645,37 @@ interface Call extends TypedEmitter<CallEventMap> {
528
645
  hold(): Promise<void>;
529
646
  unhold(): Promise<void>;
530
647
  sendDigits(digits: string): void;
531
- /** Blind transfer (SIP REFER). Ends this call. */
648
+ /**
649
+ * Blind transfer (SIP REFER). Ends this call — but **whether the transfer itself succeeded or
650
+ * failed is unknowable from here**: against Zadarma, a REFER's outcome is not reported back to the
651
+ * browser at all (confirmed on real calls — `docs/specs/transfer-availability.md` §2), so a REFER to
652
+ * an unreachable target ends this call exactly the same way a successful one does. Prefer
653
+ * `startConsult`/`completeTransfer` (below) when the caller must not be put at risk — that path
654
+ * correctly reports busy/no-answer/failed because the consult leg is an ordinary outbound call, not
655
+ * a REFER.
656
+ */
532
657
  transfer(target: string): Promise<void>;
533
658
  /**
534
659
  * Attended transfer, part 1: call the target while holding this call.
535
660
  *
536
- * Returns the consult leg — itself a Call, with its own state machine and events.
661
+ * Returns the consult leg — itself a Call, with its own state machine and events. Unlike blind
662
+ * `transfer()`, a consult leg that goes unanswered reports a real reason (busy/no-answer/rejected/
663
+ * failed) and never puts this call at risk — see `docs/specs/transfer-availability.md` §5.
537
664
  */
538
665
  startConsult(target: string): Promise<Call>;
539
- /** Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces) */
666
+ /**
667
+ * Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces)
668
+ *
669
+ * **Confirmed non-functional against Zadarma, on real calls, three times
670
+ * (`docs/specs/transfer-availability.md` §12.3).** Zadarma accepts the REFER…Replaces but never
671
+ * actually bridges the two legs — the consult leg is silently orphaned and the caller is lost, with
672
+ * no error thrown here to signal it. The pattern that DOES work (the same one `/ui`'s own
673
+ * `ConsultScreen` uses): hang up the consult leg, then call `transfer(target)` on THIS call, to the
674
+ * same already-confirmed-answering target — a blind transfer to a target already known to be there
675
+ * works, even though `completeTransfer()` itself does not. Left in the type for hosts against a
676
+ * different (non-Zadarma) SIP provider where REFER…Replaces may work as specified; do not reach for
677
+ * it against Zadarma.
678
+ */
540
679
  completeTransfer(): Promise<void>;
541
680
  /** Abandon the consult and resume this call. */
542
681
  cancelConsult(): Promise<void>;
@@ -647,6 +786,14 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
647
786
  */
648
787
  readonly sources: BackendSources | null;
649
788
  readonly audio: AudioDevices;
789
+ /**
790
+ * The live agent-presence list (Phase B, `docs/specs/transfer-presence-picker.md`) — every org
791
+ * member with a phone extension, whatever their state. Empty when there is no `PresenceSource`
792
+ * (no Coventia backend, or a host that omitted one), or while this tab is not `online && isLeader`
793
+ * — never an error, just nothing to show (spec §4.4's own success criterion). Updates fire
794
+ * `SoftphoneEvent.PresenceChanged`; `usePresence()` is the reactive read.
795
+ */
796
+ readonly presence: readonly PresenceEntry[];
650
797
  /**
651
798
  * False when the browser has blocked audio playback.
652
799
  *
@@ -713,8 +860,8 @@ interface SoftphoneElementConfig {
713
860
  */
714
861
  readonly requireMic?: boolean;
715
862
  /**
716
- * Play call audio — the remote party, the inbound ring, the outbound ringback. **On by default**;
717
- * see `SoftphoneWidgetProps.sounds`.
863
+ * Play call audio — the remote party, the inbound ring, the outbound ringback, the end-of-call cue
864
+ * and the keypad's key tones. **On by default**; see `SoftphoneWidgetProps.sounds`.
718
865
  *
719
866
  * Turning this off makes a connected call silent in both directions, because nothing else attaches
720
867
  * the remote `MediaStream`. Only correct when the host attaches `call.remoteStream` itself, or for
@@ -791,6 +938,14 @@ interface SoftphoneElementEventMap {
791
938
  'cv-leadership-changed': CustomEvent<{
792
939
  readonly isLeader: boolean;
793
940
  }>;
941
+ /**
942
+ * A transfer's final handoff step resolved — `status` says whether the caller actually reached
943
+ * `target`. Polled, not instant (Phase A, `docs/specs/transfer-outcome-relay.md`); only for that
944
+ * handoff, never for the consult leg itself, which already reports its own outcome in real time via
945
+ * `cv-call-ended` on that leg. **The widget already shows its own dismissable notice for this** —
946
+ * listen here only for your own logging, or if you are replacing the built-in UI.
947
+ */
948
+ 'cv-transfer-outcome': CustomEvent<TransferOutcomeEvent>;
794
949
  }
795
950
  declare global {
796
951
  /** `document.querySelector('coventia-softphone')` returns the real type — no cast to set `.softphone`. */