@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 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/errors.ts","../../src/testing/mock.ts","../../src/testing/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * The error taxonomy.\r\n *\r\n * Two things make this worth more than `onError(Error)`:\r\n *\r\n * 1. **Fatal vs non-fatal, as a discriminated union.** A fatal error means the call (or the engine)\r\n * is over and the host must react. A non-fatal one is a warning the host may surface or ignore.\r\n * Collapsing them into one channel — which is what almost every SDK does — forces every\r\n * integrator to re-derive the distinction from string matching. Daily's split is the model.\r\n *\r\n * 2. **A coded catalogue with a `solution` per code.** Twilio and Telnyx both ship this and\r\n * integrators love it, because the support conversation becomes \"send me the code\" instead of\r\n * \"send me your logs\".\r\n *\r\n * The catalogue below is the SINGLE source of truth: `fatal` is derived from it at the type level,\r\n * so a code cannot be fatal in the table and non-fatal in a type, and adding a code without deciding\r\n * its severity is a compile error.\r\n */\r\n\r\ninterface ErrorSpec {\r\n readonly fatal: boolean;\r\n /** What went wrong, for a developer. */\r\n readonly message: string;\r\n /** What to do about it. Shown in logs and in our docs. */\r\n readonly solution: string;\r\n}\r\n\r\nexport const ERROR_CATALOGUE = {\r\n // ── Engine lifecycle ────────────────────────────────────────────────────────\r\n 'engine/duplicate-instance': {\r\n fatal: true,\r\n message: 'A Softphone instance is already running on this page.',\r\n solution:\r\n 'Construct one Softphone and share it. React StrictMode double-mounts in development — hold the instance above the tree, or in a ref, and call destroy() on unmount.',\r\n },\r\n 'engine/not-started': {\r\n fatal: false,\r\n message: 'The softphone has not been started.',\r\n solution: 'Await softphone.start() before placing or answering a call.',\r\n },\r\n 'engine/destroyed': {\r\n fatal: true,\r\n message: 'This Softphone has been destroyed and cannot be reused.',\r\n solution: 'Construct a new Softphone.',\r\n },\r\n\r\n // ── Auth ────────────────────────────────────────────────────────────────────\r\n 'auth/token-fetch-failed': {\r\n fatal: true,\r\n message: 'Could not obtain a voice token from the token endpoint.',\r\n solution:\r\n 'Check that the token endpoint is reachable and that the headers callback returns a valid host-app session token.',\r\n },\r\n 'auth/token-rejected': {\r\n fatal: true,\r\n message: 'The voice token was rejected.',\r\n solution:\r\n 'The token is expired, malformed, or scoped to a different organization. Mint a fresh one.',\r\n },\r\n\r\n // ── Media ───────────────────────────────────────────────────────────────────\r\n /*\r\n NONE of the four codes below are ever actually raised via `createError` — nothing in the SDK\r\n constructs a `SoftphoneError` with these codes today. Deleting them was the other option\r\n (SPEC discussion); they stay, commented, because the taxonomy and Spanish copy (`error-text.ts`)\r\n are worth keeping intact for whenever a real caller wants them, and a `SoftphoneErrorCode` union\r\n is a cheap thing to keep honest with a comment, expensive to keep churning as a breaking change.\r\n */\r\n 'media/permission-denied': {\r\n fatal: true,\r\n message: 'Microphone permission was denied.',\r\n solution:\r\n 'The permission prompt names the TOP-LEVEL origin. Ask for it from a user gesture, and re-request via the browser UI if previously blocked.',\r\n // NEVER RAISED. `MicPermissionGate` (ui/permission/) handles this condition today — it predates\r\n // this catalogue and has BETTER, browser-specific unblock instructions than a generic gate could\r\n // show. It does not import this catalogue at all; the two are unconnected on purpose.\r\n },\r\n 'media/no-input-device': {\r\n fatal: true,\r\n message: 'No microphone is available.',\r\n solution: 'Ask the user to connect a microphone, then retry.',\r\n // NEVER RAISED — same reason as `media/permission-denied` above: `MicPermissionGate` already\r\n // owns this.\r\n },\r\n 'media/webrtc-unsupported': {\r\n fatal: true,\r\n message: 'This browser does not expose the WebRTC APIs.',\r\n solution:\r\n 'Usually a privacy extension (uBlock Origin Extra, WebRTC Leak Prevent) disabling RTCPeerConnection — Twilio documents the same. Ask the user to disable it for this origin.',\r\n // NEVER RAISED — same reason again: `MicPermissionGate` already owns this.\r\n },\r\n 'media/playback-blocked': {\r\n fatal: false,\r\n message: 'The browser blocked audio playback (autoplay policy).',\r\n solution:\r\n 'Call softphone.startAudio() from a click handler. Until then the user cannot hear the caller, and an INCOMING RINGTONE WILL BE SILENT — this is how calls get missed.',\r\n /*\r\n NEVER RAISED — different reason from the other three. This is not superseded by an existing\r\n UI; the feature it describes (autoplay-recovery) is simply not built yet. `Softphone.canPlayAudio`\r\n / `startAudio()` (core/softphone.ts) are Phase 2 and currently no-op stubs — `canPlayAudio`\r\n always reports `true`. This is where that future work would report the block once it exists.\r\n */\r\n },\r\n\r\n // ── Network / signalling ────────────────────────────────────────────────────\r\n 'net/registration-failed': {\r\n fatal: true,\r\n message: 'SIP registration failed.',\r\n solution: 'Usually a stale credential. The engine refreshes and retries; if it persists, the extension may not exist.',\r\n },\r\n 'net/transport-failed': {\r\n fatal: true,\r\n message: 'The signalling transport could not be established.',\r\n solution: 'Check the CSP connect-src directive and any corporate proxy blocking WSS.',\r\n },\r\n 'net/ice-failed': {\r\n fatal: false,\r\n message: 'Media connectivity was lost; attempting an ICE restart.',\r\n solution:\r\n 'Usually a network handoff (wifi → cellular). The engine recovers on its own; surface a \"reconnecting\" banner rather than ending the call.',\r\n /*\r\n NEVER RAISED. The \"reconnecting\" banner this `solution` describes already exists — CallScreen\r\n shows \"Reconectando…\" directly off the call's own `reconnecting` STATE (`STATUS_LABEL` in\r\n views/CallScreen.tsx), which the SIP engine drives without going through the error channel at\r\n all. Raising this too would say the same fact twice through two different mechanisms. It would\r\n only earn its keep for host-side OBSERVABILITY — counting ICE restarts independently of the UI\r\n — which nobody has asked for yet.\r\n */\r\n },\r\n\r\n // ── Call ────────────────────────────────────────────────────────────────────\r\n 'call/not-answerable': {\r\n fatal: false,\r\n message: 'This call cannot be answered in its current state.',\r\n solution: 'Only a call in `ringing` may be answered. Check call.state first.',\r\n },\r\n 'call/busy': {\r\n fatal: false,\r\n message: 'The remote party is busy.',\r\n solution:\r\n 'Nothing to fix. Note SIP 486 is also what most endpoints send for a manual decline, so do not ' +\r\n 'label this \"busy\" alone in your UI — it is equally often \"did not answer\".',\r\n },\r\n 'call/no-answer': {\r\n fatal: false,\r\n message: 'The remote party did not answer.',\r\n solution: 'Nothing to fix. Surface it to the user.',\r\n },\r\n 'call/rejected': {\r\n fatal: false,\r\n message: 'The remote party rejected the call.',\r\n solution: 'Nothing to fix. Surface it to the user.',\r\n },\r\n 'call/missed': {\r\n fatal: false,\r\n message: 'An incoming call was cancelled before it was answered.',\r\n solution:\r\n 'Nothing to fix — the caller gave up while it rang. Surface it, and log it as a missed call.',\r\n },\r\n 'call/failed': {\r\n fatal: false,\r\n message: 'The call could not be completed.',\r\n solution:\r\n 'The catch-all for a call that never connected — a bad number, a rejected offer, a media ' +\r\n 'failure. `cause` carries the underlying SIP cause. Nothing is fatal: the phone still works.',\r\n },\r\n 'call/transfer-failed': {\r\n fatal: false,\r\n message: 'The transfer could not be completed.',\r\n solution: 'The original call is still up. Let the user retry or resume.',\r\n },\r\n 'call/unsupported': {\r\n fatal: false,\r\n message: 'The provider does not support this operation.',\r\n solution: 'Guard the UI on the operation before offering it.',\r\n },\r\n\r\n // ── Provider ────────────────────────────────────────────────────────────────\r\n 'provider/provision-failed': {\r\n fatal: true,\r\n message: 'The provider could not issue SIP credentials.',\r\n solution:\r\n 'The backend reached the carrier but got no usable credential. Check the org has a SIP extension provisioned.',\r\n },\r\n} as const satisfies Record<string, ErrorSpec>;\r\n\r\nexport type SoftphoneErrorCode = keyof typeof ERROR_CATALOGUE;\r\n\r\n/** Codes whose spec says `fatal: true`. Derived — cannot drift from the catalogue. */\r\nexport type FatalErrorCode = {\r\n [K in SoftphoneErrorCode]: (typeof ERROR_CATALOGUE)[K]['fatal'] extends true ? K : never;\r\n}[SoftphoneErrorCode];\r\n\r\nexport type NonFatalErrorCode = Exclude<SoftphoneErrorCode, FatalErrorCode>;\r\n\r\n/**\r\n * A fatal error: the call, or the engine, is over.\r\n *\r\n * `if (err.fatal)` narrows `err.code` to the fatal codes — which is the entire point of the split,\r\n * and the thing JSDoc could not have expressed (SPEC §2.2).\r\n */\r\nexport interface FatalSoftphoneError extends Error {\r\n readonly fatal: true;\r\n readonly code: FatalErrorCode;\r\n readonly solution: string;\r\n}\r\n\r\n/** A non-fatal error: a warning. The call, if any, continues. */\r\nexport interface NonFatalSoftphoneError extends Error {\r\n readonly fatal: false;\r\n readonly code: NonFatalErrorCode;\r\n readonly solution: string;\r\n}\r\n\r\nexport type SoftphoneError = FatalSoftphoneError | NonFatalSoftphoneError;\r\n\r\n/**\r\n * The one concrete error class. Not exported: the public contract is the `SoftphoneError` union and\r\n * the `code` on it, so nothing outside should be reaching for `instanceof`. It is a real `Error`\r\n * subclass, so `instanceof Error`, `.stack` and console formatting all behave as a host expects.\r\n */\r\nclass CatalogueError extends Error {\r\n readonly fatal: boolean;\r\n readonly code: SoftphoneErrorCode;\r\n readonly solution: string;\r\n\r\n constructor(code: SoftphoneErrorCode, cause?: unknown) {\r\n const spec: ErrorSpec = ERROR_CATALOGUE[code];\r\n // `cause` is only passed when there IS one: `{ cause: undefined }` would still define the\r\n // property, and an error carrying an explicit `undefined` cause reads as \"we looked and found\r\n // nothing\" rather than \"nothing was passed\".\r\n super(spec.message, cause === undefined ? undefined : { cause });\r\n this.name = 'SoftphoneError';\r\n this.code = code;\r\n this.fatal = spec.fatal;\r\n this.solution = spec.solution;\r\n }\r\n}\r\n\r\n/**\r\n * Is this already one of ours?\r\n *\r\n * Used where a layer catches whatever a lower one threw: a failure that already carries a catalogue\r\n * code should be reported AS that code, not flattened into the generic one for the layer that caught\r\n * it. Without this, a rejected voice token surfaces as \"the provider could not issue credentials\",\r\n * which is true and useless.\r\n *\r\n * Structural, not `instanceof` — the concrete class is not exported, and a duck-typed object with a\r\n * real code is as good as one of ours.\r\n */\r\nexport function isSoftphoneError(value: unknown): value is SoftphoneError {\r\n if (typeof value !== 'object' || value === null) return false;\r\n const candidate = value as { code?: unknown; fatal?: unknown };\r\n return typeof candidate.fatal === 'boolean' && (candidate.code as string) in ERROR_CATALOGUE;\r\n}\r\n\r\n/** Type guard, for hosts that would rather not rely on structural narrowing. */\r\nexport function isFatal(error: SoftphoneError): error is FatalSoftphoneError {\r\n return error.fatal;\r\n}\r\n\r\n/**\r\n * Construct a catalogue-backed error. **The only way the SDK raises one** — every `message` and\r\n * `solution` a host sees comes from the table above, so no call site invents error prose.\r\n *\r\n * The return type narrows on the code: `createError('call/busy')` is a `NonFatalSoftphoneError`, and\r\n * reading `.fatal` on it gives `false`, not `boolean`.\r\n */\r\nexport function createError<C extends SoftphoneErrorCode>(\r\n code: C,\r\n cause?: unknown,\r\n): C extends FatalErrorCode ? FatalSoftphoneError : NonFatalSoftphoneError {\r\n // The one cast in this module. A conditional return type cannot be verified from inside the\r\n // implementation — TypeScript has no way to prove the branch for an unresolved `C` — so the\r\n // guarantee rests on `fatal` being copied from the catalogue that DERIVES `FatalErrorCode`. The\r\n // test suite asserts that agreement for every code, which is the check the compiler cannot make.\r\n return new CatalogueError(code, cause) as C extends FatalErrorCode\r\n ? FatalSoftphoneError\r\n : NonFatalSoftphoneError;\r\n}\r\n","import type { CallEngine, EngineCall, EngineCallEvent } from '../core/engine.js';\r\nimport type { CallDirection, CallOptions, SoftphoneState } from '../core/types.js';\r\nimport type { HangupReason } from '../core/call.js';\r\nimport { createError, type SoftphoneError, type SoftphoneErrorCode } from '../core/errors.js';\r\n\r\n/**\r\n * MockProvider — a `CallEngine` that scripts a full call lifecycle with no Zadarma, no microphone, no\r\n * network and no real phone call (SPEC §5.7).\r\n *\r\n * It is DOUBLE-DUTY, and that is the point:\r\n * - the engine behind the playgrounds, so a full call (ring → answer → hold → transfer → hang up)\r\n * is drivable in a browser instantly;\r\n * - the test suite's fake.\r\n * The SAME object both ways, kept honest by the CallEngine contract suite — so what the playground\r\n * exercises is what the real engine must also satisfy.\r\n *\r\n * Everything is explicit and deterministic: nothing auto-advances on a timer. The host (a test, or a\r\n * playground button) decides when the remote answers, holds, drops the network, or hangs up. That is\r\n * what makes it usable as a test fake and as a demo without divergence.\r\n */\r\n\r\nlet seq = 0;\r\nfunction nextId(prefix: string): string {\r\n seq += 1;\r\n return `${prefix}-${seq}`;\r\n}\r\n\r\nclass MockEngineCall implements EngineCall {\r\n readonly #listeners = new Set<(event: EngineCallEvent) => void>();\r\n #ended = false;\r\n\r\n constructor(\r\n readonly id: string,\r\n readonly direction: CallDirection,\r\n readonly remoteNumber: string,\r\n ) {}\r\n\r\n onEvent(listener: (event: EngineCallEvent) => void): () => void {\r\n this.#listeners.add(listener);\r\n return () => this.#listeners.delete(listener);\r\n }\r\n\r\n /** @internal — fire a raw engine signal, asynchronously, as a real transport would. */\r\n fire(event: EngineCallEvent): void {\r\n if (this.#ended) return;\r\n if (event.type === 'ended') this.#ended = true;\r\n queueMicrotask(() => {\r\n for (const listener of [...this.#listeners]) listener(event);\r\n });\r\n }\r\n\r\n get ended(): boolean {\r\n return this.#ended;\r\n }\r\n\r\n // ── Commands (local side) ─────────────────────────────────────────────────\r\n async answer(): Promise<void> {\r\n // Inbound: the local user accepted. Media comes up.\r\n this.fire({ type: 'accepted' });\r\n }\r\n async reject(): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'rejected' });\r\n }\r\n async hangup(): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'local' });\r\n }\r\n mute(_muted: boolean): void {\r\n // The engine only carries the effect; Call owns the mute state + event.\r\n }\r\n async hold(): Promise<void> {\r\n this.fire({ type: 'held', originator: 'local' });\r\n }\r\n async unhold(): Promise<void> {\r\n this.fire({ type: 'unheld', originator: 'local' });\r\n }\r\n sendDigits(_digits: string): void {\r\n // A real engine sends RFC 2833 / SIP INFO; the mock has nothing to send to.\r\n }\r\n async transfer(_target: string): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'transferred' });\r\n }\r\n async startConsult(target: string): Promise<EngineCall> {\r\n return new MockEngineCall(nextId('consult'), 'outbound', target);\r\n }\r\n async completeTransfer(_consult: EngineCall): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'transferred' });\r\n }\r\n}\r\n\r\nexport interface SimulateIncomingOptions {\r\n readonly from: string;\r\n /**\r\n * No `displayName` here, deliberately. `EngineCall` carries only `remoteNumber` — the seam has no\r\n * display-name channel, because caller identity is resolved from CONTACTS, not from the SIP `From`\r\n * header (`CallImpl.setRemoteContact`). Until then `remote.displayName` IS the number.\r\n *\r\n * Accepting a name here would let consumers write passing tests for behaviour the real engine can\r\n * never produce. The mock must not outrun the seam it stands in for.\r\n */\r\n}\r\n\r\nexport class MockProvider implements CallEngine {\r\n #state: SoftphoneState = 'stopped';\r\n #current: MockEngineCall | null = null;\r\n readonly #incoming = new Set<(call: EngineCall) => void>();\r\n readonly #stateListeners = new Set<(state: SoftphoneState) => void>();\r\n readonly #errorListeners = new Set<(error: SoftphoneError) => void>();\r\n\r\n get state(): SoftphoneState {\r\n return this.#state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.#setState('online');\r\n }\r\n async stop(): Promise<void> {\r\n this.#current = null;\r\n this.#setState('stopped');\r\n }\r\n\r\n onIncoming(listener: (call: EngineCall) => void): () => void {\r\n this.#incoming.add(listener);\r\n return () => this.#incoming.delete(listener);\r\n }\r\n onStateChange(listener: (state: SoftphoneState) => void): () => void {\r\n this.#stateListeners.add(listener);\r\n return () => this.#stateListeners.delete(listener);\r\n }\r\n onError(listener: (error: SoftphoneError) => void): () => void {\r\n this.#errorListeners.add(listener);\r\n return () => this.#errorListeners.delete(listener);\r\n }\r\n\r\n async placeCall(destination: string, _options?: CallOptions): Promise<EngineCall> {\r\n const call = new MockEngineCall(nextId('out'), 'outbound', destination);\r\n this.#current = call;\r\n return call;\r\n }\r\n\r\n // ── The simulation surface (SPEC §5.7) ────────────────────────────────────\r\n\r\n /** An inbound INVITE lands. Fires the engine's `onIncoming` — the Softphone raises IncomingCall. */\r\n simulateIncoming(options: SimulateIncomingOptions): void {\r\n const call = new MockEngineCall(nextId('in'), 'inbound', options.from);\r\n this.#current = call;\r\n for (const listener of [...this.#incoming]) listener(call);\r\n }\r\n\r\n /** The remote answered our outbound call (or a provisional 180/183 first, if `progress` is set). */\r\n simulateRemoteAnswer(): void {\r\n this.#requireCurrent('simulateRemoteAnswer').fire({ type: 'accepted' });\r\n }\r\n\r\n /** Outbound provisional response — remote is ringing. Optional early media. */\r\n simulateProgress(kind: 'ringing' | 'early' = 'ringing'): void {\r\n this.#requireCurrent('simulateProgress').fire({ type: 'progress', response: kind });\r\n }\r\n\r\n /**\r\n * Remote media arrives — what `ontrack` produces on a real call.\r\n *\r\n * Added after the SDK shipped with NO audio at all: `core` hands the stream out and the UI attaches\r\n * it, but nothing did, and no test could see that because the mock had no way to produce a stream.\r\n * The seam always carried one (`EngineCallEvent` has `{ type: 'stream' }`); only the driver was\r\n * missing — unlike `displayName`, which the seam genuinely cannot carry (see SimulateIncomingOptions).\r\n *\r\n * The default is a stand-in object: jsdom has no `MediaStream`, and every consumer only ever hands\r\n * this to `srcObject`.\r\n */\r\n simulateRemoteStream(stream: MediaStream = {} as MediaStream): void {\r\n this.#requireCurrent('simulateRemoteStream').fire({ type: 'stream', stream });\r\n }\r\n\r\n simulateRemoteHold(): void {\r\n this.#requireCurrent('simulateRemoteHold').fire({ type: 'held', originator: 'remote' });\r\n }\r\n simulateRemoteUnhold(): void {\r\n this.#requireCurrent('simulateRemoteUnhold').fire({ type: 'unheld', originator: 'remote' });\r\n }\r\n\r\n simulateRemoteHangup(reason: HangupReason = 'remote'): void {\r\n this.#requireCurrent('simulateRemoteHangup').fire({ type: 'ended', reason });\r\n }\r\n\r\n /**\r\n * Raise any catalogued failure, exactly as a real engine would.\r\n *\r\n * The point is the two TREATMENTS: pass a fatal code (`net/registration-failed`) and the widget\r\n * replaces the dialpad with a gate that cannot be dismissed; pass a non-fatal one (`call/missed`)\r\n * and it shows a muted line under the number that the next keypress clears. A playground wants\r\n * both on a button, and neither can be reached by driving a mock call to a happy ending.\r\n */\r\n simulateError(code: SoftphoneErrorCode, cause?: unknown): void {\r\n const error = createError(code, cause);\r\n for (const listener of [...this.#errorListeners]) listener(error);\r\n }\r\n\r\n /** Media lost — the engine begins an ICE restart. The call is NOT over. */\r\n simulateNetworkDrop(): void {\r\n this.#requireCurrent('simulateNetworkDrop').fire({ type: 'reconnecting' });\r\n }\r\n simulateNetworkRecover(): void {\r\n this.#requireCurrent('simulateNetworkRecover').fire({ type: 'reconnected' });\r\n }\r\n\r\n // ── internals ─────────────────────────────────────────────────────────────\r\n #setState(state: SoftphoneState): void {\r\n if (state === this.#state) return;\r\n this.#state = state;\r\n for (const listener of [...this.#stateListeners]) listener(state);\r\n }\r\n\r\n #requireCurrent(op: string): MockEngineCall {\r\n if (!this.#current || this.#current.ended) {\r\n throw new Error(`MockProvider.${op}() called with no active call.`);\r\n }\r\n return this.#current;\r\n }\r\n}\r\n","/**\r\n * @coventia/softphone/testing — MockProvider and test helpers.\r\n *\r\n * Its own entry point so it never lands in a consumer's production bundle. See SPEC §5.7.\r\n */\r\nimport type { TokenSource, VoiceToken } from '../core/types.js';\r\n\r\nexport { MockProvider } from './mock.js';\r\nexport type { SimulateIncomingOptions } from './mock.js';\r\n\r\n/**\r\n * A TokenSource that returns a fixed, far-future fake token.\r\n *\r\n * `SoftphoneConfig.tokenSource` is required (SPEC §6.1) because the real Zadarma path needs it — but\r\n * the mock engine never mints credentials, so tests and playgrounds need something to satisfy the\r\n * type without a backend. This is that something. Never ship it.\r\n */\r\nexport function mockTokenSource(): TokenSource {\r\n return {\r\n async getToken(): Promise<VoiceToken> {\r\n return {\r\n token: 'mock-voice-token',\r\n identity: 'mock-user',\r\n // Far future; the mock does not refresh.\r\n expiresAt: new Date(4102444800000), // 2100-01-01\r\n };\r\n },\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { o as SoftphoneState, k as SoftphoneError, e as CallOptions, H as HangupReason, l as SoftphoneErrorCode, p as TokenSource } from '../call-BTYlT2SU.cjs';
|
|
2
|
+
import { C as CallEngine, E as EngineCall } from '../engine-D9nbeY08.cjs';
|
|
3
|
+
|
|
4
|
+
interface SimulateIncomingOptions {
|
|
5
|
+
readonly from: string;
|
|
6
|
+
}
|
|
7
|
+
declare class MockProvider implements CallEngine {
|
|
8
|
+
#private;
|
|
9
|
+
get state(): SoftphoneState;
|
|
10
|
+
start(): Promise<void>;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
onIncoming(listener: (call: EngineCall) => void): () => void;
|
|
13
|
+
onStateChange(listener: (state: SoftphoneState) => void): () => void;
|
|
14
|
+
onError(listener: (error: SoftphoneError) => void): () => void;
|
|
15
|
+
placeCall(destination: string, _options?: CallOptions): Promise<EngineCall>;
|
|
16
|
+
/** An inbound INVITE lands. Fires the engine's `onIncoming` — the Softphone raises IncomingCall. */
|
|
17
|
+
simulateIncoming(options: SimulateIncomingOptions): void;
|
|
18
|
+
/** The remote answered our outbound call (or a provisional 180/183 first, if `progress` is set). */
|
|
19
|
+
simulateRemoteAnswer(): void;
|
|
20
|
+
/** Outbound provisional response — remote is ringing. Optional early media. */
|
|
21
|
+
simulateProgress(kind?: 'ringing' | 'early'): void;
|
|
22
|
+
/**
|
|
23
|
+
* Remote media arrives — what `ontrack` produces on a real call.
|
|
24
|
+
*
|
|
25
|
+
* Added after the SDK shipped with NO audio at all: `core` hands the stream out and the UI attaches
|
|
26
|
+
* it, but nothing did, and no test could see that because the mock had no way to produce a stream.
|
|
27
|
+
* The seam always carried one (`EngineCallEvent` has `{ type: 'stream' }`); only the driver was
|
|
28
|
+
* missing — unlike `displayName`, which the seam genuinely cannot carry (see SimulateIncomingOptions).
|
|
29
|
+
*
|
|
30
|
+
* The default is a stand-in object: jsdom has no `MediaStream`, and every consumer only ever hands
|
|
31
|
+
* this to `srcObject`.
|
|
32
|
+
*/
|
|
33
|
+
simulateRemoteStream(stream?: MediaStream): void;
|
|
34
|
+
simulateRemoteHold(): void;
|
|
35
|
+
simulateRemoteUnhold(): void;
|
|
36
|
+
simulateRemoteHangup(reason?: HangupReason): void;
|
|
37
|
+
/**
|
|
38
|
+
* Raise any catalogued failure, exactly as a real engine would.
|
|
39
|
+
*
|
|
40
|
+
* The point is the two TREATMENTS: pass a fatal code (`net/registration-failed`) and the widget
|
|
41
|
+
* replaces the dialpad with a gate that cannot be dismissed; pass a non-fatal one (`call/missed`)
|
|
42
|
+
* and it shows a muted line under the number that the next keypress clears. A playground wants
|
|
43
|
+
* both on a button, and neither can be reached by driving a mock call to a happy ending.
|
|
44
|
+
*/
|
|
45
|
+
simulateError(code: SoftphoneErrorCode, cause?: unknown): void;
|
|
46
|
+
/** Media lost — the engine begins an ICE restart. The call is NOT over. */
|
|
47
|
+
simulateNetworkDrop(): void;
|
|
48
|
+
simulateNetworkRecover(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @coventia/softphone/testing — MockProvider and test helpers.
|
|
53
|
+
*
|
|
54
|
+
* Its own entry point so it never lands in a consumer's production bundle. See SPEC §5.7.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A TokenSource that returns a fixed, far-future fake token.
|
|
59
|
+
*
|
|
60
|
+
* `SoftphoneConfig.tokenSource` is required (SPEC §6.1) because the real Zadarma path needs it — but
|
|
61
|
+
* the mock engine never mints credentials, so tests and playgrounds need something to satisfy the
|
|
62
|
+
* type without a backend. This is that something. Never ship it.
|
|
63
|
+
*/
|
|
64
|
+
declare function mockTokenSource(): TokenSource;
|
|
65
|
+
|
|
66
|
+
export { MockProvider, type SimulateIncomingOptions, mockTokenSource };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { o as SoftphoneState, k as SoftphoneError, e as CallOptions, H as HangupReason, l as SoftphoneErrorCode, p as TokenSource } from '../call-BTYlT2SU.js';
|
|
2
|
+
import { C as CallEngine, E as EngineCall } from '../engine-Doa6Brkm.js';
|
|
3
|
+
|
|
4
|
+
interface SimulateIncomingOptions {
|
|
5
|
+
readonly from: string;
|
|
6
|
+
}
|
|
7
|
+
declare class MockProvider implements CallEngine {
|
|
8
|
+
#private;
|
|
9
|
+
get state(): SoftphoneState;
|
|
10
|
+
start(): Promise<void>;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
onIncoming(listener: (call: EngineCall) => void): () => void;
|
|
13
|
+
onStateChange(listener: (state: SoftphoneState) => void): () => void;
|
|
14
|
+
onError(listener: (error: SoftphoneError) => void): () => void;
|
|
15
|
+
placeCall(destination: string, _options?: CallOptions): Promise<EngineCall>;
|
|
16
|
+
/** An inbound INVITE lands. Fires the engine's `onIncoming` — the Softphone raises IncomingCall. */
|
|
17
|
+
simulateIncoming(options: SimulateIncomingOptions): void;
|
|
18
|
+
/** The remote answered our outbound call (or a provisional 180/183 first, if `progress` is set). */
|
|
19
|
+
simulateRemoteAnswer(): void;
|
|
20
|
+
/** Outbound provisional response — remote is ringing. Optional early media. */
|
|
21
|
+
simulateProgress(kind?: 'ringing' | 'early'): void;
|
|
22
|
+
/**
|
|
23
|
+
* Remote media arrives — what `ontrack` produces on a real call.
|
|
24
|
+
*
|
|
25
|
+
* Added after the SDK shipped with NO audio at all: `core` hands the stream out and the UI attaches
|
|
26
|
+
* it, but nothing did, and no test could see that because the mock had no way to produce a stream.
|
|
27
|
+
* The seam always carried one (`EngineCallEvent` has `{ type: 'stream' }`); only the driver was
|
|
28
|
+
* missing — unlike `displayName`, which the seam genuinely cannot carry (see SimulateIncomingOptions).
|
|
29
|
+
*
|
|
30
|
+
* The default is a stand-in object: jsdom has no `MediaStream`, and every consumer only ever hands
|
|
31
|
+
* this to `srcObject`.
|
|
32
|
+
*/
|
|
33
|
+
simulateRemoteStream(stream?: MediaStream): void;
|
|
34
|
+
simulateRemoteHold(): void;
|
|
35
|
+
simulateRemoteUnhold(): void;
|
|
36
|
+
simulateRemoteHangup(reason?: HangupReason): void;
|
|
37
|
+
/**
|
|
38
|
+
* Raise any catalogued failure, exactly as a real engine would.
|
|
39
|
+
*
|
|
40
|
+
* The point is the two TREATMENTS: pass a fatal code (`net/registration-failed`) and the widget
|
|
41
|
+
* replaces the dialpad with a gate that cannot be dismissed; pass a non-fatal one (`call/missed`)
|
|
42
|
+
* and it shows a muted line under the number that the next keypress clears. A playground wants
|
|
43
|
+
* both on a button, and neither can be reached by driving a mock call to a happy ending.
|
|
44
|
+
*/
|
|
45
|
+
simulateError(code: SoftphoneErrorCode, cause?: unknown): void;
|
|
46
|
+
/** Media lost — the engine begins an ICE restart. The call is NOT over. */
|
|
47
|
+
simulateNetworkDrop(): void;
|
|
48
|
+
simulateNetworkRecover(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @coventia/softphone/testing — MockProvider and test helpers.
|
|
53
|
+
*
|
|
54
|
+
* Its own entry point so it never lands in a consumer's production bundle. See SPEC §5.7.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A TokenSource that returns a fixed, far-future fake token.
|
|
59
|
+
*
|
|
60
|
+
* `SoftphoneConfig.tokenSource` is required (SPEC §6.1) because the real Zadarma path needs it — but
|
|
61
|
+
* the mock engine never mints credentials, so tests and playgrounds need something to satisfy the
|
|
62
|
+
* type without a backend. This is that something. Never ship it.
|
|
63
|
+
*/
|
|
64
|
+
declare function mockTokenSource(): TokenSource;
|
|
65
|
+
|
|
66
|
+
export { MockProvider, type SimulateIncomingOptions, mockTokenSource };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createError } from '../chunk-2ZSOGGPO.js';
|
|
2
|
+
|
|
3
|
+
// src/testing/mock.ts
|
|
4
|
+
var seq = 0;
|
|
5
|
+
function nextId(prefix) {
|
|
6
|
+
seq += 1;
|
|
7
|
+
return `${prefix}-${seq}`;
|
|
8
|
+
}
|
|
9
|
+
var MockEngineCall = class _MockEngineCall {
|
|
10
|
+
constructor(id, direction, remoteNumber) {
|
|
11
|
+
this.id = id;
|
|
12
|
+
this.direction = direction;
|
|
13
|
+
this.remoteNumber = remoteNumber;
|
|
14
|
+
}
|
|
15
|
+
id;
|
|
16
|
+
direction;
|
|
17
|
+
remoteNumber;
|
|
18
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
19
|
+
#ended = false;
|
|
20
|
+
onEvent(listener) {
|
|
21
|
+
this.#listeners.add(listener);
|
|
22
|
+
return () => this.#listeners.delete(listener);
|
|
23
|
+
}
|
|
24
|
+
/** @internal — fire a raw engine signal, asynchronously, as a real transport would. */
|
|
25
|
+
fire(event) {
|
|
26
|
+
if (this.#ended) return;
|
|
27
|
+
if (event.type === "ended") this.#ended = true;
|
|
28
|
+
queueMicrotask(() => {
|
|
29
|
+
for (const listener of [...this.#listeners]) listener(event);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
get ended() {
|
|
33
|
+
return this.#ended;
|
|
34
|
+
}
|
|
35
|
+
// ── Commands (local side) ─────────────────────────────────────────────────
|
|
36
|
+
async answer() {
|
|
37
|
+
this.fire({ type: "accepted" });
|
|
38
|
+
}
|
|
39
|
+
async reject() {
|
|
40
|
+
this.fire({ type: "ended", reason: "rejected" });
|
|
41
|
+
}
|
|
42
|
+
async hangup() {
|
|
43
|
+
this.fire({ type: "ended", reason: "local" });
|
|
44
|
+
}
|
|
45
|
+
mute(_muted) {
|
|
46
|
+
}
|
|
47
|
+
async hold() {
|
|
48
|
+
this.fire({ type: "held", originator: "local" });
|
|
49
|
+
}
|
|
50
|
+
async unhold() {
|
|
51
|
+
this.fire({ type: "unheld", originator: "local" });
|
|
52
|
+
}
|
|
53
|
+
sendDigits(_digits) {
|
|
54
|
+
}
|
|
55
|
+
async transfer(_target) {
|
|
56
|
+
this.fire({ type: "ended", reason: "transferred" });
|
|
57
|
+
}
|
|
58
|
+
async startConsult(target) {
|
|
59
|
+
return new _MockEngineCall(nextId("consult"), "outbound", target);
|
|
60
|
+
}
|
|
61
|
+
async completeTransfer(_consult) {
|
|
62
|
+
this.fire({ type: "ended", reason: "transferred" });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var MockProvider = class {
|
|
66
|
+
#state = "stopped";
|
|
67
|
+
#current = null;
|
|
68
|
+
#incoming = /* @__PURE__ */ new Set();
|
|
69
|
+
#stateListeners = /* @__PURE__ */ new Set();
|
|
70
|
+
#errorListeners = /* @__PURE__ */ new Set();
|
|
71
|
+
get state() {
|
|
72
|
+
return this.#state;
|
|
73
|
+
}
|
|
74
|
+
async start() {
|
|
75
|
+
this.#setState("online");
|
|
76
|
+
}
|
|
77
|
+
async stop() {
|
|
78
|
+
this.#current = null;
|
|
79
|
+
this.#setState("stopped");
|
|
80
|
+
}
|
|
81
|
+
onIncoming(listener) {
|
|
82
|
+
this.#incoming.add(listener);
|
|
83
|
+
return () => this.#incoming.delete(listener);
|
|
84
|
+
}
|
|
85
|
+
onStateChange(listener) {
|
|
86
|
+
this.#stateListeners.add(listener);
|
|
87
|
+
return () => this.#stateListeners.delete(listener);
|
|
88
|
+
}
|
|
89
|
+
onError(listener) {
|
|
90
|
+
this.#errorListeners.add(listener);
|
|
91
|
+
return () => this.#errorListeners.delete(listener);
|
|
92
|
+
}
|
|
93
|
+
async placeCall(destination, _options) {
|
|
94
|
+
const call = new MockEngineCall(nextId("out"), "outbound", destination);
|
|
95
|
+
this.#current = call;
|
|
96
|
+
return call;
|
|
97
|
+
}
|
|
98
|
+
// ── The simulation surface (SPEC §5.7) ────────────────────────────────────
|
|
99
|
+
/** An inbound INVITE lands. Fires the engine's `onIncoming` — the Softphone raises IncomingCall. */
|
|
100
|
+
simulateIncoming(options) {
|
|
101
|
+
const call = new MockEngineCall(nextId("in"), "inbound", options.from);
|
|
102
|
+
this.#current = call;
|
|
103
|
+
for (const listener of [...this.#incoming]) listener(call);
|
|
104
|
+
}
|
|
105
|
+
/** The remote answered our outbound call (or a provisional 180/183 first, if `progress` is set). */
|
|
106
|
+
simulateRemoteAnswer() {
|
|
107
|
+
this.#requireCurrent("simulateRemoteAnswer").fire({ type: "accepted" });
|
|
108
|
+
}
|
|
109
|
+
/** Outbound provisional response — remote is ringing. Optional early media. */
|
|
110
|
+
simulateProgress(kind = "ringing") {
|
|
111
|
+
this.#requireCurrent("simulateProgress").fire({ type: "progress", response: kind });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Remote media arrives — what `ontrack` produces on a real call.
|
|
115
|
+
*
|
|
116
|
+
* Added after the SDK shipped with NO audio at all: `core` hands the stream out and the UI attaches
|
|
117
|
+
* it, but nothing did, and no test could see that because the mock had no way to produce a stream.
|
|
118
|
+
* The seam always carried one (`EngineCallEvent` has `{ type: 'stream' }`); only the driver was
|
|
119
|
+
* missing — unlike `displayName`, which the seam genuinely cannot carry (see SimulateIncomingOptions).
|
|
120
|
+
*
|
|
121
|
+
* The default is a stand-in object: jsdom has no `MediaStream`, and every consumer only ever hands
|
|
122
|
+
* this to `srcObject`.
|
|
123
|
+
*/
|
|
124
|
+
simulateRemoteStream(stream = {}) {
|
|
125
|
+
this.#requireCurrent("simulateRemoteStream").fire({ type: "stream", stream });
|
|
126
|
+
}
|
|
127
|
+
simulateRemoteHold() {
|
|
128
|
+
this.#requireCurrent("simulateRemoteHold").fire({ type: "held", originator: "remote" });
|
|
129
|
+
}
|
|
130
|
+
simulateRemoteUnhold() {
|
|
131
|
+
this.#requireCurrent("simulateRemoteUnhold").fire({ type: "unheld", originator: "remote" });
|
|
132
|
+
}
|
|
133
|
+
simulateRemoteHangup(reason = "remote") {
|
|
134
|
+
this.#requireCurrent("simulateRemoteHangup").fire({ type: "ended", reason });
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Raise any catalogued failure, exactly as a real engine would.
|
|
138
|
+
*
|
|
139
|
+
* The point is the two TREATMENTS: pass a fatal code (`net/registration-failed`) and the widget
|
|
140
|
+
* replaces the dialpad with a gate that cannot be dismissed; pass a non-fatal one (`call/missed`)
|
|
141
|
+
* and it shows a muted line under the number that the next keypress clears. A playground wants
|
|
142
|
+
* both on a button, and neither can be reached by driving a mock call to a happy ending.
|
|
143
|
+
*/
|
|
144
|
+
simulateError(code, cause) {
|
|
145
|
+
const error = createError(code, cause);
|
|
146
|
+
for (const listener of [...this.#errorListeners]) listener(error);
|
|
147
|
+
}
|
|
148
|
+
/** Media lost — the engine begins an ICE restart. The call is NOT over. */
|
|
149
|
+
simulateNetworkDrop() {
|
|
150
|
+
this.#requireCurrent("simulateNetworkDrop").fire({ type: "reconnecting" });
|
|
151
|
+
}
|
|
152
|
+
simulateNetworkRecover() {
|
|
153
|
+
this.#requireCurrent("simulateNetworkRecover").fire({ type: "reconnected" });
|
|
154
|
+
}
|
|
155
|
+
// ── internals ─────────────────────────────────────────────────────────────
|
|
156
|
+
#setState(state) {
|
|
157
|
+
if (state === this.#state) return;
|
|
158
|
+
this.#state = state;
|
|
159
|
+
for (const listener of [...this.#stateListeners]) listener(state);
|
|
160
|
+
}
|
|
161
|
+
#requireCurrent(op) {
|
|
162
|
+
if (!this.#current || this.#current.ended) {
|
|
163
|
+
throw new Error(`MockProvider.${op}() called with no active call.`);
|
|
164
|
+
}
|
|
165
|
+
return this.#current;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/testing/index.ts
|
|
170
|
+
function mockTokenSource() {
|
|
171
|
+
return {
|
|
172
|
+
async getToken() {
|
|
173
|
+
return {
|
|
174
|
+
token: "mock-voice-token",
|
|
175
|
+
identity: "mock-user",
|
|
176
|
+
// Far future; the mock does not refresh.
|
|
177
|
+
expiresAt: /* @__PURE__ */ new Date(41024448e5)
|
|
178
|
+
// 2100-01-01
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { MockProvider, mockTokenSource };
|
|
185
|
+
//# sourceMappingURL=index.js.map
|
|
186
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/testing/mock.ts","../../src/testing/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { CallEngine, EngineCall, EngineCallEvent } from '../core/engine.js';\r\nimport type { CallDirection, CallOptions, SoftphoneState } from '../core/types.js';\r\nimport type { HangupReason } from '../core/call.js';\r\nimport { createError, type SoftphoneError, type SoftphoneErrorCode } from '../core/errors.js';\r\n\r\n/**\r\n * MockProvider — a `CallEngine` that scripts a full call lifecycle with no Zadarma, no microphone, no\r\n * network and no real phone call (SPEC §5.7).\r\n *\r\n * It is DOUBLE-DUTY, and that is the point:\r\n * - the engine behind the playgrounds, so a full call (ring → answer → hold → transfer → hang up)\r\n * is drivable in a browser instantly;\r\n * - the test suite's fake.\r\n * The SAME object both ways, kept honest by the CallEngine contract suite — so what the playground\r\n * exercises is what the real engine must also satisfy.\r\n *\r\n * Everything is explicit and deterministic: nothing auto-advances on a timer. The host (a test, or a\r\n * playground button) decides when the remote answers, holds, drops the network, or hangs up. That is\r\n * what makes it usable as a test fake and as a demo without divergence.\r\n */\r\n\r\nlet seq = 0;\r\nfunction nextId(prefix: string): string {\r\n seq += 1;\r\n return `${prefix}-${seq}`;\r\n}\r\n\r\nclass MockEngineCall implements EngineCall {\r\n readonly #listeners = new Set<(event: EngineCallEvent) => void>();\r\n #ended = false;\r\n\r\n constructor(\r\n readonly id: string,\r\n readonly direction: CallDirection,\r\n readonly remoteNumber: string,\r\n ) {}\r\n\r\n onEvent(listener: (event: EngineCallEvent) => void): () => void {\r\n this.#listeners.add(listener);\r\n return () => this.#listeners.delete(listener);\r\n }\r\n\r\n /** @internal — fire a raw engine signal, asynchronously, as a real transport would. */\r\n fire(event: EngineCallEvent): void {\r\n if (this.#ended) return;\r\n if (event.type === 'ended') this.#ended = true;\r\n queueMicrotask(() => {\r\n for (const listener of [...this.#listeners]) listener(event);\r\n });\r\n }\r\n\r\n get ended(): boolean {\r\n return this.#ended;\r\n }\r\n\r\n // ── Commands (local side) ─────────────────────────────────────────────────\r\n async answer(): Promise<void> {\r\n // Inbound: the local user accepted. Media comes up.\r\n this.fire({ type: 'accepted' });\r\n }\r\n async reject(): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'rejected' });\r\n }\r\n async hangup(): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'local' });\r\n }\r\n mute(_muted: boolean): void {\r\n // The engine only carries the effect; Call owns the mute state + event.\r\n }\r\n async hold(): Promise<void> {\r\n this.fire({ type: 'held', originator: 'local' });\r\n }\r\n async unhold(): Promise<void> {\r\n this.fire({ type: 'unheld', originator: 'local' });\r\n }\r\n sendDigits(_digits: string): void {\r\n // A real engine sends RFC 2833 / SIP INFO; the mock has nothing to send to.\r\n }\r\n async transfer(_target: string): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'transferred' });\r\n }\r\n async startConsult(target: string): Promise<EngineCall> {\r\n return new MockEngineCall(nextId('consult'), 'outbound', target);\r\n }\r\n async completeTransfer(_consult: EngineCall): Promise<void> {\r\n this.fire({ type: 'ended', reason: 'transferred' });\r\n }\r\n}\r\n\r\nexport interface SimulateIncomingOptions {\r\n readonly from: string;\r\n /**\r\n * No `displayName` here, deliberately. `EngineCall` carries only `remoteNumber` — the seam has no\r\n * display-name channel, because caller identity is resolved from CONTACTS, not from the SIP `From`\r\n * header (`CallImpl.setRemoteContact`). Until then `remote.displayName` IS the number.\r\n *\r\n * Accepting a name here would let consumers write passing tests for behaviour the real engine can\r\n * never produce. The mock must not outrun the seam it stands in for.\r\n */\r\n}\r\n\r\nexport class MockProvider implements CallEngine {\r\n #state: SoftphoneState = 'stopped';\r\n #current: MockEngineCall | null = null;\r\n readonly #incoming = new Set<(call: EngineCall) => void>();\r\n readonly #stateListeners = new Set<(state: SoftphoneState) => void>();\r\n readonly #errorListeners = new Set<(error: SoftphoneError) => void>();\r\n\r\n get state(): SoftphoneState {\r\n return this.#state;\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.#setState('online');\r\n }\r\n async stop(): Promise<void> {\r\n this.#current = null;\r\n this.#setState('stopped');\r\n }\r\n\r\n onIncoming(listener: (call: EngineCall) => void): () => void {\r\n this.#incoming.add(listener);\r\n return () => this.#incoming.delete(listener);\r\n }\r\n onStateChange(listener: (state: SoftphoneState) => void): () => void {\r\n this.#stateListeners.add(listener);\r\n return () => this.#stateListeners.delete(listener);\r\n }\r\n onError(listener: (error: SoftphoneError) => void): () => void {\r\n this.#errorListeners.add(listener);\r\n return () => this.#errorListeners.delete(listener);\r\n }\r\n\r\n async placeCall(destination: string, _options?: CallOptions): Promise<EngineCall> {\r\n const call = new MockEngineCall(nextId('out'), 'outbound', destination);\r\n this.#current = call;\r\n return call;\r\n }\r\n\r\n // ── The simulation surface (SPEC §5.7) ────────────────────────────────────\r\n\r\n /** An inbound INVITE lands. Fires the engine's `onIncoming` — the Softphone raises IncomingCall. */\r\n simulateIncoming(options: SimulateIncomingOptions): void {\r\n const call = new MockEngineCall(nextId('in'), 'inbound', options.from);\r\n this.#current = call;\r\n for (const listener of [...this.#incoming]) listener(call);\r\n }\r\n\r\n /** The remote answered our outbound call (or a provisional 180/183 first, if `progress` is set). */\r\n simulateRemoteAnswer(): void {\r\n this.#requireCurrent('simulateRemoteAnswer').fire({ type: 'accepted' });\r\n }\r\n\r\n /** Outbound provisional response — remote is ringing. Optional early media. */\r\n simulateProgress(kind: 'ringing' | 'early' = 'ringing'): void {\r\n this.#requireCurrent('simulateProgress').fire({ type: 'progress', response: kind });\r\n }\r\n\r\n /**\r\n * Remote media arrives — what `ontrack` produces on a real call.\r\n *\r\n * Added after the SDK shipped with NO audio at all: `core` hands the stream out and the UI attaches\r\n * it, but nothing did, and no test could see that because the mock had no way to produce a stream.\r\n * The seam always carried one (`EngineCallEvent` has `{ type: 'stream' }`); only the driver was\r\n * missing — unlike `displayName`, which the seam genuinely cannot carry (see SimulateIncomingOptions).\r\n *\r\n * The default is a stand-in object: jsdom has no `MediaStream`, and every consumer only ever hands\r\n * this to `srcObject`.\r\n */\r\n simulateRemoteStream(stream: MediaStream = {} as MediaStream): void {\r\n this.#requireCurrent('simulateRemoteStream').fire({ type: 'stream', stream });\r\n }\r\n\r\n simulateRemoteHold(): void {\r\n this.#requireCurrent('simulateRemoteHold').fire({ type: 'held', originator: 'remote' });\r\n }\r\n simulateRemoteUnhold(): void {\r\n this.#requireCurrent('simulateRemoteUnhold').fire({ type: 'unheld', originator: 'remote' });\r\n }\r\n\r\n simulateRemoteHangup(reason: HangupReason = 'remote'): void {\r\n this.#requireCurrent('simulateRemoteHangup').fire({ type: 'ended', reason });\r\n }\r\n\r\n /**\r\n * Raise any catalogued failure, exactly as a real engine would.\r\n *\r\n * The point is the two TREATMENTS: pass a fatal code (`net/registration-failed`) and the widget\r\n * replaces the dialpad with a gate that cannot be dismissed; pass a non-fatal one (`call/missed`)\r\n * and it shows a muted line under the number that the next keypress clears. A playground wants\r\n * both on a button, and neither can be reached by driving a mock call to a happy ending.\r\n */\r\n simulateError(code: SoftphoneErrorCode, cause?: unknown): void {\r\n const error = createError(code, cause);\r\n for (const listener of [...this.#errorListeners]) listener(error);\r\n }\r\n\r\n /** Media lost — the engine begins an ICE restart. The call is NOT over. */\r\n simulateNetworkDrop(): void {\r\n this.#requireCurrent('simulateNetworkDrop').fire({ type: 'reconnecting' });\r\n }\r\n simulateNetworkRecover(): void {\r\n this.#requireCurrent('simulateNetworkRecover').fire({ type: 'reconnected' });\r\n }\r\n\r\n // ── internals ─────────────────────────────────────────────────────────────\r\n #setState(state: SoftphoneState): void {\r\n if (state === this.#state) return;\r\n this.#state = state;\r\n for (const listener of [...this.#stateListeners]) listener(state);\r\n }\r\n\r\n #requireCurrent(op: string): MockEngineCall {\r\n if (!this.#current || this.#current.ended) {\r\n throw new Error(`MockProvider.${op}() called with no active call.`);\r\n }\r\n return this.#current;\r\n }\r\n}\r\n","/**\r\n * @coventia/softphone/testing — MockProvider and test helpers.\r\n *\r\n * Its own entry point so it never lands in a consumer's production bundle. See SPEC §5.7.\r\n */\r\nimport type { TokenSource, VoiceToken } from '../core/types.js';\r\n\r\nexport { MockProvider } from './mock.js';\r\nexport type { SimulateIncomingOptions } from './mock.js';\r\n\r\n/**\r\n * A TokenSource that returns a fixed, far-future fake token.\r\n *\r\n * `SoftphoneConfig.tokenSource` is required (SPEC §6.1) because the real Zadarma path needs it — but\r\n * the mock engine never mints credentials, so tests and playgrounds need something to satisfy the\r\n * type without a backend. This is that something. Never ship it.\r\n */\r\nexport function mockTokenSource(): TokenSource {\r\n return {\r\n async getToken(): Promise<VoiceToken> {\r\n return {\r\n token: 'mock-voice-token',\r\n identity: 'mock-user',\r\n // Far future; the mock does not refresh.\r\n expiresAt: new Date(4102444800000), // 2100-01-01\r\n };\r\n },\r\n };\r\n}\r\n"]}
|