@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/CHANGELOG.md +19 -0
- package/dist/_shared/clientFanout.d.ts +1 -2
- package/dist/_shared/errorCodes.d.ts +1 -1
- package/dist/_shared/physicalReceiver.d.ts +4 -0
- package/dist/_shared/redactToken.d.ts +8 -0
- package/dist/_shared/streamEmitters.d.ts +6 -2
- package/dist/{chunk-AJ36D2PQ.js → chunk-IFVFF47D.js} +2 -1
- package/dist/{chunk-AJ36D2PQ.js.map → chunk-IFVFF47D.js.map} +1 -1
- package/dist/client.js +19 -3
- package/dist/client.js.map +1 -1
- package/dist/handleHttpSyncRequest.d.ts +6 -0
- package/dist/index.js +149 -116
- package/dist/index.js.map +1 -1
- package/docs/room-fanout.md +5 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SYNC_ERROR_CODES
|
|
3
|
-
} from "./chunk-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
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
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
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
|
|
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
|
|
1853
|
-
if (
|
|
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
|
}
|