@coventia/softphone 0.2.1 → 0.4.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-C6YpT6uR.d.cts} +107 -1
- package/dist/{call-VKq1I8yP.d.ts → call-C6YpT6uR.d.ts} +107 -1
- package/dist/chunk-5E5OEXS2.js +14 -0
- package/dist/chunk-5E5OEXS2.js.map +1 -0
- package/dist/{chunk-LMJXX4YM.js → chunk-UHNLEBBO.js} +4 -2
- package/dist/chunk-UHNLEBBO.js.map +1 -0
- package/dist/element/index.d.ts +124 -9
- package/dist/element/index.js +1837 -413
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-BlQBo2Ys.d.cts → engine-CDdaSojx.d.cts} +1 -1
- package/dist/{engine-C0BOkIcW.d.ts → engine-DPft99Wj.d.ts} +1 -1
- package/dist/index.cjs +213 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -6
- package/dist/index.d.ts +16 -6
- package/dist/index.js +75 -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-DlGV12Xp.d.cts → softphone-CY5d2WWY.d.cts} +28 -10
- package/dist/{softphone-Cyq3hiIM.d.ts → softphone-DXgmTyrX.d.ts} +28 -10
- package/dist/softphone.css +364 -35
- package/dist/{sources-IRKNQPWW.js → sources-ONYQW733.js} +125 -6
- package/dist/sources-ONYQW733.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 +1450 -33
- 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 +1445 -36
- 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
|
@@ -240,6 +240,11 @@ interface CallHistoryEntry {
|
|
|
240
240
|
readonly outcome?: CallOutcome;
|
|
241
241
|
/** The org's own number that was dialled — present only (and only meaningful) for an inbound call. */
|
|
242
242
|
readonly did?: string;
|
|
243
|
+
/**
|
|
244
|
+
* The org's own number an outbound call was made from (what the callee saw) — present only for outbound calls the
|
|
245
|
+
* backend recorded it for (outbound-number-per-agent.md §5.7). Digits, no `+`.
|
|
246
|
+
*/
|
|
247
|
+
readonly from?: string;
|
|
243
248
|
/** True when Coventia has at least one recording for this call. */
|
|
244
249
|
readonly recorded: boolean;
|
|
245
250
|
/**
|
|
@@ -372,6 +377,107 @@ interface PresenceCallbacks {
|
|
|
372
377
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
378
|
*/
|
|
374
379
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
380
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
381
|
+
interface RingtoneOption {
|
|
382
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
383
|
+
readonly id: string;
|
|
384
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
385
|
+
readonly name: string;
|
|
386
|
+
/**
|
|
387
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
388
|
+
* that is always available and plays whenever a file is not ready.
|
|
389
|
+
*/
|
|
390
|
+
readonly url: string | null;
|
|
391
|
+
/**
|
|
392
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
393
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
394
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
395
|
+
*/
|
|
396
|
+
readonly loop: {
|
|
397
|
+
readonly start: number;
|
|
398
|
+
readonly end: number;
|
|
399
|
+
} | null;
|
|
400
|
+
}
|
|
401
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
402
|
+
interface RingtoneSettings {
|
|
403
|
+
/** In display order. Always contains the built-in tone. */
|
|
404
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
405
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
406
|
+
readonly orgDefault: string | null;
|
|
407
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
408
|
+
readonly mine: string | null;
|
|
409
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
410
|
+
readonly effective: string;
|
|
411
|
+
}
|
|
412
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
413
|
+
interface RingtoneSource {
|
|
414
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
415
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
416
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
417
|
+
/**
|
|
418
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
419
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
420
|
+
*/
|
|
421
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* What kind of number this is, for the tags next to it in "Llamar desde" (outbound-number-per-agent.md §5.5).
|
|
425
|
+
* Display only — it never blocks a choice. `mobile` must not be used for customer care or unsolicited commercial
|
|
426
|
+
* calls; `commercial` (the 400 range) is for outbound commercial calls only and receives no calls.
|
|
427
|
+
*/
|
|
428
|
+
type OutboundNumberKind = 'landline' | 'mobile' | 'commercial' | 'other';
|
|
429
|
+
/** One of the organization's numbers an agent can call from. */
|
|
430
|
+
interface OutboundNumber {
|
|
431
|
+
/** Stable id — what is stored as the agent's choice. */
|
|
432
|
+
readonly id: string;
|
|
433
|
+
/** International format, digits only, no `+` — e.g. `34960000001`. */
|
|
434
|
+
readonly number: string;
|
|
435
|
+
/** The name the organization gave the number, or `null`. */
|
|
436
|
+
readonly label: string | null;
|
|
437
|
+
readonly kind: OutboundNumberKind;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Where the agent's extension stands. `pending`/`failed`: an assignment change could not update it yet, so the
|
|
441
|
+
* extension may still present an earlier number. `unassigned`: the agent has no extension in this organization.
|
|
442
|
+
*/
|
|
443
|
+
type OutboundNumberState = 'applied' | 'pending' | 'failed' | 'unassigned';
|
|
444
|
+
/** The organization's numbers and the caller's choice, as the backend resolves them. */
|
|
445
|
+
interface OutboundNumberSettings {
|
|
446
|
+
/** The organization's active numbers, ordered by number. */
|
|
447
|
+
readonly numbers: readonly OutboundNumber[];
|
|
448
|
+
/** The organization default (an `OutboundNumber.id`), or `null`. */
|
|
449
|
+
readonly orgDefault: string | null;
|
|
450
|
+
/** This agent's own choice, or `null` = "use the organization default". */
|
|
451
|
+
readonly mine: string | null;
|
|
452
|
+
/** The number this agent's calls show: `mine`, else `orgDefault`. */
|
|
453
|
+
readonly effective: string | null;
|
|
454
|
+
/** The agent's extension ("100"), or `null` when they have none. */
|
|
455
|
+
readonly extension: string | null;
|
|
456
|
+
readonly state: OutboundNumberState;
|
|
457
|
+
/** 0 when a change could go out now; otherwise the seconds until it can. */
|
|
458
|
+
readonly changeAvailableInSeconds: number;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Why a change was refused before anything was sent. `platform`: the shared limit on number changes (nothing the
|
|
462
|
+
* agent did). `user`: the agent changed it moments ago. Either way the previous number stays in use.
|
|
463
|
+
*/
|
|
464
|
+
interface OutboundNumberRefusal {
|
|
465
|
+
readonly reason: 'platform' | 'user';
|
|
466
|
+
/** Seconds until a change can go out — always at least 1. */
|
|
467
|
+
readonly retryAfterSeconds: number;
|
|
468
|
+
}
|
|
469
|
+
/** The outbound number of the agent the softphone is signed in as, in its organization. */
|
|
470
|
+
interface OutboundNumbersSource {
|
|
471
|
+
get(signal?: AbortSignal): Promise<OutboundNumberSettings>;
|
|
472
|
+
/**
|
|
473
|
+
* Changes this agent's number; `null` = back to the organization default. Resolves to the new settings once the
|
|
474
|
+
* change is in effect. When the request limits refuse it, rejects with an `Error` carrying
|
|
475
|
+
* `refusal: OutboundNumberRefusal` (check with `isOutboundNumberRefused`) — nothing was stored or sent.
|
|
476
|
+
*/
|
|
477
|
+
setMine(id: string | null): Promise<OutboundNumberSettings>;
|
|
478
|
+
/** The settings this agent last had on this device, if remembered — instant, no network. Optional. */
|
|
479
|
+
cached?(): Promise<OutboundNumberSettings | null>;
|
|
480
|
+
}
|
|
375
481
|
|
|
376
482
|
/**
|
|
377
483
|
* The error taxonomy.
|
|
@@ -830,4 +936,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
830
936
|
cancelConsult(): Promise<void>;
|
|
831
937
|
}
|
|
832
938
|
|
|
833
|
-
export {
|
|
939
|
+
export { isFatal as A, isTerminal as B, CALL_TRANSITIONS as C, type ContactsSource as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HistorySource as G, type HangupReason as H, INITIAL_STATES as I, type AgentsDirectory as J, type TransferOutcomesPage as K, type LogLevel as L, type PresenceSource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type CallHistoryEntry as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type ContactsPage as U, 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 OutboundNumber as k, type OutboundNumberKind as l, type OutboundNumberSettings as m, type OutboundNumberState as n, type OutboundNumbersSource as o, type RingtoneOption as p, type RingtoneSettings as q, type RingtoneSource as r, type SoftphoneError as s, type SoftphoneErrorCode as t, SoftphoneEvent as u, type SoftphoneEventMap as v, type SoftphoneState as w, type TokenSource as x, type TypedEmitter as y, canTransition as z };
|
|
@@ -240,6 +240,11 @@ interface CallHistoryEntry {
|
|
|
240
240
|
readonly outcome?: CallOutcome;
|
|
241
241
|
/** The org's own number that was dialled — present only (and only meaningful) for an inbound call. */
|
|
242
242
|
readonly did?: string;
|
|
243
|
+
/**
|
|
244
|
+
* The org's own number an outbound call was made from (what the callee saw) — present only for outbound calls the
|
|
245
|
+
* backend recorded it for (outbound-number-per-agent.md §5.7). Digits, no `+`.
|
|
246
|
+
*/
|
|
247
|
+
readonly from?: string;
|
|
243
248
|
/** True when Coventia has at least one recording for this call. */
|
|
244
249
|
readonly recorded: boolean;
|
|
245
250
|
/**
|
|
@@ -372,6 +377,107 @@ interface PresenceCallbacks {
|
|
|
372
377
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
373
378
|
*/
|
|
374
379
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
380
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
381
|
+
interface RingtoneOption {
|
|
382
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
383
|
+
readonly id: string;
|
|
384
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
385
|
+
readonly name: string;
|
|
386
|
+
/**
|
|
387
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
388
|
+
* that is always available and plays whenever a file is not ready.
|
|
389
|
+
*/
|
|
390
|
+
readonly url: string | null;
|
|
391
|
+
/**
|
|
392
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
393
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
394
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
395
|
+
*/
|
|
396
|
+
readonly loop: {
|
|
397
|
+
readonly start: number;
|
|
398
|
+
readonly end: number;
|
|
399
|
+
} | null;
|
|
400
|
+
}
|
|
401
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
402
|
+
interface RingtoneSettings {
|
|
403
|
+
/** In display order. Always contains the built-in tone. */
|
|
404
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
405
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
406
|
+
readonly orgDefault: string | null;
|
|
407
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
408
|
+
readonly mine: string | null;
|
|
409
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
410
|
+
readonly effective: string;
|
|
411
|
+
}
|
|
412
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
413
|
+
interface RingtoneSource {
|
|
414
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
415
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
416
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
417
|
+
/**
|
|
418
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
419
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
420
|
+
*/
|
|
421
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* What kind of number this is, for the tags next to it in "Llamar desde" (outbound-number-per-agent.md §5.5).
|
|
425
|
+
* Display only — it never blocks a choice. `mobile` must not be used for customer care or unsolicited commercial
|
|
426
|
+
* calls; `commercial` (the 400 range) is for outbound commercial calls only and receives no calls.
|
|
427
|
+
*/
|
|
428
|
+
type OutboundNumberKind = 'landline' | 'mobile' | 'commercial' | 'other';
|
|
429
|
+
/** One of the organization's numbers an agent can call from. */
|
|
430
|
+
interface OutboundNumber {
|
|
431
|
+
/** Stable id — what is stored as the agent's choice. */
|
|
432
|
+
readonly id: string;
|
|
433
|
+
/** International format, digits only, no `+` — e.g. `34960000001`. */
|
|
434
|
+
readonly number: string;
|
|
435
|
+
/** The name the organization gave the number, or `null`. */
|
|
436
|
+
readonly label: string | null;
|
|
437
|
+
readonly kind: OutboundNumberKind;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Where the agent's extension stands. `pending`/`failed`: an assignment change could not update it yet, so the
|
|
441
|
+
* extension may still present an earlier number. `unassigned`: the agent has no extension in this organization.
|
|
442
|
+
*/
|
|
443
|
+
type OutboundNumberState = 'applied' | 'pending' | 'failed' | 'unassigned';
|
|
444
|
+
/** The organization's numbers and the caller's choice, as the backend resolves them. */
|
|
445
|
+
interface OutboundNumberSettings {
|
|
446
|
+
/** The organization's active numbers, ordered by number. */
|
|
447
|
+
readonly numbers: readonly OutboundNumber[];
|
|
448
|
+
/** The organization default (an `OutboundNumber.id`), or `null`. */
|
|
449
|
+
readonly orgDefault: string | null;
|
|
450
|
+
/** This agent's own choice, or `null` = "use the organization default". */
|
|
451
|
+
readonly mine: string | null;
|
|
452
|
+
/** The number this agent's calls show: `mine`, else `orgDefault`. */
|
|
453
|
+
readonly effective: string | null;
|
|
454
|
+
/** The agent's extension ("100"), or `null` when they have none. */
|
|
455
|
+
readonly extension: string | null;
|
|
456
|
+
readonly state: OutboundNumberState;
|
|
457
|
+
/** 0 when a change could go out now; otherwise the seconds until it can. */
|
|
458
|
+
readonly changeAvailableInSeconds: number;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Why a change was refused before anything was sent. `platform`: the shared limit on number changes (nothing the
|
|
462
|
+
* agent did). `user`: the agent changed it moments ago. Either way the previous number stays in use.
|
|
463
|
+
*/
|
|
464
|
+
interface OutboundNumberRefusal {
|
|
465
|
+
readonly reason: 'platform' | 'user';
|
|
466
|
+
/** Seconds until a change can go out — always at least 1. */
|
|
467
|
+
readonly retryAfterSeconds: number;
|
|
468
|
+
}
|
|
469
|
+
/** The outbound number of the agent the softphone is signed in as, in its organization. */
|
|
470
|
+
interface OutboundNumbersSource {
|
|
471
|
+
get(signal?: AbortSignal): Promise<OutboundNumberSettings>;
|
|
472
|
+
/**
|
|
473
|
+
* Changes this agent's number; `null` = back to the organization default. Resolves to the new settings once the
|
|
474
|
+
* change is in effect. When the request limits refuse it, rejects with an `Error` carrying
|
|
475
|
+
* `refusal: OutboundNumberRefusal` (check with `isOutboundNumberRefused`) — nothing was stored or sent.
|
|
476
|
+
*/
|
|
477
|
+
setMine(id: string | null): Promise<OutboundNumberSettings>;
|
|
478
|
+
/** The settings this agent last had on this device, if remembered — instant, no network. Optional. */
|
|
479
|
+
cached?(): Promise<OutboundNumberSettings | null>;
|
|
480
|
+
}
|
|
375
481
|
|
|
376
482
|
/**
|
|
377
483
|
* The error taxonomy.
|
|
@@ -830,4 +936,4 @@ interface Call extends TypedEmitter<CallEventMap> {
|
|
|
830
936
|
cancelConsult(): Promise<void>;
|
|
831
937
|
}
|
|
832
938
|
|
|
833
|
-
export {
|
|
939
|
+
export { isFatal as A, isTerminal as B, CALL_TRANSITIONS as C, type ContactsSource as D, ERROR_CATALOGUE as E, type FatalErrorCode as F, type HistorySource as G, type HangupReason as H, INITIAL_STATES as I, type AgentsDirectory as J, type TransferOutcomesPage as K, type LogLevel as L, type PresenceSource as M, type NonFatalErrorCode as N, type OutboundNumberRefusal as O, type PresenceEntry as P, type CallHistoryEntry as Q, type RemoteParty as R, type SoftphoneConfig as S, type TokenEndpointOptions as T, type ContactsPage as U, 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 OutboundNumber as k, type OutboundNumberKind as l, type OutboundNumberSettings as m, type OutboundNumberState as n, type OutboundNumbersSource as o, type RingtoneOption as p, type RingtoneSettings as q, type RingtoneSource as r, type SoftphoneError as s, type SoftphoneErrorCode as t, SoftphoneEvent as u, type SoftphoneEventMap as v, type SoftphoneState as w, type TokenSource as x, type TypedEmitter as y, canTransition as z };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/core/outbound-numbers.ts
|
|
2
|
+
function isOutboundNumberRefused(value) {
|
|
3
|
+
const refusal = value?.refusal;
|
|
4
|
+
return value instanceof Error && !!refusal && (refusal.reason === "platform" || refusal.reason === "user") && typeof refusal.retryAfterSeconds === "number";
|
|
5
|
+
}
|
|
6
|
+
function outboundNumberRefused(refusal) {
|
|
7
|
+
const error = new Error(`Outbound number change refused (${refusal.reason}); retry in ${refusal.retryAfterSeconds}s`);
|
|
8
|
+
error.name = "OutboundNumberRefused";
|
|
9
|
+
return Object.assign(error, { refusal });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { isOutboundNumberRefused, outboundNumberRefused };
|
|
13
|
+
//# sourceMappingURL=chunk-5E5OEXS2.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-5E5OEXS2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/outbound-numbers.ts"],"names":[],"mappings":";AAWO,SAAS,wBAAwB,KAAA,EAAqD;AAC3F,EAAA,MAAM,UAAW,KAAA,EAA+D,OAAA;AAChF,EAAA,OACE,KAAA,YAAiB,KAAA,IACjB,CAAC,CAAC,OAAA,KACD,OAAA,CAAQ,MAAA,KAAW,UAAA,IAAc,OAAA,CAAQ,MAAA,KAAW,MAAA,CAAA,IACrD,OAAO,QAAQ,iBAAA,KAAsB,QAAA;AAEzC;AAGO,SAAS,sBAAsB,OAAA,EAA4D;AAChG,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,QAAQ,MAAM,CAAA,YAAA,EAAe,OAAA,CAAQ,iBAAiB,CAAA,CAAA,CAAG,CAAA;AACpH,EAAA,KAAA,CAAM,IAAA,GAAO,uBAAA;AACb,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,EAAE,SAAS,CAAA;AACzC","file":"chunk-5E5OEXS2.js","sourcesContent":["import type { OutboundNumberRefusal } from './types.js';\r\n\r\n/**\r\n * A change of outbound number the request limits refused (outbound-number-per-agent.md §5.3–§5.5). Structural,\r\n * like `isSoftphoneError`: the concrete error is a plain `Error` with a `refusal`, so a host — or a second copy\r\n * of the SDK on the page — can recognise it without sharing a class.\r\n */\r\nexport interface OutboundNumberRefusedError extends Error {\r\n readonly refusal: OutboundNumberRefusal;\r\n}\r\n\r\nexport function isOutboundNumberRefused(value: unknown): value is OutboundNumberRefusedError {\r\n const refusal = (value as { refusal?: Partial<OutboundNumberRefusal> } | null)?.refusal;\r\n return (\r\n value instanceof Error &&\r\n !!refusal &&\r\n (refusal.reason === 'platform' || refusal.reason === 'user') &&\r\n typeof refusal.retryAfterSeconds === 'number'\r\n );\r\n}\r\n\r\n/** @internal — raised by the Coventia source; hosts only read these. */\r\nexport function outboundNumberRefused(refusal: OutboundNumberRefusal): OutboundNumberRefusedError {\r\n const error = new Error(`Outbound number change refused (${refusal.reason}); retry in ${refusal.retryAfterSeconds}s`);\r\n error.name = 'OutboundNumberRefused';\r\n return Object.assign(error, { refusal });\r\n}\r\n"]}
|
|
@@ -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
|
@@ -109,6 +109,11 @@ interface CallHistoryEntry {
|
|
|
109
109
|
readonly outcome?: CallOutcome;
|
|
110
110
|
/** The org's own number that was dialled — present only (and only meaningful) for an inbound call. */
|
|
111
111
|
readonly did?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The org's own number an outbound call was made from (what the callee saw) — present only for outbound calls the
|
|
114
|
+
* backend recorded it for (outbound-number-per-agent.md §5.7). Digits, no `+`.
|
|
115
|
+
*/
|
|
116
|
+
readonly from?: string;
|
|
112
117
|
/** True when Coventia has at least one recording for this call. */
|
|
113
118
|
readonly recorded: boolean;
|
|
114
119
|
/**
|
|
@@ -233,9 +238,101 @@ interface PresenceCallbacks {
|
|
|
233
238
|
* long-lived — the returned promise resolves once, when the connection opens, not once per update.
|
|
234
239
|
*/
|
|
235
240
|
type PresenceSource = (callbacks: PresenceCallbacks) => Promise<PresenceConnection>;
|
|
241
|
+
/** One ringtone a user can pick (custom-ringtone.md §4.1). */
|
|
242
|
+
interface RingtoneOption {
|
|
243
|
+
/** Stable id — what is stored as the user's or the org's choice. */
|
|
244
|
+
readonly id: string;
|
|
245
|
+
/** Display name, in Spanish, as both pickers show it. */
|
|
246
|
+
readonly name: string;
|
|
247
|
+
/**
|
|
248
|
+
* Absolute URL of the MP3, or `null` for the tone the SDK synthesises itself (`clasico`) — the one
|
|
249
|
+
* that is always available and plays whenever a file is not ready.
|
|
250
|
+
*/
|
|
251
|
+
readonly url: string | null;
|
|
252
|
+
/**
|
|
253
|
+
* Where the music sits inside the decoded file, in seconds — loop between these, not over the whole file. An
|
|
254
|
+
* MP3 round trip adds a fixed delay at the start and padding at the end; looping the whole buffer would make a
|
|
255
|
+
* continuous ringtone stumble at every restart. `null` for the built-in tone, or when the backend gave none.
|
|
256
|
+
*/
|
|
257
|
+
readonly loop: {
|
|
258
|
+
readonly start: number;
|
|
259
|
+
readonly end: number;
|
|
260
|
+
} | null;
|
|
261
|
+
}
|
|
262
|
+
/** The catalogue and the three levels of choice, as the backend resolves them for the caller. */
|
|
263
|
+
interface RingtoneSettings {
|
|
264
|
+
/** In display order. Always contains the built-in tone. */
|
|
265
|
+
readonly catalogue: readonly RingtoneOption[];
|
|
266
|
+
/** The org's default, or `null` when the org has not set one. */
|
|
267
|
+
readonly orgDefault: string | null;
|
|
268
|
+
/** This user's own choice, or `null` = "use the org default". */
|
|
269
|
+
readonly mine: string | null;
|
|
270
|
+
/** What actually rings: `mine`, else `orgDefault`, else the built-in tone — always in `catalogue`. */
|
|
271
|
+
readonly effective: string;
|
|
272
|
+
}
|
|
273
|
+
/** The ringtone settings of the user the softphone is signed in as, in its organization. */
|
|
274
|
+
interface RingtoneSource {
|
|
275
|
+
get(signal?: AbortSignal): Promise<RingtoneSettings>;
|
|
276
|
+
/** Saves this user's choice; `null` clears it (back to the org default). Resolves to the new settings. */
|
|
277
|
+
setMine(id: string | null): Promise<RingtoneSettings>;
|
|
278
|
+
/**
|
|
279
|
+
* The settings this user last had on this device, if remembered — instant, no network. Used only to start
|
|
280
|
+
* loading the likely ringtone early; `get()` is always the truth. Optional: a source may not remember.
|
|
281
|
+
*/
|
|
282
|
+
cached?(): Promise<RingtoneSettings | null>;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* What kind of number this is, for the tags next to it in "Llamar desde" (outbound-number-per-agent.md §5.5).
|
|
286
|
+
* Display only — it never blocks a choice. `mobile` must not be used for customer care or unsolicited commercial
|
|
287
|
+
* calls; `commercial` (the 400 range) is for outbound commercial calls only and receives no calls.
|
|
288
|
+
*/
|
|
289
|
+
type OutboundNumberKind = 'landline' | 'mobile' | 'commercial' | 'other';
|
|
290
|
+
/** One of the organization's numbers an agent can call from. */
|
|
291
|
+
interface OutboundNumber {
|
|
292
|
+
/** Stable id — what is stored as the agent's choice. */
|
|
293
|
+
readonly id: string;
|
|
294
|
+
/** International format, digits only, no `+` — e.g. `34960000001`. */
|
|
295
|
+
readonly number: string;
|
|
296
|
+
/** The name the organization gave the number, or `null`. */
|
|
297
|
+
readonly label: string | null;
|
|
298
|
+
readonly kind: OutboundNumberKind;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Where the agent's extension stands. `pending`/`failed`: an assignment change could not update it yet, so the
|
|
302
|
+
* extension may still present an earlier number. `unassigned`: the agent has no extension in this organization.
|
|
303
|
+
*/
|
|
304
|
+
type OutboundNumberState = 'applied' | 'pending' | 'failed' | 'unassigned';
|
|
305
|
+
/** The organization's numbers and the caller's choice, as the backend resolves them. */
|
|
306
|
+
interface OutboundNumberSettings {
|
|
307
|
+
/** The organization's active numbers, ordered by number. */
|
|
308
|
+
readonly numbers: readonly OutboundNumber[];
|
|
309
|
+
/** The organization default (an `OutboundNumber.id`), or `null`. */
|
|
310
|
+
readonly orgDefault: string | null;
|
|
311
|
+
/** This agent's own choice, or `null` = "use the organization default". */
|
|
312
|
+
readonly mine: string | null;
|
|
313
|
+
/** The number this agent's calls show: `mine`, else `orgDefault`. */
|
|
314
|
+
readonly effective: string | null;
|
|
315
|
+
/** The agent's extension ("100"), or `null` when they have none. */
|
|
316
|
+
readonly extension: string | null;
|
|
317
|
+
readonly state: OutboundNumberState;
|
|
318
|
+
/** 0 when a change could go out now; otherwise the seconds until it can. */
|
|
319
|
+
readonly changeAvailableInSeconds: number;
|
|
320
|
+
}
|
|
321
|
+
/** The outbound number of the agent the softphone is signed in as, in its organization. */
|
|
322
|
+
interface OutboundNumbersSource {
|
|
323
|
+
get(signal?: AbortSignal): Promise<OutboundNumberSettings>;
|
|
324
|
+
/**
|
|
325
|
+
* Changes this agent's number; `null` = back to the organization default. Resolves to the new settings once the
|
|
326
|
+
* change is in effect. When the request limits refuse it, rejects with an `Error` carrying
|
|
327
|
+
* `refusal: OutboundNumberRefusal` (check with `isOutboundNumberRefused`) — nothing was stored or sent.
|
|
328
|
+
*/
|
|
329
|
+
setMine(id: string | null): Promise<OutboundNumberSettings>;
|
|
330
|
+
/** The settings this agent last had on this device, if remembered — instant, no network. Optional. */
|
|
331
|
+
cached?(): Promise<OutboundNumberSettings | null>;
|
|
332
|
+
}
|
|
236
333
|
|
|
237
|
-
/** Contacts, history, caller ID, the agents directory, transfer outcomes
|
|
238
|
-
* config. */
|
|
334
|
+
/** Contacts, history, caller ID, the agents directory, transfer outcomes, presence and ringtone
|
|
335
|
+
* settings, all from one config. */
|
|
239
336
|
interface BackendSources {
|
|
240
337
|
readonly contacts: ContactsSource;
|
|
241
338
|
readonly history: HistorySource;
|
|
@@ -248,6 +345,10 @@ interface BackendSources {
|
|
|
248
345
|
}) => Promise<TransferOutcomesPage>;
|
|
249
346
|
/** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */
|
|
250
347
|
readonly presence: PresenceSource;
|
|
348
|
+
/** The caller's ringtone settings (custom-ringtone.md §4.4). Always Coventia's — no override. */
|
|
349
|
+
readonly ringtones: RingtoneSource;
|
|
350
|
+
/** The caller's outbound number (outbound-number-per-agent.md §5.5). Always Coventia's — no override. */
|
|
351
|
+
readonly outboundNumbers: OutboundNumbersSource;
|
|
251
352
|
}
|
|
252
353
|
|
|
253
354
|
/**
|
|
@@ -690,12 +791,20 @@ interface AudioDevices {
|
|
|
690
791
|
readonly inputs: readonly MediaDeviceInfo[];
|
|
691
792
|
readonly outputs: readonly MediaDeviceInfo[];
|
|
692
793
|
/**
|
|
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
|
-
*
|
|
794
|
+
* The currently-selected input/output/ringtone output, or `null` — no explicit choice made, the
|
|
795
|
+
* browser's own default is in use. Never a stale ID: if the selected device is unplugged, its
|
|
796
|
+
* selection reads `null` rather than pointing at a device that no longer exists.
|
|
797
|
+
*
|
|
798
|
+
* **Choices are remembered** — in `localStorage` (`coventia-softphone:devices`), so they survive a
|
|
799
|
+
* reload. What is remembered is the user's *preference*, kept apart from these current values: an
|
|
800
|
+
* unplugged headset reads `null` here, and reads as selected again the moment it is plugged back in,
|
|
801
|
+
* without the user choosing it twice. Device ids are per browser and per site, which is exactly the
|
|
802
|
+
* scope of that storage (custom-ringtone.md §4.5). Where storage is blocked (private mode, a
|
|
803
|
+
* sandboxed iframe) choices simply last for the page, as they used to.
|
|
696
804
|
*/
|
|
697
805
|
readonly currentInputId: string | null;
|
|
698
806
|
readonly currentOutputId: string | null;
|
|
807
|
+
readonly currentRingtoneOutputId: string | null;
|
|
699
808
|
/**
|
|
700
809
|
* Re-enumerate. `inputs`/`outputs` already stay current on their own — a device physically
|
|
701
810
|
* plugged or unplugged fires the browser's own `devicechange` and needs no help — but a
|
|
@@ -720,17 +829,23 @@ interface AudioDevices {
|
|
|
720
829
|
*/
|
|
721
830
|
setOutput(deviceId: string | null): Promise<void>;
|
|
722
831
|
/**
|
|
723
|
-
* The RINGTONE output, separate from the call speaker.
|
|
832
|
+
* The RINGTONE output, separate from the call speaker, or `null` for the browser's default device.
|
|
724
833
|
*
|
|
725
834
|
* 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.
|
|
835
|
+
* you hear it across the room, take the call in the headset. Only the inbound ring (and its preview
|
|
836
|
+
* in Ajustes) follows it; the call itself, the ringback and the short cues stay where they were.
|
|
727
837
|
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
838
|
+
* Like `setOutput`, this only records the choice and fires `SoftphoneEvent.DeviceChange` — the `/ui`
|
|
839
|
+
* layer is what routes the ring. A no-op for routing when `canSelectRingtoneOutput` is false.
|
|
730
840
|
*/
|
|
731
841
|
setRingtoneOutput(deviceId: string | null): Promise<void>;
|
|
732
842
|
/** False when the browser lacks `setSinkId` (Safari, most of Firefox) — `setOutput` is then a no-op. */
|
|
733
843
|
readonly canSelectOutput: boolean;
|
|
844
|
+
/**
|
|
845
|
+
* Whether the ring can be sent to a chosen device. False on Safari (no `setSinkId` at all) — the
|
|
846
|
+
* ring then plays on the default output and a UI should not offer the picker.
|
|
847
|
+
*/
|
|
848
|
+
readonly canSelectRingtoneOutput: boolean;
|
|
734
849
|
}
|
|
735
850
|
/**
|
|
736
851
|
* The engine. Long-lived; owns registration, credentials, devices, and the provider.
|