@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/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 { wireTelephonyOutboundPipeline } from "./outbound-playout-pipeline.js";
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
  }
@@ -68,7 +68,7 @@ export function createRoutedWebSocketServer(
68
68
  perMessageDeflate: false,
69
69
  });
70
70
  const router = getOrCreateRouter(httpServer);
71
- const handler: UpgradeHandler = (request, socket, head) => {
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 {