@absolutejs/voice 0.0.22-beta.658 → 0.0.22-beta.659

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 (2) hide show
  1. package/dist/index.js +75 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39836,6 +39836,10 @@ var createVoiceMonitorRunnerRoutes = (options) => {
39836
39836
 
39837
39837
  // src/core/plugin.ts
39838
39838
  var socketIdentity = (ws) => ws.raw && typeof ws.raw === "object" ? ws.raw : ws;
39839
+ var socketIsOpen = (ws) => {
39840
+ const identity = socketIdentity(ws);
39841
+ return identity.readyState === undefined || identity.readyState === 1;
39842
+ };
39839
39843
  var resolveQueryScenario = (query) => {
39840
39844
  if (typeof query?.scenarioId === "string" && query.scenarioId.trim()) {
39841
39845
  return query.scenarioId.trim();
@@ -40160,6 +40164,7 @@ var voice = (config) => {
40160
40164
  socketSessions: new WeakMap
40161
40165
  };
40162
40166
  const authorizedSockets = new WeakSet;
40167
+ const closedSockets = new WeakSet;
40163
40168
  const pendingSocketAuthorizations = new WeakMap;
40164
40169
  const authorizeSocket = (ws, sessionId, scenarioId) => {
40165
40170
  const identity = socketIdentity(ws);
@@ -40167,6 +40172,8 @@ var voice = (config) => {
40167
40172
  if (existing)
40168
40173
  return existing;
40169
40174
  const authorization = (async () => {
40175
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40176
+ return false;
40170
40177
  if (!config.authorizeConnection) {
40171
40178
  authorizedSockets.add(identity);
40172
40179
  return true;
@@ -40184,6 +40191,8 @@ var voice = (config) => {
40184
40191
  } catch (error) {
40185
40192
  runtime.logger.warn?.(`[voice] connection authorization failed for session "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40186
40193
  }
40194
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40195
+ return false;
40187
40196
  if (!authorized) {
40188
40197
  ws.close(4401, "unauthorized");
40189
40198
  return false;
@@ -40196,12 +40205,14 @@ var voice = (config) => {
40196
40205
  };
40197
40206
  const waitForSocketAuthorization = async (ws) => {
40198
40207
  const identity = socketIdentity(ws);
40208
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40209
+ return false;
40199
40210
  if (authorizedSockets.has(identity))
40200
40211
  return true;
40201
40212
  const pending = pendingSocketAuthorizations.get(identity);
40202
40213
  if (!pending)
40203
40214
  return false;
40204
- return pending;
40215
+ return await pending && !closedSockets.has(identity) && socketIsOpen(ws);
40205
40216
  };
40206
40217
  const { monitor } = config;
40207
40218
  const registerMonitorSession = (sessionId, handle) => {
@@ -40370,28 +40381,59 @@ var voice = (config) => {
40370
40381
  });
40371
40382
  };
40372
40383
  const createAndConnectSession = async (ws, sessionId, scenarioId) => {
40384
+ const identity = socketIdentity(ws);
40373
40385
  const session = await createManagedSession(ws, sessionId, scenarioId);
40374
40386
  const typedSession = session;
40387
+ if (closedSockets.has(identity) || !socketIsOpen(ws) || runtime.activeSockets.get(sessionId) !== identity) {
40388
+ ws.close(1000, "superseded");
40389
+ throw new Error("Voice socket was superseded during session creation");
40390
+ }
40391
+ try {
40392
+ await session.connect(buildSocketAdapter(ws, sessionId));
40393
+ } catch (error) {
40394
+ runtime.logger.warn?.(`[voice] session initialization failed for "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40395
+ closedSockets.add(identity);
40396
+ ws.close(1011, "voice session initialization failed");
40397
+ throw error;
40398
+ }
40399
+ if (closedSockets.has(identity) || !socketIsOpen(ws) || runtime.activeSockets.get(sessionId) !== identity) {
40400
+ await session.disconnect({
40401
+ code: 1000,
40402
+ reason: "superseded",
40403
+ recoverable: true,
40404
+ type: "close"
40405
+ });
40406
+ ws.close(1000, "superseded");
40407
+ throw new Error("Voice socket was superseded during session connection");
40408
+ }
40375
40409
  runtime.activeSessions.set(sessionId, typedSession);
40376
40410
  registerMonitorSession(sessionId, typedSession);
40377
- await session.connect(buildSocketAdapter(ws, sessionId));
40378
40411
  return typedSession;
40379
40412
  };
40380
40413
  const ensureManagedSession = async (ws, sessionId, scenarioId) => {
40414
+ const identity = socketIdentity(ws);
40415
+ if (closedSockets.has(identity) || !socketIsOpen(ws) || runtime.activeSockets.get(sessionId) !== identity) {
40416
+ throw new Error("Voice socket is no longer active");
40417
+ }
40381
40418
  const active = runtime.activeSessions.get(sessionId);
40382
40419
  if (active) {
40383
40420
  return active;
40384
40421
  }
40385
40422
  const inFlight = runtime.pendingSessions.get(sessionId);
40386
- if (inFlight) {
40387
- return inFlight;
40423
+ if (inFlight?.identity === identity) {
40424
+ return inFlight.promise;
40388
40425
  }
40389
40426
  const creation = createAndConnectSession(ws, sessionId, scenarioId);
40390
- runtime.pendingSessions.set(sessionId, creation);
40427
+ runtime.pendingSessions.set(sessionId, {
40428
+ identity,
40429
+ promise: creation
40430
+ });
40391
40431
  try {
40392
40432
  return await creation;
40393
40433
  } finally {
40394
- runtime.pendingSessions.delete(sessionId);
40434
+ if (runtime.pendingSessions.get(sessionId)?.promise === creation) {
40435
+ runtime.pendingSessions.delete(sessionId);
40436
+ }
40395
40437
  }
40396
40438
  };
40397
40439
  const mountSurface = (app, value, factory) => {
@@ -40512,6 +40554,7 @@ var voice = (config) => {
40512
40554
  return new Elysia70({ name: "absolutejs-voice" }).ws(config.path, {
40513
40555
  close: async (ws, code, reason) => {
40514
40556
  const identity = socketIdentity(ws);
40557
+ closedSockets.add(identity);
40515
40558
  const socketState = runtime.socketSessions.get(identity);
40516
40559
  if (!socketState || runtime.activeSockets.get(socketState.sessionId) !== identity) {
40517
40560
  return;
@@ -40639,24 +40682,33 @@ var voice = (config) => {
40639
40682
  const session = current ?? await ensureManagedSession(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined);
40640
40683
  await session.receiveAudio(audio);
40641
40684
  },
40642
- open: async (ws) => {
40685
+ open: (ws) => {
40643
40686
  const sessionState = resolveSessionId3(runtime, ws);
40644
- if (!await authorizeSocket(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined)) {
40645
- return;
40646
- }
40647
- const existing = runtime.activeSessions.get(sessionState.sessionId);
40648
- runtime.activeSockets.set(sessionState.sessionId, socketIdentity(ws));
40649
- if (existing) {
40650
- await existing.disconnect({
40651
- code: 1006,
40652
- reason: "superseded",
40653
- recoverable: true,
40654
- type: "close"
40655
- });
40656
- runtime.activeSessions.delete(sessionState.sessionId);
40657
- deregisterMonitorSession(sessionState.sessionId, "superseded");
40658
- }
40659
- await ensureManagedSession(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined);
40687
+ (async () => {
40688
+ if (!await authorizeSocket(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined)) {
40689
+ return;
40690
+ }
40691
+ const identity = socketIdentity(ws);
40692
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40693
+ return;
40694
+ const existing = runtime.activeSessions.get(sessionState.sessionId);
40695
+ runtime.activeSockets.set(sessionState.sessionId, identity);
40696
+ if (existing) {
40697
+ await existing.disconnect({
40698
+ code: 1006,
40699
+ reason: "superseded",
40700
+ recoverable: true,
40701
+ type: "close"
40702
+ });
40703
+ runtime.activeSessions.delete(sessionState.sessionId);
40704
+ deregisterMonitorSession(sessionState.sessionId, "superseded");
40705
+ }
40706
+ await ensureManagedSession(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined);
40707
+ })().catch((error) => {
40708
+ if (closedSockets.has(socketIdentity(ws)))
40709
+ return;
40710
+ runtime.logger.warn?.(`[voice] session initialization failed for "${sessionState.sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40711
+ });
40660
40712
  }
40661
40713
  }).use(htmxRoutes()).use(surfaceRoutes());
40662
40714
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.658",
3
+ "version": "0.0.22-beta.659",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",