@memberjunction/server 6.1.0-edge.5 → 6.1.0-edge.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -11
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.js +2 -2
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +9 -4
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +2 -8
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +4 -12
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/principals.d.ts +92 -0
- package/dist/auth/principals.d.ts.map +1 -0
- package/dist/auth/principals.js +193 -0
- package/dist/auth/principals.js.map +1 -0
- package/dist/config.d.ts +106 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts +30 -0
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts.map +1 -1
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js +58 -5
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +7607 -6853
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +25141 -21645
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +152 -2
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +389 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +26 -2
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/refusalExtensions.d.ts +52 -0
- package/dist/generic/refusalExtensions.d.ts.map +1 -0
- package/dist/generic/refusalExtensions.js +16 -0
- package/dist/generic/refusalExtensions.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -93
- package/dist/index.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -12
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +20 -5
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +34 -5
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +64 -7
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -2
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -0
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +80 -4
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +13 -8
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +45 -8
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +11 -2
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +8 -4
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +2 -1
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +3 -18
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +2 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +3 -16
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts +23 -0
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeSdpBrokerHandler.js +78 -0
- package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -0
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +2 -3
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/package.json +92 -91
- package/src/__tests__/EntityCRUDHandler.test.ts +18 -5
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +118 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +20 -0
- package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +191 -0
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +81 -2
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +190 -3
- package/src/__tests__/ResolverBase.readableFieldsTransport.test.ts +106 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +114 -0
- package/src/__tests__/WidgetSessionService.test.ts +4 -1
- package/src/__tests__/generatedGraphQLSchema.structural.test.ts +46 -0
- package/src/__tests__/newUserRolesDefault.test.ts +81 -0
- package/src/__tests__/newUsers.test.ts +36 -14
- package/src/__tests__/principals.callSites.test.ts +112 -0
- package/src/__tests__/principals.logging.test.ts +160 -0
- package/src/__tests__/principals.test.ts +305 -0
- package/src/__tests__/resolverBase.fls.test.ts +483 -0
- package/src/__tests__/resolverBase.recordChangesFls.test.ts +358 -0
- package/src/__tests__/resolverBase.refusalExtensions.test.ts +101 -0
- package/src/__tests__/resolverBase.refusalMessage.test.ts +88 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +79 -11
- package/src/__tests__/visitorIdentity.resolve.test.ts +61 -0
- package/src/agentSessions/SessionJanitor.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +9 -6
- package/src/auth/magicLink/MagicLinkService.ts +2 -8
- package/src/auth/newUsers.ts +7 -15
- package/src/auth/principals.ts +257 -0
- package/src/config.ts +44 -4
- package/src/entitySubclasses/MJEntityPermissionEntityServer.server.ts +65 -6
- package/src/generated/graphql-schemas/__mj.ts +18966 -16452
- package/src/generic/ResolverBase.ts +467 -9
- package/src/generic/RunViewResolver.ts +28 -2
- package/src/generic/refusalExtensions.ts +59 -0
- package/src/index.ts +154 -102
- package/src/realtimeWidget/WidgetSessionService.ts +2 -14
- package/src/realtimeWidget/visitorIdentity.ts +21 -6
- package/src/resolvers/EntityRecordNameResolver.ts +74 -8
- package/src/resolvers/FileCategoryResolver.ts +2 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -0
- package/src/resolvers/QuerySystemUserResolver.ts +1 -1
- package/src/resolvers/RealtimeClientSessionResolver.ts +128 -2
- package/src/resolvers/RemoteBrowserActionResolver.ts +47 -8
- package/src/resolvers/SyncRolesUsersResolver.ts +11 -2
- package/src/resolvers/UserViewResolver.ts +8 -4
- package/src/rest/EntityCRUDHandler.ts +3 -19
- package/src/rest/RESTEndpointHandler.ts +3 -17
- package/src/rest/RealtimeSdpBrokerHandler.ts +101 -0
- package/src/serverExtensionReservedRoots.ts +2 -3
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts +0 -27
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/RingCentralTelephonyResolver.js +0 -87
- package/dist/resolvers/RingCentralTelephonyResolver.js.map +0 -1
- package/dist/resolvers/TeamsMeetingsResolver.d.ts +0 -28
- package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +0 -1
- package/dist/resolvers/TeamsMeetingsResolver.js +0 -91
- package/dist/resolvers/TeamsMeetingsResolver.js.map +0 -1
- package/dist/resolvers/TelephonyResolver.d.ts +0 -26
- package/dist/resolvers/TelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/TelephonyResolver.js +0 -86
- package/dist/resolvers/TelephonyResolver.js.map +0 -1
- package/dist/resolvers/VonageTelephonyResolver.d.ts +0 -26
- package/dist/resolvers/VonageTelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/VonageTelephonyResolver.js +0 -86
- package/dist/resolvers/VonageTelephonyResolver.js.map +0 -1
- package/dist/telephony/RingCentralTelephonyService.d.ts +0 -115
- package/dist/telephony/RingCentralTelephonyService.d.ts.map +0 -1
- package/dist/telephony/RingCentralTelephonyService.js +0 -262
- package/dist/telephony/RingCentralTelephonyService.js.map +0 -1
- package/dist/telephony/TeamsMeetingsRouter.d.ts +0 -40
- package/dist/telephony/TeamsMeetingsRouter.d.ts.map +0 -1
- package/dist/telephony/TeamsMeetingsRouter.js +0 -96
- package/dist/telephony/TeamsMeetingsRouter.js.map +0 -1
- package/dist/telephony/TeamsMeetingsService.d.ts +0 -113
- package/dist/telephony/TeamsMeetingsService.d.ts.map +0 -1
- package/dist/telephony/TeamsMeetingsService.js +0 -196
- package/dist/telephony/TeamsMeetingsService.js.map +0 -1
- package/dist/telephony/TwilioTelephonyRouter.d.ts +0 -36
- package/dist/telephony/TwilioTelephonyRouter.d.ts.map +0 -1
- package/dist/telephony/TwilioTelephonyRouter.js +0 -143
- package/dist/telephony/TwilioTelephonyRouter.js.map +0 -1
- package/dist/telephony/TwilioTelephonyService.d.ts +0 -95
- package/dist/telephony/TwilioTelephonyService.d.ts.map +0 -1
- package/dist/telephony/TwilioTelephonyService.js +0 -193
- package/dist/telephony/TwilioTelephonyService.js.map +0 -1
- package/dist/telephony/VonageTelephonyRouter.d.ts +0 -41
- package/dist/telephony/VonageTelephonyRouter.d.ts.map +0 -1
- package/dist/telephony/VonageTelephonyRouter.js +0 -201
- package/dist/telephony/VonageTelephonyRouter.js.map +0 -1
- package/dist/telephony/VonageTelephonyService.d.ts +0 -90
- package/dist/telephony/VonageTelephonyService.d.ts.map +0 -1
- package/dist/telephony/VonageTelephonyService.js +0 -191
- package/dist/telephony/VonageTelephonyService.js.map +0 -1
- package/dist/telephony/calendar-scheduler.d.ts +0 -67
- package/dist/telephony/calendar-scheduler.d.ts.map +0 -1
- package/dist/telephony/calendar-scheduler.js +0 -133
- package/dist/telephony/calendar-scheduler.js.map +0 -1
- package/dist/telephony/index.d.ts +0 -29
- package/dist/telephony/index.d.ts.map +0 -1
- package/dist/telephony/index.js +0 -38
- package/dist/telephony/index.js.map +0 -1
- package/dist/telephony/media-upgrade-router.d.ts +0 -47
- package/dist/telephony/media-upgrade-router.d.ts.map +0 -1
- package/dist/telephony/media-upgrade-router.js +0 -72
- package/dist/telephony/media-upgrade-router.js.map +0 -1
- package/dist/telephony/ringcentral-runtime.d.ts +0 -14
- package/dist/telephony/ringcentral-runtime.d.ts.map +0 -1
- package/dist/telephony/ringcentral-runtime.js +0 -18
- package/dist/telephony/ringcentral-runtime.js.map +0 -1
- package/dist/telephony/teams-meetings-runtime.d.ts +0 -16
- package/dist/telephony/teams-meetings-runtime.d.ts.map +0 -1
- package/dist/telephony/teams-meetings-runtime.js +0 -20
- package/dist/telephony/teams-meetings-runtime.js.map +0 -1
- package/dist/telephony/teamsAcsMediaRegistry.d.ts +0 -69
- package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/teamsAcsMediaRegistry.js +0 -118
- package/dist/telephony/teamsAcsMediaRegistry.js.map +0 -1
- package/dist/telephony/telephony-runtime.d.ts +0 -14
- package/dist/telephony/telephony-runtime.d.ts.map +0 -1
- package/dist/telephony/telephony-runtime.js +0 -18
- package/dist/telephony/telephony-runtime.js.map +0 -1
- package/dist/telephony/twilioMediaRegistry.d.ts +0 -60
- package/dist/telephony/twilioMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/twilioMediaRegistry.js +0 -106
- package/dist/telephony/twilioMediaRegistry.js.map +0 -1
- package/dist/telephony/vonage-runtime.d.ts +0 -14
- package/dist/telephony/vonage-runtime.d.ts.map +0 -1
- package/dist/telephony/vonage-runtime.js +0 -18
- package/dist/telephony/vonage-runtime.js.map +0 -1
- package/dist/telephony/vonageMediaRegistry.d.ts +0 -78
- package/dist/telephony/vonageMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/vonageMediaRegistry.js +0 -158
- package/dist/telephony/vonageMediaRegistry.js.map +0 -1
- package/src/__tests__/media-upgrade-router.test.ts +0 -43
- package/src/__tests__/teams-meetings-service.test.ts +0 -65
- package/src/__tests__/teamsAcsMediaRegistry.test.ts +0 -82
- package/src/__tests__/twilio-telephony-service.test.ts +0 -23
- package/src/__tests__/twilioMediaRegistry.test.ts +0 -103
- package/src/__tests__/vonageMediaRegistry.test.ts +0 -180
- package/src/resolvers/RingCentralTelephonyResolver.ts +0 -64
- package/src/resolvers/TeamsMeetingsResolver.ts +0 -68
- package/src/resolvers/TelephonyResolver.ts +0 -63
- package/src/resolvers/VonageTelephonyResolver.ts +0 -63
- package/src/telephony/RingCentralTelephonyService.ts +0 -342
- package/src/telephony/TeamsMeetingsRouter.ts +0 -124
- package/src/telephony/TeamsMeetingsService.ts +0 -268
- package/src/telephony/TwilioTelephonyRouter.ts +0 -184
- package/src/telephony/TwilioTelephonyService.ts +0 -261
- package/src/telephony/VonageTelephonyRouter.ts +0 -239
- package/src/telephony/VonageTelephonyService.ts +0 -259
- package/src/telephony/calendar-scheduler.ts +0 -176
- package/src/telephony/index.ts +0 -43
- package/src/telephony/media-upgrade-router.ts +0 -79
- package/src/telephony/ringcentral-runtime.ts +0 -22
- package/src/telephony/teams-meetings-runtime.ts +0 -24
- package/src/telephony/teamsAcsMediaRegistry.ts +0 -152
- package/src/telephony/telephony-runtime.ts +0 -22
- package/src/telephony/twilioMediaRegistry.ts +0 -137
- package/src/telephony/vonage-runtime.ts +0 -22
- package/src/telephony/vonageMediaRegistry.ts +0 -200
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,158 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vonageMediaRegistry.js","sourceRoot":"","sources":["../../src/telephony/vonageMediaRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { IsGraphQLWsPath } from '../telephony/media-upgrade-router.js';
|
|
3
|
-
|
|
4
|
-
describe('IsGraphQLWsPath', () => {
|
|
5
|
-
describe('bare-root default (GRAPHQL_ROOT_PATH unset — the stock-install case)', () => {
|
|
6
|
-
it('accepts an exact match on the bare root', () => {
|
|
7
|
-
expect(IsGraphQLWsPath('/', '/')).toBe(true);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('accepts the conventional /graphql suffix as an alias of the bare root', () => {
|
|
11
|
-
// This is the fix: MJExplorer's shipped environment.ts hardcodes
|
|
12
|
-
// GRAPHQL_WS_URI: 'ws://<host>/graphql', which never matched the server's
|
|
13
|
-
// bare-root default before this alias existed — every subscription upgrade
|
|
14
|
-
// was silently rejected (400) on a stock install with no .env overrides.
|
|
15
|
-
expect(IsGraphQLWsPath('/graphql', '/')).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('rejects an unrelated path', () => {
|
|
19
|
-
expect(IsGraphQLWsPath('/telephony/twilio/media', '/')).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('rejects a path that merely starts with /graphql (no accidental prefix match)', () => {
|
|
23
|
-
expect(IsGraphQLWsPath('/graphql-admin', '/')).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('non-default configured root (custom GRAPHQL_ROOT_PATH — unchanged behavior)', () => {
|
|
28
|
-
it('accepts an exact match on the configured root', () => {
|
|
29
|
-
expect(IsGraphQLWsPath('/api/graphql', '/api/graphql')).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('does NOT apply the /graphql alias when the configured root is not the bare root', () => {
|
|
33
|
-
// The alias is scoped to the bare-root default only — a deployment that has
|
|
34
|
-
// deliberately set a custom root keeps requiring an exact match, so this
|
|
35
|
-
// fix can't silently widen what a custom-configured server accepts.
|
|
36
|
-
expect(IsGraphQLWsPath('/graphql', '/api/graphql')).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('rejects a mismatched custom path', () => {
|
|
40
|
-
expect(IsGraphQLWsPath('/', '/api/graphql')).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { TeamsMeetingsService } from '../telephony/TeamsMeetingsService.js';
|
|
3
|
-
import { TeamsAcsMediaRegistry } from '../telephony/teamsAcsMediaRegistry.js';
|
|
4
|
-
import { RealTeamsBindings } from '@memberjunction/ai-bridge-teams';
|
|
5
|
-
import type { BaseRealtimeBridge } from '@memberjunction/ai-bridge-base';
|
|
6
|
-
import type { TeamsMeetingsConfig } from '../config.js';
|
|
7
|
-
|
|
8
|
-
const CONFIG: TeamsMeetingsConfig = {
|
|
9
|
-
enabled: true,
|
|
10
|
-
tenantId: 'tenant-1',
|
|
11
|
-
botAccessToken: 'tok',
|
|
12
|
-
notificationClientState: 'secret',
|
|
13
|
-
acsSampleRate: 16000,
|
|
14
|
-
modelSampleRate: 16000,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe('TeamsMeetingsService.buildBindSdk', () => {
|
|
18
|
-
it('binds a RealTeamsBindings factory onto the Teams driver (the live Graph + ACS wiring seam)', () => {
|
|
19
|
-
const service = new TeamsMeetingsService(CONFIG, new TeamsAcsMediaRegistry());
|
|
20
|
-
const setSdkFactory = vi.fn();
|
|
21
|
-
const fakeDriver = { SetSdkFactory: setSdkFactory } as unknown as BaseRealtimeBridge;
|
|
22
|
-
// A minimal fake Graph client (only used as a constructor arg here).
|
|
23
|
-
const graphClient = { CreateCall: vi.fn() } as unknown as Parameters<typeof service.buildBindSdk>[0];
|
|
24
|
-
|
|
25
|
-
service.buildBindSdk(graphClient)(fakeDriver);
|
|
26
|
-
|
|
27
|
-
expect(setSdkFactory).toHaveBeenCalledTimes(1);
|
|
28
|
-
const factory = setSdkFactory.mock.calls[0][0] as () => unknown;
|
|
29
|
-
expect(typeof factory).toBe('function');
|
|
30
|
-
// The factory yields a real (bound) RealTeamsBindings — proving the offline seam meets live plumbing.
|
|
31
|
-
expect(factory()).toBeInstanceOf(RealTeamsBindings);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('TeamsMeetingsService.JoinMeetingByUrl', () => {
|
|
36
|
-
it('returns accepted=false (never throws) when the agent identity is missing', async () => {
|
|
37
|
-
const provider = {
|
|
38
|
-
GetEntityObject: vi.fn().mockResolvedValue({ Load: vi.fn().mockResolvedValue(false), IsActive: false }),
|
|
39
|
-
};
|
|
40
|
-
const service = new TeamsMeetingsService(CONFIG, new TeamsAcsMediaRegistry());
|
|
41
|
-
const result = await service.JoinMeetingByUrl(
|
|
42
|
-
'identity-1',
|
|
43
|
-
'https://teams.microsoft.com/l/meetup-join/x',
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal UserInfo stub for a pure-branch test
|
|
45
|
-
{ ID: 'u1' } as never,
|
|
46
|
-
provider as never,
|
|
47
|
-
);
|
|
48
|
-
expect(result.accepted).toBe(false);
|
|
49
|
-
expect(result.reason).toMatch(/not found or inactive/i);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('TeamsMeetingsService notification drive helpers', () => {
|
|
54
|
-
it('tears down the call + registry on call-ended (no throw when no client registered)', () => {
|
|
55
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
56
|
-
registry.RegisterCall('call-1');
|
|
57
|
-
expect(registry.HasCall('call-1')).toBe(true);
|
|
58
|
-
const service = new TeamsMeetingsService(CONFIG, registry);
|
|
59
|
-
|
|
60
|
-
service.DriveCallEnded('call-1');
|
|
61
|
-
expect(registry.HasCall('call-1')).toBe(false);
|
|
62
|
-
// An unknown roster drive is a silent no-op.
|
|
63
|
-
expect(() => service.DriveParticipantsUpdated('unknown', [{ id: 'p1' }])).not.toThrow();
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { TeamsAcsMediaRegistry, type IAcsMediaTransport } from '../telephony/teamsAcsMediaRegistry.js';
|
|
3
|
-
import type { AcsInboundAudioFrame } from '@memberjunction/ai-bridge-teams';
|
|
4
|
-
|
|
5
|
-
class FakeTransport implements IAcsMediaTransport {
|
|
6
|
-
public readonly Sent: ArrayBuffer[] = [];
|
|
7
|
-
public Closed = false;
|
|
8
|
-
public sendAudioFrame(pcm: ArrayBuffer): void {
|
|
9
|
-
this.Sent.push(pcm);
|
|
10
|
-
}
|
|
11
|
-
public close(): void {
|
|
12
|
-
this.Closed = true;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const frame = (bytes: number): ArrayBuffer => new ArrayBuffer(bytes);
|
|
17
|
-
|
|
18
|
-
describe('TeamsAcsMediaRegistry', () => {
|
|
19
|
-
it('exposes the configured ACS sample rate', () => {
|
|
20
|
-
expect(new TeamsAcsMediaRegistry().SampleRate).toBe(16000);
|
|
21
|
-
expect(new TeamsAcsMediaRegistry(8000).SampleRate).toBe(8000);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('buffers outbound PCM before the transport attaches, then flushes on attach', () => {
|
|
25
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
26
|
-
const a = frame(4);
|
|
27
|
-
const b = frame(8);
|
|
28
|
-
registry.Send('call-1', a);
|
|
29
|
-
registry.Send('call-1', b);
|
|
30
|
-
|
|
31
|
-
const transport = new FakeTransport();
|
|
32
|
-
registry.AttachTransport('call-1', transport);
|
|
33
|
-
|
|
34
|
-
expect(transport.Sent).toEqual([a, b]);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('sends immediately once the transport is attached', () => {
|
|
38
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
39
|
-
const transport = new FakeTransport();
|
|
40
|
-
registry.AttachTransport('call-1', transport);
|
|
41
|
-
const a = frame(4);
|
|
42
|
-
registry.Send('call-1', a);
|
|
43
|
-
expect(transport.Sent).toEqual([a]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('dispatches inbound frames to all registered handlers for the call', () => {
|
|
47
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
48
|
-
const seen: AcsInboundAudioFrame[] = [];
|
|
49
|
-
registry.OnFrame('call-1', (f) => seen.push(f));
|
|
50
|
-
registry.OnFrame('call-1', (f) => seen.push(f));
|
|
51
|
-
const f: AcsInboundAudioFrame = { Pcm: frame(2), ParticipantId: 'p1' };
|
|
52
|
-
registry.DispatchInbound('call-1', f);
|
|
53
|
-
expect(seen).toEqual([f, f]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('dispatches hand-raise signals to registered hand-raise handlers', () => {
|
|
57
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
58
|
-
let raised: boolean | undefined;
|
|
59
|
-
registry.OnHandRaise('call-1', (_p, r) => (raised = r));
|
|
60
|
-
registry.DispatchHandRaise('call-1', 'p1', true);
|
|
61
|
-
expect(raised).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('ignores inbound dispatch + hand-raise for unknown calls (no throw)', () => {
|
|
65
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
66
|
-
expect(() => registry.DispatchInbound('nope', { Pcm: frame(2), ParticipantId: 'p1' })).not.toThrow();
|
|
67
|
-
expect(() => registry.DispatchHandRaise('nope', 'p1', true)).not.toThrow();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('closes the transport and forgets the call on EndCall', () => {
|
|
71
|
-
const registry = new TeamsAcsMediaRegistry();
|
|
72
|
-
const transport = new FakeTransport();
|
|
73
|
-
registry.AttachTransport('call-1', transport);
|
|
74
|
-
expect(registry.HasCall('call-1')).toBe(true);
|
|
75
|
-
|
|
76
|
-
registry.EndCall('call-1');
|
|
77
|
-
expect(transport.Closed).toBe(true);
|
|
78
|
-
expect(registry.HasCall('call-1')).toBe(false);
|
|
79
|
-
// EndCall on an unknown call is a silent no-op.
|
|
80
|
-
expect(() => registry.EndCall('unknown')).not.toThrow();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { TwilioTelephonyService } from '../telephony/TwilioTelephonyService.js';
|
|
3
|
-
import { TwilioCallMediaRegistry } from '../telephony/twilioMediaRegistry.js';
|
|
4
|
-
import { TwilioCallSdk } from '@memberjunction/ai-bridge-twilio';
|
|
5
|
-
import type { BaseTelephonyBridge } from '@memberjunction/ai-bridge-base';
|
|
6
|
-
|
|
7
|
-
const CONFIG = { accountSid: 'AC1', authToken: 'tok', streamPublicUrl: 'wss://api.test/telephony/twilio/media' };
|
|
8
|
-
|
|
9
|
-
describe('TwilioTelephonyService.buildBindSdk', () => {
|
|
10
|
-
it('binds a TwilioCallSdk factory onto the telephony driver (the live REST + media wiring seam)', () => {
|
|
11
|
-
const service = new TwilioTelephonyService(CONFIG, new TwilioCallMediaRegistry());
|
|
12
|
-
const setSdkFactory = vi.fn();
|
|
13
|
-
const fakeDriver = { SetSdkFactory: setSdkFactory } as unknown as BaseTelephonyBridge;
|
|
14
|
-
|
|
15
|
-
service.buildBindSdk()(fakeDriver);
|
|
16
|
-
|
|
17
|
-
expect(setSdkFactory).toHaveBeenCalledTimes(1);
|
|
18
|
-
const factory = setSdkFactory.mock.calls[0][0] as () => unknown;
|
|
19
|
-
expect(typeof factory).toBe('function');
|
|
20
|
-
// The factory yields a real (bound) TwilioCallSdk — proving the offline seam meets live plumbing.
|
|
21
|
-
expect(factory()).toBeInstanceOf(TwilioCallSdk);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { TwilioCallMediaRegistry, type ITelephonyMediaSocket } from '../telephony/twilioMediaRegistry.js';
|
|
3
|
-
import type { TwilioMediaFrame } from '@memberjunction/ai-bridge-twilio';
|
|
4
|
-
|
|
5
|
-
/** A fake Media-Streams socket capturing sends + tracking close. */
|
|
6
|
-
function fakeSocket(): ITelephonyMediaSocket & { sent: string[]; closed: boolean } {
|
|
7
|
-
const sent: string[] = [];
|
|
8
|
-
return {
|
|
9
|
-
sent,
|
|
10
|
-
closed: false,
|
|
11
|
-
send(data: string) {
|
|
12
|
-
sent.push(data);
|
|
13
|
-
},
|
|
14
|
-
close() {
|
|
15
|
-
(this as { closed: boolean }).closed = true;
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const mediaFrame = (payload: string): TwilioMediaFrame => ({ event: 'media', streamSid: 'MZ1', media: { payload } });
|
|
21
|
-
|
|
22
|
-
describe('TwilioCallMediaRegistry', () => {
|
|
23
|
-
describe('outbound buffering', () => {
|
|
24
|
-
it('buffers outbound frames before the socket connects, then flushes on AttachSocket in order', () => {
|
|
25
|
-
const reg = new TwilioCallMediaRegistry();
|
|
26
|
-
reg.RegisterCall('CA1');
|
|
27
|
-
reg.Send('CA1', mediaFrame('a'));
|
|
28
|
-
reg.Send('CA1', mediaFrame('b'));
|
|
29
|
-
|
|
30
|
-
const sock = fakeSocket();
|
|
31
|
-
reg.AttachSocket('CA1', sock, 'MZ1');
|
|
32
|
-
|
|
33
|
-
expect(sock.sent).toHaveLength(2);
|
|
34
|
-
expect(JSON.parse(sock.sent[0]).media.payload).toBe('a');
|
|
35
|
-
expect(JSON.parse(sock.sent[1]).media.payload).toBe('b');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('sends immediately once the socket is attached', () => {
|
|
39
|
-
const reg = new TwilioCallMediaRegistry();
|
|
40
|
-
const sock = fakeSocket();
|
|
41
|
-
reg.AttachSocket('CA1', sock, 'MZ1');
|
|
42
|
-
reg.Send('CA1', mediaFrame('c'));
|
|
43
|
-
expect(sock.sent).toHaveLength(1);
|
|
44
|
-
expect(JSON.parse(sock.sent[0]).media.payload).toBe('c');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('inbound dispatch', () => {
|
|
49
|
-
it('delivers inbound frames to handlers registered before the socket existed', () => {
|
|
50
|
-
const reg = new TwilioCallMediaRegistry();
|
|
51
|
-
const received: TwilioMediaFrame[] = [];
|
|
52
|
-
reg.OnFrame('CA1', (f) => received.push(f));
|
|
53
|
-
|
|
54
|
-
reg.DispatchInbound('CA1', mediaFrame('in'));
|
|
55
|
-
|
|
56
|
-
expect(received).toHaveLength(1);
|
|
57
|
-
expect(received[0].media?.payload).toBe('in');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('captures the stream SID from a start frame and exposes it via GetStreamSid', () => {
|
|
61
|
-
const reg = new TwilioCallMediaRegistry();
|
|
62
|
-
reg.RegisterCall('CA1');
|
|
63
|
-
expect(reg.GetStreamSid('CA1')).toBe('');
|
|
64
|
-
reg.DispatchInbound('CA1', { event: 'start', streamSid: 'MZ-XYZ' });
|
|
65
|
-
expect(reg.GetStreamSid('CA1')).toBe('MZ-XYZ');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('drops inbound frames for an unknown call (no channel) without throwing', () => {
|
|
69
|
-
const reg = new TwilioCallMediaRegistry();
|
|
70
|
-
expect(() => reg.DispatchInbound('UNKNOWN', mediaFrame('x'))).not.toThrow();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('fans out to multiple handlers (audio + dtmf + status all registered on one call)', () => {
|
|
74
|
-
const reg = new TwilioCallMediaRegistry();
|
|
75
|
-
const h1 = vi.fn();
|
|
76
|
-
const h2 = vi.fn();
|
|
77
|
-
reg.OnFrame('CA1', h1);
|
|
78
|
-
reg.OnFrame('CA1', h2);
|
|
79
|
-
reg.DispatchInbound('CA1', mediaFrame('y'));
|
|
80
|
-
expect(h1).toHaveBeenCalledTimes(1);
|
|
81
|
-
expect(h2).toHaveBeenCalledTimes(1);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('lifecycle', () => {
|
|
86
|
-
it('EndCall closes the socket and forgets the channel', () => {
|
|
87
|
-
const reg = new TwilioCallMediaRegistry();
|
|
88
|
-
const sock = fakeSocket();
|
|
89
|
-
reg.AttachSocket('CA1', sock, 'MZ1');
|
|
90
|
-
expect(reg.HasCall('CA1')).toBe(true);
|
|
91
|
-
|
|
92
|
-
reg.EndCall('CA1');
|
|
93
|
-
|
|
94
|
-
expect(sock.closed).toBe(true);
|
|
95
|
-
expect(reg.HasCall('CA1')).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('EndCall on an unknown call is a no-op', () => {
|
|
99
|
-
const reg = new TwilioCallMediaRegistry();
|
|
100
|
-
expect(() => reg.EndCall('NOPE')).not.toThrow();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|