@kuralle-syrinx/server-websocket 3.1.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/admission-control.test.ts +23 -0
- package/src/background-audio.test.ts +172 -0
- package/src/background-audio.ts +263 -0
- package/src/browser-pacing.test.ts +1 -1
- package/src/edge-twilio.test.ts +79 -1
- package/src/edge-twilio.ts +77 -21
- package/src/edge.test.ts +139 -0
- package/src/edge.ts +44 -8
- package/src/inbound-audio.test.ts +58 -0
- package/src/inbound-audio.ts +9 -2
- package/src/index.test.ts +6 -3
- package/src/index.ts +84 -8
- package/src/outbound-playout-pipeline.test.ts +107 -1
- package/src/outbound-playout-pipeline.ts +55 -6
- package/src/smartpbx.ts +11 -1
- package/src/telnyx.ts +11 -1
- package/src/transport-host.ts +12 -1
- package/src/twilio-auth.test.ts +36 -0
- package/src/twilio-auth.ts +55 -0
- package/src/twilio.ts +11 -1
- package/src/websocket-upgrade.ts +20 -1
package/src/twilio.ts
CHANGED
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
} from "./json-message.js";
|
|
23
23
|
import { createRoutedWebSocketServer } from "./websocket-upgrade.js";
|
|
24
24
|
import { runWebSocketConnection, type GracefulCloseOptions, type TransportAdapter, type TransportHostConfig, TRANSPORT_ADMISSION_REJECTED_METRIC } from "./transport-host.js";
|
|
25
|
-
import {
|
|
25
|
+
import { BackgroundAudioMixer, type BackgroundAudioConfig } from "./background-audio.js";
|
|
26
|
+
import { wireTelephonyOutboundPipeline, installTelephonyTurnRotation } from "./outbound-playout-pipeline.js";
|
|
26
27
|
import {
|
|
27
28
|
decodeStrictBase64,
|
|
28
29
|
nonNegativeInteger,
|
|
@@ -45,6 +46,8 @@ export interface TwilioMediaStreamServerOptions {
|
|
|
45
46
|
readonly twilioSampleRateHz?: number;
|
|
46
47
|
readonly outboundFrameDurationMs?: number;
|
|
47
48
|
readonly maxQueuedOutputAudioMs?: number;
|
|
49
|
+
/** Ambient/thinking bed mixed (ducked) under assistant speech (see BackgroundAudioConfig). */
|
|
50
|
+
readonly backgroundAudio?: BackgroundAudioConfig;
|
|
48
51
|
readonly heartbeatIntervalMs?: number;
|
|
49
52
|
readonly startupTimeoutMs?: number;
|
|
50
53
|
readonly maxSessionDurationMs?: number;
|
|
@@ -90,6 +93,8 @@ interface TwilioMediaMessage {
|
|
|
90
93
|
interface TwilioConnectionState {
|
|
91
94
|
streamSid: string;
|
|
92
95
|
contextId: string;
|
|
96
|
+
contextBase: string;
|
|
97
|
+
turnCounter: number;
|
|
93
98
|
started: boolean;
|
|
94
99
|
stopped: boolean;
|
|
95
100
|
lastInboundSequenceNumber: number | null;
|
|
@@ -148,6 +153,8 @@ export async function createTwilioMediaStreamServer(
|
|
|
148
153
|
createState: () => ({
|
|
149
154
|
streamSid: "",
|
|
150
155
|
contextId: "",
|
|
156
|
+
contextBase: "",
|
|
157
|
+
turnCounter: 0,
|
|
151
158
|
started: false,
|
|
152
159
|
stopped: false,
|
|
153
160
|
lastInboundSequenceNumber: null,
|
|
@@ -196,6 +203,7 @@ export async function createTwilioMediaStreamServer(
|
|
|
196
203
|
disposers,
|
|
197
204
|
outboundFrameDurationMs,
|
|
198
205
|
maxQueuedOutputAudioMs,
|
|
206
|
+
...(options.backgroundAudio ? { backgroundAudio: new BackgroundAudioMixer(options.backgroundAudio) } : {}),
|
|
199
207
|
callbacks: {
|
|
200
208
|
carrierLabel: "twilio",
|
|
201
209
|
getContextId: () => state.contextId,
|
|
@@ -277,6 +285,7 @@ export async function createTwilioMediaStreamServer(
|
|
|
277
285
|
},
|
|
278
286
|
});
|
|
279
287
|
state.clearPlayout = outbound.clearPlayout;
|
|
288
|
+
installTelephonyTurnRotation(session, disposers, state);
|
|
280
289
|
gracefulCloseRegistry.set(socket, (deadlineMs) => outbound.drainAndClose(socket, deadlineMs));
|
|
281
290
|
disposers.push(() => gracefulCloseRegistry.delete(socket));
|
|
282
291
|
return (reason) => state.clearPlayout(reason);
|
|
@@ -296,6 +305,7 @@ export async function createTwilioMediaStreamServer(
|
|
|
296
305
|
state.streamSid = start.streamSid ?? message.streamSid ?? "";
|
|
297
306
|
if (!state.streamSid) throw new Error("Twilio start event is missing streamSid");
|
|
298
307
|
state.contextId = contextIdFn(start);
|
|
308
|
+
state.contextBase = state.contextId;
|
|
299
309
|
state.started = true;
|
|
300
310
|
return;
|
|
301
311
|
}
|
package/src/websocket-upgrade.ts
CHANGED
|
@@ -68,7 +68,7 @@ export function createRoutedWebSocketServer(
|
|
|
68
68
|
perMessageDeflate: false,
|
|
69
69
|
});
|
|
70
70
|
const router = getOrCreateRouter(httpServer);
|
|
71
|
-
const
|
|
71
|
+
const accept = (request: IncomingMessage, socket: Socket, head: Buffer): void => {
|
|
72
72
|
const maxConcurrentSessions = admission?.maxConcurrentSessions;
|
|
73
73
|
const scope = admission?.maxConcurrentSessionsScope ?? "path";
|
|
74
74
|
const activeSessions = scope === "server"
|
|
@@ -86,6 +86,25 @@ export function createRoutedWebSocketServer(
|
|
|
86
86
|
wsServer.emit("connection", websocket, request);
|
|
87
87
|
});
|
|
88
88
|
};
|
|
89
|
+
const handler: UpgradeHandler = (request, socket, head) => {
|
|
90
|
+
if (!admission?.authorize) {
|
|
91
|
+
accept(request, socket, head);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Authorize before completing the upgrade; reject with 4401 on failure/throw.
|
|
95
|
+
void Promise.resolve()
|
|
96
|
+
.then(() => admission.authorize!(request))
|
|
97
|
+
.then((ok) => {
|
|
98
|
+
if (ok) {
|
|
99
|
+
accept(request, socket, head);
|
|
100
|
+
} else {
|
|
101
|
+
rejectWebSocketAdmission(wsServer, request, socket, head, 4401, "unauthorized");
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
.catch(() => {
|
|
105
|
+
rejectWebSocketAdmission(wsServer, request, socket, head, 4401, "unauthorized");
|
|
106
|
+
});
|
|
107
|
+
};
|
|
89
108
|
router.handlers.set(path, handler);
|
|
90
109
|
router.servers.add(wsServer);
|
|
91
110
|
return {
|