@coventia/softphone 0.2.1 → 0.3.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-VKq1I8yP.d.cts → call-CaPDWVk3.d.cts} +44 -1
- package/dist/{call-VKq1I8yP.d.ts → call-CaPDWVk3.d.ts} +44 -1
- package/dist/{chunk-LMJXX4YM.js → chunk-UHNLEBBO.js} +4 -2
- package/dist/chunk-UHNLEBBO.js.map +1 -0
- package/dist/element/index.d.ts +68 -9
- package/dist/element/index.js +962 -114
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-C0BOkIcW.d.ts → engine-BgBsIcEa.d.ts} +1 -1
- package/dist/{engine-BlQBo2Ys.d.cts → engine-CU2rEo3g.d.cts} +1 -1
- package/dist/index.cjs +120 -10
- 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 +69 -10
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/{softphone-Cyq3hiIM.d.ts → softphone-DEt0TtQw.d.ts} +26 -10
- package/dist/{softphone-DlGV12Xp.d.cts → softphone-dZMkW8V1.d.cts} +26 -10
- package/dist/softphone.css +110 -12
- package/dist/{sources-IRKNQPWW.js → sources-M3GYMS6V.js} +54 -3
- package/dist/sources-M3GYMS6V.js.map +1 -0
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/ui/index.cjs +885 -28
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +25 -3
- package/dist/ui/index.d.ts +25 -3
- package/dist/ui/index.js +885 -31
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.d.cts +2 -2
- package/dist/zadarma/index.d.ts +2 -2
- package/package.json +1 -1
- package/dist/chunk-LMJXX4YM.js.map +0 -1
- package/dist/sources-IRKNQPWW.js.map +0 -1
|
@@ -372,6 +372,49 @@ interface PresenceCallbacks {
|
|
|
372
372
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
373
|
*/
|
|
374
374
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
375
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
376
|
+
interface RingtoneOption {
|
|
377
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
378
|
+
readonly id: string;
|
|
379
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
380
|
+
readonly name: string;
|
|
381
|
+
/**
|
|
382
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
383
|
+
* that is always available and plays whenever a file is not ready.
|
|
384
|
+
*/
|
|
385
|
+
readonly url: string | null;
|
|
386
|
+
/**
|
|
387
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
388
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
389
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
390
|
+
*/
|
|
391
|
+
readonly loop: {
|
|
392
|
+
readonly start: number;
|
|
393
|
+
readonly end: number;
|
|
394
|
+
} | null;
|
|
395
|
+
}
|
|
396
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
397
|
+
interface RingtoneSettings {
|
|
398
|
+
/** In display order. Always contains the built-in tone. */
|
|
399
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
400
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
401
|
+
readonly orgDefault: string | null;
|
|
402
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
403
|
+
readonly mine: string | null;
|
|
404
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
405
|
+
readonly effective: string;
|
|
406
|
+
}
|
|
407
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
408
|
+
interface RingtoneSource {
|
|
409
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
410
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
411
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
412
|
+
/**
|
|
413
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
414
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
415
|
+
*/
|
|
416
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
417
|
+
}
|
|
375
418
|
|
|
376
419
|
/**
|
|
377
420
|
* The error taxonomy.
|
|
@@ -830,4 +873,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
830
873
|
cancelConsult(): Promise<void>;
|
|
831
874
|
}
|
|
832
875
|
|
|
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
|
|
876
|
+
export { type AgentsDirectory as A, type PresenceSource as B, CALL_TRANSITIONS as C, type CallHistoryEntry as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type ContactsPage as G, 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 RingtoneOption as k, type RingtoneSettings as l, type RingtoneSource as m, type SoftphoneError as n, type SoftphoneErrorCode as o, SoftphoneEvent as p, type SoftphoneEventMap as q, type SoftphoneState as r, type TokenSource as s, type TypedEmitter as t, canTransition as u, isFatal as v, isTerminal as w, type ContactsSource as x, type HistorySource as y, type TransferOutcomesPage as z };
|
|
@@ -372,6 +372,49 @@ interface PresenceCallbacks {
|
|
|
372
372
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
373
|
*/
|
|
374
374
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
375
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
376
|
+
interface RingtoneOption {
|
|
377
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
378
|
+
readonly id: string;
|
|
379
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
380
|
+
readonly name: string;
|
|
381
|
+
/**
|
|
382
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
383
|
+
* that is always available and plays whenever a file is not ready.
|
|
384
|
+
*/
|
|
385
|
+
readonly url: string | null;
|
|
386
|
+
/**
|
|
387
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
388
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
389
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
390
|
+
*/
|
|
391
|
+
readonly loop: {
|
|
392
|
+
readonly start: number;
|
|
393
|
+
readonly end: number;
|
|
394
|
+
} | null;
|
|
395
|
+
}
|
|
396
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
397
|
+
interface RingtoneSettings {
|
|
398
|
+
/** In display order. Always contains the built-in tone. */
|
|
399
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
400
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
401
|
+
readonly orgDefault: string | null;
|
|
402
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
403
|
+
readonly mine: string | null;
|
|
404
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
405
|
+
readonly effective: string;
|
|
406
|
+
}
|
|
407
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
408
|
+
interface RingtoneSource {
|
|
409
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
410
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
411
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
412
|
+
/**
|
|
413
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
414
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
415
|
+
*/
|
|
416
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
417
|
+
}
|
|
375
418
|
|
|
376
419
|
/**
|
|
377
420
|
* The error taxonomy.
|
|
@@ -830,4 +873,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
830
873
|
cancelConsult(): Promise<void>;
|
|
831
874
|
}
|
|
832
875
|
|
|
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
|
|
876
|
+
export { type AgentsDirectory as A, type PresenceSource as B, CALL_TRANSITIONS as C, type CallHistoryEntry as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type ContactsPage as G, 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 RingtoneOption as k, type RingtoneSettings as l, type RingtoneSource as m, type SoftphoneError as n, type SoftphoneErrorCode as o, SoftphoneEvent as p, type SoftphoneEventMap as q, type SoftphoneState as r, type TokenSource as s, type TypedEmitter as t, canTransition as u, isFatal as v, isTerminal as w, type ContactsSource as x, type HistorySource as y, type TransferOutcomesPage as z };
|
|
@@ -126,7 +126,9 @@ function useDevices() {
|
|
|
126
126
|
outputs: audio.outputs,
|
|
127
127
|
currentInputId: audio.currentInputId,
|
|
128
128
|
currentOutputId: audio.currentOutputId,
|
|
129
|
+
currentRingtoneOutputId: audio.currentRingtoneOutputId,
|
|
129
130
|
canSelectOutput: audio.canSelectOutput,
|
|
131
|
+
canSelectRingtoneOutput: audio.canSelectRingtoneOutput,
|
|
130
132
|
setInput: (deviceId) => audio.setInput(deviceId),
|
|
131
133
|
setOutput: (deviceId) => audio.setOutput(deviceId),
|
|
132
134
|
setRingtoneOutput: (deviceId) => audio.setRingtoneOutput(deviceId),
|
|
@@ -139,5 +141,5 @@ function useDevices() {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
export { SoftphoneProvider, useActiveCall, useCall, useDevices, useIsLeader, useLastError, usePresence, useSoftphone, useSoftphoneState };
|
|
142
|
-
//# sourceMappingURL=chunk-
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
144
|
+
//# sourceMappingURL=chunk-UHNLEBBO.js.map
|
|
145
|
+
//# sourceMappingURL=chunk-UHNLEBBO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/context.tsx","../src/react/hooks.ts"],"names":[],"mappings":"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","file":"chunk-UHNLEBBO.js","sourcesContent":["import { createContext, useContext, type ReactNode } from 'react';\r\nimport type { Softphone } from '../core/softphone.js';\r\n\r\n/**\r\n * The provider takes an INSTANCE the host constructed — not config props.\r\n *\r\n * This is LiveKit's hard-won lesson (SPEC §5.1): they are deprecating their config-props\r\n * `<LiveKitRoom>` in favour of a provider that accepts an instance. Holding the `Softphone` above the\r\n * React tree lets the app own the connection, survive route changes and unmounts without dropping a\r\n * call, and unit-test the engine with no React at all. Config-props would rebuild the engine on\r\n * every prop change — catastrophic for something holding a live `RTCPeerConnection`.\r\n */\r\nconst SoftphoneContext = createContext<Softphone | null>(null);\r\n\r\nexport interface SoftphoneProviderProps {\r\n readonly softphone: Softphone;\r\n readonly children: ReactNode;\r\n}\r\n\r\nexport function SoftphoneProvider({ softphone, children }: SoftphoneProviderProps) {\r\n return <SoftphoneContext.Provider value={softphone}>{children}</SoftphoneContext.Provider>;\r\n}\r\n\r\n/** The `Softphone` instance from context. Stable — does not re-render on state changes. */\r\nexport function useSoftphone(): Softphone {\r\n const softphone = useContext(SoftphoneContext);\r\n if (!softphone) {\r\n throw new Error('useSoftphone() must be used within a <SoftphoneProvider>.');\r\n }\r\n return softphone;\r\n}\r\n","// Built-in, no shim: the peer floor is React 18 (SPEC §2.7), which ships useSyncExternalStore.\r\nimport { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useSoftphone } from './context.js';\r\nimport { SoftphoneEvent, CallEvent } from '../core/events.js';\r\nimport type { Call, CallState } from '../core/call.js';\r\nimport type { PresenceEntry, RemoteParty, SoftphoneState } from '../core/types.js';\r\nimport type { AudioDevices } from '../core/softphone.js';\r\nimport type { FatalSoftphoneError } from '../core/errors.js';\r\n\r\n/** Reactive softphone lifecycle state (`stopped` → `online` → …). */\r\n/**\r\n * The reason the phone cannot currently be used, or `null`.\r\n *\r\n * Reads `softphone.lastError`, which is STATE rather than only an event — the widget is usually\r\n * opened *after* something broke, and a listener mounted at that point would have missed the event\r\n * entirely and rendered a dialpad with no explanation.\r\n *\r\n * Re-reads on both channels because both can change it: `Error` sets it, and `StateChanged` clears it\r\n * on reaching `online`. Subscribing to only the first would leave a stale gate on screen after the\r\n * phone recovered.\r\n */\r\nexport function useLastError(): FatalSoftphoneError | null {\r\n const phone = useSoftphone();\r\n return useSyncExternalStore(\r\n useCallback(\r\n (cb: () => void) => {\r\n const offError = phone.on(SoftphoneEvent.Error, () => cb());\r\n const offState = phone.on(SoftphoneEvent.StateChanged, () => cb());\r\n return () => {\r\n offError();\r\n offState();\r\n };\r\n },\r\n [phone],\r\n ),\r\n useCallback(() => phone.lastError, [phone]),\r\n );\r\n}\r\n\r\nexport function useSoftphoneState(): SoftphoneState {\r\n const phone = useSoftphone();\r\n return useSyncExternalStore(\r\n useCallback((cb: () => void) => phone.on(SoftphoneEvent.StateChanged, () => cb()), [phone]),\r\n useCallback(() => phone.state, [phone]),\r\n );\r\n}\r\n\r\n/**\r\n * Whether THIS tab is the one allowed to be reachable (`multi-tab-leader-election.md`). `false` by\r\n * default and while waiting for another open tab to release it — show your own \"active in another\r\n * tab\" state for that duration; the `/ui` widget already does.\r\n */\r\nexport function useIsLeader(): boolean {\r\n const phone = useSoftphone();\r\n return useSyncExternalStore(\r\n useCallback((cb: () => void) => phone.on(SoftphoneEvent.LeaderChanged, () => cb()), [phone]),\r\n useCallback(() => phone.isLeader, [phone]),\r\n );\r\n}\r\n\r\n/**\r\n * The current call, or null. Re-renders when it changes — inbound, outbound, or ended.\r\n *\r\n * `phone.activeCall` returns a stable reference for a given call, so it is a safe snapshot.\r\n */\r\nexport function useActiveCall(): Call | null {\r\n const phone = useSoftphone();\r\n return useSyncExternalStore(\r\n useCallback((cb: () => void) => phone.on(SoftphoneEvent.ActiveCallChanged, () => cb()), [phone]),\r\n useCallback(() => phone.activeCall, [phone]),\r\n );\r\n}\r\n\r\n/**\r\n * The live agent-presence list (`phone.presence`, Phase B) — reactive on `SoftphoneEvent.\r\n * PresenceChanged`. `active` gates the SUBSCRIPTION only, not the underlying connection: the socket\r\n * itself stays governed entirely by `online && isLeader` inside the softphone (spec's own\r\n * always-connected decision), so passing `false` here does not close it — it only stops this\r\n * particular component from re-rendering on every update while it has no reason to care, e.g. the\r\n * transfer step being closed (spec §4.5: \"subscribe when the step opens... unsubscribe when it\r\n * closes\"). `phone.presence` is still read fresh on every call, so flipping `active` back to `true`\r\n * never shows a stale snapshot.\r\n */\r\nexport function usePresence(active: boolean): readonly PresenceEntry[] {\r\n const phone = useSoftphone();\r\n return useSyncExternalStore(\r\n useCallback(\r\n (cb: () => void) => {\r\n if (!active) return () => {};\r\n return phone.on(SoftphoneEvent.PresenceChanged, () => cb());\r\n },\r\n [phone, active],\r\n ),\r\n useCallback(() => phone.presence, [phone]),\r\n );\r\n}\r\n\r\n/** An immutable snapshot of a call's reactive fields. */\r\nexport interface CallView {\r\n readonly state: CallState;\r\n readonly isMuted: boolean;\r\n readonly isOnHold: boolean;\r\n readonly remote: RemoteParty;\r\n readonly remoteStream: MediaStream | null;\r\n readonly answeredAt: Date | null;\r\n}\r\n\r\nconst CALL_EVENTS: readonly CallEvent[] = [\r\n CallEvent.StateChanged,\r\n CallEvent.Muted,\r\n CallEvent.Held,\r\n CallEvent.Unheld,\r\n CallEvent.RemotePartyUpdated,\r\n CallEvent.RemoteStream,\r\n CallEvent.Ended,\r\n];\r\n\r\n/**\r\n * Reactive view of a specific call. Re-renders only when one of the call's own events fires — no\r\n * polling, no re-render storms.\r\n *\r\n * The snapshot is CACHED and invalidated only on an event, so `useSyncExternalStore` gets a stable\r\n * reference between changes (returning a fresh object every render would infinite-loop).\r\n */\r\nexport function useCall(call: Call | null): CallView | null {\r\n const cache = useRef<{ call: Call; view: CallView } | null>(null);\r\n\r\n const subscribe = useCallback(\r\n (cb: () => void) => {\r\n if (!call) return () => {};\r\n const unsubs = CALL_EVENTS.map((event) =>\r\n call.on(event, () => {\r\n cache.current = null; // invalidate; getSnapshot rebuilds on the next read\r\n cb();\r\n }),\r\n );\r\n return () => {\r\n for (const off of unsubs) off();\r\n };\r\n },\r\n [call],\r\n );\r\n\r\n const getSnapshot = useCallback((): CallView | null => {\r\n if (!call) return null;\r\n if (cache.current?.call === call) return cache.current.view;\r\n const view: CallView = {\r\n state: call.state,\r\n isMuted: call.isMuted,\r\n isOnHold: call.isOnHold,\r\n remote: call.remote,\r\n remoteStream: call.remoteStream,\r\n answeredAt: call.answeredAt,\r\n };\r\n cache.current = { call, view };\r\n return view;\r\n }, [call]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n\r\n/**\r\n * The audio device surface (`phone.audio`) — reactive. Re-renders on `SoftphoneEvent.DeviceChange`: a\r\n * device plugged/unplugged, or a selection changed (SPEC §7 Phase 2).\r\n *\r\n * The snapshot is CACHED and invalidated only on that event, same reasoning as `useCall`'s own\r\n * `CallView`: `phone.audio` is ONE stable object whose fields mutate in place, so returning it\r\n * directly would never change by reference and `useSyncExternalStore` would never re-render. A fresh\r\n * object, rebuilt only when something actually changed, is what makes this reactive without polling.\r\n */\r\nexport function useDevices(): AudioDevices {\r\n const phone = useSoftphone();\r\n const cache = useRef<{ audio: AudioDevices; view: AudioDevices } | null>(null);\r\n\r\n const subscribe = useCallback(\r\n (cb: () => void) =>\r\n phone.on(SoftphoneEvent.DeviceChange, () => {\r\n cache.current = null; // invalidate; getSnapshot rebuilds on the next read\r\n cb();\r\n }),\r\n [phone],\r\n );\r\n\r\n const getSnapshot = useCallback((): AudioDevices => {\r\n const audio = phone.audio;\r\n if (cache.current?.audio === audio) return cache.current.view;\r\n const view: AudioDevices = {\r\n inputs: audio.inputs,\r\n outputs: audio.outputs,\r\n currentInputId: audio.currentInputId,\r\n currentOutputId: audio.currentOutputId,\r\n currentRingtoneOutputId: audio.currentRingtoneOutputId,\r\n canSelectOutput: audio.canSelectOutput,\r\n canSelectRingtoneOutput: audio.canSelectRingtoneOutput,\r\n setInput: (deviceId) => audio.setInput(deviceId),\r\n setOutput: (deviceId) => audio.setOutput(deviceId),\r\n setRingtoneOutput: (deviceId) => audio.setRingtoneOutput(deviceId),\r\n refresh: () => audio.refresh(),\r\n };\r\n cache.current = { audio, view };\r\n return view;\r\n }, [phone]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"]}
|
package/dist/element/index.d.ts
CHANGED
|
@@ -233,9 +233,52 @@ interface PresenceCallbacks {
|
|
|
233
233
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
234
234
|
*/
|
|
235
235
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
236
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
237
|
+
interface RingtoneOption {
|
|
238
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
239
|
+
readonly id: string;
|
|
240
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
241
|
+
readonly name: string;
|
|
242
|
+
/**
|
|
243
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
244
|
+
* that is always available and plays whenever a file is not ready.
|
|
245
|
+
*/
|
|
246
|
+
readonly url: string | null;
|
|
247
|
+
/**
|
|
248
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
249
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
250
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
251
|
+
*/
|
|
252
|
+
readonly loop: {
|
|
253
|
+
readonly start: number;
|
|
254
|
+
readonly end: number;
|
|
255
|
+
} | null;
|
|
256
|
+
}
|
|
257
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
258
|
+
interface RingtoneSettings {
|
|
259
|
+
/** In display order. Always contains the built-in tone. */
|
|
260
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
261
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
262
|
+
readonly orgDefault: string | null;
|
|
263
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
264
|
+
readonly mine: string | null;
|
|
265
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
266
|
+
readonly effective: string;
|
|
267
|
+
}
|
|
268
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
269
|
+
interface RingtoneSource {
|
|
270
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
271
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
272
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
273
|
+
/**
|
|
274
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
275
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
276
|
+
*/
|
|
277
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
278
|
+
}
|
|
236
279
|
|
|
237
|
-
/** Contacts, history, caller ID, the agents directory, transfer outcomes
|
|
238
|
-
* config. */
|
|
280
|
+
/** Contacts, history, caller ID, the agents directory, transfer outcomes, presence and ringtone
|
|
281
|
+
* settings, all from one config. */
|
|
239
282
|
interface BackendSources {
|
|
240
283
|
readonly contacts: ContactsSource;
|
|
241
284
|
readonly history: HistorySource;
|
|
@@ -248,6 +291,8 @@ interface BackendSources {
|
|
|
248
291
|
}) => Promise<TransferOutcomesPage>;
|
|
249
292
|
/** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */
|
|
250
293
|
readonly presence: PresenceSource;
|
|
294
|
+
/** The caller's ringtone settings (custom-ringtone.md §4.4). Always Coventia's — no override. */
|
|
295
|
+
readonly ringtones: RingtoneSource;
|
|
251
296
|
}
|
|
252
297
|
|
|
253
298
|
/**
|
|
@@ -690,12 +735,20 @@ interface AudioDevices {
|
|
|
690
735
|
readonly inputs: readonly MediaDeviceInfo[];
|
|
691
736
|
readonly outputs: readonly MediaDeviceInfo[];
|
|
692
737
|
/**
|
|
693
|
-
* The currently-selected input/output, or `null` — no explicit choice made, the
|
|
694
|
-
* default is in use. Never a stale ID: if the selected device is unplugged, its
|
|
695
|
-
*
|
|
738
|
+
* The currently-selected input/output/ringtone output, or `null` — no explicit choice made, the
|
|
739
|
+
* browser's own default is in use. Never a stale ID: if the selected device is unplugged, its
|
|
740
|
+
* selection reads `null` rather than pointing at a device that no longer exists.
|
|
741
|
+
*
|
|
742
|
+
* **Choices are remembered** — in `localStorage` (`coventia-softphone:devices`), so they survive a
|
|
743
|
+
* reload. What is remembered is the user's *preference*, kept apart from these current values: an
|
|
744
|
+
* unplugged headset reads `null` here, and reads as selected again the moment it is plugged back in,
|
|
745
|
+
* without the user choosing it twice. Device ids are per browser and per site, which is exactly the
|
|
746
|
+
* scope of that storage (custom-ringtone.md §4.5). Where storage is blocked (private mode, a
|
|
747
|
+
* sandboxed iframe) choices simply last for the page, as they used to.
|
|
696
748
|
*/
|
|
697
749
|
readonly currentInputId: string | null;
|
|
698
750
|
readonly currentOutputId: string | null;
|
|
751
|
+
readonly currentRingtoneOutputId: string | null;
|
|
699
752
|
/**
|
|
700
753
|
* Re-enumerate. `inputs`/`outputs` already stay current on their own — a device physically
|
|
701
754
|
* plugged or unplugged fires the browser's own `devicechange` and needs no help — but a
|
|
@@ -720,17 +773,23 @@ interface AudioDevices {
|
|
|
720
773
|
*/
|
|
721
774
|
setOutput(deviceId: string | null): Promise<void>;
|
|
722
775
|
/**
|
|
723
|
-
* The RINGTONE output, separate from the call speaker.
|
|
776
|
+
* The RINGTONE output, separate from the call speaker, or `null` for the browser's default device.
|
|
724
777
|
*
|
|
725
778
|
* Of every SDK surveyed only Twilio ships this, and users want it: ring on the laptop speakers so
|
|
726
|
-
* you hear it across the room, take the call in the headset.
|
|
779
|
+
* you hear it across the room, take the call in the headset. Only the inbound ring (and its preview
|
|
780
|
+
* in Ajustes) follows it; the call itself, the ringback and the short cues stay where they were.
|
|
727
781
|
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
782
|
+
* Like `setOutput`, this only records the choice and fires `SoftphoneEvent.DeviceChange` — the `/ui`
|
|
783
|
+
* layer is what routes the ring. A no-op for routing when `canSelectRingtoneOutput` is false.
|
|
730
784
|
*/
|
|
731
785
|
setRingtoneOutput(deviceId: string | null): Promise<void>;
|
|
732
786
|
/** False when the browser lacks `setSinkId` (Safari, most of Firefox) — `setOutput` is then a no-op. */
|
|
733
787
|
readonly canSelectOutput: boolean;
|
|
788
|
+
/**
|
|
789
|
+
* Whether the ring can be sent to a chosen device. False on Safari (no `setSinkId` at all) — the
|
|
790
|
+
* ring then plays on the default output and a UI should not offer the picker.
|
|
791
|
+
*/
|
|
792
|
+
readonly canSelectRingtoneOutput: boolean;
|
|
734
793
|
}
|
|
735
794
|
/**
|
|
736
795
|
* The engine. Long-lived; owns registration, credentials, devices, and the provider.
|