@luckystack/sync 0.13.0 → 0.15.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SYNC_ERROR_CODES
3
- } from "./chunk-AJ36D2PQ.js";
3
+ } from "./chunk-IFVFF47D.js";
4
4
 
5
5
  // src/handleSyncRequest.ts
6
6
  import { randomUUID } from "crypto";
@@ -10,7 +10,6 @@ import {
10
10
  getRuntimeSyncMaps,
11
11
  validateRequest,
12
12
  extractTokenFromSocket,
13
- formatRoomName as formatRoomName2,
14
13
  getIoInstance as getIoInstance2,
15
14
  tryCatch as tryCatch2,
16
15
  parseTransportRouteName,
@@ -35,7 +34,6 @@ import {
35
34
  // src/_shared/streamEmitters.ts
36
35
  import {
37
36
  dispatchHook,
38
- formatRoomName,
39
37
  getIoInstance,
40
38
  getLogger,
41
39
  getProjectConfig as getProjectConfig2,
@@ -54,8 +52,20 @@ var redactToken = (token) => {
54
52
  if (token.length <= VISIBLE_PREFIX) return token;
55
53
  return `${token.slice(0, VISIBLE_PREFIX)}\u2026`;
56
54
  };
55
+ var redactTokenRoom = (receiver, knownTokens) => {
56
+ if (!knownTokens.some((token) => Boolean(token) && token === receiver)) return receiver;
57
+ return redactToken(receiver) ?? receiver;
58
+ };
57
59
  var redactTokens = (tokens) => tokens.map((t) => redactToken(t) ?? t);
58
60
 
61
+ // src/_shared/physicalReceiver.ts
62
+ import { formatRoomName } from "@luckystack/core";
63
+ var resolvePhysicalReceiver = (receiver, { token, userId }) => {
64
+ if (receiver === "all") return "all";
65
+ if (token && receiver === token) return receiver;
66
+ return formatRoomName(receiver, { purpose: "broadcast", userId });
67
+ };
68
+
59
69
  // src/_shared/streamEmitters.ts
60
70
  var DEFAULT_THRESHOLD_BYTES = 1048576;
61
71
  var AVG_PACKET_BYTES = 1024;
@@ -103,8 +113,8 @@ var collectRoomSocketsForPressure = (receiver) => {
103
113
  return out;
104
114
  };
105
115
  var buildSyncStreamEmitters = ({
106
- cb,
107
116
  receiver,
117
+ token,
108
118
  userId,
109
119
  resolvedName,
110
120
  emitOriginatorChunk,
@@ -112,7 +122,7 @@ var buildSyncStreamEmitters = ({
112
122
  signal,
113
123
  originatorSocket
114
124
  }) => {
115
- const physicalReceiver = !receiver || receiver === "all" ? receiver : formatRoomName(receiver, { purpose: "broadcast", userId: userId ?? null });
125
+ const physicalReceiver = receiver ? resolvePhysicalReceiver(receiver, { token, userId: userId ?? null }) : receiver;
116
126
  const chunkCounters = /* @__PURE__ */ new Map();
117
127
  const bumpChunkIndex = (recipient) => {
118
128
  const next = (chunkCounters.get(recipient) ?? 0) + 1;
@@ -126,7 +136,7 @@ var buildSyncStreamEmitters = ({
126
136
  };
127
137
  const buildBroadcastFrame = (payload) => ({
128
138
  ...payload,
129
- cb,
139
+ cb: resolvedName,
130
140
  fullName: resolvedName,
131
141
  status: "stream"
132
142
  });
@@ -159,7 +169,12 @@ var buildSyncStreamEmitters = ({
159
169
  const io = getIoInstance();
160
170
  if (!io) return;
161
171
  dispatchStreamHooks(receiver, payload);
162
- io.to(physicalReceiver).emit(socketEventNames.sync, buildBroadcastFrame(payload));
172
+ const frame = buildBroadcastFrame(payload);
173
+ if (physicalReceiver === "all") {
174
+ io.emit(socketEventNames.sync, frame);
175
+ return;
176
+ }
177
+ io.to(physicalReceiver).emit(socketEventNames.sync, frame);
163
178
  };
164
179
  const emitStreamToTokens = (tokens, payload = {}) => {
165
180
  if (isAborted()) {
@@ -254,7 +269,6 @@ var processClientSyncForRecipient = async ({
254
269
  serverOutput,
255
270
  receiver,
256
271
  resolvedName,
257
- callbackKey,
258
272
  transport,
259
273
  handlerName,
260
274
  logLabel,
@@ -270,7 +284,7 @@ var processClientSyncForRecipient = async ({
270
284
  }
271
285
  tempSocket.emit(socketEventNames2.sync, {
272
286
  ...payload,
273
- cb: callbackKey,
287
+ cb: resolvedName,
274
288
  fullName: resolvedName,
275
289
  status: "stream"
276
290
  });
@@ -288,14 +302,17 @@ var processClientSyncForRecipient = async ({
288
302
  //? a usable credential (defeats HttpOnly-cookie mode). The 4-char prefix
289
303
  //? still correlates error events.
290
304
  targetToken: redactToken(tempToken),
291
- receiver,
305
+ //? A token room's name IS the token: every socket in it carries that
306
+ //? token, so comparing against the recipient's token catches any token
307
+ //? room, not only the originator's own.
308
+ receiver: redactTokenRoom(receiver, [tempToken]),
292
309
  transport
293
310
  }
294
311
  );
295
312
  const preferred = resolvePreferredLocale(tempSocket.handshake.headers);
296
313
  if (clientSyncError) {
297
314
  tempSocket.emit(socketEventNames2.sync, {
298
- cb: callbackKey,
315
+ cb: resolvedName,
299
316
  fullName: resolvedName,
300
317
  ...buildSyncError({
301
318
  response: { status: "error", errorCode: "sync.clientExecutionFailed" },
@@ -306,7 +323,7 @@ var processClientSyncForRecipient = async ({
306
323
  }
307
324
  if (clientSyncResult?.status === "error") {
308
325
  tempSocket.emit(socketEventNames2.sync, {
309
- cb: callbackKey,
326
+ cb: resolvedName,
310
327
  fullName: resolvedName,
311
328
  ...buildSyncError({
312
329
  response: ensureSyncErrorShape(clientSyncResult),
@@ -317,7 +334,7 @@ var processClientSyncForRecipient = async ({
317
334
  }
318
335
  if (clientSyncResult?.status !== "success") {
319
336
  tempSocket.emit(socketEventNames2.sync, {
320
- cb: callbackKey,
337
+ cb: resolvedName,
321
338
  fullName: resolvedName,
322
339
  ...buildSyncError({
323
340
  response: { status: "error", errorCode: "sync.invalidClientResponse" },
@@ -327,7 +344,7 @@ var processClientSyncForRecipient = async ({
327
344
  return;
328
345
  }
329
346
  const result = {
330
- cb: callbackKey,
347
+ cb: resolvedName,
331
348
  fullName: resolvedName,
332
349
  serverOutput,
333
350
  clientOutput: clientSyncResult,
@@ -661,6 +678,7 @@ function runReceiverAuth({
661
678
  buildSyncError,
662
679
  preferredLocale,
663
680
  user,
681
+ token,
664
682
  cleanupRequest
665
683
  }) {
666
684
  const syncConfig = getProjectConfig3().sync;
@@ -674,9 +692,7 @@ function runReceiverAuth({
674
692
  //? NOT the raw client `receiver`. Otherwise a non-identity
675
693
  //? registerRoomNameFormatter (multi-tenant) makes every joined member fail the
676
694
  //? check on the socket transport while HTTP still works.
677
- isMember: () => socket.rooms.has(
678
- receiver === "all" ? "all" : formatRoomName2(receiver, { purpose: "broadcast", userId: user?.id ?? null })
679
- )
695
+ isMember: () => socket.rooms.has(resolvePhysicalReceiver(receiver, { token, userId: user?.id ?? null }))
680
696
  });
681
697
  if (!receiverAuth.allowed) {
682
698
  if (shouldLogDev()) {
@@ -750,7 +766,8 @@ async function runSyncServerExecution({
750
766
  emitBroadcastSyncStream,
751
767
  emitStreamToTokens,
752
768
  flushPressure,
753
- abortController
769
+ abortController,
770
+ token
754
771
  }) {
755
772
  if (!serverSyncEntry) return { ok: true, serverOutput: {} };
756
773
  const { main: serverMain, inputType, inputTypeFilePath } = serverSyncEntry;
@@ -856,7 +873,8 @@ async function runSyncServerExecution({
856
873
  sync: resolvedName,
857
874
  stage: "server",
858
875
  userId: user?.id,
859
- receiver,
876
+ //? A sync to your own token room carries the bearer token as `receiver`.
877
+ receiver: redactTokenRoom(receiver, [token]),
860
878
  transport: "socket"
861
879
  }
862
880
  );
@@ -902,13 +920,14 @@ async function resolveSyncRecipients({
902
920
  receiver,
903
921
  resolvedName,
904
922
  user,
923
+ token,
905
924
  socket,
906
925
  responseIndex,
907
926
  buildSyncError,
908
927
  preferredLocale,
909
928
  cleanupRequest
910
929
  }) {
911
- const physicalReceiver = receiver === "all" ? "all" : formatRoomName2(receiver, { purpose: "broadcast", userId: user?.id ?? null });
930
+ const physicalReceiver = resolvePhysicalReceiver(receiver, { token, userId: user?.id ?? null });
912
931
  const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
913
932
  if (sockets.length === 0) {
914
933
  if (shouldLogDev()) {
@@ -1008,14 +1027,10 @@ async function runSyncFanout({
1008
1027
  functionsObject,
1009
1028
  serverOutput: effectiveServerOutput,
1010
1029
  receiver,
1030
+ //? Recipient frames are keyed on `resolvedName` inside the helper (SYNC
1031
+ //? security: no client `cb` reaches another socket). The client `cb`
1032
+ //? stays only for the originator's own cancel handshake.
1011
1033
  resolvedName,
1012
- //? Server-authoritative callback key (SYNC security): route recipient
1013
- //? callbacks off the SERVER-resolved route, never the client-supplied
1014
- //? `cb`. Otherwise a room co-member could send a legit `name` but a `cb`
1015
- //? for a DIFFERENT route and fire other clients' handlers for that route
1016
- //? (cross-route callback spoofing). The client `cb` stays only for the
1017
- //? originator's own cancel handshake.
1018
- callbackKey: resolvedName,
1019
1034
  transport: "socket",
1020
1035
  handlerName: "handleSyncRequest",
1021
1036
  logLabel: "sync",
@@ -1110,8 +1125,8 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1110
1125
  const [handlerError] = await tryCatch2(async () => {
1111
1126
  if (!checkRouteExists({ syncObject, resolvedName, name, socket, responseIndex, buildSyncError, preferredLocale, user, cleanupRequest })) return;
1112
1127
  const { emitServerSyncStream, emitBroadcastSyncStream, emitStreamToTokens, flushPressure } = buildSyncStreamEmitters({
1113
- cb,
1114
1128
  receiver,
1129
+ token,
1115
1130
  userId: user?.id ?? null,
1116
1131
  resolvedName,
1117
1132
  logLabel: "sync",
@@ -1125,7 +1140,7 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1125
1140
  const serverSyncEntry = syncObject[`${resolvedName}_server`];
1126
1141
  currentPerRouteFormatter = serverSyncEntry?.errorFormatter;
1127
1142
  if (!runSyncAuth({ serverSyncEntry, user, resolvedName, socket, responseIndex, buildSyncError, preferredLocale, cleanupRequest })) return;
1128
- if (!runReceiverAuth({ receiver, resolvedName, socket, responseIndex, buildSyncError, preferredLocale, user, cleanupRequest })) return;
1143
+ if (!runReceiverAuth({ receiver, resolvedName, socket, responseIndex, buildSyncError, preferredLocale, user, token, cleanupRequest })) return;
1129
1144
  const rateLimitOk = await applySyncRateLimits({
1130
1145
  resolvedName,
1131
1146
  token,
@@ -1146,6 +1161,7 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1146
1161
  receiver,
1147
1162
  resolvedName,
1148
1163
  user,
1164
+ token,
1149
1165
  socket,
1150
1166
  responseIndex,
1151
1167
  buildSyncError,
@@ -1169,7 +1185,8 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1169
1185
  emitBroadcastSyncStream,
1170
1186
  emitStreamToTokens,
1171
1187
  flushPressure,
1172
- abortController
1188
+ abortController,
1189
+ token
1173
1190
  });
1174
1191
  if (!executionResult.ok) return;
1175
1192
  const { serverOutput } = executionResult;
@@ -1199,7 +1216,7 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1199
1216
  sync: currentRouteName,
1200
1217
  stage: "handler",
1201
1218
  userId: currentUserId,
1202
- receiver,
1219
+ receiver: redactTokenRoom(receiver, [token]),
1203
1220
  transport: "socket"
1204
1221
  });
1205
1222
  if (handlerError) {
@@ -1223,7 +1240,6 @@ import {
1223
1240
  getRuntimeSyncMaps as getRuntimeSyncMapsFromSource,
1224
1241
  validateRequest as validateRequest2,
1225
1242
  extractTokenFromSocket as extractTokenFromSocket2,
1226
- formatRoomName as formatRoomName3,
1227
1243
  getIoInstance as getIoInstance3,
1228
1244
  tryCatch as tryCatch3,
1229
1245
  parseTransportRouteName as parseTransportRouteName2,
@@ -1319,7 +1335,7 @@ var applyHttpSyncRateLimits = async ({
1319
1335
  return null;
1320
1336
  };
1321
1337
  async function stageResolveRoute(ctx) {
1322
- const { name, cb, data, normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
1338
+ const { name, data, normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
1323
1339
  if (!name || typeof name !== "string") {
1324
1340
  return {
1325
1341
  error: buildSyncError({
@@ -1353,7 +1369,6 @@ async function stageResolveRoute(ctx) {
1353
1369
  };
1354
1370
  }
1355
1371
  const resolvedName = parsedRoute.normalizedFullName;
1356
- const callbackName = typeof cb === "string" && cb.trim().length > 0 ? cb.trim() : `${parsedRoute.serviceRoute.normalizedRouteName}/${parsedRoute.version}`;
1357
1372
  if (!normalizedReceiver) {
1358
1373
  return {
1359
1374
  error: buildSyncError({
@@ -1385,7 +1400,7 @@ async function stageResolveRoute(ctx) {
1385
1400
  })
1386
1401
  };
1387
1402
  }
1388
- return { resolvedName, callbackName };
1403
+ return { resolvedName };
1389
1404
  }
1390
1405
  function stageCheckAuth(serverSyncEntry, ctx) {
1391
1406
  const { user, preferredLocale, buildSyncError } = ctx;
@@ -1538,11 +1553,11 @@ async function stageValidateInput(resolvedName, serverSyncEntry, ctx) {
1538
1553
  return null;
1539
1554
  }
1540
1555
  async function stageExecuteServer(resolvedName, serverSyncEntry, functionsObject, ctx) {
1541
- const { data, normalizedReceiver, user, preferredLocale, buildSyncError, effectiveAbortSignal, cb, stream } = ctx;
1556
+ const { data, normalizedReceiver, user, preferredLocale, buildSyncError, effectiveAbortSignal, token, stream } = ctx;
1542
1557
  const { main: serverMain } = serverSyncEntry;
1543
1558
  const { emitServerSyncStream, emitBroadcastSyncStream, emitStreamToTokens, flushPressure } = buildSyncStreamEmitters({
1544
- cb,
1545
1559
  receiver: normalizedReceiver,
1560
+ token,
1546
1561
  userId: user?.id ?? null,
1547
1562
  resolvedName,
1548
1563
  logLabel: "http sync",
@@ -1601,7 +1616,8 @@ async function stageExecuteServer(resolvedName, serverSyncEntry, functionsObject
1601
1616
  userId: user?.id,
1602
1617
  //? Bug fix: was `name` (the raw route-name string) — should be
1603
1618
  //? `normalizedReceiver` to match the field's semantics and the socket handler.
1604
- receiver: normalizedReceiver,
1619
+ //? A sync to your own token room carries the bearer token as `receiver`.
1620
+ receiver: redactTokenRoom(normalizedReceiver, [token]),
1605
1621
  transport: "http"
1606
1622
  }
1607
1623
  );
@@ -1639,9 +1655,9 @@ async function stageExecuteServer(resolvedName, serverSyncEntry, functionsObject
1639
1655
  return { serverOutput: serverSyncResult };
1640
1656
  }
1641
1657
  async function stageResolveRecipients(resolvedName, ctx) {
1642
- const { normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
1658
+ const { normalizedReceiver, token, user, buildSyncError, preferredLocale } = ctx;
1643
1659
  const ioInstance = getIoInstance3();
1644
- const physicalReceiver = normalizedReceiver === "all" ? "all" : formatRoomName3(normalizedReceiver, { purpose: "broadcast", userId: user?.id ?? null });
1660
+ const physicalReceiver = resolvePhysicalReceiver(normalizedReceiver, { token, userId: user?.id ?? null });
1645
1661
  const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
1646
1662
  if (sockets.length === 0) {
1647
1663
  if (shouldLogDev()) {
@@ -1657,7 +1673,7 @@ async function stageResolveRecipients(resolvedName, ctx) {
1657
1673
  }
1658
1674
  return { sockets };
1659
1675
  }
1660
- async function stageFanout(resolvedName, callbackName, serverOutput, sockets, syncObject, functionsObject, ctx) {
1676
+ async function stageFanout(resolvedName, serverOutput, sockets, syncObject, functionsObject, ctx) {
1661
1677
  const { data, normalizedReceiver, token, ignoreSelf, user, buildSyncError, preferredLocale } = ctx;
1662
1678
  if (shouldLogDev() && token && getProjectConfig4().sync.requireRoomMembership && normalizedReceiver !== "all" && normalizedReceiver !== token && !sockets.some((recipient) => extractTokenFromSocket2(recipient) === token)) {
1663
1679
  getLogger4().warn(
@@ -1676,87 +1692,91 @@ async function stageFanout(resolvedName, callbackName, serverOutput, sockets, sy
1676
1692
  };
1677
1693
  const preFanoutResult = await dispatchHook3("preSyncFanout", fanoutPayload);
1678
1694
  if (preFanoutResult.stopped) {
1679
- return buildSyncError({
1680
- response: {
1681
- status: "error",
1682
- errorCode: preFanoutResult.signal.errorCode,
1683
- httpStatus: preFanoutResult.signal.httpStatus
1684
- },
1685
- preferred: preferredLocale,
1686
- userLanguage: user?.language
1687
- });
1695
+ return {
1696
+ error: buildSyncError({
1697
+ response: {
1698
+ status: "error",
1699
+ errorCode: preFanoutResult.signal.errorCode,
1700
+ httpStatus: preFanoutResult.signal.httpStatus
1701
+ },
1702
+ preferred: preferredLocale,
1703
+ userLanguage: user?.language
1704
+ })
1705
+ };
1688
1706
  }
1689
- let recipientCount = 0;
1690
- const { fanoutYieldEvery, fanoutYieldMs } = getProjectConfig4().sync;
1691
- const yieldEvery = Math.max(1, fanoutYieldEvery);
1692
- let fanoutIterCount = 1;
1693
- for (const tempSocket of sockets) {
1694
- fanoutIterCount++;
1695
- if (fanoutIterCount % yieldEvery === 0) {
1696
- await new Promise((resolve) => setTimeout(resolve, fanoutYieldMs));
1697
- }
1698
- const tempToken = extractTokenFromSocket2(tempSocket);
1699
- if (ignoreSelf && typeof ignoreSelf === "boolean" && token && token === tempToken) {
1700
- continue;
1701
- }
1702
- const preRecipientResult = await dispatchHook3("preSyncRecipient", {
1703
- routeName: resolvedName,
1704
- receiver: normalizedReceiver,
1705
- recipientSocketId: tempSocket.id,
1706
- recipientToken: tempToken,
1707
- recipientUserId: null,
1708
- serverOutput
1709
- });
1710
- if (preRecipientResult.stopped && preRecipientResult.signal.overrideOutput === void 0) {
1711
- continue;
1712
- }
1713
- const effectiveServerOutput = preRecipientResult.stopped ? preRecipientResult.signal.overrideOutput : serverOutput;
1714
- recipientCount++;
1715
- if (syncObject[`${resolvedName}_client`]) {
1716
- const clientSyncHandler = syncObject[`${resolvedName}_client`];
1717
- await processClientSyncForRecipient({
1718
- tempSocket,
1719
- tempToken,
1720
- clientSyncHandler,
1721
- data,
1722
- functionsObject,
1723
- serverOutput: effectiveServerOutput,
1707
+ const deliver = async () => {
1708
+ let recipientCount = 0;
1709
+ const { fanoutYieldEvery, fanoutYieldMs } = getProjectConfig4().sync;
1710
+ const yieldEvery = Math.max(1, fanoutYieldEvery);
1711
+ let fanoutIterCount = 1;
1712
+ for (const tempSocket of sockets) {
1713
+ fanoutIterCount++;
1714
+ if (fanoutIterCount % yieldEvery === 0) {
1715
+ await new Promise((resolve) => setTimeout(resolve, fanoutYieldMs));
1716
+ }
1717
+ const tempToken = extractTokenFromSocket2(tempSocket);
1718
+ if (ignoreSelf && typeof ignoreSelf === "boolean" && token && token === tempToken) {
1719
+ continue;
1720
+ }
1721
+ const preRecipientResult = await dispatchHook3("preSyncRecipient", {
1722
+ routeName: resolvedName,
1724
1723
  receiver: normalizedReceiver,
1725
- resolvedName,
1726
- callbackKey: callbackName,
1727
- transport: "http",
1728
- handlerName: "handleHttpSyncRequest",
1729
- logLabel: "http sync",
1730
- shouldLogDev,
1731
- shouldLogStream,
1732
- buildSyncError,
1733
- //? HTTP transport prefers `accept-language` over `x-language`
1734
- //? (reversed vs. the socket handler) — preserved verbatim from the
1735
- //? previous inlined error path.
1736
- resolvePreferredLocale: (headers) => extractLanguageFromHeader2(headers["accept-language"] || headers["x-language"]),
1737
- sourceUserId: user?.id
1724
+ recipientSocketId: tempSocket.id,
1725
+ recipientToken: tempToken,
1726
+ recipientUserId: null,
1727
+ serverOutput
1728
+ });
1729
+ if (preRecipientResult.stopped && preRecipientResult.signal.overrideOutput === void 0) {
1730
+ continue;
1731
+ }
1732
+ const effectiveServerOutput = preRecipientResult.stopped ? preRecipientResult.signal.overrideOutput : serverOutput;
1733
+ recipientCount++;
1734
+ if (syncObject[`${resolvedName}_client`]) {
1735
+ const clientSyncHandler = syncObject[`${resolvedName}_client`];
1736
+ await processClientSyncForRecipient({
1737
+ tempSocket,
1738
+ tempToken,
1739
+ clientSyncHandler,
1740
+ data,
1741
+ functionsObject,
1742
+ serverOutput: effectiveServerOutput,
1743
+ receiver: normalizedReceiver,
1744
+ resolvedName,
1745
+ transport: "http",
1746
+ handlerName: "handleHttpSyncRequest",
1747
+ logLabel: "http sync",
1748
+ shouldLogDev,
1749
+ shouldLogStream,
1750
+ buildSyncError,
1751
+ //? HTTP transport prefers `accept-language` over `x-language`
1752
+ //? (reversed vs. the socket handler) — preserved verbatim from the
1753
+ //? previous inlined error path.
1754
+ resolvePreferredLocale: (headers) => extractLanguageFromHeader2(headers["accept-language"] || headers["x-language"]),
1755
+ sourceUserId: user?.id
1756
+ });
1757
+ continue;
1758
+ }
1759
+ tempSocket.emit(socketEventNames4.sync, {
1760
+ //? Server-resolved route, never the client `cb` (parity with the socket
1761
+ //? handler): recipients fire callbacks for both `fullName` and `cb`.
1762
+ cb: resolvedName,
1763
+ fullName: resolvedName,
1764
+ serverOutput: effectiveServerOutput,
1765
+ clientOutput: {},
1766
+ message: `${resolvedName} sync success`,
1767
+ status: "success"
1738
1768
  });
1739
- continue;
1740
1769
  }
1741
- tempSocket.emit(socketEventNames4.sync, {
1742
- cb: callbackName,
1743
- fullName: resolvedName,
1744
- serverOutput: effectiveServerOutput,
1745
- clientOutput: {},
1746
- message: `${resolvedName} sync success`,
1747
- status: "success"
1748
- });
1749
- }
1750
- fanoutPayload.recipientCount = recipientCount;
1751
- await dispatchHook3("postSyncFanout", fanoutPayload);
1752
- return null;
1770
+ fanoutPayload.recipientCount = recipientCount;
1771
+ await dispatchHook3("postSyncFanout", fanoutPayload);
1772
+ };
1773
+ return { deliver };
1753
1774
  }
1754
1775
  async function handleHttpSyncRequest(args) {
1755
1776
  return runWithErrorTrackerIdentityScope2(() => handleHttpSyncRequestScoped(args));
1756
1777
  }
1757
1778
  async function handleHttpSyncRequestScoped({
1758
1779
  name,
1759
- cb,
1760
1780
  data,
1761
1781
  receiver,
1762
1782
  ignoreSelf,
@@ -1801,7 +1821,6 @@ async function handleHttpSyncRequestScoped({
1801
1821
  }
1802
1822
  const ctx = {
1803
1823
  name,
1804
- cb,
1805
1824
  data,
1806
1825
  normalizedReceiver,
1807
1826
  ignoreSelf,
@@ -1815,7 +1834,7 @@ async function handleHttpSyncRequestScoped({
1815
1834
  };
1816
1835
  const routeResult = await stageResolveRoute(ctx);
1817
1836
  if (routeResult.error) return routeResult.error;
1818
- const { resolvedName, callbackName } = routeResult;
1837
+ const { resolvedName } = routeResult;
1819
1838
  currentRouteName = resolvedName;
1820
1839
  const { syncObject, functionsObject } = await getRuntimeSyncMapsFromSource();
1821
1840
  const serverSyncEntry = syncObject[`${resolvedName}_server`];
@@ -1849,8 +1868,22 @@ async function handleHttpSyncRequestScoped({
1849
1868
  if (executeResult.error) return executeResult.error;
1850
1869
  serverOutput = executeResult.serverOutput;
1851
1870
  }
1852
- const fanoutError = await stageFanout(resolvedName, callbackName, serverOutput, sockets, syncObject, functionsObject, ctx);
1853
- if (fanoutError) return fanoutError;
1871
+ const fanout = await stageFanout(resolvedName, serverOutput, sockets, syncObject, functionsObject, ctx);
1872
+ if (fanout.error) return fanout.error;
1873
+ void tryCatch3(fanout.deliver, void 0, {
1874
+ handler: "handleHttpSyncRequest",
1875
+ sync: resolvedName,
1876
+ stage: "fanout",
1877
+ userId: user?.id,
1878
+ receiver: redactTokenRoom(normalizedReceiver, [token]),
1879
+ transport: "http"
1880
+ }).then(([fanoutError]) => {
1881
+ if (fanoutError) {
1882
+ getLogger4().error(`http sync: ${resolvedName} fan-out failed after the originator was answered`, fanoutError, { sync: resolvedName, transport: "http" });
1883
+ } else if (shouldLogDev()) {
1884
+ getLogger4().debug(`http sync: ${resolvedName} fan-out completed`);
1885
+ }
1886
+ });
1854
1887
  if (shouldLogDev()) {
1855
1888
  getLogger4().debug(`http sync: ${resolvedName} completed`);
1856
1889
  }