@memberjunction/server 5.44.0 → 5.45.1

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 (267) hide show
  1. package/dist/agentSessions/ReturningVisitorRecap.d.ts +39 -0
  2. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
  3. package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
  4. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
  5. package/dist/agentSessions/SessionJanitor.d.ts +13 -0
  6. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionJanitor.js +65 -7
  8. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  9. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  10. package/dist/agentSessions/SessionManager.js +37 -0
  11. package/dist/agentSessions/SessionManager.js.map +1 -1
  12. package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
  13. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  14. package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
  15. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  16. package/dist/auth/magicLink/MagicLinkService.d.ts +7 -0
  17. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  18. package/dist/auth/magicLink/MagicLinkService.js +13 -3
  19. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  20. package/dist/auth/magicLink/index.d.ts +1 -1
  21. package/dist/auth/magicLink/index.d.ts.map +1 -1
  22. package/dist/auth/magicLink/index.js +1 -1
  23. package/dist/auth/magicLink/index.js.map +1 -1
  24. package/dist/auth/magicLink/magicLinkCore.d.ts +38 -1
  25. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
  26. package/dist/auth/magicLink/magicLinkCore.js +42 -19
  27. package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
  28. package/dist/auth/magicLink/types.d.ts +26 -0
  29. package/dist/auth/magicLink/types.d.ts.map +1 -1
  30. package/dist/config.d.ts +1883 -144
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +160 -36
  33. package/dist/config.js.map +1 -1
  34. package/dist/context.d.ts.map +1 -1
  35. package/dist/context.js +45 -2
  36. package/dist/context.js.map +1 -1
  37. package/dist/generated/generated.d.ts +522 -0
  38. package/dist/generated/generated.d.ts.map +1 -1
  39. package/dist/generated/generated.js +2860 -0
  40. package/dist/generated/generated.js.map +1 -1
  41. package/dist/generic/ResolverBase.d.ts +13 -0
  42. package/dist/generic/ResolverBase.d.ts.map +1 -1
  43. package/dist/generic/ResolverBase.js +22 -0
  44. package/dist/generic/ResolverBase.js.map +1 -1
  45. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  46. package/dist/generic/RunViewResolver.js +4 -0
  47. package/dist/generic/RunViewResolver.js.map +1 -1
  48. package/dist/index.d.ts +0 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +101 -3
  51. package/dist/index.js.map +1 -1
  52. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  53. package/dist/integration/CustomColumnPromoter.js +6 -0
  54. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  55. package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
  56. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
  57. package/dist/realtimeWidget/WidgetRouter.js +182 -0
  58. package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
  59. package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
  60. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
  61. package/dist/realtimeWidget/WidgetSessionService.js +477 -0
  62. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
  63. package/dist/realtimeWidget/host-identity.d.ts +40 -0
  64. package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
  65. package/dist/realtimeWidget/host-identity.js +58 -0
  66. package/dist/realtimeWidget/host-identity.js.map +1 -0
  67. package/dist/realtimeWidget/index.d.ts +10 -0
  68. package/dist/realtimeWidget/index.d.ts.map +1 -0
  69. package/dist/realtimeWidget/index.js +9 -0
  70. package/dist/realtimeWidget/index.js.map +1 -0
  71. package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
  72. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
  73. package/dist/realtimeWidget/visitorIdentity.js +202 -0
  74. package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
  75. package/dist/realtimeWidget/widgetCore.d.ts +106 -0
  76. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
  77. package/dist/realtimeWidget/widgetCore.js +173 -0
  78. package/dist/realtimeWidget/widgetCore.js.map +1 -0
  79. package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
  80. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
  81. package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
  82. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
  83. package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
  84. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  85. package/dist/resolvers/ComponentRegistryResolver.js +31 -8
  86. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  87. package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
  88. package/dist/resolvers/EntityPermissionResolver.js +1 -2
  89. package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
  90. package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
  91. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  92. package/dist/resolvers/QuerySystemUserResolver.js +15 -13
  93. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  94. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
  95. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  96. package/dist/resolvers/RealtimeClientSessionResolver.js +47 -3
  97. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  98. package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
  99. package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
  100. package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
  101. package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
  102. package/dist/resolvers/RunAIAgentResolver.d.ts +9 -0
  103. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  104. package/dist/resolvers/RunAIAgentResolver.js +20 -4
  105. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  106. package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
  107. package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
  108. package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
  109. package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
  110. package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
  111. package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
  112. package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
  113. package/dist/resolvers/TelephonyResolver.d.ts +26 -0
  114. package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
  115. package/dist/resolvers/TelephonyResolver.js +86 -0
  116. package/dist/resolvers/TelephonyResolver.js.map +1 -0
  117. package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
  118. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  119. package/dist/resolvers/TestQuerySQLResolver.js +2 -3
  120. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  121. package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
  122. package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
  123. package/dist/resolvers/VonageTelephonyResolver.js +86 -0
  124. package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
  125. package/dist/rest/MediaStreamHandler.js +4 -1
  126. package/dist/rest/MediaStreamHandler.js.map +1 -1
  127. package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
  128. package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
  129. package/dist/telephony/RingCentralTelephonyService.js +262 -0
  130. package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
  131. package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
  132. package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
  133. package/dist/telephony/TeamsMeetingsRouter.js +96 -0
  134. package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
  135. package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
  136. package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
  137. package/dist/telephony/TeamsMeetingsService.js +196 -0
  138. package/dist/telephony/TeamsMeetingsService.js.map +1 -0
  139. package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
  140. package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
  141. package/dist/telephony/TwilioTelephonyRouter.js +143 -0
  142. package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
  143. package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
  144. package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
  145. package/dist/telephony/TwilioTelephonyService.js +193 -0
  146. package/dist/telephony/TwilioTelephonyService.js.map +1 -0
  147. package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
  148. package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
  149. package/dist/telephony/VonageTelephonyRouter.js +201 -0
  150. package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
  151. package/dist/telephony/VonageTelephonyService.d.ts +90 -0
  152. package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
  153. package/dist/telephony/VonageTelephonyService.js +191 -0
  154. package/dist/telephony/VonageTelephonyService.js.map +1 -0
  155. package/dist/telephony/calendar-scheduler.d.ts +67 -0
  156. package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
  157. package/dist/telephony/calendar-scheduler.js +133 -0
  158. package/dist/telephony/calendar-scheduler.js.map +1 -0
  159. package/dist/telephony/index.d.ts +29 -0
  160. package/dist/telephony/index.d.ts.map +1 -0
  161. package/dist/telephony/index.js +38 -0
  162. package/dist/telephony/index.js.map +1 -0
  163. package/dist/telephony/media-upgrade-router.d.ts +33 -0
  164. package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
  165. package/dist/telephony/media-upgrade-router.js +56 -0
  166. package/dist/telephony/media-upgrade-router.js.map +1 -0
  167. package/dist/telephony/ringcentral-runtime.d.ts +14 -0
  168. package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
  169. package/dist/telephony/ringcentral-runtime.js +18 -0
  170. package/dist/telephony/ringcentral-runtime.js.map +1 -0
  171. package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
  172. package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
  173. package/dist/telephony/teams-meetings-runtime.js +20 -0
  174. package/dist/telephony/teams-meetings-runtime.js.map +1 -0
  175. package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
  176. package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
  177. package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
  178. package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
  179. package/dist/telephony/telephony-runtime.d.ts +14 -0
  180. package/dist/telephony/telephony-runtime.d.ts.map +1 -0
  181. package/dist/telephony/telephony-runtime.js +18 -0
  182. package/dist/telephony/telephony-runtime.js.map +1 -0
  183. package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
  184. package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
  185. package/dist/telephony/twilioMediaRegistry.js +106 -0
  186. package/dist/telephony/twilioMediaRegistry.js.map +1 -0
  187. package/dist/telephony/vonage-runtime.d.ts +14 -0
  188. package/dist/telephony/vonage-runtime.d.ts.map +1 -0
  189. package/dist/telephony/vonage-runtime.js +18 -0
  190. package/dist/telephony/vonage-runtime.js.map +1 -0
  191. package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
  192. package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
  193. package/dist/telephony/vonageMediaRegistry.js +158 -0
  194. package/dist/telephony/vonageMediaRegistry.js.map +1 -0
  195. package/package.json +88 -83
  196. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +128 -0
  197. package/src/__tests__/magicLink.test.ts +53 -3
  198. package/src/__tests__/search-knowledge-tags.test.ts +9 -9
  199. package/src/__tests__/teams-meetings-service.test.ts +65 -0
  200. package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
  201. package/src/__tests__/twilio-telephony-service.test.ts +23 -0
  202. package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
  203. package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
  204. package/src/__tests__/widget.test.ts +204 -0
  205. package/src/__tests__/widgetGuestElevation.test.ts +57 -0
  206. package/src/__tests__/widgetHostIdentity.test.ts +117 -0
  207. package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
  208. package/src/agentSessions/SessionJanitor.ts +66 -6
  209. package/src/agentSessions/SessionManager.ts +38 -0
  210. package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
  211. package/src/auth/magicLink/MagicLinkService.ts +13 -3
  212. package/src/auth/magicLink/index.ts +1 -0
  213. package/src/auth/magicLink/magicLinkCore.ts +48 -20
  214. package/src/auth/magicLink/types.ts +26 -0
  215. package/src/config.ts +172 -38
  216. package/src/context.ts +50 -3
  217. package/src/generated/generated.ts +1991 -1
  218. package/src/generic/ResolverBase.ts +28 -0
  219. package/src/generic/RunViewResolver.ts +4 -0
  220. package/src/index.ts +109 -3
  221. package/src/integration/CustomColumnPromoter.ts +6 -0
  222. package/src/realtimeWidget/WidgetRouter.ts +204 -0
  223. package/src/realtimeWidget/WidgetSessionService.ts +714 -0
  224. package/src/realtimeWidget/host-identity.ts +84 -0
  225. package/src/realtimeWidget/index.ts +15 -0
  226. package/src/realtimeWidget/visitorIdentity.ts +258 -0
  227. package/src/realtimeWidget/widgetCore.ts +231 -0
  228. package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
  229. package/src/resolvers/ComponentRegistryResolver.ts +36 -10
  230. package/src/resolvers/EntityPermissionResolver.ts +1 -2
  231. package/src/resolvers/QuerySystemUserResolver.ts +14 -10
  232. package/src/resolvers/RealtimeClientSessionResolver.ts +59 -2
  233. package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
  234. package/src/resolvers/RunAIAgentResolver.ts +32 -8
  235. package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
  236. package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
  237. package/src/resolvers/TelephonyResolver.ts +63 -0
  238. package/src/resolvers/TestQuerySQLResolver.ts +2 -3
  239. package/src/resolvers/VonageTelephonyResolver.ts +63 -0
  240. package/src/rest/MediaStreamHandler.ts +4 -1
  241. package/src/telephony/RingCentralTelephonyService.ts +342 -0
  242. package/src/telephony/TeamsMeetingsRouter.ts +124 -0
  243. package/src/telephony/TeamsMeetingsService.ts +268 -0
  244. package/src/telephony/TwilioTelephonyRouter.ts +184 -0
  245. package/src/telephony/TwilioTelephonyService.ts +261 -0
  246. package/src/telephony/VonageTelephonyRouter.ts +239 -0
  247. package/src/telephony/VonageTelephonyService.ts +259 -0
  248. package/src/telephony/calendar-scheduler.ts +176 -0
  249. package/src/telephony/index.ts +43 -0
  250. package/src/telephony/media-upgrade-router.ts +62 -0
  251. package/src/telephony/ringcentral-runtime.ts +22 -0
  252. package/src/telephony/teams-meetings-runtime.ts +24 -0
  253. package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
  254. package/src/telephony/telephony-runtime.ts +22 -0
  255. package/src/telephony/twilioMediaRegistry.ts +137 -0
  256. package/src/telephony/vonage-runtime.ts +22 -0
  257. package/src/telephony/vonageMediaRegistry.ts +200 -0
  258. package/dist/agents/skip-agent.d.ts +0 -111
  259. package/dist/agents/skip-agent.d.ts.map +0 -1
  260. package/dist/agents/skip-agent.js +0 -1487
  261. package/dist/agents/skip-agent.js.map +0 -1
  262. package/dist/agents/skip-sdk.d.ts +0 -261
  263. package/dist/agents/skip-sdk.d.ts.map +0 -1
  264. package/dist/agents/skip-sdk.js +0 -909
  265. package/dist/agents/skip-sdk.js.map +0 -1
  266. package/src/agents/skip-agent.ts +0 -1594
  267. package/src/agents/skip-sdk.ts +0 -1210
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Process-wide holder for the startup-constructed telephony service, so the
3
+ * outbound `PlaceTwilioCall` GraphQL resolver reaches the SAME {@link TwilioTelephonyService}
4
+ * (and thus the same {@link TwilioCallMediaRegistry} the Media-Streams WSS attaches sockets to)
5
+ * that the inbound webhook router uses. Set once during server boot; read by the resolver.
6
+ *
7
+ * @module @memberjunction/server/telephony
8
+ */
9
+ let twilioService;
10
+ /** Binds the startup-constructed Twilio telephony service (called from server boot). */
11
+ export function SetTwilioTelephonyService(service) {
12
+ twilioService = service;
13
+ }
14
+ /** Returns the bound Twilio telephony service, or `undefined` when telephony is not configured. */
15
+ export function GetTwilioTelephonyService() {
16
+ return twilioService;
17
+ }
18
+ //# sourceMappingURL=telephony-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telephony-runtime.js","sourceRoot":"","sources":["../../src/telephony/telephony-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,IAAI,aAAiD,CAAC;AAEtD,wFAAwF;AACxF,MAAM,UAAU,yBAAyB,CAAC,OAA+B;IACrE,aAAa,GAAG,OAAO,CAAC;AAC5B,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,yBAAyB;IACrC,OAAO,aAAa,CAAC;AACzB,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @fileoverview Per-call Media-Streams pump registry for the Twilio telephony ingress.
3
+ *
4
+ * This is the server half of the Twilio media plane: it implements the provider package's
5
+ * `ITwilioMediaPump` seam (which `RealTwilioBindings` drives) on top of the live Media-Streams
6
+ * websockets MJAPI accepts at `WSS /telephony/twilio/media`. It exists because of a lifecycle gap:
7
+ * when an INBOUND call's webhook fires, the bridge session starts and `RealTwilioBindings` registers
8
+ * its inbound-audio handler — but Twilio has not yet opened the media websocket (it does so only AFTER
9
+ * the webhook's `<Connect><Stream>` TwiML response). The registry bridges that gap by buffering
10
+ * handler registrations + outbound frames per Call SID and binding them to the socket once it connects
11
+ * (matched on the `start` frame's Call SID).
12
+ *
13
+ * Pure + unit-testable: the websocket is injected as the minimal {@link ITelephonyMediaSocket} surface,
14
+ * so the registry's buffering / dispatch / stream-SID capture logic is exercised with a fake socket and
15
+ * no `ws` install or network.
16
+ *
17
+ * @module @memberjunction/server/telephony
18
+ */
19
+ import type { TwilioMediaFrame } from '@memberjunction/ai-bridge-twilio';
20
+ import type { ITwilioMediaPump } from '@memberjunction/ai-bridge-twilio';
21
+ /**
22
+ * The minimal websocket surface the registry drives — a structural subset of a `ws` socket, so the
23
+ * registry never imports `ws` and is testable with a fake. Outbound frames are sent as JSON strings.
24
+ */
25
+ export interface ITelephonyMediaSocket {
26
+ /** Sends a serialized outbound Media-Streams frame on the call's socket. */
27
+ send(data: string): void;
28
+ /** Closes the socket (best-effort; called on call end). */
29
+ close(): void;
30
+ }
31
+ /**
32
+ * Coordinates Media-Streams sockets with the bridge bindings, keyed by Call SID. One instance per
33
+ * server, shared between the inbound-webhook service (which binds `RealTwilioBindings` over this as the
34
+ * `ITwilioMediaPump`) and the WSS server (which attaches sockets + dispatches inbound frames).
35
+ */
36
+ export declare class TwilioCallMediaRegistry implements ITwilioMediaPump {
37
+ private readonly channels;
38
+ /** Ensures a channel exists for a call (called when a bridge session for the call starts). */
39
+ RegisterCall(callSid: string): void;
40
+ /** @inheritdoc — buffers when the socket has not yet connected; sends immediately once it has. */
41
+ Send(callSid: string, frame: TwilioMediaFrame): void;
42
+ /** @inheritdoc — stores the handler so frames delivered before AND after socket connect reach it. */
43
+ OnFrame(callSid: string, handler: (frame: TwilioMediaFrame) => void): void;
44
+ /** @inheritdoc — the captured stream SID, or `''` until the `start` frame arrives. */
45
+ GetStreamSid(callSid: string): string;
46
+ /**
47
+ * Binds a connected Media-Streams socket (and its captured stream SID) to a call, flushing any
48
+ * outbound frames produced while the socket was still connecting. Called by the WSS server on the
49
+ * `start` frame.
50
+ */
51
+ AttachSocket(callSid: string, socket: ITelephonyMediaSocket, streamSid: string): void;
52
+ /** Dispatches one inbound Media-Streams frame to all registered handlers for the call. */
53
+ DispatchInbound(callSid: string, frame: TwilioMediaFrame): void;
54
+ /** Tears down a call's channel and closes its socket (called on socket close / call end). */
55
+ EndCall(callSid: string): void;
56
+ /** Whether a channel is currently tracked for the call (test/observability helper). */
57
+ HasCall(callSid: string): boolean;
58
+ private ensureChannel;
59
+ }
60
+ //# sourceMappingURL=twilioMediaRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twilioMediaRegistry.d.ts","sourceRoot":"","sources":["../../src/telephony/twilioMediaRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,4EAA4E;IAC5E,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2DAA2D;IAC3D,KAAK,IAAI,IAAI,CAAC;CACjB;AAYD;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,8FAA8F;IACvF,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAM1C,kGAAkG;IAC3F,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAS3D,qGAAqG;IAC9F,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI;IAIjF,sFAAsF;IAC/E,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAM5C;;;;OAIG;IACI,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAU5F,0FAA0F;IACnF,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAatE,6FAA6F;IACtF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAarC,uFAAuF;IAChF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIxC,OAAO,CAAC,aAAa;CAQxB"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Per-call Media-Streams pump registry for the Twilio telephony ingress.
3
+ *
4
+ * This is the server half of the Twilio media plane: it implements the provider package's
5
+ * `ITwilioMediaPump` seam (which `RealTwilioBindings` drives) on top of the live Media-Streams
6
+ * websockets MJAPI accepts at `WSS /telephony/twilio/media`. It exists because of a lifecycle gap:
7
+ * when an INBOUND call's webhook fires, the bridge session starts and `RealTwilioBindings` registers
8
+ * its inbound-audio handler — but Twilio has not yet opened the media websocket (it does so only AFTER
9
+ * the webhook's `<Connect><Stream>` TwiML response). The registry bridges that gap by buffering
10
+ * handler registrations + outbound frames per Call SID and binding them to the socket once it connects
11
+ * (matched on the `start` frame's Call SID).
12
+ *
13
+ * Pure + unit-testable: the websocket is injected as the minimal {@link ITelephonyMediaSocket} surface,
14
+ * so the registry's buffering / dispatch / stream-SID capture logic is exercised with a fake socket and
15
+ * no `ws` install or network.
16
+ *
17
+ * @module @memberjunction/server/telephony
18
+ */
19
+ /**
20
+ * Coordinates Media-Streams sockets with the bridge bindings, keyed by Call SID. One instance per
21
+ * server, shared between the inbound-webhook service (which binds `RealTwilioBindings` over this as the
22
+ * `ITwilioMediaPump`) and the WSS server (which attaches sockets + dispatches inbound frames).
23
+ */
24
+ export class TwilioCallMediaRegistry {
25
+ constructor() {
26
+ this.channels = new Map();
27
+ }
28
+ /** Ensures a channel exists for a call (called when a bridge session for the call starts). */
29
+ RegisterCall(callSid) {
30
+ this.ensureChannel(callSid);
31
+ }
32
+ // ── ITwilioMediaPump ─────────────────────────────────────────────────────────
33
+ /** @inheritdoc — buffers when the socket has not yet connected; sends immediately once it has. */
34
+ Send(callSid, frame) {
35
+ const channel = this.ensureChannel(callSid);
36
+ if (channel.socket) {
37
+ channel.socket.send(JSON.stringify(frame));
38
+ }
39
+ else {
40
+ channel.outboundBuffer.push(frame);
41
+ }
42
+ }
43
+ /** @inheritdoc — stores the handler so frames delivered before AND after socket connect reach it. */
44
+ OnFrame(callSid, handler) {
45
+ this.ensureChannel(callSid).frameHandlers.push(handler);
46
+ }
47
+ /** @inheritdoc — the captured stream SID, or `''` until the `start` frame arrives. */
48
+ GetStreamSid(callSid) {
49
+ return this.channels.get(callSid)?.streamSid ?? '';
50
+ }
51
+ // ── WSS-server side ─────────────────────────────────────────────────────────
52
+ /**
53
+ * Binds a connected Media-Streams socket (and its captured stream SID) to a call, flushing any
54
+ * outbound frames produced while the socket was still connecting. Called by the WSS server on the
55
+ * `start` frame.
56
+ */
57
+ AttachSocket(callSid, socket, streamSid) {
58
+ const channel = this.ensureChannel(callSid);
59
+ channel.socket = socket;
60
+ channel.streamSid = streamSid;
61
+ const buffered = channel.outboundBuffer.splice(0, channel.outboundBuffer.length);
62
+ for (const frame of buffered) {
63
+ socket.send(JSON.stringify(frame));
64
+ }
65
+ }
66
+ /** Dispatches one inbound Media-Streams frame to all registered handlers for the call. */
67
+ DispatchInbound(callSid, frame) {
68
+ const channel = this.channels.get(callSid);
69
+ if (!channel) {
70
+ return;
71
+ }
72
+ if (frame.event === 'start' && frame.streamSid) {
73
+ channel.streamSid = frame.streamSid;
74
+ }
75
+ for (const handler of channel.frameHandlers) {
76
+ handler(frame);
77
+ }
78
+ }
79
+ /** Tears down a call's channel and closes its socket (called on socket close / call end). */
80
+ EndCall(callSid) {
81
+ const channel = this.channels.get(callSid);
82
+ if (!channel) {
83
+ return;
84
+ }
85
+ try {
86
+ channel.socket?.close();
87
+ }
88
+ catch {
89
+ /* best-effort */
90
+ }
91
+ this.channels.delete(callSid);
92
+ }
93
+ /** Whether a channel is currently tracked for the call (test/observability helper). */
94
+ HasCall(callSid) {
95
+ return this.channels.has(callSid);
96
+ }
97
+ ensureChannel(callSid) {
98
+ let channel = this.channels.get(callSid);
99
+ if (!channel) {
100
+ channel = { frameHandlers: [], outboundBuffer: [] };
101
+ this.channels.set(callSid, channel);
102
+ }
103
+ return channel;
104
+ }
105
+ }
106
+ //# sourceMappingURL=twilioMediaRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twilioMediaRegistry.js","sourceRoot":"","sources":["../../src/telephony/twilioMediaRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @fileoverview Process-wide holder for the startup-constructed Vonage telephony service, so the
3
+ * outbound `PlaceVonageCall` GraphQL resolver reaches the SAME {@link VonageTelephonyService}
4
+ * (and thus the same {@link VonageCallMediaRegistry} the media WSS attaches sockets to) that the
5
+ * inbound webhook router uses. Set once during server boot; read by the resolver.
6
+ *
7
+ * @module @memberjunction/server/telephony
8
+ */
9
+ import type { VonageTelephonyService } from './VonageTelephonyService.js';
10
+ /** Binds the startup-constructed Vonage telephony service (called from server boot). */
11
+ export declare function SetVonageTelephonyService(service: VonageTelephonyService): void;
12
+ /** Returns the bound Vonage telephony service, or `undefined` when Vonage telephony is not configured. */
13
+ export declare function GetVonageTelephonyService(): VonageTelephonyService | undefined;
14
+ //# sourceMappingURL=vonage-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vonage-runtime.d.ts","sourceRoot":"","sources":["../../src/telephony/vonage-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAI1E,wFAAwF;AACxF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAE/E;AAED,0GAA0G;AAC1G,wBAAgB,yBAAyB,IAAI,sBAAsB,GAAG,SAAS,CAE9E"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Process-wide holder for the startup-constructed Vonage telephony service, so the
3
+ * outbound `PlaceVonageCall` GraphQL resolver reaches the SAME {@link VonageTelephonyService}
4
+ * (and thus the same {@link VonageCallMediaRegistry} the media WSS attaches sockets to) that the
5
+ * inbound webhook router uses. Set once during server boot; read by the resolver.
6
+ *
7
+ * @module @memberjunction/server/telephony
8
+ */
9
+ let vonageService;
10
+ /** Binds the startup-constructed Vonage telephony service (called from server boot). */
11
+ export function SetVonageTelephonyService(service) {
12
+ vonageService = service;
13
+ }
14
+ /** Returns the bound Vonage telephony service, or `undefined` when Vonage telephony is not configured. */
15
+ export function GetVonageTelephonyService() {
16
+ return vonageService;
17
+ }
18
+ //# sourceMappingURL=vonage-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vonage-runtime.js","sourceRoot":"","sources":["../../src/telephony/vonage-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,IAAI,aAAiD,CAAC;AAEtD,wFAAwF;AACxF,MAAM,UAAU,yBAAyB,CAAC,OAA+B;IACrE,aAAa,GAAG,OAAO,CAAC;AAC5B,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,yBAAyB;IACrC,OAAO,aAAa,CAAC;AACzB,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @fileoverview Per-call WebSocket-media pump registry for the Vonage telephony ingress.
3
+ *
4
+ * This is the server half of the Vonage media plane: it implements the provider package's
5
+ * `IVonageMediaPump` seam (which `RealVonageBindings` drives) on top of the live bidirectional media
6
+ * websockets MJAPI accepts at `WSS /telephony/vonage/media`. It exists because of the same lifecycle
7
+ * gap the Twilio ingress has: when an INBOUND call's answer webhook fires, the bridge session starts and
8
+ * `RealVonageBindings` registers its inbound-audio handler — but Vonage has not yet opened the media
9
+ * websocket (it does so only AFTER the answer-webhook's `connect`-websocket NCCO response). The registry
10
+ * bridges that gap by buffering handler registrations + outbound frames per call UUID and binding them to
11
+ * the socket once it connects (matched on the call UUID Vonage forwards on the websocket handshake).
12
+ *
13
+ * Pure + unit-testable: the websocket is injected as the minimal {@link ITelephonyMediaSocket} surface,
14
+ * so the registry's buffering / dispatch logic is exercised with a fake socket and no `ws` install or
15
+ * network.
16
+ *
17
+ * @module @memberjunction/server/telephony
18
+ */
19
+ import type { VonageControlEvent, IVonageMediaPump } from '@memberjunction/ai-bridge-vonage';
20
+ /**
21
+ * The minimal websocket surface the registry drives — a structural subset of a `ws` socket, so the
22
+ * registry never imports `ws` and is testable with a fake. Vonage audio is sent as BINARY frames (raw
23
+ * L16 PCM); the registry never sends text frames outbound (Vonage's media socket only accepts audio).
24
+ */
25
+ export interface ITelephonyMediaSocket {
26
+ /** Sends one outbound BINARY audio frame (raw L16 PCM) on the call's socket. */
27
+ sendBinary(data: Uint8Array): void;
28
+ /** Sends one outbound TEXT control frame (JSON command, e.g. `{"action":"clear"}`) on the call's socket. */
29
+ sendText(data: string): void;
30
+ /** Closes the socket (best-effort; called on call end). */
31
+ close(): void;
32
+ }
33
+ /**
34
+ * Coordinates Vonage media websockets with the bridge bindings, keyed by call UUID. One instance per
35
+ * server, shared between the inbound-webhook service (which binds `RealVonageBindings` over this as the
36
+ * `IVonageMediaPump`) and the WSS server (which attaches sockets + dispatches inbound audio/events).
37
+ *
38
+ * Models Vonage's wire split: outbound audio → binary frames; inbound binary frames → audio handlers;
39
+ * inbound JSON text frames → parsed {@link VonageControlEvent}s → event handlers. There is no base64
40
+ * envelope or μ-law transcode — that's the Twilio path, not Vonage's.
41
+ */
42
+ export declare class VonageCallMediaRegistry implements IVonageMediaPump {
43
+ private readonly channels;
44
+ /** Ensures a channel exists for a call (called when a bridge session for the call starts). */
45
+ RegisterCall(callUuid: string): void;
46
+ /**
47
+ * @inheritdoc — re-slices the (already-8 kHz) delta into fixed 320-byte / 20 ms frames before sending,
48
+ * carrying any sub-frame remainder into the next delta so the stream stays continuous (no silence gaps).
49
+ * Each whole frame is sent immediately if the socket is live, else buffered for {@link AttachSocket}.
50
+ */
51
+ SendAudio(callUuid: string, pcm: ArrayBuffer): void;
52
+ /** @inheritdoc — stores the audio handler so frames delivered before AND after socket connect reach it. */
53
+ OnAudio(callUuid: string, handler: (pcm: ArrayBuffer) => void): void;
54
+ /** @inheritdoc — stores the control-event handler (DTMF / connected / close). */
55
+ OnEvent(callUuid: string, handler: (event: VonageControlEvent) => void): void;
56
+ /**
57
+ * @inheritdoc — barge-in flush: drop any outbound audio buffered locally (socket not yet connected) AND,
58
+ * if the socket is live, send Vonage's `{"action":"clear"}` text command to discard its playback queue.
59
+ * Safe to call for an unknown/uninitialized call (no-op).
60
+ */
61
+ Clear(callUuid: string): void;
62
+ /**
63
+ * Binds a connected media socket to a call, flushing any outbound audio produced while the socket
64
+ * was still connecting. Called by the WSS server once it resolves the call UUID for the socket (from
65
+ * the `call_uuid` query param the answer NCCO embedded on the media URL).
66
+ */
67
+ AttachSocket(callUuid: string, socket: ITelephonyMediaSocket): void;
68
+ /** Dispatches one inbound binary audio frame (raw L16 PCM) to all audio handlers for the call. */
69
+ DispatchInboundAudio(callUuid: string, pcm: ArrayBuffer): void;
70
+ /** Dispatches one inbound control event (parsed from a JSON text frame) to all event handlers for the call. */
71
+ DispatchInboundEvent(callUuid: string, event: VonageControlEvent): void;
72
+ /** Tears down a call's channel and closes its socket (called on socket close / call end). */
73
+ EndCall(callUuid: string): void;
74
+ /** Whether a channel is currently tracked for the call (test/observability helper). */
75
+ HasCall(callUuid: string): boolean;
76
+ private ensureChannel;
77
+ }
78
+ //# sourceMappingURL=vonageMediaRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vonageMediaRegistry.d.ts","sourceRoot":"","sources":["../../src/telephony/vonageMediaRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAE7F;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC,gFAAgF;IAChF,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,2DAA2D;IAC3D,KAAK,IAAI,IAAI,CAAC;CACjB;AAwBD;;;;;;;;GAQG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE,8FAA8F;IACvF,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM3C;;;;OAIG;IACI,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI;IAoB1D,2GAA2G;IACpG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAI3E,iFAAiF;IAC1E,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI;IAIpF;;;;OAIG;IACI,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYpC;;;;OAIG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI;IAS1E,kGAAkG;IAC3F,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI;IAUrE,+GAA+G;IACxG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAU9E,6FAA6F;IACtF,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAatC,uFAAuF;IAChF,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIzC,OAAO,CAAC,aAAa;CAQxB"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview Per-call WebSocket-media pump registry for the Vonage telephony ingress.
3
+ *
4
+ * This is the server half of the Vonage media plane: it implements the provider package's
5
+ * `IVonageMediaPump` seam (which `RealVonageBindings` drives) on top of the live bidirectional media
6
+ * websockets MJAPI accepts at `WSS /telephony/vonage/media`. It exists because of the same lifecycle
7
+ * gap the Twilio ingress has: when an INBOUND call's answer webhook fires, the bridge session starts and
8
+ * `RealVonageBindings` registers its inbound-audio handler — but Vonage has not yet opened the media
9
+ * websocket (it does so only AFTER the answer-webhook's `connect`-websocket NCCO response). The registry
10
+ * bridges that gap by buffering handler registrations + outbound frames per call UUID and binding them to
11
+ * the socket once it connects (matched on the call UUID Vonage forwards on the websocket handshake).
12
+ *
13
+ * Pure + unit-testable: the websocket is injected as the minimal {@link ITelephonyMediaSocket} surface,
14
+ * so the registry's buffering / dispatch logic is exercised with a fake socket and no `ws` install or
15
+ * network.
16
+ *
17
+ * @module @memberjunction/server/telephony
18
+ */
19
+ /**
20
+ * The exact byte size of one outbound audio frame Vonage expects on the `audio/l16;rate=8000` leg: a
21
+ * 20 ms slice = 8000 Hz × 0.020 s = 160 samples × 2 bytes = **320 bytes**. The realtime model emits audio
22
+ * in arbitrary-size deltas; the bridge resamples each whole delta to 8 kHz, so the registry MUST re-slice
23
+ * into these fixed frames before sending. Handing Vonage one oversized blob per delta garbles playback and
24
+ * (worse) defeats barge-in: `{"action":"clear"}` can drop QUEUED frames but not a giant in-flight one.
25
+ */
26
+ const OUTBOUND_FRAME_BYTES = 320;
27
+ /**
28
+ * Coordinates Vonage media websockets with the bridge bindings, keyed by call UUID. One instance per
29
+ * server, shared between the inbound-webhook service (which binds `RealVonageBindings` over this as the
30
+ * `IVonageMediaPump`) and the WSS server (which attaches sockets + dispatches inbound audio/events).
31
+ *
32
+ * Models Vonage's wire split: outbound audio → binary frames; inbound binary frames → audio handlers;
33
+ * inbound JSON text frames → parsed {@link VonageControlEvent}s → event handlers. There is no base64
34
+ * envelope or μ-law transcode — that's the Twilio path, not Vonage's.
35
+ */
36
+ export class VonageCallMediaRegistry {
37
+ constructor() {
38
+ this.channels = new Map();
39
+ }
40
+ /** Ensures a channel exists for a call (called when a bridge session for the call starts). */
41
+ RegisterCall(callUuid) {
42
+ this.ensureChannel(callUuid);
43
+ }
44
+ // ── IVonageMediaPump ─────────────────────────────────────────────────────────
45
+ /**
46
+ * @inheritdoc — re-slices the (already-8 kHz) delta into fixed 320-byte / 20 ms frames before sending,
47
+ * carrying any sub-frame remainder into the next delta so the stream stays continuous (no silence gaps).
48
+ * Each whole frame is sent immediately if the socket is live, else buffered for {@link AttachSocket}.
49
+ */
50
+ SendAudio(callUuid, pcm) {
51
+ const channel = this.ensureChannel(callUuid);
52
+ const incoming = new Uint8Array(pcm);
53
+ const data = channel.partial.length > 0 ? concatBytes(channel.partial, incoming) : incoming;
54
+ let offset = 0;
55
+ while (data.length - offset >= OUTBOUND_FRAME_BYTES) {
56
+ // `.slice` returns a fresh copy, so a buffered frame never retains the larger backing buffer.
57
+ const frame = data.slice(offset, offset + OUTBOUND_FRAME_BYTES);
58
+ offset += OUTBOUND_FRAME_BYTES;
59
+ if (channel.socket) {
60
+ channel.socket.sendBinary(frame);
61
+ }
62
+ else {
63
+ channel.outboundBuffer.push(frame);
64
+ }
65
+ }
66
+ channel.partial = offset < data.length ? data.slice(offset) : new Uint8Array(0);
67
+ }
68
+ /** @inheritdoc — stores the audio handler so frames delivered before AND after socket connect reach it. */
69
+ OnAudio(callUuid, handler) {
70
+ this.ensureChannel(callUuid).audioHandlers.push(handler);
71
+ }
72
+ /** @inheritdoc — stores the control-event handler (DTMF / connected / close). */
73
+ OnEvent(callUuid, handler) {
74
+ this.ensureChannel(callUuid).eventHandlers.push(handler);
75
+ }
76
+ /**
77
+ * @inheritdoc — barge-in flush: drop any outbound audio buffered locally (socket not yet connected) AND,
78
+ * if the socket is live, send Vonage's `{"action":"clear"}` text command to discard its playback queue.
79
+ * Safe to call for an unknown/uninitialized call (no-op).
80
+ */
81
+ Clear(callUuid) {
82
+ const channel = this.channels.get(callUuid);
83
+ if (!channel) {
84
+ return;
85
+ }
86
+ channel.outboundBuffer.length = 0;
87
+ channel.partial = new Uint8Array(0); // drop the half-built frame too, or it would prepend to the next turn
88
+ channel.socket?.sendText(JSON.stringify({ action: 'clear' }));
89
+ }
90
+ // ── WSS-server side ─────────────────────────────────────────────────────────
91
+ /**
92
+ * Binds a connected media socket to a call, flushing any outbound audio produced while the socket
93
+ * was still connecting. Called by the WSS server once it resolves the call UUID for the socket (from
94
+ * the `call_uuid` query param the answer NCCO embedded on the media URL).
95
+ */
96
+ AttachSocket(callUuid, socket) {
97
+ const channel = this.ensureChannel(callUuid);
98
+ channel.socket = socket;
99
+ const buffered = channel.outboundBuffer.splice(0, channel.outboundBuffer.length);
100
+ for (const frame of buffered) {
101
+ socket.sendBinary(frame);
102
+ }
103
+ }
104
+ /** Dispatches one inbound binary audio frame (raw L16 PCM) to all audio handlers for the call. */
105
+ DispatchInboundAudio(callUuid, pcm) {
106
+ const channel = this.channels.get(callUuid);
107
+ if (!channel) {
108
+ return;
109
+ }
110
+ for (const handler of channel.audioHandlers) {
111
+ handler(pcm);
112
+ }
113
+ }
114
+ /** Dispatches one inbound control event (parsed from a JSON text frame) to all event handlers for the call. */
115
+ DispatchInboundEvent(callUuid, event) {
116
+ const channel = this.channels.get(callUuid);
117
+ if (!channel) {
118
+ return;
119
+ }
120
+ for (const handler of channel.eventHandlers) {
121
+ handler(event);
122
+ }
123
+ }
124
+ /** Tears down a call's channel and closes its socket (called on socket close / call end). */
125
+ EndCall(callUuid) {
126
+ const channel = this.channels.get(callUuid);
127
+ if (!channel) {
128
+ return;
129
+ }
130
+ try {
131
+ channel.socket?.close();
132
+ }
133
+ catch {
134
+ /* best-effort */
135
+ }
136
+ this.channels.delete(callUuid);
137
+ }
138
+ /** Whether a channel is currently tracked for the call (test/observability helper). */
139
+ HasCall(callUuid) {
140
+ return this.channels.has(callUuid);
141
+ }
142
+ ensureChannel(callUuid) {
143
+ let channel = this.channels.get(callUuid);
144
+ if (!channel) {
145
+ channel = { audioHandlers: [], eventHandlers: [], outboundBuffer: [], partial: new Uint8Array(0) };
146
+ this.channels.set(callUuid, channel);
147
+ }
148
+ return channel;
149
+ }
150
+ }
151
+ /** Concatenates two byte arrays into a fresh `Uint8Array` (the carry-over remainder + the next delta). */
152
+ function concatBytes(a, b) {
153
+ const out = new Uint8Array(a.length + b.length);
154
+ out.set(a, 0);
155
+ out.set(b, a.length);
156
+ return out;
157
+ }
158
+ //# sourceMappingURL=vonageMediaRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vonageMediaRegistry.js","sourceRoot":"","sources":["../../src/telephony/vonageMediaRegistry.ts"],"names":[],"mappings":"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"}