@polymorfa/sdk 0.1.0-dev.20260922093854

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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1881 -0
  3. package/dist/bridge.d.ts +31 -0
  4. package/dist/bridge.d.ts.map +1 -0
  5. package/dist/bridge.js +39 -0
  6. package/dist/bridge.js.map +1 -0
  7. package/dist/calls/api.d.ts +109 -0
  8. package/dist/calls/api.d.ts.map +1 -0
  9. package/dist/calls/api.js +179 -0
  10. package/dist/calls/api.js.map +1 -0
  11. package/dist/calls/call.d.ts +286 -0
  12. package/dist/calls/call.d.ts.map +1 -0
  13. package/dist/calls/call.js +860 -0
  14. package/dist/calls/call.js.map +1 -0
  15. package/dist/calls/client.d.ts +124 -0
  16. package/dist/calls/client.d.ts.map +1 -0
  17. package/dist/calls/client.js +447 -0
  18. package/dist/calls/client.js.map +1 -0
  19. package/dist/calls/diagnostics.d.ts +74 -0
  20. package/dist/calls/diagnostics.d.ts.map +1 -0
  21. package/dist/calls/diagnostics.js +133 -0
  22. package/dist/calls/diagnostics.js.map +1 -0
  23. package/dist/calls/errors.d.ts +37 -0
  24. package/dist/calls/errors.d.ts.map +1 -0
  25. package/dist/calls/errors.js +52 -0
  26. package/dist/calls/errors.js.map +1 -0
  27. package/dist/calls/events.d.ts +15 -0
  28. package/dist/calls/events.d.ts.map +1 -0
  29. package/dist/calls/events.js +34 -0
  30. package/dist/calls/events.js.map +1 -0
  31. package/dist/calls/index.d.ts +6 -0
  32. package/dist/calls/index.d.ts.map +1 -0
  33. package/dist/calls/index.js +7 -0
  34. package/dist/calls/index.js.map +1 -0
  35. package/dist/calls/internal.d.ts +18 -0
  36. package/dist/calls/internal.d.ts.map +1 -0
  37. package/dist/calls/internal.js +18 -0
  38. package/dist/calls/internal.js.map +1 -0
  39. package/dist/calls/lifecycle.d.ts +111 -0
  40. package/dist/calls/lifecycle.d.ts.map +1 -0
  41. package/dist/calls/lifecycle.js +490 -0
  42. package/dist/calls/lifecycle.js.map +1 -0
  43. package/dist/calls/media.d.ts +102 -0
  44. package/dist/calls/media.d.ts.map +1 -0
  45. package/dist/calls/media.js +414 -0
  46. package/dist/calls/media.js.map +1 -0
  47. package/dist/calls/protocol.d.ts +216 -0
  48. package/dist/calls/protocol.d.ts.map +1 -0
  49. package/dist/calls/protocol.js +268 -0
  50. package/dist/calls/protocol.js.map +1 -0
  51. package/dist/calls/token.d.ts +43 -0
  52. package/dist/calls/token.d.ts.map +1 -0
  53. package/dist/calls/token.js +146 -0
  54. package/dist/calls/token.js.map +1 -0
  55. package/dist/client.d.ts +65 -0
  56. package/dist/client.d.ts.map +1 -0
  57. package/dist/client.js +119 -0
  58. package/dist/client.js.map +1 -0
  59. package/dist/credentials.d.ts +50 -0
  60. package/dist/credentials.d.ts.map +1 -0
  61. package/dist/credentials.js +73 -0
  62. package/dist/credentials.js.map +1 -0
  63. package/dist/errors.d.ts +71 -0
  64. package/dist/errors.d.ts.map +1 -0
  65. package/dist/errors.js +129 -0
  66. package/dist/errors.js.map +1 -0
  67. package/dist/index.d.ts +70 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +61 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/media/whatsapp.d.ts +105 -0
  72. package/dist/media/whatsapp.d.ts.map +1 -0
  73. package/dist/media/whatsapp.js +624 -0
  74. package/dist/media/whatsapp.js.map +1 -0
  75. package/dist/messaging/bansafe.d.ts +30 -0
  76. package/dist/messaging/bansafe.d.ts.map +1 -0
  77. package/dist/messaging/bansafe.js +86 -0
  78. package/dist/messaging/bansafe.js.map +1 -0
  79. package/dist/messaging/business.d.ts +35 -0
  80. package/dist/messaging/business.d.ts.map +1 -0
  81. package/dist/messaging/business.js +145 -0
  82. package/dist/messaging/business.js.map +1 -0
  83. package/dist/messaging/calls.d.ts +9 -0
  84. package/dist/messaging/calls.d.ts.map +1 -0
  85. package/dist/messaging/calls.js +16 -0
  86. package/dist/messaging/calls.js.map +1 -0
  87. package/dist/messaging/campaigns.d.ts +19 -0
  88. package/dist/messaging/campaigns.d.ts.map +1 -0
  89. package/dist/messaging/campaigns.js +77 -0
  90. package/dist/messaging/campaigns.js.map +1 -0
  91. package/dist/messaging/channels.d.ts +23 -0
  92. package/dist/messaging/channels.d.ts.map +1 -0
  93. package/dist/messaging/channels.js +104 -0
  94. package/dist/messaging/channels.js.map +1 -0
  95. package/dist/messaging/chats.d.ts +14 -0
  96. package/dist/messaging/chats.d.ts.map +1 -0
  97. package/dist/messaging/chats.js +51 -0
  98. package/dist/messaging/chats.js.map +1 -0
  99. package/dist/messaging/client-tokens.d.ts +15 -0
  100. package/dist/messaging/client-tokens.d.ts.map +1 -0
  101. package/dist/messaging/client-tokens.js +64 -0
  102. package/dist/messaging/client-tokens.js.map +1 -0
  103. package/dist/messaging/client.d.ts +58 -0
  104. package/dist/messaging/client.d.ts.map +1 -0
  105. package/dist/messaging/client.js +100 -0
  106. package/dist/messaging/client.js.map +1 -0
  107. package/dist/messaging/contacts.d.ts +20 -0
  108. package/dist/messaging/contacts.d.ts.map +1 -0
  109. package/dist/messaging/contacts.js +75 -0
  110. package/dist/messaging/contacts.js.map +1 -0
  111. package/dist/messaging/groups.d.ts +34 -0
  112. package/dist/messaging/groups.d.ts.map +1 -0
  113. package/dist/messaging/groups.js +97 -0
  114. package/dist/messaging/groups.js.map +1 -0
  115. package/dist/messaging/identities.d.ts +9 -0
  116. package/dist/messaging/identities.d.ts.map +1 -0
  117. package/dist/messaging/identities.js +21 -0
  118. package/dist/messaging/identities.js.map +1 -0
  119. package/dist/messaging/labels.d.ts +16 -0
  120. package/dist/messaging/labels.d.ts.map +1 -0
  121. package/dist/messaging/labels.js +65 -0
  122. package/dist/messaging/labels.js.map +1 -0
  123. package/dist/messaging/media.d.ts +72 -0
  124. package/dist/messaging/media.d.ts.map +1 -0
  125. package/dist/messaging/media.js +142 -0
  126. package/dist/messaging/media.js.map +1 -0
  127. package/dist/messaging/messages.d.ts +18 -0
  128. package/dist/messaging/messages.d.ts.map +1 -0
  129. package/dist/messaging/messages.js +32 -0
  130. package/dist/messaging/messages.js.map +1 -0
  131. package/dist/messaging/observation-policies.d.ts +10 -0
  132. package/dist/messaging/observation-policies.d.ts.map +1 -0
  133. package/dist/messaging/observation-policies.js +28 -0
  134. package/dist/messaging/observation-policies.js.map +1 -0
  135. package/dist/messaging/onboarding.d.ts +53 -0
  136. package/dist/messaging/onboarding.d.ts.map +1 -0
  137. package/dist/messaging/onboarding.js +69 -0
  138. package/dist/messaging/onboarding.js.map +1 -0
  139. package/dist/messaging/presence.d.ts +12 -0
  140. package/dist/messaging/presence.d.ts.map +1 -0
  141. package/dist/messaging/presence.js +43 -0
  142. package/dist/messaging/presence.js.map +1 -0
  143. package/dist/messaging/privacy.d.ts +11 -0
  144. package/dist/messaging/privacy.d.ts.map +1 -0
  145. package/dist/messaging/privacy.js +34 -0
  146. package/dist/messaging/privacy.js.map +1 -0
  147. package/dist/messaging/profile.d.ts +13 -0
  148. package/dist/messaging/profile.d.ts.map +1 -0
  149. package/dist/messaging/profile.js +49 -0
  150. package/dist/messaging/profile.js.map +1 -0
  151. package/dist/messaging/quick-replies.d.ts +13 -0
  152. package/dist/messaging/quick-replies.d.ts.map +1 -0
  153. package/dist/messaging/quick-replies.js +45 -0
  154. package/dist/messaging/quick-replies.js.map +1 -0
  155. package/dist/messaging/quicklinks.d.ts +69 -0
  156. package/dist/messaging/quicklinks.d.ts.map +1 -0
  157. package/dist/messaging/quicklinks.js +45 -0
  158. package/dist/messaging/quicklinks.js.map +1 -0
  159. package/dist/messaging/session-configuration.d.ts +67 -0
  160. package/dist/messaging/session-configuration.d.ts.map +1 -0
  161. package/dist/messaging/session-configuration.js +2 -0
  162. package/dist/messaging/session-configuration.js.map +1 -0
  163. package/dist/messaging/sessions.d.ts +26 -0
  164. package/dist/messaging/sessions.d.ts.map +1 -0
  165. package/dist/messaging/sessions.js +108 -0
  166. package/dist/messaging/sessions.js.map +1 -0
  167. package/dist/messaging/templates.d.ts +15 -0
  168. package/dist/messaging/templates.d.ts.map +1 -0
  169. package/dist/messaging/templates.js +67 -0
  170. package/dist/messaging/templates.js.map +1 -0
  171. package/dist/messaging/testing-configuration.d.ts +89 -0
  172. package/dist/messaging/testing-configuration.d.ts.map +1 -0
  173. package/dist/messaging/testing-configuration.js +14 -0
  174. package/dist/messaging/testing-configuration.js.map +1 -0
  175. package/dist/messaging/types.d.ts +1793 -0
  176. package/dist/messaging/types.d.ts.map +1 -0
  177. package/dist/messaging/types.js +25 -0
  178. package/dist/messaging/types.js.map +1 -0
  179. package/dist/messaging/users.d.ts +9 -0
  180. package/dist/messaging/users.d.ts.map +1 -0
  181. package/dist/messaging/users.js +15 -0
  182. package/dist/messaging/users.js.map +1 -0
  183. package/dist/messaging/voip.d.ts +51 -0
  184. package/dist/messaging/voip.d.ts.map +1 -0
  185. package/dist/messaging/voip.js +293 -0
  186. package/dist/messaging/voip.js.map +1 -0
  187. package/dist/messaging/webhooks.d.ts +13 -0
  188. package/dist/messaging/webhooks.d.ts.map +1 -0
  189. package/dist/messaging/webhooks.js +48 -0
  190. package/dist/messaging/webhooks.js.map +1 -0
  191. package/dist/node.d.ts +55 -0
  192. package/dist/node.d.ts.map +1 -0
  193. package/dist/node.js +237 -0
  194. package/dist/node.js.map +1 -0
  195. package/dist/pagination.d.ts +24 -0
  196. package/dist/pagination.d.ts.map +1 -0
  197. package/dist/pagination.js +28 -0
  198. package/dist/pagination.js.map +1 -0
  199. package/dist/platform/api-keys.d.ts +10 -0
  200. package/dist/platform/api-keys.d.ts.map +1 -0
  201. package/dist/platform/api-keys.js +22 -0
  202. package/dist/platform/api-keys.js.map +1 -0
  203. package/dist/platform/audiences.d.ts +16 -0
  204. package/dist/platform/audiences.d.ts.map +1 -0
  205. package/dist/platform/audiences.js +46 -0
  206. package/dist/platform/audiences.js.map +1 -0
  207. package/dist/platform/audit-logs.d.ts +9 -0
  208. package/dist/platform/audit-logs.d.ts.map +1 -0
  209. package/dist/platform/audit-logs.js +20 -0
  210. package/dist/platform/audit-logs.js.map +1 -0
  211. package/dist/platform/bansafe.d.ts +24 -0
  212. package/dist/platform/bansafe.d.ts.map +1 -0
  213. package/dist/platform/bansafe.js +70 -0
  214. package/dist/platform/bansafe.js.map +1 -0
  215. package/dist/platform/billing.d.ts +13 -0
  216. package/dist/platform/billing.d.ts.map +1 -0
  217. package/dist/platform/billing.js +27 -0
  218. package/dist/platform/billing.js.map +1 -0
  219. package/dist/platform/campaigns.d.ts +28 -0
  220. package/dist/platform/campaigns.d.ts.map +1 -0
  221. package/dist/platform/campaigns.js +92 -0
  222. package/dist/platform/campaigns.js.map +1 -0
  223. package/dist/platform/customers.d.ts +23 -0
  224. package/dist/platform/customers.d.ts.map +1 -0
  225. package/dist/platform/customers.js +140 -0
  226. package/dist/platform/customers.js.map +1 -0
  227. package/dist/platform/developer-resources.d.ts +101 -0
  228. package/dist/platform/developer-resources.d.ts.map +1 -0
  229. package/dist/platform/developer-resources.js +250 -0
  230. package/dist/platform/developer-resources.js.map +1 -0
  231. package/dist/platform/developer-types.d.ts +378 -0
  232. package/dist/platform/developer-types.d.ts.map +1 -0
  233. package/dist/platform/developer-types.js +2 -0
  234. package/dist/platform/developer-types.js.map +1 -0
  235. package/dist/platform/event-stream.d.ts +114 -0
  236. package/dist/platform/event-stream.d.ts.map +1 -0
  237. package/dist/platform/event-stream.js +308 -0
  238. package/dist/platform/event-stream.js.map +1 -0
  239. package/dist/platform/media.d.ts +13 -0
  240. package/dist/platform/media.d.ts.map +1 -0
  241. package/dist/platform/media.js +33 -0
  242. package/dist/platform/media.js.map +1 -0
  243. package/dist/platform/members.d.ts +9 -0
  244. package/dist/platform/members.d.ts.map +1 -0
  245. package/dist/platform/members.js +15 -0
  246. package/dist/platform/members.js.map +1 -0
  247. package/dist/platform/opt-outs.d.ts +15 -0
  248. package/dist/platform/opt-outs.d.ts.map +1 -0
  249. package/dist/platform/opt-outs.js +36 -0
  250. package/dist/platform/opt-outs.js.map +1 -0
  251. package/dist/platform/organizations.d.ts +9 -0
  252. package/dist/platform/organizations.d.ts.map +1 -0
  253. package/dist/platform/organizations.js +15 -0
  254. package/dist/platform/organizations.js.map +1 -0
  255. package/dist/platform/project-tokens.d.ts +9 -0
  256. package/dist/platform/project-tokens.d.ts.map +1 -0
  257. package/dist/platform/project-tokens.js +16 -0
  258. package/dist/platform/project-tokens.js.map +1 -0
  259. package/dist/platform/projects.d.ts +24 -0
  260. package/dist/platform/projects.d.ts.map +1 -0
  261. package/dist/platform/projects.js +83 -0
  262. package/dist/platform/projects.js.map +1 -0
  263. package/dist/platform/quicklink-settings.d.ts +85 -0
  264. package/dist/platform/quicklink-settings.d.ts.map +1 -0
  265. package/dist/platform/quicklink-settings.js +35 -0
  266. package/dist/platform/quicklink-settings.js.map +1 -0
  267. package/dist/platform/response.d.ts +10 -0
  268. package/dist/platform/response.d.ts.map +1 -0
  269. package/dist/platform/response.js +41 -0
  270. package/dist/platform/response.js.map +1 -0
  271. package/dist/platform/security-incidents.d.ts +10 -0
  272. package/dist/platform/security-incidents.d.ts.map +1 -0
  273. package/dist/platform/security-incidents.js +22 -0
  274. package/dist/platform/security-incidents.js.map +1 -0
  275. package/dist/platform/session-bans.d.ts +13 -0
  276. package/dist/platform/session-bans.d.ts.map +1 -0
  277. package/dist/platform/session-bans.js +21 -0
  278. package/dist/platform/session-bans.js.map +1 -0
  279. package/dist/platform/session-configuration.d.ts +15 -0
  280. package/dist/platform/session-configuration.d.ts.map +1 -0
  281. package/dist/platform/session-configuration.js +36 -0
  282. package/dist/platform/session-configuration.js.map +1 -0
  283. package/dist/platform/sessions.d.ts +26 -0
  284. package/dist/platform/sessions.d.ts.map +1 -0
  285. package/dist/platform/sessions.js +121 -0
  286. package/dist/platform/sessions.js.map +1 -0
  287. package/dist/platform/sip-trunks.d.ts +141 -0
  288. package/dist/platform/sip-trunks.d.ts.map +1 -0
  289. package/dist/platform/sip-trunks.js +140 -0
  290. package/dist/platform/sip-trunks.js.map +1 -0
  291. package/dist/platform/types.d.ts +897 -0
  292. package/dist/platform/types.d.ts.map +1 -0
  293. package/dist/platform/types.js +2 -0
  294. package/dist/platform/types.js.map +1 -0
  295. package/dist/raw.d.ts +26 -0
  296. package/dist/raw.d.ts.map +1 -0
  297. package/dist/raw.js +79 -0
  298. package/dist/raw.js.map +1 -0
  299. package/dist/system.d.ts +37 -0
  300. package/dist/system.d.ts.map +1 -0
  301. package/dist/system.js +45 -0
  302. package/dist/system.js.map +1 -0
  303. package/dist/transport/body.d.ts +6 -0
  304. package/dist/transport/body.d.ts.map +1 -0
  305. package/dist/transport/body.js +33 -0
  306. package/dist/transport/body.js.map +1 -0
  307. package/dist/transport/content-disposition.d.ts +9 -0
  308. package/dist/transport/content-disposition.d.ts.map +1 -0
  309. package/dist/transport/content-disposition.js +107 -0
  310. package/dist/transport/content-disposition.js.map +1 -0
  311. package/dist/transport/http.d.ts +35 -0
  312. package/dist/transport/http.d.ts.map +1 -0
  313. package/dist/transport/http.js +660 -0
  314. package/dist/transport/http.js.map +1 -0
  315. package/dist/transport/idempotency.d.ts +10 -0
  316. package/dist/transport/idempotency.d.ts.map +1 -0
  317. package/dist/transport/idempotency.js +13 -0
  318. package/dist/transport/idempotency.js.map +1 -0
  319. package/dist/transport/retry.d.ts +11 -0
  320. package/dist/transport/retry.d.ts.map +1 -0
  321. package/dist/transport/retry.js +45 -0
  322. package/dist/transport/retry.js.map +1 -0
  323. package/dist/transport/types.d.ts +58 -0
  324. package/dist/transport/types.d.ts.map +1 -0
  325. package/dist/transport/types.js +2 -0
  326. package/dist/transport/types.js.map +1 -0
  327. package/dist/version.d.ts +2 -0
  328. package/dist/version.d.ts.map +1 -0
  329. package/dist/version.js +2 -0
  330. package/dist/version.js.map +1 -0
  331. package/dist/webhooks/events.d.ts +741 -0
  332. package/dist/webhooks/events.d.ts.map +1 -0
  333. package/dist/webhooks/events.js +80 -0
  334. package/dist/webhooks/events.js.map +1 -0
  335. package/dist/webhooks/index.d.ts +4 -0
  336. package/dist/webhooks/index.d.ts.map +1 -0
  337. package/dist/webhooks/index.js +4 -0
  338. package/dist/webhooks/index.js.map +1 -0
  339. package/dist/webhooks/utilities.d.ts +33 -0
  340. package/dist/webhooks/utilities.d.ts.map +1 -0
  341. package/dist/webhooks/utilities.js +67 -0
  342. package/dist/webhooks/utilities.js.map +1 -0
  343. package/dist/webhooks/verify.d.ts +9 -0
  344. package/dist/webhooks/verify.d.ts.map +1 -0
  345. package/dist/webhooks/verify.js +77 -0
  346. package/dist/webhooks/verify.js.map +1 -0
  347. package/package.json +58 -0
