@luckystack/sync 0.9.2 → 0.10.1
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 +16 -1
- package/CLAUDE.md +6 -3
- package/dist/chunk-AJ36D2PQ.js +35 -0
- package/dist/chunk-AJ36D2PQ.js.map +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +5 -0
- package/dist/client.js.map +1 -1
- package/dist/errorCodes-B8GoTenr.d.ts +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +73 -16
- package/dist/index.js.map +1 -1
- package/docs/error-states.md +9 -4
- package/docs/room-fanout.md +48 -24
- package/docs/server-vs-client-handlers.md +8 -6
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SYNC_ERROR_CODES
|
|
3
|
+
} from "./chunk-AJ36D2PQ.js";
|
|
4
|
+
|
|
1
5
|
// src/handleSyncRequest.ts
|
|
2
6
|
import { randomUUID } from "crypto";
|
|
3
7
|
import {
|
|
@@ -73,7 +77,7 @@ var waitUntilSocketDrained = async (socket, packetThreshold, isAborted) => {
|
|
|
73
77
|
}
|
|
74
78
|
};
|
|
75
79
|
var collectRoomSocketsForPressure = (receiver) => {
|
|
76
|
-
const io = getIoInstance();
|
|
80
|
+
const io = getIoInstance({ raw: true });
|
|
77
81
|
if (!io) return [];
|
|
78
82
|
if (!receiver) return [];
|
|
79
83
|
if (receiver === "all") {
|
|
@@ -893,22 +897,16 @@ async function runSyncServerExecution({
|
|
|
893
897
|
}
|
|
894
898
|
return { ok: true, serverOutput: serverSyncResult };
|
|
895
899
|
}
|
|
896
|
-
async function
|
|
900
|
+
async function resolveSyncRecipients({
|
|
897
901
|
ioInstance,
|
|
898
902
|
receiver,
|
|
899
903
|
resolvedName,
|
|
900
|
-
normalizedData,
|
|
901
|
-
serverOutput,
|
|
902
904
|
user,
|
|
903
905
|
socket,
|
|
904
906
|
responseIndex,
|
|
905
907
|
buildSyncError,
|
|
906
908
|
preferredLocale,
|
|
907
|
-
cleanupRequest
|
|
908
|
-
ignoreSelf,
|
|
909
|
-
token,
|
|
910
|
-
syncObject,
|
|
911
|
-
functionsObject
|
|
909
|
+
cleanupRequest
|
|
912
910
|
}) {
|
|
913
911
|
const physicalReceiver = receiver === "all" ? "all" : formatRoomName2(receiver, { purpose: "broadcast", userId: user?.id ?? null });
|
|
914
912
|
const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
|
|
@@ -918,8 +916,27 @@ async function runSyncFanout({
|
|
|
918
916
|
}
|
|
919
917
|
cleanupRequest();
|
|
920
918
|
rejectSync({ socket, responseIndex, buildSyncError, response: { status: "error", errorCode: "sync.noReceiversFound" }, preferred: preferredLocale, userLanguage: user?.language });
|
|
921
|
-
return
|
|
919
|
+
return null;
|
|
922
920
|
}
|
|
921
|
+
return sockets;
|
|
922
|
+
}
|
|
923
|
+
async function runSyncFanout({
|
|
924
|
+
sockets,
|
|
925
|
+
receiver,
|
|
926
|
+
resolvedName,
|
|
927
|
+
normalizedData,
|
|
928
|
+
serverOutput,
|
|
929
|
+
user,
|
|
930
|
+
socket,
|
|
931
|
+
responseIndex,
|
|
932
|
+
buildSyncError,
|
|
933
|
+
preferredLocale,
|
|
934
|
+
cleanupRequest,
|
|
935
|
+
ignoreSelf,
|
|
936
|
+
token,
|
|
937
|
+
syncObject,
|
|
938
|
+
functionsObject
|
|
939
|
+
}) {
|
|
923
940
|
const fanoutPayload = {
|
|
924
941
|
routeName: resolvedName,
|
|
925
942
|
data: normalizedData,
|
|
@@ -972,6 +989,7 @@ async function runSyncFanout({
|
|
|
972
989
|
routeName: resolvedName,
|
|
973
990
|
receiver,
|
|
974
991
|
recipientSocketId: tempSocket.id,
|
|
992
|
+
recipientToken: tempToken,
|
|
975
993
|
recipientUserId: null,
|
|
976
994
|
serverOutput
|
|
977
995
|
});
|
|
@@ -1123,6 +1141,18 @@ async function handleSyncRequestInner({ msg, socket, token }) {
|
|
|
1123
1141
|
return;
|
|
1124
1142
|
}
|
|
1125
1143
|
if (!await runPreAuthorizeHook({ resolvedName, normalizedData, user, receiver, socket, responseIndex, buildSyncError, preferredLocale, cleanupRequest })) return;
|
|
1144
|
+
const sockets = await resolveSyncRecipients({
|
|
1145
|
+
ioInstance,
|
|
1146
|
+
receiver,
|
|
1147
|
+
resolvedName,
|
|
1148
|
+
user,
|
|
1149
|
+
socket,
|
|
1150
|
+
responseIndex,
|
|
1151
|
+
buildSyncError,
|
|
1152
|
+
preferredLocale,
|
|
1153
|
+
cleanupRequest
|
|
1154
|
+
});
|
|
1155
|
+
if (sockets === null) return;
|
|
1126
1156
|
const executionResult = await runSyncServerExecution({
|
|
1127
1157
|
serverSyncEntry,
|
|
1128
1158
|
resolvedName,
|
|
@@ -1144,7 +1174,7 @@ async function handleSyncRequestInner({ msg, socket, token }) {
|
|
|
1144
1174
|
if (!executionResult.ok) return;
|
|
1145
1175
|
const { serverOutput } = executionResult;
|
|
1146
1176
|
const fanoutAcked = await runSyncFanout({
|
|
1147
|
-
|
|
1177
|
+
sockets,
|
|
1148
1178
|
receiver,
|
|
1149
1179
|
resolvedName,
|
|
1150
1180
|
normalizedData,
|
|
@@ -1608,9 +1638,33 @@ async function stageExecuteServer(resolvedName, serverSyncEntry, functionsObject
|
|
|
1608
1638
|
}
|
|
1609
1639
|
return { serverOutput: serverSyncResult };
|
|
1610
1640
|
}
|
|
1611
|
-
async function
|
|
1612
|
-
const {
|
|
1641
|
+
async function stageResolveRecipients(resolvedName, ctx) {
|
|
1642
|
+
const { normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
|
|
1613
1643
|
const ioInstance = getIoInstance3();
|
|
1644
|
+
const physicalReceiver = normalizedReceiver === "all" ? "all" : formatRoomName3(normalizedReceiver, { purpose: "broadcast", userId: user?.id ?? null });
|
|
1645
|
+
const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
|
|
1646
|
+
if (sockets.length === 0) {
|
|
1647
|
+
if (shouldLogDev()) {
|
|
1648
|
+
getLogger4().warn("http sync: no sockets found for receiver", { receiver: normalizedReceiver, sync: resolvedName, transport: "http" });
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
error: buildSyncError({
|
|
1652
|
+
response: { status: "error", errorCode: "sync.noReceiversFound", httpStatus: 404 },
|
|
1653
|
+
preferred: preferredLocale,
|
|
1654
|
+
userLanguage: user?.language
|
|
1655
|
+
})
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
return { sockets };
|
|
1659
|
+
}
|
|
1660
|
+
async function stageFanout(resolvedName, callbackName, serverOutput, sockets, syncObject, functionsObject, ctx) {
|
|
1661
|
+
const { data, normalizedReceiver, token, ignoreSelf, user, buildSyncError, preferredLocale } = ctx;
|
|
1662
|
+
if (shouldLogDev() && token && getProjectConfig4().sync.requireRoomMembership && normalizedReceiver !== "all" && normalizedReceiver !== token && !sockets.some((recipient) => extractTokenFromSocket2(recipient) === token)) {
|
|
1663
|
+
getLogger4().warn(
|
|
1664
|
+
`http sync: ${resolvedName} was authorized on the session's roomCodes, but the caller has no socket in "${normalizedReceiver}" \u2014 it will not receive this fan-out`,
|
|
1665
|
+
{ sync: resolvedName, receiver: normalizedReceiver, transport: "http" }
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1614
1668
|
const fanoutPayload = {
|
|
1615
1669
|
routeName: resolvedName,
|
|
1616
1670
|
data,
|
|
@@ -1632,8 +1686,6 @@ async function stageFanout(resolvedName, callbackName, serverOutput, syncObject,
|
|
|
1632
1686
|
userLanguage: user?.language
|
|
1633
1687
|
});
|
|
1634
1688
|
}
|
|
1635
|
-
const physicalReceiver = normalizedReceiver === "all" ? "all" : formatRoomName3(normalizedReceiver, { purpose: "broadcast", userId: user?.id ?? null });
|
|
1636
|
-
const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
|
|
1637
1689
|
let recipientCount = 0;
|
|
1638
1690
|
const { fanoutYieldEvery, fanoutYieldMs } = getProjectConfig4().sync;
|
|
1639
1691
|
const yieldEvery = Math.max(1, fanoutYieldEvery);
|
|
@@ -1651,6 +1703,7 @@ async function stageFanout(resolvedName, callbackName, serverOutput, syncObject,
|
|
|
1651
1703
|
routeName: resolvedName,
|
|
1652
1704
|
receiver: normalizedReceiver,
|
|
1653
1705
|
recipientSocketId: tempSocket.id,
|
|
1706
|
+
recipientToken: tempToken,
|
|
1654
1707
|
recipientUserId: null,
|
|
1655
1708
|
serverOutput
|
|
1656
1709
|
});
|
|
@@ -1785,6 +1838,9 @@ async function handleHttpSyncRequestScoped({
|
|
|
1785
1838
|
if (rateLimitResult) return rateLimitResult;
|
|
1786
1839
|
const authorizeHookError = await stageRunAuthorizeHooks(resolvedName, ctx);
|
|
1787
1840
|
if (authorizeHookError) return authorizeHookError;
|
|
1841
|
+
const recipientsResult = await stageResolveRecipients(resolvedName, ctx);
|
|
1842
|
+
if (recipientsResult.error) return recipientsResult.error;
|
|
1843
|
+
const { sockets } = recipientsResult;
|
|
1788
1844
|
let serverOutput = {};
|
|
1789
1845
|
if (serverSyncEntry) {
|
|
1790
1846
|
const validationError = await stageValidateInput(resolvedName, serverSyncEntry, ctx);
|
|
@@ -1793,7 +1849,7 @@ async function handleHttpSyncRequestScoped({
|
|
|
1793
1849
|
if (executeResult.error) return executeResult.error;
|
|
1794
1850
|
serverOutput = executeResult.serverOutput;
|
|
1795
1851
|
}
|
|
1796
|
-
const fanoutError = await stageFanout(resolvedName, callbackName, serverOutput, syncObject, functionsObject, ctx);
|
|
1852
|
+
const fanoutError = await stageFanout(resolvedName, callbackName, serverOutput, sockets, syncObject, functionsObject, ctx);
|
|
1797
1853
|
if (fanoutError) return fanoutError;
|
|
1798
1854
|
if (shouldLogDev()) {
|
|
1799
1855
|
getLogger4().debug(`http sync: ${resolvedName} completed`);
|
|
@@ -1872,6 +1928,7 @@ var createStreamThrottle = (options = {}) => {
|
|
|
1872
1928
|
};
|
|
1873
1929
|
};
|
|
1874
1930
|
export {
|
|
1931
|
+
SYNC_ERROR_CODES,
|
|
1875
1932
|
createStreamThrottle,
|
|
1876
1933
|
handleHttpSyncRequest,
|
|
1877
1934
|
handleSyncRequest
|