@polymorfa/browser 0.1.0-dev.20260922094209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/dist/calls/api.d.ts +22 -0
  4. package/dist/calls/api.d.ts.map +1 -0
  5. package/dist/calls/api.js +104 -0
  6. package/dist/calls/api.js.map +1 -0
  7. package/dist/calls/backend.d.ts +85 -0
  8. package/dist/calls/backend.d.ts.map +1 -0
  9. package/dist/calls/backend.js +141 -0
  10. package/dist/calls/backend.js.map +1 -0
  11. package/dist/calls/client.d.ts +56 -0
  12. package/dist/calls/client.d.ts.map +1 -0
  13. package/dist/calls/client.js +260 -0
  14. package/dist/calls/client.js.map +1 -0
  15. package/dist/calls/controller.d.ts +329 -0
  16. package/dist/calls/controller.d.ts.map +1 -0
  17. package/dist/calls/controller.js +1328 -0
  18. package/dist/calls/controller.js.map +1 -0
  19. package/dist/calls/diagnostics.d.ts +52 -0
  20. package/dist/calls/diagnostics.d.ts.map +1 -0
  21. package/dist/calls/diagnostics.js +188 -0
  22. package/dist/calls/diagnostics.js.map +1 -0
  23. package/dist/calls/index.d.ts +3 -0
  24. package/dist/calls/index.d.ts.map +1 -0
  25. package/dist/calls/index.js +5 -0
  26. package/dist/calls/index.js.map +1 -0
  27. package/dist/calls/internal.d.ts +8 -0
  28. package/dist/calls/internal.d.ts.map +1 -0
  29. package/dist/calls/internal.js +8 -0
  30. package/dist/calls/internal.js.map +1 -0
  31. package/dist/calls/media.d.ts +144 -0
  32. package/dist/calls/media.d.ts.map +1 -0
  33. package/dist/calls/media.js +512 -0
  34. package/dist/calls/media.js.map +1 -0
  35. package/dist/calls/signaling.d.ts +74 -0
  36. package/dist/calls/signaling.d.ts.map +1 -0
  37. package/dist/calls/signaling.js +169 -0
  38. package/dist/calls/signaling.js.map +1 -0
  39. package/dist/calls/socket.d.ts +95 -0
  40. package/dist/calls/socket.d.ts.map +1 -0
  41. package/dist/calls/socket.js +267 -0
  42. package/dist/calls/socket.js.map +1 -0
  43. package/dist/chat/composer.d.ts +59 -0
  44. package/dist/chat/composer.d.ts.map +1 -0
  45. package/dist/chat/composer.js +217 -0
  46. package/dist/chat/composer.js.map +1 -0
  47. package/dist/chat/conversation.d.ts +71 -0
  48. package/dist/chat/conversation.d.ts.map +1 -0
  49. package/dist/chat/conversation.js +204 -0
  50. package/dist/chat/conversation.js.map +1 -0
  51. package/dist/chat/index.d.ts +4 -0
  52. package/dist/chat/index.d.ts.map +1 -0
  53. package/dist/chat/index.js +4 -0
  54. package/dist/chat/index.js.map +1 -0
  55. package/dist/chat/voice-recorder.d.ts +75 -0
  56. package/dist/chat/voice-recorder.d.ts.map +1 -0
  57. package/dist/chat/voice-recorder.js +346 -0
  58. package/dist/chat/voice-recorder.js.map +1 -0
  59. package/dist/controller.d.ts +16 -0
  60. package/dist/controller.d.ts.map +1 -0
  61. package/dist/controller.js +50 -0
  62. package/dist/controller.js.map +1 -0
  63. package/dist/diagnostics.d.ts +27 -0
  64. package/dist/diagnostics.d.ts.map +1 -0
  65. package/dist/diagnostics.js +2 -0
  66. package/dist/diagnostics.js.map +1 -0
  67. package/dist/dropin/client.d.ts +82 -0
  68. package/dist/dropin/client.d.ts.map +1 -0
  69. package/dist/dropin/client.js +314 -0
  70. package/dist/dropin/client.js.map +1 -0
  71. package/dist/dropin/connect.d.ts +23 -0
  72. package/dist/dropin/connect.d.ts.map +1 -0
  73. package/dist/dropin/connect.js +54 -0
  74. package/dist/dropin/connect.js.map +1 -0
  75. package/dist/dropin/inbox.d.ts +131 -0
  76. package/dist/dropin/inbox.d.ts.map +1 -0
  77. package/dist/dropin/inbox.js +406 -0
  78. package/dist/dropin/inbox.js.map +1 -0
  79. package/dist/dropin/index.d.ts +5 -0
  80. package/dist/dropin/index.d.ts.map +1 -0
  81. package/dist/dropin/index.js +5 -0
  82. package/dist/dropin/index.js.map +1 -0
  83. package/dist/dropin/permissions.d.ts +29 -0
  84. package/dist/dropin/permissions.d.ts.map +1 -0
  85. package/dist/dropin/permissions.js +53 -0
  86. package/dist/dropin/permissions.js.map +1 -0
  87. package/dist/errors.d.ts +39 -0
  88. package/dist/errors.d.ts.map +1 -0
  89. package/dist/errors.js +41 -0
  90. package/dist/errors.js.map +1 -0
  91. package/dist/index.d.ts +11 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/internal.d.ts +12 -0
  96. package/dist/internal.d.ts.map +1 -0
  97. package/dist/internal.js +13 -0
  98. package/dist/internal.js.map +1 -0
  99. package/dist/messaging/client.d.ts +54 -0
  100. package/dist/messaging/client.d.ts.map +1 -0
  101. package/dist/messaging/client.js +153 -0
  102. package/dist/messaging/client.js.map +1 -0
  103. package/dist/messaging/composer.d.ts +12 -0
  104. package/dist/messaging/composer.d.ts.map +1 -0
  105. package/dist/messaging/composer.js +36 -0
  106. package/dist/messaging/composer.js.map +1 -0
  107. package/dist/messaging/index.d.ts +4 -0
  108. package/dist/messaging/index.d.ts.map +1 -0
  109. package/dist/messaging/index.js +3 -0
  110. package/dist/messaging/index.js.map +1 -0
  111. package/dist/messaging/types.d.ts +209 -0
  112. package/dist/messaging/types.d.ts.map +1 -0
  113. package/dist/messaging/types.js +2 -0
  114. package/dist/messaging/types.js.map +1 -0
  115. package/dist/templates/controller.d.ts +45 -0
  116. package/dist/templates/controller.d.ts.map +1 -0
  117. package/dist/templates/controller.js +298 -0
  118. package/dist/templates/controller.js.map +1 -0
  119. package/dist/templates/index.d.ts +4 -0
  120. package/dist/templates/index.d.ts.map +1 -0
  121. package/dist/templates/index.js +3 -0
  122. package/dist/templates/index.js.map +1 -0
  123. package/dist/templates/transport.d.ts +7 -0
  124. package/dist/templates/transport.d.ts.map +1 -0
  125. package/dist/templates/transport.js +124 -0
  126. package/dist/templates/transport.js.map +1 -0
  127. package/dist/templates/types.d.ts +141 -0
  128. package/dist/templates/types.d.ts.map +1 -0
  129. package/dist/templates/types.js +2 -0
  130. package/dist/templates/types.js.map +1 -0
  131. package/dist/token.d.ts +37 -0
  132. package/dist/token.d.ts.map +1 -0
  133. package/dist/token.js +152 -0
  134. package/dist/token.js.map +1 -0
  135. package/dist/transport.d.ts +53 -0
  136. package/dist/transport.d.ts.map +1 -0
  137. package/dist/transport.js +328 -0
  138. package/dist/transport.js.map +1 -0
  139. package/package.json +45 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Polymorfa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # `@polymorfa/browser`
