@coventia/softphone 0.1.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/3x2-2HQTJDKZ.js +6583 -0
- package/dist/3x2-2HQTJDKZ.js.map +1 -0
- package/dist/3x2-INKWPNLA.js +6585 -0
- package/dist/3x2-INKWPNLA.js.map +1 -0
- package/dist/call-BTYlT2SU.d.cts +680 -0
- package/dist/call-BTYlT2SU.d.ts +680 -0
- package/dist/chunk-2ZSOGGPO.js +169 -0
- package/dist/chunk-2ZSOGGPO.js.map +1 -0
- package/dist/chunk-3V2L6UTK.js +129 -0
- package/dist/chunk-3V2L6UTK.js.map +1 -0
- package/dist/chunk-5BGEHMPP.js +945 -0
- package/dist/chunk-5BGEHMPP.js.map +1 -0
- package/dist/chunk-5WRI5ZAA.js +29 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/chunk-KAXEMJ7E.js +11 -0
- package/dist/chunk-KAXEMJ7E.js.map +1 -0
- package/dist/chunk-MHOJHG5E.js +39 -0
- package/dist/chunk-MHOJHG5E.js.map +1 -0
- package/dist/chunk-MP7CSA24.js +299 -0
- package/dist/chunk-MP7CSA24.js.map +1 -0
- package/dist/chunk-N2N2EQQJ.js +29 -0
- package/dist/chunk-N2N2EQQJ.js.map +1 -0
- package/dist/element/index.d.ts +853 -0
- package/dist/element/index.js +9635 -0
- package/dist/element/index.js.map +1 -0
- package/dist/engine-D9nbeY08.d.cts +115 -0
- package/dist/engine-Doa6Brkm.d.ts +115 -0
- package/dist/index.cjs +2133 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +630 -0
- package/dist/index.js.map +1 -0
- package/dist/min-64GIMWWL.js +5456 -0
- package/dist/min-64GIMWWL.js.map +1 -0
- package/dist/min-ATXYR23R.js +5458 -0
- package/dist/min-ATXYR23R.js.map +1 -0
- package/dist/react/index.cjs +138 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +68 -0
- package/dist/react/index.d.ts +68 -0
- package/dist/react/index.js +4 -0
- package/dist/react/index.js.map +1 -0
- package/dist/softphone-DHmhotbf.d.ts +128 -0
- package/dist/softphone-DhuhcKCy.d.cts +128 -0
- package/dist/softphone.css +1923 -0
- package/dist/sources-N4BOEDRL.js +226 -0
- package/dist/sources-N4BOEDRL.js.map +1 -0
- package/dist/testing/index.cjs +345 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +66 -0
- package/dist/testing/index.d.ts +66 -0
- package/dist/testing/index.js +186 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/ui/index.cjs +15428 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +188 -0
- package/dist/ui/index.d.ts +188 -0
- package/dist/ui/index.js +2256 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/zadarma/index.cjs +1144 -0
- package/dist/zadarma/index.cjs.map +1 -0
- package/dist/zadarma/index.d.cts +86 -0
- package/dist/zadarma/index.d.ts +86 -0
- package/dist/zadarma/index.js +5 -0
- package/dist/zadarma/index.js.map +1 -0
- package/dist/zadarma-engine-DN2SZSQ3.js +5 -0
- package/dist/zadarma-engine-DN2SZSQ3.js.map +1 -0
- package/package.json +123 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/react/context.tsx
|
|
7
|
+
var SoftphoneContext = react.createContext(null);
|
|
8
|
+
function SoftphoneProvider({ softphone, children }) {
|
|
9
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SoftphoneContext.Provider, { value: softphone, children });
|
|
10
|
+
}
|
|
11
|
+
function useSoftphone() {
|
|
12
|
+
const softphone = react.useContext(SoftphoneContext);
|
|
13
|
+
if (!softphone) {
|
|
14
|
+
throw new Error("useSoftphone() must be used within a <SoftphoneProvider>.");
|
|
15
|
+
}
|
|
16
|
+
return softphone;
|
|
17
|
+
}
|
|
18
|
+
function useLastError() {
|
|
19
|
+
const phone = useSoftphone();
|
|
20
|
+
return react.useSyncExternalStore(
|
|
21
|
+
react.useCallback(
|
|
22
|
+
(cb) => {
|
|
23
|
+
const offError = phone.on("error" /* Error */, () => cb());
|
|
24
|
+
const offState = phone.on("stateChanged" /* StateChanged */, () => cb());
|
|
25
|
+
return () => {
|
|
26
|
+
offError();
|
|
27
|
+
offState();
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
[phone]
|
|
31
|
+
),
|
|
32
|
+
react.useCallback(() => phone.lastError, [phone])
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function useSoftphoneState() {
|
|
36
|
+
const phone = useSoftphone();
|
|
37
|
+
return react.useSyncExternalStore(
|
|
38
|
+
react.useCallback((cb) => phone.on("stateChanged" /* StateChanged */, () => cb()), [phone]),
|
|
39
|
+
react.useCallback(() => phone.state, [phone])
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function useIsLeader() {
|
|
43
|
+
const phone = useSoftphone();
|
|
44
|
+
return react.useSyncExternalStore(
|
|
45
|
+
react.useCallback((cb) => phone.on("leaderChanged" /* LeaderChanged */, () => cb()), [phone]),
|
|
46
|
+
react.useCallback(() => phone.isLeader, [phone])
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function useActiveCall() {
|
|
50
|
+
const phone = useSoftphone();
|
|
51
|
+
return react.useSyncExternalStore(
|
|
52
|
+
react.useCallback((cb) => phone.on("activeCallChanged" /* ActiveCallChanged */, () => cb()), [phone]),
|
|
53
|
+
react.useCallback(() => phone.activeCall, [phone])
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
var CALL_EVENTS = [
|
|
57
|
+
"stateChanged" /* StateChanged */,
|
|
58
|
+
"muted" /* Muted */,
|
|
59
|
+
"held" /* Held */,
|
|
60
|
+
"unheld" /* Unheld */,
|
|
61
|
+
"remotePartyUpdated" /* RemotePartyUpdated */,
|
|
62
|
+
"remoteStream" /* RemoteStream */,
|
|
63
|
+
"ended" /* Ended */
|
|
64
|
+
];
|
|
65
|
+
function useCall(call) {
|
|
66
|
+
const cache = react.useRef(null);
|
|
67
|
+
const subscribe = react.useCallback(
|
|
68
|
+
(cb) => {
|
|
69
|
+
if (!call) return () => {
|
|
70
|
+
};
|
|
71
|
+
const unsubs = CALL_EVENTS.map(
|
|
72
|
+
(event) => call.on(event, () => {
|
|
73
|
+
cache.current = null;
|
|
74
|
+
cb();
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
return () => {
|
|
78
|
+
for (const off of unsubs) off();
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
[call]
|
|
82
|
+
);
|
|
83
|
+
const getSnapshot = react.useCallback(() => {
|
|
84
|
+
if (!call) return null;
|
|
85
|
+
if (cache.current?.call === call) return cache.current.view;
|
|
86
|
+
const view = {
|
|
87
|
+
state: call.state,
|
|
88
|
+
isMuted: call.isMuted,
|
|
89
|
+
isOnHold: call.isOnHold,
|
|
90
|
+
remote: call.remote,
|
|
91
|
+
remoteStream: call.remoteStream,
|
|
92
|
+
answeredAt: call.answeredAt
|
|
93
|
+
};
|
|
94
|
+
cache.current = { call, view };
|
|
95
|
+
return view;
|
|
96
|
+
}, [call]);
|
|
97
|
+
return react.useSyncExternalStore(subscribe, getSnapshot);
|
|
98
|
+
}
|
|
99
|
+
function useDevices() {
|
|
100
|
+
const phone = useSoftphone();
|
|
101
|
+
const cache = react.useRef(null);
|
|
102
|
+
const subscribe = react.useCallback(
|
|
103
|
+
(cb) => phone.on("deviceChange" /* DeviceChange */, () => {
|
|
104
|
+
cache.current = null;
|
|
105
|
+
cb();
|
|
106
|
+
}),
|
|
107
|
+
[phone]
|
|
108
|
+
);
|
|
109
|
+
const getSnapshot = react.useCallback(() => {
|
|
110
|
+
const audio = phone.audio;
|
|
111
|
+
if (cache.current?.audio === audio) return cache.current.view;
|
|
112
|
+
const view = {
|
|
113
|
+
inputs: audio.inputs,
|
|
114
|
+
outputs: audio.outputs,
|
|
115
|
+
currentInputId: audio.currentInputId,
|
|
116
|
+
currentOutputId: audio.currentOutputId,
|
|
117
|
+
canSelectOutput: audio.canSelectOutput,
|
|
118
|
+
setInput: (deviceId) => audio.setInput(deviceId),
|
|
119
|
+
setOutput: (deviceId) => audio.setOutput(deviceId),
|
|
120
|
+
setRingtoneOutput: (deviceId) => audio.setRingtoneOutput(deviceId),
|
|
121
|
+
refresh: () => audio.refresh()
|
|
122
|
+
};
|
|
123
|
+
cache.current = { audio, view };
|
|
124
|
+
return view;
|
|
125
|
+
}, [phone]);
|
|
126
|
+
return react.useSyncExternalStore(subscribe, getSnapshot);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
exports.SoftphoneProvider = SoftphoneProvider;
|
|
130
|
+
exports.useActiveCall = useActiveCall;
|
|
131
|
+
exports.useCall = useCall;
|
|
132
|
+
exports.useDevices = useDevices;
|
|
133
|
+
exports.useIsLeader = useIsLeader;
|
|
134
|
+
exports.useLastError = useLastError;
|
|
135
|
+
exports.useSoftphone = useSoftphone;
|
|
136
|
+
exports.useSoftphoneState = useSoftphoneState;
|
|
137
|
+
//# sourceMappingURL=index.cjs.map
|
|
138
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/context.tsx","../../src/react/hooks.ts"],"names":["createContext","useContext","useSyncExternalStore","useCallback","useRef"],"mappings":"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","file":"index.cjs","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 { 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/** 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 canSelectOutput: audio.canSelectOutput,\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"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { S as Softphone, A as AudioDevices } from '../softphone-DhuhcKCy.cjs';
|
|
4
|
+
import { f as CallState, R as RemoteParty, a as Call, h as FatalSoftphoneError, o as SoftphoneState } from '../call-BTYlT2SU.cjs';
|
|
5
|
+
|
|
6
|
+
interface SoftphoneProviderProps {
|
|
7
|
+
readonly softphone: Softphone;
|
|
8
|
+
readonly children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
declare function SoftphoneProvider({ softphone, children }: SoftphoneProviderProps): react.JSX.Element;
|
|
11
|
+
/** The `Softphone` instance from context. Stable — does not re-render on state changes. */
|
|
12
|
+
declare function useSoftphone(): Softphone;
|
|
13
|
+
|
|
14
|
+
/** Reactive softphone lifecycle state (`stopped` → `online` → …). */
|
|
15
|
+
/**
|
|
16
|
+
* The reason the phone cannot currently be used, or `null`.
|
|
17
|
+
*
|
|
18
|
+
* Reads `softphone.lastError`, which is STATE rather than only an event — the widget is usually
|
|
19
|
+
* opened *after* something broke, and a listener mounted at that point would have missed the event
|
|
20
|
+
* entirely and rendered a dialpad with no explanation.
|
|
21
|
+
*
|
|
22
|
+
* Re-reads on both channels because both can change it: `Error` sets it, and `StateChanged` clears it
|
|
23
|
+
* on reaching `online`. Subscribing to only the first would leave a stale gate on screen after the
|
|
24
|
+
* phone recovered.
|
|
25
|
+
*/
|
|
26
|
+
declare function useLastError(): FatalSoftphoneError | null;
|
|
27
|
+
declare function useSoftphoneState(): SoftphoneState;
|
|
28
|
+
/**
|
|
29
|
+
* Whether THIS tab is the one allowed to be reachable (`multi-tab-leader-election.md`). `false` by
|
|
30
|
+
* default and while waiting for another open tab to release it — show your own "active in another
|
|
31
|
+
* tab" state for that duration; the `/ui` widget already does.
|
|
32
|
+
*/
|
|
33
|
+
declare function useIsLeader(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The current call, or null. Re-renders when it changes — inbound, outbound, or ended.
|
|
36
|
+
*
|
|
37
|
+
* `phone.activeCall` returns a stable reference for a given call, so it is a safe snapshot.
|
|
38
|
+
*/
|
|
39
|
+
declare function useActiveCall(): Call | null;
|
|
40
|
+
/** An immutable snapshot of a call's reactive fields. */
|
|
41
|
+
interface CallView {
|
|
42
|
+
readonly state: CallState;
|
|
43
|
+
readonly isMuted: boolean;
|
|
44
|
+
readonly isOnHold: boolean;
|
|
45
|
+
readonly remote: RemoteParty;
|
|
46
|
+
readonly remoteStream: MediaStream | null;
|
|
47
|
+
readonly answeredAt: Date | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reactive view of a specific call. Re-renders only when one of the call's own events fires — no
|
|
51
|
+
* polling, no re-render storms.
|
|
52
|
+
*
|
|
53
|
+
* The snapshot is CACHED and invalidated only on an event, so `useSyncExternalStore` gets a stable
|
|
54
|
+
* reference between changes (returning a fresh object every render would infinite-loop).
|
|
55
|
+
*/
|
|
56
|
+
declare function useCall(call: Call | null): CallView | null;
|
|
57
|
+
/**
|
|
58
|
+
* The audio device surface (`phone.audio`) — reactive. Re-renders on `SoftphoneEvent.DeviceChange`: a
|
|
59
|
+
* device plugged/unplugged, or a selection changed (SPEC §7 Phase 2).
|
|
60
|
+
*
|
|
61
|
+
* The snapshot is CACHED and invalidated only on that event, same reasoning as `useCall`'s own
|
|
62
|
+
* `CallView`: `phone.audio` is ONE stable object whose fields mutate in place, so returning it
|
|
63
|
+
* directly would never change by reference and `useSyncExternalStore` would never re-render. A fresh
|
|
64
|
+
* object, rebuilt only when something actually changed, is what makes this reactive without polling.
|
|
65
|
+
*/
|
|
66
|
+
declare function useDevices(): AudioDevices;
|
|
67
|
+
|
|
68
|
+
export { type CallView, SoftphoneProvider, type SoftphoneProviderProps, useActiveCall, useCall, useDevices, useIsLeader, useLastError, useSoftphone, useSoftphoneState };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { S as Softphone, A as AudioDevices } from '../softphone-DHmhotbf.js';
|
|
4
|
+
import { f as CallState, R as RemoteParty, a as Call, h as FatalSoftphoneError, o as SoftphoneState } from '../call-BTYlT2SU.js';
|
|
5
|
+
|
|
6
|
+
interface SoftphoneProviderProps {
|
|
7
|
+
readonly softphone: Softphone;
|
|
8
|
+
readonly children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
declare function SoftphoneProvider({ softphone, children }: SoftphoneProviderProps): react.JSX.Element;
|
|
11
|
+
/** The `Softphone` instance from context. Stable — does not re-render on state changes. */
|
|
12
|
+
declare function useSoftphone(): Softphone;
|
|
13
|
+
|
|
14
|
+
/** Reactive softphone lifecycle state (`stopped` → `online` → …). */
|
|
15
|
+
/**
|
|
16
|
+
* The reason the phone cannot currently be used, or `null`.
|
|
17
|
+
*
|
|
18
|
+
* Reads `softphone.lastError`, which is STATE rather than only an event — the widget is usually
|
|
19
|
+
* opened *after* something broke, and a listener mounted at that point would have missed the event
|
|
20
|
+
* entirely and rendered a dialpad with no explanation.
|
|
21
|
+
*
|
|
22
|
+
* Re-reads on both channels because both can change it: `Error` sets it, and `StateChanged` clears it
|
|
23
|
+
* on reaching `online`. Subscribing to only the first would leave a stale gate on screen after the
|
|
24
|
+
* phone recovered.
|
|
25
|
+
*/
|
|
26
|
+
declare function useLastError(): FatalSoftphoneError | null;
|
|
27
|
+
declare function useSoftphoneState(): SoftphoneState;
|
|
28
|
+
/**
|
|
29
|
+
* Whether THIS tab is the one allowed to be reachable (`multi-tab-leader-election.md`). `false` by
|
|
30
|
+
* default and while waiting for another open tab to release it — show your own "active in another
|
|
31
|
+
* tab" state for that duration; the `/ui` widget already does.
|
|
32
|
+
*/
|
|
33
|
+
declare function useIsLeader(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The current call, or null. Re-renders when it changes — inbound, outbound, or ended.
|
|
36
|
+
*
|
|
37
|
+
* `phone.activeCall` returns a stable reference for a given call, so it is a safe snapshot.
|
|
38
|
+
*/
|
|
39
|
+
declare function useActiveCall(): Call | null;
|
|
40
|
+
/** An immutable snapshot of a call's reactive fields. */
|
|
41
|
+
interface CallView {
|
|
42
|
+
readonly state: CallState;
|
|
43
|
+
readonly isMuted: boolean;
|
|
44
|
+
readonly isOnHold: boolean;
|
|
45
|
+
readonly remote: RemoteParty;
|
|
46
|
+
readonly remoteStream: MediaStream | null;
|
|
47
|
+
readonly answeredAt: Date | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reactive view of a specific call. Re-renders only when one of the call's own events fires — no
|
|
51
|
+
* polling, no re-render storms.
|
|
52
|
+
*
|
|
53
|
+
* The snapshot is CACHED and invalidated only on an event, so `useSyncExternalStore` gets a stable
|
|
54
|
+
* reference between changes (returning a fresh object every render would infinite-loop).
|
|
55
|
+
*/
|
|
56
|
+
declare function useCall(call: Call | null): CallView | null;
|
|
57
|
+
/**
|
|
58
|
+
* The audio device surface (`phone.audio`) — reactive. Re-renders on `SoftphoneEvent.DeviceChange`: a
|
|
59
|
+
* device plugged/unplugged, or a selection changed (SPEC §7 Phase 2).
|
|
60
|
+
*
|
|
61
|
+
* The snapshot is CACHED and invalidated only on that event, same reasoning as `useCall`'s own
|
|
62
|
+
* `CallView`: `phone.audio` is ONE stable object whose fields mutate in place, so returning it
|
|
63
|
+
* directly would never change by reference and `useSyncExternalStore` would never re-render. A fresh
|
|
64
|
+
* object, rebuilt only when something actually changed, is what makes this reactive without polling.
|
|
65
|
+
*/
|
|
66
|
+
declare function useDevices(): AudioDevices;
|
|
67
|
+
|
|
68
|
+
export { type CallView, SoftphoneProvider, type SoftphoneProviderProps, useActiveCall, useCall, useDevices, useIsLeader, useLastError, useSoftphone, useSoftphoneState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call } from './call-BTYlT2SU.js';
|
|
2
|
+
|
|
3
|
+
/** Contacts, history, caller ID and the agents directory, all from one config. */
|
|
4
|
+
interface BackendSources {
|
|
5
|
+
readonly contacts: ContactsSource;
|
|
6
|
+
readonly history: HistorySource;
|
|
7
|
+
readonly resolveContact: (number: string) => Promise<Contact | null>;
|
|
8
|
+
/** The Historial tab's "filter by person" directory. Always Coventia's own org membership. */
|
|
9
|
+
readonly listAgents: () => Promise<AgentsDirectory>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Audio device control (SPEC §7 Phase 2). Reactive: `SoftphoneEvent.DeviceChange` fires whenever any
|
|
14
|
+
* field here changes — a device was plugged/unplugged, or a selection changed — so `useDevices()`
|
|
15
|
+
* (and any other listener) always reads a current snapshot rather than one frozen at mount.
|
|
16
|
+
*/
|
|
17
|
+
interface AudioDevices {
|
|
18
|
+
readonly inputs: readonly MediaDeviceInfo[];
|
|
19
|
+
readonly outputs: readonly MediaDeviceInfo[];
|
|
20
|
+
/**
|
|
21
|
+
* The currently-selected input/output, or `null` — no explicit choice made, the browser's own
|
|
22
|
+
* default is in use. Never a stale ID: if the selected device is unplugged, its selection is
|
|
23
|
+
* cleared back to `null` rather than pointing at a device that no longer exists.
|
|
24
|
+
*/
|
|
25
|
+
readonly currentInputId: string | null;
|
|
26
|
+
readonly currentOutputId: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Re-enumerate. `inputs`/`outputs` already stay current on their own — a device physically
|
|
29
|
+
* plugged or unplugged fires the browser's own `devicechange` and needs no help — but a
|
|
30
|
+
* *permission* grant does not: the labels enumerate as blank strings until the microphone has been
|
|
31
|
+
* granted at least once, and granting it later fires no event at all. Call this when a UI surface
|
|
32
|
+
* that shows device names is about to become visible (SPEC §7 Phase 2 note: the settings tab calls
|
|
33
|
+
* it on mount for exactly this reason).
|
|
34
|
+
*/
|
|
35
|
+
refresh(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Switches the microphone, or `null` to reset to the browser's own default device. Applies to the
|
|
38
|
+
* call in progress, if any — immediately, via `RTCRtpSender.replaceTrack`, no renegotiation and no
|
|
39
|
+
* interruption the far end can hear — and to every call placed or answered after this resolves.
|
|
40
|
+
*/
|
|
41
|
+
setInput(deviceId: string | null): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Switches where the REMOTE PARTY is heard, or `null` to reset to the browser's own default
|
|
44
|
+
* device. Applies instantly, live call or not — output routing needs no WebRTC renegotiation at
|
|
45
|
+
* all, unlike the input side.
|
|
46
|
+
*
|
|
47
|
+
* A no-op (resolves immediately) when `canSelectOutput` is false.
|
|
48
|
+
*/
|
|
49
|
+
setOutput(deviceId: string | null): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* The RINGTONE output, separate from the call speaker.
|
|
52
|
+
*
|
|
53
|
+
* Of every SDK surveyed only Twilio ships this, and users want it: ring on the laptop speakers so
|
|
54
|
+
* you hear it across the room, take the call in the headset.
|
|
55
|
+
*
|
|
56
|
+
* **Not yet implemented** — still Phase 2 groundwork, like `NoopAudioDevices` before it. Calling it
|
|
57
|
+
* resolves without effect until a later pass wires it up.
|
|
58
|
+
*/
|
|
59
|
+
setRingtoneOutput(deviceId: string | null): Promise<void>;
|
|
60
|
+
/** False when the browser lacks `setSinkId` (Safari, most of Firefox) — `setOutput` is then a no-op. */
|
|
61
|
+
readonly canSelectOutput: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The engine. Long-lived; owns registration, credentials, devices, and the provider.
|
|
65
|
+
*
|
|
66
|
+
* Implementation lands in S2.
|
|
67
|
+
*/
|
|
68
|
+
interface Softphone extends TypedEmitter<SoftphoneEventMap> {
|
|
69
|
+
readonly state: SoftphoneState;
|
|
70
|
+
/**
|
|
71
|
+
* The last **fatal** error, or `null` — the reason the phone cannot currently be used.
|
|
72
|
+
*
|
|
73
|
+
* State rather than only an event, because the common case is: the phone breaks, and *then* the
|
|
74
|
+
* user opens the panel to find out why. A widget that mounts after the failure would never see
|
|
75
|
+
* `SoftphoneEvent.Error`, and would render a dialpad with no explanation for why it does nothing.
|
|
76
|
+
*
|
|
77
|
+
* **Cleared when the softphone next reaches `online`** — the only evidence available that whatever
|
|
78
|
+
* was wrong no longer is. Non-fatal errors are emitted but never set this: a busy tone is a fact
|
|
79
|
+
* about one call, not a broken phone.
|
|
80
|
+
*/
|
|
81
|
+
readonly lastError: FatalSoftphoneError | null;
|
|
82
|
+
/**
|
|
83
|
+
* Whether THIS tab is the one allowed to be reachable (`multi-tab-leader-election.md`).
|
|
84
|
+
*
|
|
85
|
+
* Zadarma's on-demand registration means two tabs open for the same extension race each other to
|
|
86
|
+
* register on every inbound push — confirmed live, exactly one tab rings, unpredictably which. At
|
|
87
|
+
* most one open tab is ever the leader at a time; leadership passes to the next tab automatically
|
|
88
|
+
* (no reload) the moment the current leader closes, reloads, or crashes.
|
|
89
|
+
*
|
|
90
|
+
* `false` by default and while waiting for another tab to release it — a host should show its own
|
|
91
|
+
* "active in another tab" state for that duration (the `/ui` widget already does).
|
|
92
|
+
*/
|
|
93
|
+
readonly isLeader: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Start.
|
|
96
|
+
*
|
|
97
|
+
* **Call this from a user gesture.** Both the microphone permission prompt and the browser's
|
|
98
|
+
* autoplay policy key off user activation. Twilio's own docs name this as the cause of *missed
|
|
99
|
+
* calls*: with no activation, the incoming-call ringtone is silently blocked and the agent never
|
|
100
|
+
* knows anyone rang.
|
|
101
|
+
*/
|
|
102
|
+
start(): Promise<void>;
|
|
103
|
+
stop(): Promise<void>;
|
|
104
|
+
/** Idempotent. After this the instance is dead — construct a new one. */
|
|
105
|
+
destroy(): Promise<void>;
|
|
106
|
+
call(destination: string, options?: CallOptions): Promise<Call>;
|
|
107
|
+
/** The live call, or null. The consult leg of an attended transfer is reachable from it. */
|
|
108
|
+
readonly activeCall: Call | null;
|
|
109
|
+
/**
|
|
110
|
+
* Contacts + history, when `SoftphoneConfig.coventia` was given; `null` otherwise.
|
|
111
|
+
*
|
|
112
|
+
* The widget reads these rather than being handed a config, so there is exactly ONE set per
|
|
113
|
+
* softphone — one name cache, one of every request.
|
|
114
|
+
*/
|
|
115
|
+
readonly sources: BackendSources | null;
|
|
116
|
+
readonly audio: AudioDevices;
|
|
117
|
+
/**
|
|
118
|
+
* False when the browser has blocked audio playback.
|
|
119
|
+
*
|
|
120
|
+
* When false, an incoming call RINGS SILENTLY. Surface a button; call `startAudio()` from its
|
|
121
|
+
* click handler. This is the failure mode almost every voice SDK leaves undocumented.
|
|
122
|
+
*/
|
|
123
|
+
readonly canPlayAudio: boolean;
|
|
124
|
+
/** Must be called from within a user gesture. */
|
|
125
|
+
startAudio(): Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type { AudioDevices as A, Softphone as S };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call } from './call-BTYlT2SU.cjs';
|
|
2
|
+
|
|
3
|
+
/** Contacts, history, caller ID and the agents directory, all from one config. */
|
|
4
|
+
interface BackendSources {
|
|
5
|
+
readonly contacts: ContactsSource;
|
|
6
|
+
readonly history: HistorySource;
|
|
7
|
+
readonly resolveContact: (number: string) => Promise<Contact | null>;
|
|
8
|
+
/** The Historial tab's "filter by person" directory. Always Coventia's own org membership. */
|
|
9
|
+
readonly listAgents: () => Promise<AgentsDirectory>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Audio device control (SPEC §7 Phase 2). Reactive: `SoftphoneEvent.DeviceChange` fires whenever any
|
|
14
|
+
* field here changes — a device was plugged/unplugged, or a selection changed — so `useDevices()`
|
|
15
|
+
* (and any other listener) always reads a current snapshot rather than one frozen at mount.
|
|
16
|
+
*/
|
|
17
|
+
interface AudioDevices {
|
|
18
|
+
readonly inputs: readonly MediaDeviceInfo[];
|
|
19
|
+
readonly outputs: readonly MediaDeviceInfo[];
|
|
20
|
+
/**
|
|
21
|
+
* The currently-selected input/output, or `null` — no explicit choice made, the browser's own
|
|
22
|
+
* default is in use. Never a stale ID: if the selected device is unplugged, its selection is
|
|
23
|
+
* cleared back to `null` rather than pointing at a device that no longer exists.
|
|
24
|
+
*/
|
|
25
|
+
readonly currentInputId: string | null;
|
|
26
|
+
readonly currentOutputId: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Re-enumerate. `inputs`/`outputs` already stay current on their own — a device physically
|
|
29
|
+
* plugged or unplugged fires the browser's own `devicechange` and needs no help — but a
|
|
30
|
+
* *permission* grant does not: the labels enumerate as blank strings until the microphone has been
|
|
31
|
+
* granted at least once, and granting it later fires no event at all. Call this when a UI surface
|
|
32
|
+
* that shows device names is about to become visible (SPEC §7 Phase 2 note: the settings tab calls
|
|
33
|
+
* it on mount for exactly this reason).
|
|
34
|
+
*/
|
|
35
|
+
refresh(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Switches the microphone, or `null` to reset to the browser's own default device. Applies to the
|
|
38
|
+
* call in progress, if any — immediately, via `RTCRtpSender.replaceTrack`, no renegotiation and no
|
|
39
|
+
* interruption the far end can hear — and to every call placed or answered after this resolves.
|
|
40
|
+
*/
|
|
41
|
+
setInput(deviceId: string | null): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Switches where the REMOTE PARTY is heard, or `null` to reset to the browser's own default
|
|
44
|
+
* device. Applies instantly, live call or not — output routing needs no WebRTC renegotiation at
|
|
45
|
+
* all, unlike the input side.
|
|
46
|
+
*
|
|
47
|
+
* A no-op (resolves immediately) when `canSelectOutput` is false.
|
|
48
|
+
*/
|
|
49
|
+
setOutput(deviceId: string | null): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* The RINGTONE output, separate from the call speaker.
|
|
52
|
+
*
|
|
53
|
+
* Of every SDK surveyed only Twilio ships this, and users want it: ring on the laptop speakers so
|
|
54
|
+
* you hear it across the room, take the call in the headset.
|
|
55
|
+
*
|
|
56
|
+
* **Not yet implemented** — still Phase 2 groundwork, like `NoopAudioDevices` before it. Calling it
|
|
57
|
+
* resolves without effect until a later pass wires it up.
|
|
58
|
+
*/
|
|
59
|
+
setRingtoneOutput(deviceId: string | null): Promise<void>;
|
|
60
|
+
/** False when the browser lacks `setSinkId` (Safari, most of Firefox) — `setOutput` is then a no-op. */
|
|
61
|
+
readonly canSelectOutput: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The engine. Long-lived; owns registration, credentials, devices, and the provider.
|
|
65
|
+
*
|
|
66
|
+
* Implementation lands in S2.
|
|
67
|
+
*/
|
|
68
|
+
interface Softphone extends TypedEmitter<SoftphoneEventMap> {
|
|
69
|
+
readonly state: SoftphoneState;
|
|
70
|
+
/**
|
|
71
|
+
* The last **fatal** error, or `null` — the reason the phone cannot currently be used.
|
|
72
|
+
*
|
|
73
|
+
* State rather than only an event, because the common case is: the phone breaks, and *then* the
|
|
74
|
+
* user opens the panel to find out why. A widget that mounts after the failure would never see
|
|
75
|
+
* `SoftphoneEvent.Error`, and would render a dialpad with no explanation for why it does nothing.
|
|
76
|
+
*
|
|
77
|
+
* **Cleared when the softphone next reaches `online`** — the only evidence available that whatever
|
|
78
|
+
* was wrong no longer is. Non-fatal errors are emitted but never set this: a busy tone is a fact
|
|
79
|
+
* about one call, not a broken phone.
|
|
80
|
+
*/
|
|
81
|
+
readonly lastError: FatalSoftphoneError | null;
|
|
82
|
+
/**
|
|
83
|
+
* Whether THIS tab is the one allowed to be reachable (`multi-tab-leader-election.md`).
|
|
84
|
+
*
|
|
85
|
+
* Zadarma's on-demand registration means two tabs open for the same extension race each other to
|
|
86
|
+
* register on every inbound push — confirmed live, exactly one tab rings, unpredictably which. At
|
|
87
|
+
* most one open tab is ever the leader at a time; leadership passes to the next tab automatically
|
|
88
|
+
* (no reload) the moment the current leader closes, reloads, or crashes.
|
|
89
|
+
*
|
|
90
|
+
* `false` by default and while waiting for another tab to release it — a host should show its own
|
|
91
|
+
* "active in another tab" state for that duration (the `/ui` widget already does).
|
|
92
|
+
*/
|
|
93
|
+
readonly isLeader: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Start.
|
|
96
|
+
*
|
|
97
|
+
* **Call this from a user gesture.** Both the microphone permission prompt and the browser's
|
|
98
|
+
* autoplay policy key off user activation. Twilio's own docs name this as the cause of *missed
|
|
99
|
+
* calls*: with no activation, the incoming-call ringtone is silently blocked and the agent never
|
|
100
|
+
* knows anyone rang.
|
|
101
|
+
*/
|
|
102
|
+
start(): Promise<void>;
|
|
103
|
+
stop(): Promise<void>;
|
|
104
|
+
/** Idempotent. After this the instance is dead — construct a new one. */
|
|
105
|
+
destroy(): Promise<void>;
|
|
106
|
+
call(destination: string, options?: CallOptions): Promise<Call>;
|
|
107
|
+
/** The live call, or null. The consult leg of an attended transfer is reachable from it. */
|
|
108
|
+
readonly activeCall: Call | null;
|
|
109
|
+
/**
|
|
110
|
+
* Contacts + history, when `SoftphoneConfig.coventia` was given; `null` otherwise.
|
|
111
|
+
*
|
|
112
|
+
* The widget reads these rather than being handed a config, so there is exactly ONE set per
|
|
113
|
+
* softphone — one name cache, one of every request.
|
|
114
|
+
*/
|
|
115
|
+
readonly sources: BackendSources | null;
|
|
116
|
+
readonly audio: AudioDevices;
|
|
117
|
+
/**
|
|
118
|
+
* False when the browser has blocked audio playback.
|
|
119
|
+
*
|
|
120
|
+
* When false, an incoming call RINGS SILENTLY. Surface a button; call `startAudio()` from its
|
|
121
|
+
* click handler. This is the failure mode almost every voice SDK leaves undocumented.
|
|
122
|
+
*/
|
|
123
|
+
readonly canPlayAudio: boolean;
|
|
124
|
+
/** Must be called from within a user gesture. */
|
|
125
|
+
startAudio(): Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type { AudioDevices as A, Softphone as S };
|