@@ -0,0 +1,860 @@
1
+ import { CallReporter } from "./diagnostics.js";
2
+ import { CallClaimedError, CallsAuthError, CallsError } from "./errors.js";
3
+ import { Emitter } from "./events.js";
4
+ import { MediaSocket, } from "./media.js";
5
+ import { createConnectionId, isConnectionId, } from "./protocol.js";
6
+ function videoSource(source) {
7
+ if (source.participant !== undefined)
8
+ return {
9
+ id: source.source,
10
+ label: source.participant.phoneNumber ?? source.participant.id,
11
+ participant: source.participant,
12
+ };
13
+ return {
14
+ id: source.source,
15
+ label: source.connectionParticipant ?? source.connectionId,
16
+ connectionId: source.connectionId,
17
+ ...(source.connectionParticipant === undefined
18
+ ? {}
19
+ : { connectionParticipant: source.connectionParticipant }),
20
+ };
21
+ }
22
+ /**
23
+ * The call's merged audio as signed 16-bit mono PCM samples at
24
+ * {@link sampleRate}: `on("data")` is the call → you, `write()` is you → the call.
25
+ */
26
+ export class AudioTrack extends Emitter {
27
+ #socket;
28
+ #sampleRate;
29
+ constructor(sampleRate) {
30
+ super();
31
+ this.#sampleRate = sampleRate;
32
+ }
33
+ /** s16le mono PCM rate both directions use. Final once the call is connected. */
34
+ get sampleRate() {
35
+ return this.#sampleRate;
36
+ }
37
+ /** Push s16le mono PCM at {@link sampleRate}. Returns false when no media is attached. */
38
+ write(pcm) {
39
+ return this.#socket?.writeAudio(pcm) ?? false;
40
+ }
41
+ /** @internal */
42
+ _attach(socket, sampleRate) {
43
+ this.#socket = socket;
44
+ if (sampleRate !== undefined)
45
+ this.#sampleRate = sampleRate;
46
+ }
47
+ /** @internal */
48
+ _push(pcm) {
49
+ this.emit("data", pcm);
50
+ }
51
+ /** @internal */
52
+ _detach() {
53
+ this.#socket = undefined;
54
+ this.removeAllListeners();
55
+ }
56
+ }
57
+ /**
58
+ * The call's video: one outgoing H.264 stream, and one received stream per
59
+ * remote participant. Streams are separate; nothing is composed.
60
+ */
61
+ export class VideoTrack extends Emitter {
62
+ #socket;
63
+ #sources = new Map();
64
+ /** Remote participant videos by `id`. */
65
+ get sources() {
66
+ return this.#sources;
67
+ }
68
+ /** Send one encoded frame. Returns false when no media is attached. */
69
+ write(frame) {
70
+ return (this.#socket?.writeVideo({
71
+ keyframe: frame.keyframe,
72
+ timestampUs: frame.timestampUs,
73
+ data: frame.data,
74
+ }) ?? false);
75
+ }
76
+ /** @internal */
77
+ _attach(socket) {
78
+ this.#socket = socket;
79
+ }
80
+ /** @internal */
81
+ _frame(frame) {
82
+ this.emit("frame", {
83
+ source: frame.source,
84
+ keyframe: frame.keyframe,
85
+ timestampUs: frame.timestampUs,
86
+ data: frame.data,
87
+ });
88
+ }
89
+ /** @internal */
90
+ _source(source) {
91
+ const neutral = videoSource(source);
92
+ this.#sources.set(neutral.id, neutral);
93
+ this.emit("source", neutral);
94
+ }
95
+ /** @internal */
96
+ _sourceRemoved(handle) {
97
+ if (!this.#sources.delete(handle))
98
+ return;
99
+ this.emit("sourceRemoved", handle);
100
+ }
101
+ /** @internal */
102
+ _clearSources() {
103
+ for (const handle of [...this.#sources.keys()])
104
+ this._sourceRemoved(handle);
105
+ }
106
+ /** @internal */
107
+ _keyframeRequest() {
108
+ this.emit("keyframeRequest");
109
+ }
110
+ /** @internal */
111
+ _detach() {
112
+ this.#socket = undefined;
113
+ this.#sources.clear();
114
+ this.removeAllListeners();
115
+ }
116
+ }
117
+ export const DEFAULT_CALL_CAPABILITIES = Object.freeze({
118
+ video: true,
119
+ invite: true,
120
+ });
121
+ /**
122
+ * Read platform-reported capabilities, keeping `fallback` (the defaults
123
+ * unless given) for absent or malformed fields.
124
+ */
125
+ export function capabilitiesFrom(value, fallback = DEFAULT_CALL_CAPABILITIES) {
126
+ if (value === null || typeof value !== "object" || Array.isArray(value))
127
+ return fallback;
128
+ const reported = value;
129
+ return {
130
+ video: typeof reported["video"] === "boolean"
131
+ ? reported["video"]
132
+ : fallback.video,
133
+ invite: typeof reported["invite"] === "boolean"
134
+ ? reported["invite"]
135
+ : fallback.invite,
136
+ };
137
+ }
138
+ /** Upper bound on the platform release after a local media failure. */
139
+ const RELEASE_TIMEOUT_MS = 5_000;
140
+ /**
141
+ * One call. Created by the client for every inbound `call.received` and every
142
+ * successful `place()`. Lifecycle transitions arrive from the client's socket;
143
+ * media arrives on this call's own connection once accepted.
144
+ */
145
+ export class Call extends Emitter {
146
+ id;
147
+ session;
148
+ direction;
149
+ peer;
150
+ /** True when the call offered video and the call can carry it. */
151
+ get hasVideo() {
152
+ return this.#offeredVideo && this.#capabilities.video;
153
+ }
154
+ /**
155
+ * What this call supports. An outbound call starts with the defaults and
156
+ * takes the platform's report from `call.accepted` (`capabilities` event).
157
+ */
158
+ get capabilities() {
159
+ return this.#capabilities;
160
+ }
161
+ #offeredVideo;
162
+ #capabilities;
163
+ /** This client's media connection id; reused on reconnect. */
164
+ connectionId;
165
+ audio;
166
+ video;
167
+ #api;
168
+ #mediaOptions;
169
+ #participant;
170
+ #self;
171
+ #now;
172
+ #reconnectAttempts;
173
+ #participants = new Map();
174
+ #departedParticipants = new Set();
175
+ #participantRevisions = new Map();
176
+ #rosterRevision = 0;
177
+ #state;
178
+ #media;
179
+ #endReason;
180
+ #startedAt;
181
+ #connectedAt;
182
+ #endedAt;
183
+ /** The in-flight answer or join, through media attachment. */
184
+ #accepting;
185
+ /** The in-flight accept request alone, without media attachment. */
186
+ #acceptRequest;
187
+ /** This client's accept succeeded. */
188
+ #accepted = false;
189
+ #answered = false;
190
+ #answeredBy;
191
+ #exclusive = false;
192
+ #claimedByOther = false;
193
+ /** This client claimed the call: its own answer, or an exclusive placement. */
194
+ #claimedByUs = false;
195
+ #reconnectTimer;
196
+ #externalMedia;
197
+ #reporter;
198
+ /** Media attached at least once, so there is a connection to report on. */
199
+ #mediaAttached = false;
200
+ /** Successful media reattachments of this connection. */
201
+ #reconnects = 0;
202
+ #mediaReady = false;
203
+ constructor(init) {
204
+ super();
205
+ this.id = init.id;
206
+ this.session = init.session;
207
+ this.direction = init.direction;
208
+ this.peer = init.peer;
209
+ this.#capabilities = init.capabilities ?? DEFAULT_CALL_CAPABILITIES;
210
+ this.#offeredVideo = init.video;
211
+ if (init.connectionId !== undefined && !isConnectionId(init.connectionId))
212
+ throw new CallsError("invalid_connection_id", "connectionId must be 8–64 characters of A–Z, a–z, 0–9, _ or -.");
213
+ this.connectionId = init.connectionId ?? createConnectionId();
214
+ this.#api = init.api;
215
+ this.#mediaOptions = init.media;
216
+ this.#participant = init.participant;
217
+ this.#self = init.self ?? (() => undefined);
218
+ this.#externalMedia = init.mediaMode === "external";
219
+ const report = init.api.report;
220
+ this.#reporter =
221
+ init.diagnostics === undefined ||
222
+ this.#externalMedia ||
223
+ report === undefined
224
+ ? undefined
225
+ : new CallReporter({
226
+ connectionId: this.connectionId,
227
+ client: init.diagnostics,
228
+ ...(init.now === undefined ? {} : { now: init.now }),
229
+ send: (body) => report.call(init.api, this.id, {
230
+ ...body,
231
+ ...(init.participant === undefined
232
+ ? {}
233
+ : { participant: init.participant }),
234
+ }),
235
+ });
236
+ this.#reconnectAttempts = init.reconnectAttempts ?? 3;
237
+ this.#now = init.now ?? Date.now;
238
+ this.#state = init.direction === "inbound" ? "incoming" : "ringing";
239
+ this.#startedAt = this.#now();
240
+ this.audio = new AudioTrack(16_000);
241
+ this.video = new VideoTrack();
242
+ // The placing participant owns an outbound call.
243
+ if (init.direction === "outbound") {
244
+ this.#accepted = true;
245
+ this.#claimedByUs = init.exclusive === true;
246
+ }
247
+ }
248
+ get state() {
249
+ return this.#state;
250
+ }
251
+ get ended() {
252
+ return this.#state === "ended";
253
+ }
254
+ get endReason() {
255
+ return this.#endReason;
256
+ }
257
+ get startedAt() {
258
+ return this.#startedAt;
259
+ }
260
+ get connectedAt() {
261
+ return this.#connectedAt;
262
+ }
263
+ /** Seconds of connected time; frozen at the end so a retained call stops counting. */
264
+ get duration() {
265
+ if (this.#connectedAt === undefined)
266
+ return 0;
267
+ const until = this.#endedAt ?? this.#now();
268
+ return Math.max(0, Math.floor((until - this.#connectedAt) / 1000));
269
+ }
270
+ get endedAt() {
271
+ return this.#endedAt;
272
+ }
273
+ get participants() {
274
+ return [...this.#participants.values()];
275
+ }
276
+ /** Answer and claim state, as last reported by the platform. */
277
+ get claim() {
278
+ return {
279
+ answered: this.#answered,
280
+ ...(this.#answeredBy === undefined
281
+ ? {}
282
+ : { answeredBy: this.#answeredBy }),
283
+ exclusive: this.#exclusive,
284
+ claimedByOther: this.claimedByOther,
285
+ canJoin: this.canJoin,
286
+ };
287
+ }
288
+ /**
289
+ * @internal This client holds the claim: its own exclusive answer, or a
290
+ * placement with `exclusive: true`. Media adapters end such a call, rather
291
+ * than leave it, when its media fails.
292
+ */
293
+ get _claimedBySelf() {
294
+ return this.#claimedByUs;
295
+ }
296
+ get claimedByOther() {
297
+ return this.#claimedByOther && !this.#accepted && !this.ended;
298
+ }
299
+ get canJoin() {
300
+ return (this.#state === "incoming" &&
301
+ this.#answered &&
302
+ !this.#exclusive &&
303
+ !this.#claimedByOther);
304
+ }
305
+ /**
306
+ * Answer a ringing call and attach media. With `exclusive: true` the call
307
+ * is claimed: other participants stop ringing and cannot join. Resolves
308
+ * once media is connected (after acceptance for external media). Rejects
309
+ * with {@link CallClaimedError} when another participant claimed the call;
310
+ * the call then stays visible as `claimedByOther`.
311
+ */
312
+ answer(options = {}) {
313
+ return this.#accept({
314
+ exclusive: options.exclusive === true,
315
+ video: options.video ?? this.hasVideo,
316
+ });
317
+ }
318
+ /**
319
+ * Join a call another participant answered without claiming it. Never
320
+ * claims. Rejects with {@link CallClaimedError} when the call is claimed.
321
+ */
322
+ join(options = {}) {
323
+ if (!this.#answered && this.#state === "incoming")
324
+ return Promise.reject(new CallsError("call_not_answered", "Nobody has answered this call yet; answer it instead."));
325
+ return this.#accept({
326
+ exclusive: false,
327
+ video: options.video ?? this.hasVideo,
328
+ });
329
+ }
330
+ #accept(options) {
331
+ // A concurrent answer or join settles with the attempt already running,
332
+ // so it also waits for media and fails if media fails.
333
+ if (this.#accepting !== undefined)
334
+ return this.#accepting;
335
+ if (this.#state !== "incoming")
336
+ return Promise.reject(new CallsError("invalid_state", `Cannot answer a call in state "${this.#state}".`));
337
+ if (this.claimedByOther)
338
+ return Promise.reject(new CallClaimedError());
339
+ let accepted = false;
340
+ const accepting = (async () => {
341
+ this.#transition("connecting");
342
+ const result = await this.#api.accept(this.id, {
343
+ exclusive: options.exclusive,
344
+ video: options.video,
345
+ ...(this.#participant === undefined
346
+ ? {}
347
+ : { participant: this.#participant }),
348
+ });
349
+ accepted = true;
350
+ this.#accepted = true;
351
+ this.#claimedByUs = result.exclusive;
352
+ this.#applyClaim({
353
+ answered: result.answered,
354
+ answeredBy: result.answeredBy,
355
+ exclusive: result.exclusive,
356
+ });
357
+ })();
358
+ const run = accepting
359
+ .then(() => this.#bridge())
360
+ .catch(async (cause) => {
361
+ this.#accepting = undefined;
362
+ if (this.#state === "ended")
363
+ throw cause;
364
+ if (!accepted) {
365
+ // The accept itself was refused: nothing changed on the platform.
366
+ if (cause instanceof CallClaimedError) {
367
+ this.#claimedByOther = true;
368
+ this.#applyClaim({ answered: true, exclusive: true });
369
+ }
370
+ this.#transition("incoming");
371
+ throw cause;
372
+ }
373
+ this.#reportFailure(cause);
374
+ await this.#release(cause);
375
+ this.#mediaFailed(cause);
376
+ throw cause;
377
+ });
378
+ this.#accepting = run;
379
+ this.#acceptRequest = accepting;
380
+ void accepting
381
+ .finally(() => {
382
+ if (this.#acceptRequest === accepting)
383
+ this.#acceptRequest = undefined;
384
+ })
385
+ .catch(() => undefined);
386
+ // Once settled, later answer() and join() calls check the call's state
387
+ // again instead of reusing this result. Failures already clear it above.
388
+ void run.then(() => {
389
+ if (this.#accepting === run)
390
+ this.#accepting = undefined;
391
+ }, () => undefined);
392
+ return run;
393
+ }
394
+ /**
395
+ * Decline a ringing call. This ends the call for every participant, so
396
+ * applications decide when to call it; the client never declines on its own.
397
+ */
398
+ async reject() {
399
+ if (this.#state !== "incoming")
400
+ throw new CallsError("invalid_state", `Cannot reject a call in state "${this.#state}".`);
401
+ if (this.#answered)
402
+ throw new CallsError("call_not_ringing", "The call is already answered. Join it or leave it; declining is not possible.");
403
+ await this.#api.reject(this.id, undefined, this.#participant);
404
+ this.#end("rejected");
405
+ }
406
+ /**
407
+ * Leave the call: close this client's media connection. The call continues
408
+ * for everyone else. On a call this client never joined, only local
409
+ * tracking stops.
410
+ */
411
+ async leave() {
412
+ if (this.#state === "ended")
413
+ return;
414
+ // Nobody else is on a placed call the remote party has not answered, so
415
+ // leaving it would keep that party ringing with no way to stop it.
416
+ if (this.direction === "outbound" && this.#state === "ringing") {
417
+ await this.end();
418
+ return;
419
+ }
420
+ // An accept still in flight may yet succeed and put this client on the
421
+ // call (possibly with a claim); wait for it, so a successful one is left
422
+ // on the platform too instead of only dropped here.
423
+ const acceptRequest = this.#acceptRequest;
424
+ if (!this.#accepted && acceptRequest !== undefined) {
425
+ await acceptRequest.catch(() => undefined);
426
+ if (this.ended)
427
+ return;
428
+ }
429
+ const attached = this.#accepted;
430
+ const media = this.#media;
431
+ // A refused request leaves the call live so it can be retried; the local
432
+ // model only goes terminal once this client is really out of the call.
433
+ if (attached && !(media?.leave() ?? false))
434
+ await this.#api.leave(this.id, this.connectionId, undefined, this.#participant);
435
+ this.#end("left");
436
+ }
437
+ /** End the call for every participant. Idempotent. */
438
+ async end() {
439
+ if (this.#state === "ended")
440
+ return;
441
+ await this.#api.end(this.id);
442
+ this.#end("hangup");
443
+ }
444
+ /** Invite another party, turning a 1:1 call into a group call. */
445
+ async addParticipant(to) {
446
+ if (this.#state === "ended")
447
+ throw new CallsError("invalid_state", "Cannot add a participant to an ended call.");
448
+ const requestedAtRevision = this.#rosterRevision;
449
+ const participant = await this.#api.addParticipant(this.id, to);
450
+ // The lifecycle stream may move this participant forward, or report it
451
+ // left, while the request is in flight. Its acknowledgement is then older
452
+ // than the roster and must not overwrite or restore that entry.
453
+ if ((this.#participantRevisions.get(participant.id) ?? 0) <=
454
+ requestedAtRevision)
455
+ this.#applyInviteReply(participant);
456
+ return participant;
457
+ }
458
+ /**
459
+ * @internal `call.accepted` arrived. For an outbound call the remote party
460
+ * answered; for an inbound call it names who answered and whether they
461
+ * claimed it.
462
+ */
463
+ async _remoteAccepted(claim = {}) {
464
+ if (!this.ended &&
465
+ claim.capabilities !== undefined &&
466
+ (claim.capabilities.video !== this.#capabilities.video ||
467
+ claim.capabilities.invite !== this.#capabilities.invite)) {
468
+ this.#capabilities = Object.freeze({ ...claim.capabilities });
469
+ this.emit("capabilities", this.#capabilities);
470
+ }
471
+ if (this.direction === "outbound") {
472
+ if (this.#state !== "ringing")
473
+ return;
474
+ // The remote party answered. A placement with `exclusive: true` holds
475
+ // the claim even when the event does not repeat it.
476
+ this.#applyClaim({
477
+ answered: true,
478
+ ...(claim.answeredBy === undefined
479
+ ? {}
480
+ : { answeredBy: claim.answeredBy }),
481
+ exclusive: claim.exclusive === true || this.#claimedByUs,
482
+ });
483
+ this.#transition("connecting");
484
+ try {
485
+ await this.#bridge();
486
+ }
487
+ catch (cause) {
488
+ this.#reportFailure(cause);
489
+ await this.#release(cause);
490
+ this.#mediaFailed(cause);
491
+ }
492
+ return;
493
+ }
494
+ if (this.ended)
495
+ return;
496
+ const exclusive = claim.exclusive === true;
497
+ const answeredBy = claim.answeredBy;
498
+ const self = this.#self();
499
+ const byUs = answeredBy !== undefined && self !== undefined && answeredBy === self;
500
+ if (exclusive && !byUs && !this.#accepted && this.#accepting === undefined)
501
+ this.#claimedByOther = true;
502
+ this.#applyClaim({
503
+ answered: true,
504
+ ...(answeredBy === undefined ? {} : { answeredBy }),
505
+ exclusive,
506
+ });
507
+ }
508
+ /** @internal The platform reported the call over. */
509
+ _remoteEnded(reason) {
510
+ this.#end(reason);
511
+ }
512
+ /** @internal Apply a validated participant update from either transport. */
513
+ _remoteParticipant(frame) {
514
+ if (this.ended)
515
+ return;
516
+ const participantId = frame.type === "participant_left"
517
+ ? frame.participantId
518
+ : frame.participant.id;
519
+ this.#rosterRevision += 1;
520
+ this.#participantRevisions.set(participantId, this.#rosterRevision);
521
+ this.#applyParticipant(frame);
522
+ }
523
+ /**
524
+ * @internal Media owned by another Polymorfa package connected. An outbound
525
+ * call still waits for the remote party to accept. Socket media ignores this.
526
+ */
527
+ mediaConnected() {
528
+ if (!this.#externalMedia || this.ended)
529
+ return;
530
+ this.#mediaReady = true;
531
+ if (this.#state !== "connecting")
532
+ return;
533
+ this.#connectedAt = this.#now();
534
+ this.#transition("connected");
535
+ this.emit("connected");
536
+ }
537
+ #applyClaim(next) {
538
+ const before = this.claim;
539
+ this.#answered = this.#answered || next.answered;
540
+ if (next.answeredBy !== undefined)
541
+ this.#answeredBy = next.answeredBy;
542
+ this.#exclusive = next.exclusive;
543
+ const after = this.claim;
544
+ if (before.answered !== after.answered ||
545
+ before.answeredBy !== after.answeredBy ||
546
+ before.exclusive !== after.exclusive ||
547
+ before.claimedByOther !== after.claimedByOther ||
548
+ before.canJoin !== after.canJoin)
549
+ this.emit("claim", after);
550
+ }
551
+ #applyInviteReply(participant) {
552
+ if (this.ended)
553
+ return;
554
+ const previous = this.#participants.get(participant.id);
555
+ if (previous !== undefined &&
556
+ participantStateRank(participant.state) <
557
+ participantStateRank(previous.state))
558
+ return;
559
+ if (previous !== undefined && sameParticipant(previous, participant))
560
+ return;
561
+ this.#rosterRevision += 1;
562
+ this.#participantRevisions.set(participant.id, this.#rosterRevision);
563
+ this.#applyParticipant({ type: "participant_joined", participant });
564
+ }
565
+ #applyParticipant(frame) {
566
+ if (this.ended)
567
+ return;
568
+ if (frame.type === "participant_left") {
569
+ if (this.#departedParticipants.has(frame.participantId))
570
+ return;
571
+ this.#departedParticipants.add(frame.participantId);
572
+ this.#participants.delete(frame.participantId);
573
+ this.emit("participantLeft", frame.participantId, frame.reason);
574
+ return;
575
+ }
576
+ const participant = frame.participant;
577
+ if (participant.state === "left") {
578
+ this.#applyParticipant({
579
+ type: "participant_left",
580
+ participantId: participant.id,
581
+ });
582
+ return;
583
+ }
584
+ this.#departedParticipants.delete(participant.id);
585
+ const previous = this.#participants.get(participant.id);
586
+ if (previous !== undefined && sameParticipant(previous, participant))
587
+ return;
588
+ this.#participants.set(participant.id, participant);
589
+ if (previous === undefined)
590
+ this.emit("participantJoined", participant);
591
+ else
592
+ this.emit("participantState", participant);
593
+ }
594
+ #mediaFailed(cause) {
595
+ if (this.ended)
596
+ return;
597
+ // The platform accepted; only our media failed. End this client's view
598
+ // of the call and say why.
599
+ this.emit("error", cause instanceof CallsError
600
+ ? cause
601
+ : new CallsError("media_failed", cause instanceof Error ? cause.message : "Media could not attach.", { cause }));
602
+ this.#end(cause instanceof CallClaimedError ? "claimed" : "connection_failed");
603
+ }
604
+ async #bridge() {
605
+ if (this.#externalMedia) {
606
+ if (this.#mediaReady)
607
+ this.mediaConnected();
608
+ return;
609
+ }
610
+ const ready = await this.#attach(false);
611
+ if (ready === undefined)
612
+ return;
613
+ this.#connectedAt = this.#now();
614
+ this.#transition("connected");
615
+ this.emit("connected");
616
+ }
617
+ /** Open one media socket with this call's connection id. */
618
+ async #attach(refreshToken) {
619
+ if (this.ended)
620
+ throw new CallsError("call_ended", "Call ended before media attached.");
621
+ const media = new MediaSocket({
622
+ ...this.#mediaOptions,
623
+ api: this.#api,
624
+ callId: this.id,
625
+ connectionId: this.connectionId,
626
+ ...(this.#participant === undefined
627
+ ? {}
628
+ : { participant: this.#participant }),
629
+ refreshToken,
630
+ });
631
+ this.#media = media;
632
+ media.on("audio", (pcm) => this.audio._push(pcm));
633
+ media.on("video", (frame) => this.video._frame(frame));
634
+ media.on("videoSource", (source) => this.video._source(source));
635
+ media.on("videoSourceRemoved", (handle) => this.video._sourceRemoved(handle));
636
+ media.on("keyframeRequest", () => this.video._keyframeRequest());
637
+ media.on("participantJoined", (participant) => this._remoteParticipant({ type: "participant_joined", participant }));
638
+ media.on("participantState", (participant) => this._remoteParticipant({ type: "participant_state", participant }));
639
+ media.on("participantLeft", (participantId, reason) => this._remoteParticipant({
640
+ type: "participant_left",
641
+ participantId,
642
+ ...(reason === undefined ? {} : { reason }),
643
+ }));
644
+ media.on("error", (error) => this.emit("error", error));
645
+ let ready;
646
+ const readyListener = (value) => {
647
+ ready = value;
648
+ };
649
+ media.once("ready", readyListener);
650
+ let attached = false;
651
+ media.on("close", (close) => {
652
+ if (attached)
653
+ this.#mediaClosed(media, close);
654
+ });
655
+ try {
656
+ await media.connect();
657
+ }
658
+ catch (cause) {
659
+ if (this.#media === media)
660
+ this.#media = undefined;
661
+ media.close();
662
+ throw cause;
663
+ }
664
+ if (this.ended || this.#media !== media) {
665
+ media.close();
666
+ return undefined;
667
+ }
668
+ if (!media.connected) {
669
+ // Closed between `ready` and this continuation.
670
+ this.#media = undefined;
671
+ throw new CallsError("media_closed", "Media connection closed.");
672
+ }
673
+ attached = true;
674
+ this.#mediaAttached = true;
675
+ this.audio._attach(media, ready?.sampleRate ?? media.sampleRate);
676
+ this.video._attach(media);
677
+ return ready;
678
+ }
679
+ #mediaClosed(media, close) {
680
+ if (this.#media !== media || this.ended)
681
+ return;
682
+ this.#media = undefined;
683
+ this.audio._attach(undefined);
684
+ this.video._attach(undefined);
685
+ this.video._clearSources();
686
+ switch (close.reason) {
687
+ case "local":
688
+ return;
689
+ case "left":
690
+ this.#end("left");
691
+ return;
692
+ case "ended":
693
+ this.#end("remote_hangup");
694
+ return;
695
+ case "claimed":
696
+ this.#end("claimed");
697
+ return;
698
+ case "refused":
699
+ this.#reporter?.error("other");
700
+ void this.#failAfterRelease(new CallsError("media_refused", "The platform refused the media connection."));
701
+ return;
702
+ case "unauthorized":
703
+ case "lost":
704
+ this.#reconnect(close.reason === "unauthorized", 0);
705
+ return;
706
+ }
707
+ }
708
+ #reconnect(refreshToken, attempt) {
709
+ if (this.ended)
710
+ return;
711
+ if (attempt >= this.#reconnectAttempts) {
712
+ this.#reporter?.error(refreshToken ? "token_refresh_failed" : "reconnect_exhausted");
713
+ void this.#failAfterRelease(refreshToken
714
+ ? new CallsAuthError()
715
+ : new CallsError("media_lost", "Media connection lost."));
716
+ return;
717
+ }
718
+ this.#transition("reconnecting");
719
+ const delay = Math.min(8_000, 1_000 * 2 ** attempt);
720
+ this.#reconnectTimer = (this.#mediaOptions.setTimeout ?? setTimeout)(() => {
721
+ this.#reconnectTimer = undefined;
722
+ void this.#attach(refreshToken).then((ready) => {
723
+ if (ready === undefined || this.ended)
724
+ return;
725
+ this.#reconnects += 1;
726
+ this.#transition("connected");
727
+ }, (cause) => {
728
+ if (this.ended)
729
+ return;
730
+ if (cause instanceof CallClaimedError) {
731
+ this.#end("claimed");
732
+ return;
733
+ }
734
+ if (!retryable(cause)) {
735
+ // Refused or no longer available: reattaching cannot help.
736
+ this.#reportFailure(cause);
737
+ void this.#failAfterRelease(cause instanceof CallsError
738
+ ? cause
739
+ : new CallsError("media_failed", "Media could not attach."));
740
+ return;
741
+ }
742
+ this.#reconnect(cause instanceof CallsAuthError || refreshToken, attempt + 1);
743
+ });
744
+ }, delay);
745
+ }
746
+ /**
747
+ * The platform still counts this client as on the call after its media
748
+ * failed. Release it before the call becomes terminal here, so the remote
749
+ * party is not left on a call nobody controls: end a call this client
750
+ * claimed (nobody else can take it over), otherwise leave with this
751
+ * connection. A claim by someone else needs nothing. Bounded by
752
+ * RELEASE_TIMEOUT_MS; failures are swallowed so the media error stays the
753
+ * one reported. External media adapters release the call themselves.
754
+ */
755
+ async #release(cause) {
756
+ if (this.ended || !this.#accepted || this.#externalMedia)
757
+ return;
758
+ if (cause instanceof CallClaimedError)
759
+ return;
760
+ const abort = new AbortController();
761
+ let timer;
762
+ const timeout = new Promise((resolve) => {
763
+ timer = (this.#mediaOptions.setTimeout ?? setTimeout)(() => {
764
+ abort.abort();
765
+ resolve();
766
+ }, RELEASE_TIMEOUT_MS);
767
+ });
768
+ const request = this.#claimedByUs
769
+ ? this.#api.end(this.id, abort.signal)
770
+ : this.#api.leave(this.id, this.connectionId, abort.signal, this.#participant);
771
+ try {
772
+ await Promise.race([request.catch(() => undefined), timeout]);
773
+ }
774
+ finally {
775
+ (this.#mediaOptions.clearTimeout ?? clearTimeout)(timer);
776
+ }
777
+ }
778
+ /**
779
+ * Report why media failed. Claims by another participant are not a media
780
+ * failure; causes without a closer code are `other`.
781
+ */
782
+ #reportFailure(cause) {
783
+ if (this.#reporter === undefined || cause instanceof CallClaimedError)
784
+ return;
785
+ this.#reporter.error(cause instanceof CallsAuthError ||
786
+ (cause instanceof CallsError && cause.code === "token_failed")
787
+ ? "token_refresh_failed"
788
+ : cause instanceof CallsError && cause.code === "media_timeout"
789
+ ? "media_timeout"
790
+ : "other");
791
+ }
792
+ /** Release the call on the platform, then report `error` and end here. */
793
+ async #failAfterRelease(error) {
794
+ await this.#release();
795
+ if (this.ended)
796
+ return;
797
+ this.emit("error", error);
798
+ this.#end("connection_failed");
799
+ }
800
+ #transition(next) {
801
+ const previous = this.#state;
802
+ if (previous === next)
803
+ return;
804
+ this.#state = next;
805
+ this.emit("state", next, previous);
806
+ }
807
+ #end(reason) {
808
+ if (this.#state === "ended")
809
+ return;
810
+ this.#endReason = reason;
811
+ this.#endedAt = this.#now();
812
+ if (this.#reconnectTimer !== undefined) {
813
+ (this.#mediaOptions.clearTimeout ?? clearTimeout)(this.#reconnectTimer);
814
+ this.#reconnectTimer = undefined;
815
+ }
816
+ this.#transition("ended");
817
+ // The connection's final figure: how often it reconnected. Only real
818
+ // measurements are sent, and socket media measures nothing else.
819
+ if (this.#mediaAttached)
820
+ this.#reporter?.quality({ reconnects: this.#reconnects }, true);
821
+ this.#reporter?.stop();
822
+ const media = this.#media;
823
+ this.#media = undefined;
824
+ media?.close();
825
+ this.audio._detach();
826
+ this.video._detach();
827
+ this.emit("ended", reason);
828
+ this.removeAllListeners();
829
+ }
830
+ }
831
+ /** Whether a failed reattach is worth another attempt. */
832
+ function retryable(cause) {
833
+ if (cause instanceof CallsAuthError)
834
+ return true;
835
+ if (!(cause instanceof CallsError))
836
+ return true;
837
+ return ["media_closed", "media_timeout", "token_failed"].includes(cause.code);
838
+ }
839
+ function participantStateRank(state) {
840
+ switch (state) {
841
+ case "invited":
842
+ return 0;
843
+ case "ringing":
844
+ return 1;
845
+ case "connected":
846
+ return 2;
847
+ case "left":
848
+ return 3;
849
+ }
850
+ }
851
+ function sameParticipant(a, b) {
852
+ return (a.id === b.id &&
853
+ a.phoneNumber === b.phoneNumber &&
854
+ a.bsuid === b.bsuid &&
855
+ a.username === b.username &&
856
+ a.audioMuted === b.audioMuted &&
857
+ a.video === b.video &&
858
+ a.state === b.state);
859
+ }
860
+ //# sourceMappingURL=call.js.map