2
+
3
+ Framework-neutral browser transport and product controllers for Polymorfa.
4
+
5
+ ## Drop-in client
6
+
7
+ `PolymorfaClient` (`createPolymorfaClient({ tokenEndpoint })`) fetches client
8
+ tokens from your `createPolymorfaHandler` route, refreshes them before expiry
9
+ with backoff, and reports the grant through `can(permission)`. It never sends
10
+ or chooses permissions. `InboxController` with `createHandlerInboxSource()`
11
+ drives an inbox from the handler's `history` and `events` routes, and
12
+ `connectWhatsApp(client)` asks the handler for a QuickLink and opens the
13
+ hosted URL. The React and Web Component drop-ins are built on these.
14
+
15
+ `@polymorfa/browser` depends on `@polymorfa/sdk` for the Calls client
16
+ (`@polymorfa/sdk/calls`). It imports nothing else from that package, so no
17
+ server code reaches the browser, and call errors such as `CallsDisabledError`
18
+ are the classes exported from `@polymorfa/sdk/calls`.
19
+
20
+ This package accepts only short-lived `pmfa_ct_` client tokens obtained from an application callback. It never accepts or stores server API keys.
21
+
22
+ ```ts
23
+ import {
24
+ BrowserMessagingClient,
25
+ createClientTokenProvider,
26
+ } from "@polymorfa/browser";
27
+
28
+ const messaging = new BrowserMessagingClient({
29
+ session: "support",
30
+ getClientToken: createClientTokenProvider(),
31
+ });
32
+
33
+ await messaging.messages.send({
34
+ conversation: { phoneNumber: "+15551234567" },
35
+ content: { text: "Hello" },
36
+ });
37
+ ```
38
+
39
+ Requests expose status, request ID, response headers, and attempt count. Safe reads retry transient failures; mutations retry only when supplied an idempotency key. Caller cancellation and timeouts use distinct exported error types.
40
+
41
+ Product controllers use immutable snapshots and `subscribe()`/`getSnapshot()` so Web Components and framework bindings share the same behavior.
42
+
43
+ `BrowserMessagingClient` binds one session and exposes only the exact
44
+ client-token allowlist: message actions, presence reads/subscriptions, contact
45
+ reads, and permitted widget pairing actions. Session start and status require a server
46
+ Platform credential; use QuickLink for onboarding. The browser widget start/status
47
+ helpers are removed in this breaking contract revision. Conversation history,
48
+ template management and media upload remain application-owned server adapters.
49
+ `createBrowserCalls` supplies client-token call lifecycle and controls.
50
+
51
+ `createBrowserComposerActions` connects a `MessageComposerController` to the
52
+ message resource for text and reply sends. Supply an upload adapter and a
53
+ `createMessage` mapping for attachments; client tokens cannot call the media
54
+ routes directly.
55
+
56
+ ## Voice notes
57
+
58
+ `VoiceNoteRecorder` records from the microphone with `getUserMedia` and
59
+ `MediaRecorder`. Its snapshot has `status` (`idle`, `requesting`,
60
+ `recording`, `stopping`, or `error`), `elapsed` milliseconds, an input
61
+ `level` from 0 to 1, the container `mimeType`, and an `error` of
62
+ `permission-denied`, `unavailable`, or `failed`.
63
+
64
+ ```ts
65
+ import { VoiceNoteRecorder, localAttachmentFromFile } from "@polymorfa/browser";
66
+
67
+ if (VoiceNoteRecorder.isSupported()) {
68
+ const recorder = new VoiceNoteRecorder();
69
+ await recorder.start();
70
+ // later
71
+ const file = await recorder.stop(); // File or undefined
72
+ if (file) await composer.addAttachment(localAttachmentFromFile(file));
73
+ }
74
+ ```
75
+
76
+ `stop()` resolves with an `audio/webm`, `audio/ogg`, or `audio/mp4` file named
77
+ `voice-note-<time>.webm` (`.ogg` or `.m4a`); the time is ISO 8601 with `:` and `.`
78
+ replaced by `-`. `cancel()` discards the recording. The microphone tracks
79
+ stop when recording ends, when a pending permission request is cancelled,
80
+ and on `dispose()`.
81
+
82
+ ## Calls
83
+
84
+ `createBrowserCalls` is the browser calling component: incoming calls,
85
+ placement, answer, join, leave and end, microphone, camera and device
86
+ control, and per-participant video. Your server mints a short-lived client
87
+ token with `POST /platform/client-tokens`, granting `voip_place`,
88
+ `voip_answer`, and `voip_signal` and the destination and concurrency rules you
89
+ need. That token is the only credential the component uses; it never appears
90
+ in a URL.
91
+
92
+ ```ts
93
+ import {
94
+ createBrowserCalls,
95
+ createClientTokenProvider,
96
+ } from "@polymorfa/browser";
97
+
98
+ const calls = createBrowserCalls({
99
+ session: "support",
100
+ getClientToken: createClientTokenProvider(),
101
+ onError: (error) => {
102
+ // "unauthorized": the platform stopped accepting the token.
103
+ },
104
+ });
105
+ await calls.connect();
106
+ await calls.controller.place("+15550100");
107
+ // When the application releases the widget:
108
+ await calls.dispose();
109
+ ```
110
+
111
+ Pass `calls.controller` to React's `CallSurface` or the `pmfa-call` element.
112
+ `controller.call` exposes the shared `Call`, including its state, duration,
113
+ end reason, claim state and `addParticipant()` method. Direct placement
114
+ supports linked WhatsApp devices.
115
+
116
+ ### Tokens
117
+
118
+ Return `{ value, audience: "browser", expiresAt }` from `getClientToken` so
119
+ the SDK can replace the token before it expires without interrupting calls.
120
+ When a token expires or is revoked, `onError` receives
121
+ `code: "unauthorized"` and the next reconnect asks `getClientToken` for a new
122
+ token. REST requests refresh a cached token before it expires; a provider
123
+ that returns an expired token fails with `expired_client_token`.
124
+
125
+ ### Answering, joining, and leaving
126
+
127
+ Incoming calls ring until a participant answers or declines them.
128
+ `snapshot.invitations` lists every ringing call; several can ring at once and
129
+ the controller never declines one for you. The first invitation is displayed;
130
+ `controller.select(callId)` shows another while no call is active, and
131
+ `controller.dismiss(callId?)` hides one locally without declining it.
132
+
133
+ - `controller.answer({ exclusive, video, callId })` accepts the call and
134
+ connects microphone, camera and speaker. `exclusive` defaults to `false`, which leaves other
135
+ participants ringing so they can join. `exclusive: true` claims the call.
136
+ - `controller.join({ video, callId })` joins a call another participant
137
+ answered without a claim (`snapshot.canJoin`).
138
+ - `snapshot.claimedByOther` is `true` when another participant claimed the
139
+ call. `answer()` and `join()` then reject with `CallClaimedError`, and
140
+ `reject()` is refused: declining would end the claimer's call.
141
+ - While calling is turned off for the number, `place()`, `answer()` and
142
+ `join()` reject with `CallsDisabledError` from `@polymorfa/sdk/calls`.
143
+ - While an answer or join is in flight, `snapshot.answering` is `true` and
144
+ `answer()`, `join()`, `reject()` and `place()` are refused. `select()` and
145
+ `dismiss()` still work: the answered call is displayed once it is accepted,
146
+ even if you selected another invitation. If you dismissed it, the controller
147
+ leaves it (or ends it when the answer claimed it) instead of showing it.
148
+ Only that call ending cancels the answer; `end()` for another displayed
149
+ call is refused meanwhile.
150
+ - If the platform refuses an answer, the call stays displayed as `incoming`
151
+ with `snapshot.error` (`answer_failed` or `join_failed`), so it can be
152
+ retried or declined. If media fails after the call was answered, the
153
+ failed call is published as `error` (or `ended` for a terminal offer)
154
+ and the next waiting invitation is displayed. The failure stays in
155
+ `snapshot.error`; `error.callId` names the call it belongs to.
156
+ - `controller.reject()` declines a ringing call and ends it for everyone.
157
+ - `controller.leave()` closes this browser's connection; the call continues.
158
+ On a call this browser placed that has not connected, it ends the call
159
+ instead, so the callee stops ringing.
160
+ - `controller.end()` (and `hangup()`) ends the call for every participant.
161
+ - Disposing the controller leaves a joined call. It ends only a call this
162
+ browser placed that is still ringing.
163
+
164
+ After a call ends, the next waiting invitation is displayed.
165
+
166
+ ### Media
167
+
168
+ - `controller.localStream` is your microphone and camera.
169
+ - `controller.remoteStream` is the merged call audio; play it through one
170
+ element.
171
+ - `controller.remoteVideos` holds one entry per remote participant who sends
172
+ video: a `MediaStream`, a stable `key`, a display `label`, and the
173
+ `participant` or the other connection's `connectionId` and
174
+ `connectionParticipant`. `snapshot.remoteVideos` carries the same entries
175
+ without streams, so a render can react to changes. Videos are separate
176
+ streams; nothing is composed.
177
+ - `controller.setMuted()`, `enableVideo()`, `switchDevice()`,
178
+ `setPreferredDevices()` and `refreshDevices()` control capture and playback.
179
+ - `snapshot.participants` lists the call's WhatsApp participants.
180
+ - A dropped connection shows `reconnecting` and recovers or ends as
181
+ `connection_failed` after a bounded window.
182
+
183
+ Disposal releases tracks and connections.
184
+
185
+ ### Diagnostics
186
+
187
+ While a call's media is open, the controller sends call diagnostics for this
188
+ browser's connection to `POST /messaging/voip/calls/{id}/reports`, where
189
+ they appear with the call in the Console:
190
+
191
+ - Every 15 seconds, and once when the connection closes: round-trip time,
192
+ audio jitter, packets lost and received, the audio and video codecs, the
193
+ ICE candidate type (`relay` means a TURN relay), and how many times the
194
+ connection reconnected. Figures come from `RTCPeerConnection.getStats()`.
195
+ - When something fails: an error code (`media_permission_denied`,
196
+ `device_not_found`, `device_in_use`, `ice_failed`, `negotiation_failed`,
197
+ `media_timeout`, `reconnect_exhausted` or `unsupported_browser`).
198
+
199
+ Each report names the connection and `@polymorfa/browser` with its version.
200
+ Reports contain no phone numbers, names, device labels, IP addresses, audio or
201
+ video. They are best-effort: a failed report is not retried, a refused one
202
+ (other than rate limiting) stops reporting for the connection, and nothing
203
+ about reporting affects the call. The client token needs the `voip_signal`
204
+ action. Turn reporting off with `createBrowserCalls({ diagnostics: false })`.
205
+
206
+ If a reject or hangup request fails, the call stays active and its controls remain
207
+ available for retry. The UI shows a localized failure message and keeps existing
208
+ media connected until the call ends. `snapshot.error` clears when ending succeeds
209
+ or when an incoming call is successfully answered after a failed reject.
210
+
211
+ ## Template builder
212
+
213
+ The builder uses the canonical `TemplateDefinition` contract: standard,
214
+ carousel, authentication, and limited-time-offer templates with structured
215
+ headers, buttons, variables, and previews. Saving and Meta submission are
216
+ separate controller actions.
217
+
218
+ ```ts
219
+ import {
220
+ TemplateBuilderController,
221
+ createSameOriginTemplateBuilderTransport,
222
+ } from "@polymorfa/browser";
223
+
224
+ const templates = new TemplateBuilderController(
225
+ createSameOriginTemplateBuilderTransport({
226
+ path: "/api/polymorfa/templates",
227
+ }),
228
+ );
229
+
230
+ templates.create({
231
+ name: "order_ready",
232
+ definition: {
233
+ version: 1,
234
+ kind: "standard",
235
+ category: "UTILITY",
236
+ language: "en_US",
237
+ body: "Hello {{name}}",
238
+ variables: [{ name: "name", type: "text", example: "Ada" }],
239
+ },
240
+ });
241
+
242
+ await templates.save();
243
+ await templates.refreshPreview();
244
+ ```
245
+
246
+ The same-origin transport sends application actions with browser cookies. It
247
+ does not accept a server credential, project slug, or Cloud API session. The
248
+ application route resolves those values after authorizing the request.
@@ -0,0 +1,22 @@
1
+ import { type AcceptCallOptions, type AcceptCallResult, type CallReport, type CallsApi, type CallsToken, type CallsTokenRequest, type Participant, type PlaceCallRequest } from "@polymorfa/sdk/calls/internal";
2
+ import { BrowserTransport } from "../transport.js";
3
+ /**
4
+ * Client-token call controls. The token determines the session and the
5
+ * participant, so `session` and `participant` are never sent.
6
+ */
7
+ export declare class BrowserCallsApi implements CallsApi {
8
+ #private;
9
+ constructor(transport: BrowserTransport);
10
+ token(request?: CallsTokenRequest): Promise<CallsToken>;
11
+ socketUrl(path: string): string;
12
+ place(input: PlaceCallRequest, signal?: AbortSignal): Promise<{
13
+ readonly callId: string;
14
+ }>;
15
+ accept(callId: string, options: AcceptCallOptions, signal?: AbortSignal): Promise<AcceptCallResult>;
16
+ reject(callId: string, signal?: AbortSignal): Promise<void>;
17
+ leave(callId: string, connectionId: string, signal?: AbortSignal): Promise<void>;
18
+ end(callId: string, signal?: AbortSignal): Promise<void>;
19
+ report(callId: string, report: CallReport, signal?: AbortSignal): Promise<void>;
20
+ addParticipant(callId: string, to: string, signal?: AbortSignal): Promise<Participant>;
21
+ }
22
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/calls/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD;;;GAGG;AACH,qBAAa,eAAgB,YAAW,QAAQ;;gBAIlC,SAAS,EAAE,gBAAgB;IAKvC,KAAK,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC;IAI3D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMzB,KAAK,CACT,KAAK,EAAE,gBAAgB,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAyBjC,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC;IA4B5B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,KAAK,CACH,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC;IAIhB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,MAAM,CACJ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC;IAIV,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC;CAexB"}
@@ -0,0 +1,104 @@
1
+ import { CallsApiError, isParticipant, } from "@polymorfa/sdk/calls/internal";
2
+ import { BrowserTransport } from "../transport.js";
3
+ import { CallsSignalingClient, claimedError } from "./signaling.js";
4
+ /**
5
+ * Client-token call controls. The token determines the session and the
6
+ * participant, so `session` and `participant` are never sent.
7
+ */
8
+ export class BrowserCallsApi {
9
+ #transport;
10
+ #signaling;
11
+ constructor(transport) {
12
+ this.#transport = transport;
13
+ this.#signaling = new CallsSignalingClient(transport);
14
+ }
15
+ token(request = {}) {
16
+ return this.#signaling.token(request);
17
+ }
18
+ socketUrl(path) {
19
+ const url = this.#signaling.socketUrl(path);
20
+ if (!/^wss?:\/\//.test(url))
21
+ throw malformed("socket URL");
22
+ return url;
23
+ }
24
+ async place(input, signal) {
25
+ const response = await this.#transport
26
+ .request({
27
+ method: "POST",
28
+ path: "/messaging/voip/calls",
29
+ body: {
30
+ to: input.to,
31
+ video: input.video,
32
+ ...(input.exclusive === undefined
33
+ ? {}
34
+ : { exclusive: input.exclusive }),
35
+ },
36
+ idempotencyKey: input.idempotencyKey,
37
+ ...(signal === undefined ? {} : { signal }),
38
+ })
39
+ .catch((cause) => {
40
+ throw claimedError(cause);
41
+ });
42
+ const callId = response.data?.data?.callId;
43
+ if (typeof callId !== "string" || !callId)
44
+ throw malformed("call id");
45
+ return { callId };
46
+ }
47
+ async accept(callId, options, signal) {
48
+ const response = await this.#transport
49
+ .request({
50
+ method: "POST",
51
+ path: `/messaging/voip/calls/${encodeURIComponent(callId)}/accept`,
52
+ body: {
53
+ exclusive: options.exclusive === true,
54
+ ...(options.video === undefined ? {} : { video: options.video }),
55
+ },
56
+ ...(signal === undefined ? {} : { signal }),
57
+ })
58
+ .catch((cause) => {
59
+ throw claimedError(cause);
60
+ });
61
+ const data = response.data?.data;
62
+ if (typeof data?.["answered"] !== "boolean" ||
63
+ typeof data["answeredBy"] !== "string" ||
64
+ typeof data["exclusive"] !== "boolean")
65
+ throw malformed("accept result");
66
+ return {
67
+ answered: data["answered"],
68
+ answeredBy: data["answeredBy"],
69
+ exclusive: data["exclusive"],
70
+ };
71
+ }
72
+ reject(callId, signal) {
73
+ return this.#signaling.reject(callId, signal);
74
+ }
75
+ leave(callId, connectionId, signal) {
76
+ return this.#signaling.leave(callId, connectionId, signal);
77
+ }
78
+ end(callId, signal) {
79
+ return this.#signaling.end(callId, signal);
80
+ }
81
+ report(callId, report, signal) {
82
+ return this.#signaling.report(callId, report, signal);
83
+ }
84
+ async addParticipant(callId, to, signal) {
85
+ const response = await this.#transport
86
+ .request({
87
+ method: "POST",
88
+ path: `/messaging/voip/calls/${encodeURIComponent(callId)}/participants`,
89
+ body: { to },
90
+ ...(signal === undefined ? {} : { signal }),
91
+ })
92
+ .catch((cause) => {
93
+ throw claimedError(cause);
94
+ });
95
+ const p = response.data?.data;
96
+ if (!isParticipant(p))
97
+ throw malformed("participant");
98
+ return p;
99
+ }
100
+ }
101
+ function malformed(field) {
102
+ return new CallsApiError(200, "malformed_response", `Calls response has an invalid ${field}.`);
103
+ }
104
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/calls/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import type { CallEndReason, CallLifecycleEvent, CallsBackend, IncomingCall, PlaceCallInput } from "./controller.js";
2
+ import type { CallsSignaling } from "./signaling.js";
3
+ /**
4
+ * The parts of the `call.received` webhook payload the browser needs. The
5
+ * caller is a public identity reference; a bare string is accepted for hand-built
6
+ * payloads.
7
+ */
8
+ export interface CallReceivedWebhookPayload {
9
+ readonly callId: string;
10
+ readonly from: string | {
11
+ readonly id?: string;
12
+ readonly phoneNumber?: string;
13
+ readonly bsuid?: string;
14
+ readonly username?: string;
15
+ };
16
+ readonly hasVideo?: boolean;
17
+ readonly capabilities?: {
18
+ readonly video?: boolean;
19
+ readonly invite?: boolean;
20
+ };
21
+ }
22
+ /**
23
+ * Turn a `call.received` webhook payload into an {@link IncomingCall}. The
24
+ * application receives the webhook on its server, relays it to the browser
25
+ * over its own realtime channel, and hands it to {@link IncomingCallRelay}.
26
+ * The caller is shown by phone number when present, otherwise by public ID.
27
+ */
28
+ export declare function incomingCallFromWebhook(payload: CallReceivedWebhookPayload): IncomingCall;
29
+ /**
30
+ * Application-fed source of inbound call notifications for deployments that
31
+ * relay `call.received`, `call.accepted`, and `call.ended` webhook events over
32
+ * their own realtime channel. {@link CallsSocket} is the push alternative:
33
+ * it subscribes to the same lifecycle stream directly from the API.
34
+ */
35
+ export declare class IncomingCallRelay {
36
+ #private;
37
+ /** Announce an inbound call; the controller moves to `incoming`. */
38
+ receive(call: IncomingCall): void;
39
+ /**
40
+ * Announce that a call was answered (from `call.accepted`). Pass its
41
+ * `answeredBy` and `exclusive` so other browsers stop ringing for a claimed
42
+ * call or offer Join for a shared one.
43
+ */
44
+ accepted(callId: string, claim?: {
45
+ readonly answeredBy?: string;
46
+ readonly exclusive?: boolean;
47
+ /** The webhook's `capabilities`, when present. */
48
+ readonly capabilities?: {
49
+ readonly video?: boolean;
50
+ readonly invite?: boolean;
51
+ };
52
+ }): void;
53
+ /** Announce that the remote side ended a call (from `call.ended`). */
54
+ ended(callId: string, reason?: CallEndReason): void;
55
+ subscribe(listener: (event: CallLifecycleEvent) => void): () => void;
56
+ }
57
+ export interface SignalingCallsBackendOptions {
58
+ /** REST signaling client for the `/messaging/voip/calls/{id}` paths. */
59
+ readonly signaling: CallsSignaling;
60
+ /** Inbound call notifications: a {@link CallsSocket} or an application-fed {@link IncomingCallRelay}. */
61
+ readonly incoming: Pick<IncomingCallRelay, "subscribe">;
62
+ /**
63
+ * Places an outbound call through the application's own server and resolves
64
+ * the call id the platform assigned it.
65
+ *
66
+ * This custom backend delegates placement to the supplied hook. Use
67
+ * createBrowserCalls for built-in client-token placement. Without a hook,
68
+ * outbound calling reports place_failed instead of inventing a call id.
69
+ */
70
+ readonly place?: (input: PlaceCallInput, signal: AbortSignal) => Promise<string | {
71
+ readonly callId: string;
72
+ }>;
73
+ }
74
+ /**
75
+ * A {@link CallsBackend} over the REST signaling surface alone. Answering an
76
+ * inbound call accepts it (claiming it only when the caller asks for
77
+ * `exclusive`), then the media factory offers SDP for its connection. Reject
78
+ * declines a ringing call and hang-up ends the call for everyone; leaving
79
+ * closes only this browser's connection. Everything runs on the client token.
80
+ *
81
+ * Supply place for custom outbound placement. createBrowserCalls provides
82
+ * direct client-token placement through the shared Calls client.
83
+ */
84
+ export declare function createSignalingCallsBackend(options: SignalingCallsBackendOptions): CallsBackend;
85
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/calls/backend.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,cAAc,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EACT,MAAM,GACN;QACE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;IACN,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE;QACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,0BAA0B,GAClC,YAAY,CAad;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;;IAG5B,oEAAoE;IACpE,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAIjC;;;;OAIG;IACH,QAAQ,CACN,MAAM,EAAE,MAAM,EACd,KAAK,GAAE;QACL,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAC7B,kDAAkD;QAClD,QAAQ,CAAC,YAAY,CAAC,EAAE;YACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;YACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;SAC3B,CAAC;KACE,GACL,IAAI;IAyBP,sEAAsE;IACtE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI;IAQnD,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,MAAM,IAAI;CAQrE;AAED,MAAM,WAAW,4BAA4B;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,yGAAyG;IACzG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IACxD;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CACf,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,MAAM,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,YAAY,CAqDd"}
@@ -0,0 +1,141 @@
1
+ import { capabilitiesFrom, } from "@polymorfa/sdk/calls/internal";
2
+ /**
3
+ * Turn a `call.received` webhook payload into an {@link IncomingCall}. The
4
+ * application receives the webhook on its server, relays it to the browser
5
+ * over its own realtime channel, and hands it to {@link IncomingCallRelay}.
6
+ * The caller is shown by phone number when present, otherwise by public ID.
7
+ */
8
+ export function incomingCallFromWebhook(payload) {
9
+ // Empty strings fall through like absent fields, matching `peerFrom` on the
10
+ // socket path — the two inbound routes must agree on the same payload.
11
+ const from = typeof payload.from === "string"
12
+ ? payload.from
13
+ : (firstNonEmpty(payload.from.phoneNumber, payload.from.id) ?? "");
14
+ return {
15
+ callId: payload.callId,
16
+ from,
17
+ video: payload.hasVideo === true,
18
+ capabilities: capabilitiesFrom(payload.capabilities),
19
+ };
20
+ }
21
+ /**
22
+ * Application-fed source of inbound call notifications for deployments that
23
+ * relay `call.received`, `call.accepted`, and `call.ended` webhook events over
24
+ * their own realtime channel. {@link CallsSocket} is the push alternative:
25
+ * it subscribes to the same lifecycle stream directly from the API.
26
+ */
27
+ export class IncomingCallRelay {
28
+ #listeners = new Set();
29
+ /** Announce an inbound call; the controller moves to `incoming`. */
30
+ receive(call) {
31
+ this.#emit({ type: "incomingCall", call });
32
+ }
33
+ /**
34
+ * Announce that a call was answered (from `call.accepted`). Pass its
35
+ * `answeredBy` and `exclusive` so other browsers stop ringing for a claimed
36
+ * call or offer Join for a shared one.
37
+ */
38
+ accepted(callId, claim = {}) {
39
+ this.#emit({
40
+ type: "accepted",
41
+ callId,
42
+ ...(claim.answeredBy === undefined
43
+ ? {}
44
+ : { answeredBy: claim.answeredBy }),
45
+ exclusive: claim.exclusive === true,
46
+ // Reported fields only; the controller merges them with the call's
47
+ // current capabilities.
48
+ ...(claim.capabilities === undefined
49
+ ? {}
50
+ : {
51
+ capabilities: {
52
+ ...(typeof claim.capabilities.video === "boolean"
53
+ ? { video: claim.capabilities.video }
54
+ : {}),
55
+ ...(typeof claim.capabilities.invite === "boolean"
56
+ ? { invite: claim.capabilities.invite }
57
+ : {}),
58
+ },
59
+ }),
60
+ });
61
+ }
62
+ /** Announce that the remote side ended a call (from `call.ended`). */
63
+ ended(callId, reason) {
64
+ this.#emit(reason === undefined
65
+ ? { type: "ended", callId }
66
+ : { type: "ended", callId, reason });
67
+ }
68
+ subscribe(listener) {
69
+ this.#listeners.add(listener);
70
+ return () => this.#listeners.delete(listener);
71
+ }
72
+ #emit(event) {
73
+ for (const listener of [...this.#listeners])
74
+ listener(event);
75
+ }
76
+ }
77
+ /**
78
+ * A {@link CallsBackend} over the REST signaling surface alone. Answering an
79
+ * inbound call accepts it (claiming it only when the caller asks for
80
+ * `exclusive`), then the media factory offers SDP for its connection. Reject
81
+ * declines a ringing call and hang-up ends the call for everyone; leaving
82
+ * closes only this browser's connection. Everything runs on the client token.
83
+ *
84
+ * Supply place for custom outbound placement. createBrowserCalls provides
85
+ * direct client-token placement through the shared Calls client.
86
+ */
87
+ export function createSignalingCallsBackend(options) {
88
+ return {
89
+ subscribe: (listener) => options.incoming.subscribe(listener),
90
+ place: async (input, signal) => {
91
+ throwIfAborted(signal);
92
+ const placeWith = options.place;
93
+ if (placeWith === undefined)
94
+ throw new Error("This backend needs a `place` hook. Use createBrowserCalls for direct client-token placement.");
95
+ const placed = await placeWith(input, signal);
96
+ throwIfAborted(signal);
97
+ // `place` is application code behind a public interface: a response that
98
+ // did not match its declared shape must not reach signaling as an
99
+ // `undefined` call id.
100
+ const callId = typeof placed === "string"
101
+ ? placed
102
+ : placed?.callId;
103
+ if (typeof callId !== "string" || callId === "")
104
+ throw new Error("`place` resolved without a call id.");
105
+ return { callId };
106
+ },
107
+ answer: async (callId, signal, input) => {
108
+ throwIfAborted(signal);
109
+ const accept = options.signaling.accept;
110
+ if (accept === undefined)
111
+ throw new Error("This signaling client cannot answer calls.");
112
+ return accept.call(options.signaling, callId, {
113
+ exclusive: input?.exclusive === true,
114
+ ...(input === undefined ? {} : { video: input.video }),
115
+ }, signal);
116
+ },
117
+ reject: async (callId, signal) => {
118
+ const reject = options.signaling.reject;
119
+ if (reject === undefined)
120
+ throw new Error("This signaling client cannot decline calls.");
121
+ await reject.call(options.signaling, callId, signal);
122
+ },
123
+ hangup: (callId, signal) => options.signaling.end(callId, signal),
124
+ ...(options.signaling.report === undefined
125
+ ? {}
126
+ : {
127
+ report: (callId, report) => options.signaling.report(callId, report),
128
+ }),
129
+ };
130
+ }
131
+ function firstNonEmpty(...values) {
132
+ for (const value of values)
133
+ if (typeof value === "string" && value.length > 0)
134
+ return value;
135
+ return undefined;
136
+ }
137
+ function throwIfAborted(signal) {
138
+ if (signal.aborted)
139
+ throw signal.reason;
140
+ }
141
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/calls/backend.ts"],"names":[],"mappings":"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"}