@absolutejs/voice 0.0.22-beta.657 → 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 +125 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39835,6 +39835,11 @@ var createVoiceMonitorRunnerRoutes = (options) => {
39835
39835
  };
39836
39836
 
39837
39837
  // src/core/plugin.ts
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
+ };
39838
39843
  var resolveQueryScenario = (query) => {
39839
39844
  if (typeof query?.scenarioId === "string" && query.scenarioId.trim()) {
39840
39845
  return query.scenarioId.trim();
@@ -39946,14 +39951,15 @@ var parseClientMessage = (raw) => {
39946
39951
  };
39947
39952
  var resolveSessionId3 = (runtime, ws) => {
39948
39953
  const query = ws.data && typeof ws.data === "object" && "query" in ws.data ? ws.data.query : undefined;
39949
- const existing = runtime.socketSessions.get(ws);
39954
+ const identity = socketIdentity(ws);
39955
+ const existing = runtime.socketSessions.get(identity);
39950
39956
  const providedSessionId = typeof query?.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : existing?.sessionId ?? createId();
39951
39957
  const scenarioId = resolveQueryScenario(query) ?? existing?.scenarioId ?? null;
39952
39958
  const resolved = {
39953
39959
  scenarioId,
39954
39960
  sessionId: providedSessionId
39955
39961
  };
39956
- runtime.socketSessions.set(ws, resolved);
39962
+ runtime.socketSessions.set(identity, resolved);
39957
39963
  return resolved;
39958
39964
  };
39959
39965
  var resolveSocketQuery = (ws) => ws.data && typeof ws.data === "object" && ("query" in ws.data) ? ws.data.query ?? {} : {};
@@ -40158,30 +40164,55 @@ var voice = (config) => {
40158
40164
  socketSessions: new WeakMap
40159
40165
  };
40160
40166
  const authorizedSockets = new WeakSet;
40161
- const authorizeSocket = async (ws, sessionId, scenarioId) => {
40162
- if (!config.authorizeConnection) {
40163
- authorizedSockets.add(ws);
40167
+ const closedSockets = new WeakSet;
40168
+ const pendingSocketAuthorizations = new WeakMap;
40169
+ const authorizeSocket = (ws, sessionId, scenarioId) => {
40170
+ const identity = socketIdentity(ws);
40171
+ const existing = pendingSocketAuthorizations.get(identity);
40172
+ if (existing)
40173
+ return existing;
40174
+ const authorization = (async () => {
40175
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40176
+ return false;
40177
+ if (!config.authorizeConnection) {
40178
+ authorizedSockets.add(identity);
40179
+ return true;
40180
+ }
40181
+ let authorized = false;
40182
+ try {
40183
+ authorized = await config.authorizeConnection({
40184
+ context: ws.data,
40185
+ headers: resolveSocketHeaders(ws),
40186
+ path: config.path,
40187
+ query: resolveSocketQuery(ws),
40188
+ scenarioId,
40189
+ sessionId
40190
+ });
40191
+ } catch (error) {
40192
+ runtime.logger.warn?.(`[voice] connection authorization failed for session "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40193
+ }
40194
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40195
+ return false;
40196
+ if (!authorized) {
40197
+ ws.close(4401, "unauthorized");
40198
+ return false;
40199
+ }
40200
+ authorizedSockets.add(identity);
40164
40201
  return true;
40165
- }
40166
- let authorized = false;
40167
- try {
40168
- authorized = await config.authorizeConnection({
40169
- context: ws.data,
40170
- headers: resolveSocketHeaders(ws),
40171
- path: config.path,
40172
- query: resolveSocketQuery(ws),
40173
- scenarioId,
40174
- sessionId
40175
- });
40176
- } catch (error) {
40177
- runtime.logger.warn?.(`[voice] connection authorization failed for session "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40178
- }
40179
- if (!authorized) {
40180
- ws.close(4401, "unauthorized");
40202
+ })();
40203
+ pendingSocketAuthorizations.set(identity, authorization);
40204
+ return authorization;
40205
+ };
40206
+ const waitForSocketAuthorization = async (ws) => {
40207
+ const identity = socketIdentity(ws);
40208
+ if (closedSockets.has(identity) || !socketIsOpen(ws))
40181
40209
  return false;
40182
- }
40183
- authorizedSockets.add(ws);
40184
- return true;
40210
+ if (authorizedSockets.has(identity))
40211
+ return true;
40212
+ const pending = pendingSocketAuthorizations.get(identity);
40213
+ if (!pending)
40214
+ return false;
40215
+ return await pending && !closedSockets.has(identity) && socketIsOpen(ws);
40185
40216
  };
40186
40217
  const { monitor } = config;
40187
40218
  const registerMonitorSession = (sessionId, handle) => {
@@ -40350,28 +40381,59 @@ var voice = (config) => {
40350
40381
  });
40351
40382
  };
40352
40383
  const createAndConnectSession = async (ws, sessionId, scenarioId) => {
40384
+ const identity = socketIdentity(ws);
40353
40385
  const session = await createManagedSession(ws, sessionId, scenarioId);
40354
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
+ }
40355
40409
  runtime.activeSessions.set(sessionId, typedSession);
40356
40410
  registerMonitorSession(sessionId, typedSession);
40357
- await session.connect(buildSocketAdapter(ws, sessionId));
40358
40411
  return typedSession;
40359
40412
  };
40360
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
+ }
40361
40418
  const active = runtime.activeSessions.get(sessionId);
40362
40419
  if (active) {
40363
40420
  return active;
40364
40421
  }
40365
40422
  const inFlight = runtime.pendingSessions.get(sessionId);
40366
- if (inFlight) {
40367
- return inFlight;
40423
+ if (inFlight?.identity === identity) {
40424
+ return inFlight.promise;
40368
40425
  }
40369
40426
  const creation = createAndConnectSession(ws, sessionId, scenarioId);
40370
- runtime.pendingSessions.set(sessionId, creation);
40427
+ runtime.pendingSessions.set(sessionId, {
40428
+ identity,
40429
+ promise: creation
40430
+ });
40371
40431
  try {
40372
40432
  return await creation;
40373
40433
  } finally {
40374
- runtime.pendingSessions.delete(sessionId);
40434
+ if (runtime.pendingSessions.get(sessionId)?.promise === creation) {
40435
+ runtime.pendingSessions.delete(sessionId);
40436
+ }
40375
40437
  }
40376
40438
  };
40377
40439
  const mountSurface = (app, value, factory) => {
@@ -40491,8 +40553,10 @@ var voice = (config) => {
40491
40553
  };
40492
40554
  return new Elysia70({ name: "absolutejs-voice" }).ws(config.path, {
40493
40555
  close: async (ws, code, reason) => {
40494
- const socketState = runtime.socketSessions.get(ws);
40495
- if (!socketState || runtime.activeSockets.get(socketState.sessionId) !== ws) {
40556
+ const identity = socketIdentity(ws);
40557
+ closedSockets.add(identity);
40558
+ const socketState = runtime.socketSessions.get(identity);
40559
+ if (!socketState || runtime.activeSockets.get(socketState.sessionId) !== identity) {
40496
40560
  return;
40497
40561
  }
40498
40562
  runtime.activeSockets.delete(socketState.sessionId);
@@ -40509,7 +40573,7 @@ var voice = (config) => {
40509
40573
  }
40510
40574
  },
40511
40575
  message: async (ws, raw) => {
40512
- if (!authorizedSockets.has(ws)) {
40576
+ if (!await waitForSocketAuthorization(ws)) {
40513
40577
  ws.close(4401, "unauthorized");
40514
40578
  return;
40515
40579
  }
@@ -40595,8 +40659,8 @@ var voice = (config) => {
40595
40659
  deregisterMonitorSession(sessionState.sessionId, "session-switch");
40596
40660
  }
40597
40661
  sessionState.sessionId = message.sessionId;
40598
- runtime.activeSockets.set(message.sessionId, ws);
40599
- runtime.socketSessions.set(ws, {
40662
+ runtime.activeSockets.set(message.sessionId, socketIdentity(ws));
40663
+ runtime.socketSessions.set(socketIdentity(ws), {
40600
40664
  ...sessionState,
40601
40665
  sessionId: message.sessionId,
40602
40666
  scenarioId: sessionState.scenarioId
@@ -40604,7 +40668,7 @@ var voice = (config) => {
40604
40668
  }
40605
40669
  if (message.type === "start" && message.scenarioId) {
40606
40670
  sessionState.scenarioId = message.scenarioId;
40607
- runtime.socketSessions.set(ws, {
40671
+ runtime.socketSessions.set(socketIdentity(ws), {
40608
40672
  ...sessionState,
40609
40673
  scenarioId: message.scenarioId
40610
40674
  });
@@ -40618,24 +40682,33 @@ var voice = (config) => {
40618
40682
  const session = current ?? await ensureManagedSession(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined);
40619
40683
  await session.receiveAudio(audio);
40620
40684
  },
40621
- open: async (ws) => {
40685
+ open: (ws) => {
40622
40686
  const sessionState = resolveSessionId3(runtime, ws);
40623
- if (!await authorizeSocket(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined)) {
40624
- return;
40625
- }
40626
- const existing = runtime.activeSessions.get(sessionState.sessionId);
40627
- runtime.activeSockets.set(sessionState.sessionId, ws);
40628
- if (existing) {
40629
- await existing.disconnect({
40630
- code: 1006,
40631
- reason: "superseded",
40632
- recoverable: true,
40633
- type: "close"
40634
- });
40635
- runtime.activeSessions.delete(sessionState.sessionId);
40636
- deregisterMonitorSession(sessionState.sessionId, "superseded");
40637
- }
40638
- 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
+ });
40639
40712
  }
40640
40713
  }).use(htmxRoutes()).use(surfaceRoutes());
40641
40714
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.657",
3
+ "version": "0.0.22-beta.659",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",