deepspace 0.4.4 → 0.5.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/README.md +90 -0
- package/dist/cli.js +2912 -1984
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +31 -4
- package/dist/index.js +511 -366
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +29 -108
- package/dist/server.js +126 -589
- package/dist/server.js.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +29 -108
- package/dist/worker.js +126 -589
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +3 -5
- package/features/canvas/src/CanvasEditorPage.tsx +0 -2
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/LibrarySidebar.tsx +7 -1
- package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
- package/features/file-attachments/src/useFilePreview.ts +7 -5
- package/features/file-manager/src/FileManagerPage.tsx +2 -3
- package/features/integration-test/feature.json +10 -3
- package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
- package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
- package/features/messaging/src/hooks/useChatChannel.ts +5 -1
- package/features/presence-test/feature.json +2 -1
- package/features/sidebar/src/AppSidebar.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/testing/feature.json +2 -1
- package/features/testing/src/TestPage.tsx +8 -8
- package/package.json +4 -4
- package/scripts/add-feature.cjs +277 -272
package/dist/index.js
CHANGED
|
@@ -697,6 +697,22 @@ async function signOut(...args) {
|
|
|
697
697
|
}
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
+
// src/client/debug.ts
|
|
701
|
+
function readDebugFlag() {
|
|
702
|
+
try {
|
|
703
|
+
const g = globalThis;
|
|
704
|
+
if (g.DEEPSPACE_DEBUG === true || g.DEEPSPACE_DEBUG === "1") return true;
|
|
705
|
+
const ls = g.localStorage?.getItem("DEEPSPACE_DEBUG");
|
|
706
|
+
return ls === "1" || ls === "true";
|
|
707
|
+
} catch {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
var DEBUG = readDebugFlag();
|
|
712
|
+
function debugLog(...args) {
|
|
713
|
+
if (DEBUG) console.log(...args);
|
|
714
|
+
}
|
|
715
|
+
|
|
700
716
|
// src/client/auth/DeepSpaceAuthProvider.tsx
|
|
701
717
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
702
718
|
function AuthLogger() {
|
|
@@ -707,9 +723,9 @@ function AuthLogger() {
|
|
|
707
723
|
if (session.isPending) return;
|
|
708
724
|
if (prevSignedIn.current !== isSignedIn) {
|
|
709
725
|
if (isSignedIn) {
|
|
710
|
-
|
|
726
|
+
debugLog(`[ds:auth] signed in as ${session.data?.user?.name ?? session.data?.user?.id}`);
|
|
711
727
|
} else if (prevSignedIn.current !== null) {
|
|
712
|
-
|
|
728
|
+
debugLog("[ds:auth] signed out");
|
|
713
729
|
}
|
|
714
730
|
prevSignedIn.current = isSignedIn;
|
|
715
731
|
}
|
|
@@ -787,7 +803,7 @@ function AuthOverlay({
|
|
|
787
803
|
setLoading(false);
|
|
788
804
|
}
|
|
789
805
|
} catch (err) {
|
|
790
|
-
setError(err.message
|
|
806
|
+
setError(err instanceof Error ? err.message : "Something went wrong");
|
|
791
807
|
setLoading(false);
|
|
792
808
|
}
|
|
793
809
|
};
|
|
@@ -1128,6 +1144,7 @@ import {
|
|
|
1128
1144
|
|
|
1129
1145
|
// src/client/storage/store.ts
|
|
1130
1146
|
var EMPTY_RECORDS = [];
|
|
1147
|
+
var EMPTY_SNAPSHOT = { status: "loading", records: EMPTY_RECORDS };
|
|
1131
1148
|
var RecordStore = class {
|
|
1132
1149
|
queries = /* @__PURE__ */ new Map();
|
|
1133
1150
|
listeners = /* @__PURE__ */ new Map();
|
|
@@ -1146,16 +1163,13 @@ var RecordStore = class {
|
|
|
1146
1163
|
}
|
|
1147
1164
|
};
|
|
1148
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Snapshot for `useSyncExternalStore`. Bundles records + status + error so a
|
|
1168
|
+
* transition in any of them re-renders the hook. Returns a stable reference
|
|
1169
|
+
* until the next `commit()`.
|
|
1170
|
+
*/
|
|
1149
1171
|
getSnapshot(queryKey) {
|
|
1150
|
-
|
|
1151
|
-
if (!state) return EMPTY_RECORDS;
|
|
1152
|
-
return state.records;
|
|
1153
|
-
}
|
|
1154
|
-
getStatus(queryKey) {
|
|
1155
|
-
return this.queries.get(queryKey)?.status ?? "loading";
|
|
1156
|
-
}
|
|
1157
|
-
getError(queryKey) {
|
|
1158
|
-
return this.queries.get(queryKey)?.error;
|
|
1172
|
+
return this.queries.get(queryKey)?.snapshot ?? EMPTY_SNAPSHOT;
|
|
1159
1173
|
}
|
|
1160
1174
|
/**
|
|
1161
1175
|
* Initialize a query subscription.
|
|
@@ -1165,7 +1179,10 @@ var RecordStore = class {
|
|
|
1165
1179
|
const prevCount = this.refCounts.get(queryKey) ?? 0;
|
|
1166
1180
|
this.refCounts.set(queryKey, prevCount + 1);
|
|
1167
1181
|
if (!this.queries.has(queryKey)) {
|
|
1168
|
-
this.queries.set(queryKey, {
|
|
1182
|
+
this.queries.set(queryKey, {
|
|
1183
|
+
version: 0,
|
|
1184
|
+
snapshot: { status: "loading", records: EMPTY_RECORDS }
|
|
1185
|
+
});
|
|
1169
1186
|
}
|
|
1170
1187
|
return prevCount === 0;
|
|
1171
1188
|
}
|
|
@@ -1191,46 +1208,49 @@ var RecordStore = class {
|
|
|
1191
1208
|
getSubscriptionId(queryKey) {
|
|
1192
1209
|
return this.activeSubscriptions.get(queryKey);
|
|
1193
1210
|
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Replace the state for a query with a fresh snapshot and bump the version,
|
|
1213
|
+
* then notify listeners. Single write-path so every mutator mints a new
|
|
1214
|
+
* snapshot reference (which is what drives `useSyncExternalStore` re-renders).
|
|
1215
|
+
*/
|
|
1216
|
+
commit(queryKey, snapshot) {
|
|
1217
|
+
const version = (this.queries.get(queryKey)?.version ?? 0) + 1;
|
|
1218
|
+
this.queries.set(queryKey, { version, snapshot });
|
|
1219
|
+
this.notify(queryKey);
|
|
1220
|
+
}
|
|
1194
1221
|
// Handle QUERY_RESULT from server
|
|
1195
1222
|
setQueryResult(queryKey, records) {
|
|
1196
|
-
|
|
1197
|
-
this.queries.set(queryKey, {
|
|
1198
|
-
status: "ready",
|
|
1199
|
-
records,
|
|
1200
|
-
// New array reference
|
|
1201
|
-
version: (current?.version ?? 0) + 1
|
|
1202
|
-
});
|
|
1203
|
-
this.notify(queryKey);
|
|
1223
|
+
this.commit(queryKey, { status: "ready", records });
|
|
1204
1224
|
}
|
|
1205
1225
|
/** Check if a record exists in a query's results */
|
|
1206
1226
|
hasRecord(queryKey, recordId) {
|
|
1207
1227
|
const state = this.queries.get(queryKey);
|
|
1208
1228
|
if (!state) return false;
|
|
1209
|
-
return state.records.some((r) => r.recordId === recordId);
|
|
1229
|
+
return state.snapshot.records.some((r) => r.recordId === recordId);
|
|
1210
1230
|
}
|
|
1211
1231
|
// Handle RECORD_CHANGE from server
|
|
1212
1232
|
applyChange(queryKey, record, changeType) {
|
|
1213
1233
|
const state = this.queries.get(queryKey);
|
|
1214
1234
|
if (!state) return;
|
|
1235
|
+
const records = state.snapshot.records;
|
|
1215
1236
|
let newRecords;
|
|
1216
1237
|
if (changeType === "delete") {
|
|
1217
|
-
newRecords =
|
|
1238
|
+
newRecords = records.filter((r) => r.recordId !== record.recordId);
|
|
1218
1239
|
} else if (changeType === "create") {
|
|
1219
|
-
const exists =
|
|
1240
|
+
const exists = records.some((r) => r.recordId === record.recordId);
|
|
1220
1241
|
if (exists) {
|
|
1221
|
-
newRecords =
|
|
1242
|
+
newRecords = records.map((r) => r.recordId === record.recordId ? record : r);
|
|
1222
1243
|
} else {
|
|
1223
|
-
newRecords = [...
|
|
1244
|
+
newRecords = [...records, record];
|
|
1224
1245
|
}
|
|
1225
1246
|
} else {
|
|
1226
|
-
newRecords =
|
|
1247
|
+
newRecords = records.map((r) => r.recordId === record.recordId ? record : r);
|
|
1227
1248
|
}
|
|
1228
|
-
this.
|
|
1229
|
-
|
|
1249
|
+
this.commit(queryKey, {
|
|
1250
|
+
status: state.snapshot.status,
|
|
1230
1251
|
records: newRecords,
|
|
1231
|
-
|
|
1252
|
+
error: state.snapshot.error
|
|
1232
1253
|
});
|
|
1233
|
-
this.notify(queryKey);
|
|
1234
1254
|
}
|
|
1235
1255
|
/**
|
|
1236
1256
|
* Reset a query to loading state (used on WebSocket reconnect).
|
|
@@ -1239,22 +1259,11 @@ var RecordStore = class {
|
|
|
1239
1259
|
resetToLoading(queryKey) {
|
|
1240
1260
|
const current = this.queries.get(queryKey);
|
|
1241
1261
|
if (!current) return;
|
|
1242
|
-
this.
|
|
1243
|
-
...current,
|
|
1244
|
-
status: "loading",
|
|
1245
|
-
version: current.version + 1
|
|
1246
|
-
});
|
|
1247
|
-
this.notify(queryKey);
|
|
1262
|
+
this.commit(queryKey, { status: "loading", records: current.snapshot.records });
|
|
1248
1263
|
}
|
|
1249
1264
|
setError(queryKey, error) {
|
|
1250
|
-
const
|
|
1251
|
-
this.
|
|
1252
|
-
status: "error",
|
|
1253
|
-
records: current?.records ?? [],
|
|
1254
|
-
version: (current?.version ?? 0) + 1,
|
|
1255
|
-
error
|
|
1256
|
-
});
|
|
1257
|
-
this.notify(queryKey);
|
|
1265
|
+
const records = this.queries.get(queryKey)?.snapshot.records ?? EMPTY_RECORDS;
|
|
1266
|
+
this.commit(queryKey, { status: "error", records, error });
|
|
1258
1267
|
}
|
|
1259
1268
|
// Cleanup when query is no longer needed
|
|
1260
1269
|
removeQuery(queryKey) {
|
|
@@ -1421,7 +1430,6 @@ function recordMatchesWhere(record, where) {
|
|
|
1421
1430
|
}
|
|
1422
1431
|
function RecordProviderCore({
|
|
1423
1432
|
roomId,
|
|
1424
|
-
schemas,
|
|
1425
1433
|
wsUrl,
|
|
1426
1434
|
children,
|
|
1427
1435
|
fetchUser,
|
|
@@ -1435,8 +1443,12 @@ function RecordProviderCore({
|
|
|
1435
1443
|
const [usersLoaded, setUsersLoaded] = useState3(false);
|
|
1436
1444
|
const [status, setStatus] = useState3("connecting");
|
|
1437
1445
|
const [ready, setReady] = useState3(false);
|
|
1438
|
-
const onPermissionErrorRef = useRef4(
|
|
1439
|
-
|
|
1446
|
+
const onPermissionErrorRef = useRef4(
|
|
1447
|
+
void 0
|
|
1448
|
+
);
|
|
1449
|
+
const onValidationErrorRef = useRef4(
|
|
1450
|
+
void 0
|
|
1451
|
+
);
|
|
1440
1452
|
const storeRef = useRef4(new RecordStore());
|
|
1441
1453
|
const wsRef = useRef4(null);
|
|
1442
1454
|
const subscriptionMapRef = useRef4(/* @__PURE__ */ new Map());
|
|
@@ -1445,6 +1457,7 @@ function RecordProviderCore({
|
|
|
1445
1457
|
const pendingRequestsRef = useRef4(/* @__PURE__ */ new Map());
|
|
1446
1458
|
const reconnectAttemptRef = useRef4(0);
|
|
1447
1459
|
const reconnectTimeoutRef = useRef4(null);
|
|
1460
|
+
const connectTokenRef = useRef4(0);
|
|
1448
1461
|
const connectedWithProfileRef = useRef4(false);
|
|
1449
1462
|
const userProfileRef = useRef4(null);
|
|
1450
1463
|
userProfileRef.current = userProfile;
|
|
@@ -1465,9 +1478,10 @@ function RecordProviderCore({
|
|
|
1465
1478
|
if (mounted) setUserProfileLoading(false);
|
|
1466
1479
|
});
|
|
1467
1480
|
const interval = setInterval(() => {
|
|
1468
|
-
if (mounted)
|
|
1469
|
-
|
|
1470
|
-
|
|
1481
|
+
if (mounted)
|
|
1482
|
+
fetchUser().then((p) => {
|
|
1483
|
+
if (mounted) setUserProfile(p);
|
|
1484
|
+
});
|
|
1471
1485
|
}, 3e4);
|
|
1472
1486
|
return () => {
|
|
1473
1487
|
mounted = false;
|
|
@@ -1485,7 +1499,7 @@ function RecordProviderCore({
|
|
|
1485
1499
|
switch (type) {
|
|
1486
1500
|
case MSG.USER_INFO: {
|
|
1487
1501
|
const serverUser = payload;
|
|
1488
|
-
|
|
1502
|
+
debugLog("[WS] Ready:", { roomId, role: serverUser.role });
|
|
1489
1503
|
setRoomRole(serverUser.role);
|
|
1490
1504
|
setReady(true);
|
|
1491
1505
|
reconnectAttemptRef.current = 0;
|
|
@@ -1511,7 +1525,7 @@ function RecordProviderCore({
|
|
|
1511
1525
|
}
|
|
1512
1526
|
case MSG.RECORD_CHANGE: {
|
|
1513
1527
|
const { collection, record, changeType } = payload;
|
|
1514
|
-
for (const
|
|
1528
|
+
for (const queryKey of subscriptionMapRef.current.values()) {
|
|
1515
1529
|
try {
|
|
1516
1530
|
const query = JSON.parse(queryKey);
|
|
1517
1531
|
if (query.collection !== collection) continue;
|
|
@@ -1523,8 +1537,10 @@ function RecordProviderCore({
|
|
|
1523
1537
|
if (matches) storeRef.current.applyChange(queryKey, record, "create");
|
|
1524
1538
|
} else {
|
|
1525
1539
|
if (matches && exists) storeRef.current.applyChange(queryKey, record, "update");
|
|
1526
|
-
else if (matches && !exists)
|
|
1527
|
-
|
|
1540
|
+
else if (matches && !exists)
|
|
1541
|
+
storeRef.current.applyChange(queryKey, record, "create");
|
|
1542
|
+
else if (!matches && exists)
|
|
1543
|
+
storeRef.current.applyChange(queryKey, record, "delete");
|
|
1528
1544
|
}
|
|
1529
1545
|
} catch {
|
|
1530
1546
|
}
|
|
@@ -1585,13 +1601,13 @@ function RecordProviderCore({
|
|
|
1585
1601
|
} catch (e) {
|
|
1586
1602
|
console.error("[RecordProvider] Failed to parse message:", e);
|
|
1587
1603
|
}
|
|
1588
|
-
}, []);
|
|
1604
|
+
}, [roomId]);
|
|
1589
1605
|
const connect = useCallback2(async () => {
|
|
1590
1606
|
const profile = userProfileRef.current;
|
|
1591
1607
|
if (!profile && !allowAnonymous) return;
|
|
1592
1608
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1593
1609
|
if (profile && !connectedWithProfileRef.current) {
|
|
1594
|
-
|
|
1610
|
+
debugLog(`[WS] Reconnecting with profile data`, { roomId });
|
|
1595
1611
|
wsRef.current.onclose = null;
|
|
1596
1612
|
wsRef.current.close();
|
|
1597
1613
|
wsRef.current = null;
|
|
@@ -1604,6 +1620,7 @@ function RecordProviderCore({
|
|
|
1604
1620
|
clearTimeout(reconnectTimeoutRef.current);
|
|
1605
1621
|
reconnectTimeoutRef.current = null;
|
|
1606
1622
|
}
|
|
1623
|
+
const myToken = ++connectTokenRef.current;
|
|
1607
1624
|
let baseUrl;
|
|
1608
1625
|
if (wsUrl) {
|
|
1609
1626
|
baseUrl = wsUrl.replace(/^http/, "ws");
|
|
@@ -1618,30 +1635,36 @@ function RecordProviderCore({
|
|
|
1618
1635
|
if (token) params.set("token", token);
|
|
1619
1636
|
} catch {
|
|
1620
1637
|
}
|
|
1638
|
+
if (connectTokenRef.current !== myToken) return;
|
|
1621
1639
|
connectedWithProfileRef.current = !!profile;
|
|
1622
1640
|
const wsPath = `/ws/${roomId}`;
|
|
1623
1641
|
const url = `${baseUrl}${wsPath}?${params.toString()}`;
|
|
1624
|
-
|
|
1642
|
+
debugLog("[WS] Connecting:", { roomId, attempt: reconnectAttemptRef.current });
|
|
1625
1643
|
const ws = new WebSocket(url);
|
|
1626
1644
|
ws.binaryType = "arraybuffer";
|
|
1627
1645
|
ws.onopen = () => {
|
|
1628
|
-
|
|
1646
|
+
debugLog("[WS] Connected:", { roomId });
|
|
1629
1647
|
setStatus("connected");
|
|
1630
1648
|
};
|
|
1631
1649
|
ws.onmessage = handleMessage;
|
|
1632
1650
|
ws.onclose = (event) => {
|
|
1633
|
-
|
|
1651
|
+
debugLog("[WS] Closed:", {
|
|
1652
|
+
roomId,
|
|
1653
|
+
code: event.code,
|
|
1654
|
+
reason: event.reason,
|
|
1655
|
+
wasClean: event.wasClean
|
|
1656
|
+
});
|
|
1634
1657
|
setStatus("disconnected");
|
|
1635
1658
|
setReady(false);
|
|
1636
1659
|
wsRef.current = null;
|
|
1637
|
-
for (const
|
|
1660
|
+
for (const pending of pendingRequestsRef.current.values()) {
|
|
1638
1661
|
clearTimeout(pending.timer);
|
|
1639
1662
|
pending.reject(new Error("WebSocket disconnected"));
|
|
1640
1663
|
}
|
|
1641
1664
|
pendingRequestsRef.current.clear();
|
|
1642
1665
|
const attempt = reconnectAttemptRef.current;
|
|
1643
1666
|
const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
1644
|
-
|
|
1667
|
+
debugLog("[WS] Scheduling reconnect:", { roomId, attempt, delayMs: delay });
|
|
1645
1668
|
reconnectAttemptRef.current = attempt + 1;
|
|
1646
1669
|
reconnectTimeoutRef.current = setTimeout(connect, delay);
|
|
1647
1670
|
};
|
|
@@ -1659,7 +1682,7 @@ function RecordProviderCore({
|
|
|
1659
1682
|
if (document.visibilityState === "visible") {
|
|
1660
1683
|
const ws = wsRef.current;
|
|
1661
1684
|
if (ws?.readyState !== WebSocket.OPEN && (userProfileRef.current || allowAnonymous)) {
|
|
1662
|
-
|
|
1685
|
+
debugLog("[WS] Reconnecting after visibility change");
|
|
1663
1686
|
reconnectAttemptRef.current = 0;
|
|
1664
1687
|
connect();
|
|
1665
1688
|
}
|
|
@@ -1667,12 +1690,20 @@ function RecordProviderCore({
|
|
|
1667
1690
|
};
|
|
1668
1691
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1669
1692
|
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1670
|
-
}, [roomId, connect]);
|
|
1693
|
+
}, [roomId, connect, allowAnonymous]);
|
|
1671
1694
|
useEffect4(() => {
|
|
1672
1695
|
return () => {
|
|
1673
|
-
|
|
1696
|
+
debugLog("[WS] Unmounting provider:", { roomId });
|
|
1674
1697
|
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
|
1675
|
-
|
|
1698
|
+
connectTokenRef.current++;
|
|
1699
|
+
const ws = wsRef.current;
|
|
1700
|
+
if (ws) {
|
|
1701
|
+
ws.onclose = null;
|
|
1702
|
+
ws.onmessage = null;
|
|
1703
|
+
ws.onerror = null;
|
|
1704
|
+
ws.close();
|
|
1705
|
+
wsRef.current = null;
|
|
1706
|
+
}
|
|
1676
1707
|
};
|
|
1677
1708
|
}, [roomId]);
|
|
1678
1709
|
const sendMessage = useCallback2((message) => {
|
|
@@ -1680,9 +1711,12 @@ function RecordProviderCore({
|
|
|
1680
1711
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(message));
|
|
1681
1712
|
else console.warn("[RecordProvider] Cannot send message, WebSocket not connected");
|
|
1682
1713
|
}, []);
|
|
1683
|
-
const setUserRole = useCallback2(
|
|
1684
|
-
|
|
1685
|
-
|
|
1714
|
+
const setUserRole = useCallback2(
|
|
1715
|
+
(userId, role) => {
|
|
1716
|
+
sendMessage({ type: MSG.SET_ROLE, payload: { userId, role } });
|
|
1717
|
+
},
|
|
1718
|
+
[sendMessage]
|
|
1719
|
+
);
|
|
1686
1720
|
const requestUserList = useCallback2(() => {
|
|
1687
1721
|
sendMessage({ type: MSG.USER_LIST, payload: {} });
|
|
1688
1722
|
}, [sendMessage]);
|
|
@@ -1702,70 +1736,109 @@ function RecordProviderCore({
|
|
|
1702
1736
|
binaryHandlersRef.current.delete(handler);
|
|
1703
1737
|
};
|
|
1704
1738
|
}, []);
|
|
1705
|
-
const registerYjsJoinHandler = useCallback2(
|
|
1706
|
-
|
|
1707
|
-
yjsJoinHandlersRef.current.
|
|
1708
|
-
|
|
1709
|
-
yjsJoinHandlersRef.current.get(docKey).add(handler);
|
|
1710
|
-
return () => {
|
|
1711
|
-
const handlers = yjsJoinHandlersRef.current.get(docKey);
|
|
1712
|
-
if (handlers) {
|
|
1713
|
-
handlers.delete(handler);
|
|
1714
|
-
if (handlers.size === 0) yjsJoinHandlersRef.current.delete(docKey);
|
|
1739
|
+
const registerYjsJoinHandler = useCallback2(
|
|
1740
|
+
(docKey, handler) => {
|
|
1741
|
+
if (!yjsJoinHandlersRef.current.has(docKey)) {
|
|
1742
|
+
yjsJoinHandlersRef.current.set(docKey, /* @__PURE__ */ new Set());
|
|
1715
1743
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
ws
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1744
|
+
yjsJoinHandlersRef.current.get(docKey).add(handler);
|
|
1745
|
+
return () => {
|
|
1746
|
+
const handlers = yjsJoinHandlersRef.current.get(docKey);
|
|
1747
|
+
if (handlers) {
|
|
1748
|
+
handlers.delete(handler);
|
|
1749
|
+
if (handlers.size === 0) yjsJoinHandlersRef.current.delete(docKey);
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
},
|
|
1753
|
+
[]
|
|
1754
|
+
);
|
|
1755
|
+
const sendConfirmed = useCallback2(
|
|
1756
|
+
(message, timeoutMs = 1e4) => {
|
|
1757
|
+
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1758
|
+
const ws = wsRef.current;
|
|
1759
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1760
|
+
return Promise.reject(new Error("WebSocket not connected"));
|
|
1761
|
+
}
|
|
1762
|
+
return new Promise((resolve, reject) => {
|
|
1763
|
+
const timer = setTimeout(() => {
|
|
1764
|
+
pendingRequestsRef.current.delete(requestId);
|
|
1765
|
+
reject(new Error("Mutation confirmation timed out"));
|
|
1766
|
+
}, timeoutMs);
|
|
1767
|
+
pendingRequestsRef.current.set(requestId, { resolve, reject, timer });
|
|
1768
|
+
ws.send(JSON.stringify({ ...message, payload: { ...message.payload, requestId } }));
|
|
1769
|
+
});
|
|
1770
|
+
},
|
|
1771
|
+
[]
|
|
1772
|
+
);
|
|
1773
|
+
const value = useMemo2(
|
|
1774
|
+
() => ({
|
|
1775
|
+
store: storeRef.current,
|
|
1776
|
+
roomId,
|
|
1777
|
+
userProfile,
|
|
1778
|
+
userProfileLoading,
|
|
1779
|
+
refetchUserProfile,
|
|
1780
|
+
roomRole,
|
|
1781
|
+
allUsers,
|
|
1782
|
+
usersLoaded,
|
|
1783
|
+
status,
|
|
1784
|
+
ready,
|
|
1785
|
+
setUserRole,
|
|
1786
|
+
requestUserList,
|
|
1787
|
+
registerSubscription,
|
|
1788
|
+
unregisterSubscription,
|
|
1789
|
+
sendMessage,
|
|
1790
|
+
sendBinary,
|
|
1791
|
+
onBinaryMessage,
|
|
1792
|
+
registerYjsJoinHandler,
|
|
1793
|
+
sendConfirmed
|
|
1794
|
+
}),
|
|
1795
|
+
[
|
|
1796
|
+
roomId,
|
|
1797
|
+
userProfile,
|
|
1798
|
+
userProfileLoading,
|
|
1799
|
+
refetchUserProfile,
|
|
1800
|
+
roomRole,
|
|
1801
|
+
allUsers,
|
|
1802
|
+
usersLoaded,
|
|
1803
|
+
status,
|
|
1804
|
+
ready,
|
|
1805
|
+
setUserRole,
|
|
1806
|
+
requestUserList,
|
|
1807
|
+
registerSubscription,
|
|
1808
|
+
unregisterSubscription,
|
|
1809
|
+
sendMessage,
|
|
1810
|
+
sendBinary,
|
|
1811
|
+
onBinaryMessage,
|
|
1812
|
+
registerYjsJoinHandler,
|
|
1813
|
+
sendConfirmed
|
|
1814
|
+
]
|
|
1815
|
+
);
|
|
1754
1816
|
const onPermissionError = useCallback2((title, detail) => {
|
|
1755
1817
|
onPermissionErrorRef.current?.(title, detail);
|
|
1756
1818
|
}, []);
|
|
1757
1819
|
const onValidationError = useCallback2((title, detail) => {
|
|
1758
1820
|
onValidationErrorRef.current?.(title, detail);
|
|
1759
1821
|
}, []);
|
|
1760
|
-
const authValue = useMemo2(
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1822
|
+
const authValue = useMemo2(
|
|
1823
|
+
() => ({
|
|
1824
|
+
userProfile,
|
|
1825
|
+
userProfileLoading,
|
|
1826
|
+
refetchUserProfile,
|
|
1827
|
+
allowAnonymous,
|
|
1828
|
+
getAuthToken: getAuthTokenProp,
|
|
1829
|
+
onPermissionError,
|
|
1830
|
+
onValidationError
|
|
1831
|
+
}),
|
|
1832
|
+
[
|
|
1833
|
+
userProfile,
|
|
1834
|
+
userProfileLoading,
|
|
1835
|
+
refetchUserProfile,
|
|
1836
|
+
allowAnonymous,
|
|
1837
|
+
getAuthTokenProp,
|
|
1838
|
+
onPermissionError,
|
|
1839
|
+
onValidationError
|
|
1840
|
+
]
|
|
1841
|
+
);
|
|
1769
1842
|
return /* @__PURE__ */ jsx6(RecordAuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx6(ScopeRegistryProvider, { children: /* @__PURE__ */ jsx6(RecordContext.Provider, { value, children }) }) });
|
|
1770
1843
|
}
|
|
1771
1844
|
function RecordProviderAuthOnly({
|
|
@@ -1804,18 +1877,20 @@ function RecordProviderAuthOnly({
|
|
|
1804
1877
|
clearInterval(interval);
|
|
1805
1878
|
};
|
|
1806
1879
|
}, [fetchUser]);
|
|
1807
|
-
const authValue = useMemo2(
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1880
|
+
const authValue = useMemo2(
|
|
1881
|
+
() => ({
|
|
1882
|
+
userProfile,
|
|
1883
|
+
userProfileLoading,
|
|
1884
|
+
refetchUserProfile,
|
|
1885
|
+
allowAnonymous,
|
|
1886
|
+
getAuthToken: getAuthTokenProp
|
|
1887
|
+
}),
|
|
1888
|
+
[userProfile, userProfileLoading, refetchUserProfile, allowAnonymous, getAuthTokenProp]
|
|
1889
|
+
);
|
|
1814
1890
|
return /* @__PURE__ */ jsx6(RecordAuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx6(ScopeRegistryProvider, { children }) });
|
|
1815
1891
|
}
|
|
1816
1892
|
function RecordProvider({
|
|
1817
1893
|
roomId,
|
|
1818
|
-
schemas = [],
|
|
1819
1894
|
wsUrl,
|
|
1820
1895
|
children,
|
|
1821
1896
|
allowAnonymous = false,
|
|
@@ -1855,7 +1930,6 @@ function RecordProvider({
|
|
|
1855
1930
|
RecordProviderCore,
|
|
1856
1931
|
{
|
|
1857
1932
|
roomId,
|
|
1858
|
-
schemas,
|
|
1859
1933
|
wsUrl,
|
|
1860
1934
|
fetchUser,
|
|
1861
1935
|
allowAnonymous,
|
|
@@ -1876,20 +1950,14 @@ function RecordProvider({
|
|
|
1876
1950
|
}
|
|
1877
1951
|
|
|
1878
1952
|
// src/client/storage/RecordScope.tsx
|
|
1879
|
-
import {
|
|
1880
|
-
useEffect as useEffect5,
|
|
1881
|
-
useState as useState4,
|
|
1882
|
-
useCallback as useCallback3,
|
|
1883
|
-
useRef as useRef5,
|
|
1884
|
-
useMemo as useMemo3
|
|
1885
|
-
} from "react";
|
|
1953
|
+
import { useEffect as useEffect5, useState as useState4, useCallback as useCallback3, useRef as useRef5, useMemo as useMemo3 } from "react";
|
|
1886
1954
|
|
|
1887
1955
|
// src/client/storage/ws-log.ts
|
|
1888
1956
|
var activeCount = 0;
|
|
1889
1957
|
function wsLog(event, label) {
|
|
1890
1958
|
if (event === "connected") activeCount++;
|
|
1891
1959
|
if (event === "closing") activeCount--;
|
|
1892
|
-
|
|
1960
|
+
debugLog(`[ds:ws] ${event} \u2192 ${label} (${activeCount} active)`);
|
|
1893
1961
|
}
|
|
1894
1962
|
|
|
1895
1963
|
// src/client/storage/RecordScope.tsx
|
|
@@ -1906,7 +1974,13 @@ function recordMatchesWhere2(record, where) {
|
|
|
1906
1974
|
return false;
|
|
1907
1975
|
});
|
|
1908
1976
|
}
|
|
1909
|
-
function HeadlessScope({
|
|
1977
|
+
function HeadlessScope({
|
|
1978
|
+
roomId,
|
|
1979
|
+
schemas,
|
|
1980
|
+
appId,
|
|
1981
|
+
wsUrl,
|
|
1982
|
+
wsPathPrefix
|
|
1983
|
+
}) {
|
|
1910
1984
|
return /* @__PURE__ */ jsx7(
|
|
1911
1985
|
ScopeConnection,
|
|
1912
1986
|
{
|
|
@@ -1944,6 +2018,7 @@ function ScopeConnection({
|
|
|
1944
2018
|
const pendingRequestsRef = useRef5(/* @__PURE__ */ new Map());
|
|
1945
2019
|
const reconnectAttemptRef = useRef5(0);
|
|
1946
2020
|
const reconnectTimeoutRef = useRef5(null);
|
|
2021
|
+
const connectTokenRef = useRef5(0);
|
|
1947
2022
|
const userProfileRef = useRef5(auth?.userProfile ?? null);
|
|
1948
2023
|
userProfileRef.current = auth?.userProfile ?? null;
|
|
1949
2024
|
const getAuthTokenRef = useRef5(auth?.getAuthToken ?? null);
|
|
@@ -1955,131 +2030,145 @@ function ScopeConnection({
|
|
|
1955
2030
|
authCallbacksRef.current.onPermissionError = auth?.onPermissionError;
|
|
1956
2031
|
authCallbacksRef.current.onValidationError = auth?.onValidationError;
|
|
1957
2032
|
const allowAnonymous = auth?.allowAnonymous ?? false;
|
|
1958
|
-
const handleMessage = useCallback3(
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
let msg;
|
|
1964
|
-
try {
|
|
1965
|
-
msg = JSON.parse(event.data);
|
|
1966
|
-
} catch {
|
|
1967
|
-
console.error("[RecordScope] Failed to parse message");
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
const { type, payload } = msg;
|
|
1971
|
-
switch (type) {
|
|
1972
|
-
case MSG.USER_INFO: {
|
|
1973
|
-
const { role } = payload;
|
|
1974
|
-
setRoomRole(role);
|
|
1975
|
-
setReady(true);
|
|
1976
|
-
reconnectAttemptRef.current = 0;
|
|
1977
|
-
const ws = wsRef.current;
|
|
1978
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
1979
|
-
ws.send(JSON.stringify({ type: MSG.USER_LIST, payload: {} }));
|
|
1980
|
-
}
|
|
1981
|
-
break;
|
|
2033
|
+
const handleMessage = useCallback3(
|
|
2034
|
+
(event) => {
|
|
2035
|
+
if (event.data instanceof ArrayBuffer) {
|
|
2036
|
+
binaryHandlersRef.current.forEach((h) => h(event.data));
|
|
2037
|
+
return;
|
|
1982
2038
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
for (const [subId, queryKey] of subscriptionMapRef.current) {
|
|
1990
|
-
if (subId === subscriptionId) {
|
|
1991
|
-
storeRef.current.setQueryResult(queryKey, records);
|
|
1992
|
-
break;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
break;
|
|
2039
|
+
let msg;
|
|
2040
|
+
try {
|
|
2041
|
+
msg = JSON.parse(event.data);
|
|
2042
|
+
} catch {
|
|
2043
|
+
console.error("[RecordScope] Failed to parse message");
|
|
2044
|
+
return;
|
|
1996
2045
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
} else if (changeType === "create") {
|
|
2008
|
-
if (matches) storeRef.current.applyChange(queryKey, record, "create");
|
|
2009
|
-
} else {
|
|
2010
|
-
if (matches && exists) storeRef.current.applyChange(queryKey, record, "update");
|
|
2011
|
-
else if (matches && !exists) storeRef.current.applyChange(queryKey, record, "create");
|
|
2012
|
-
else if (!matches && exists) storeRef.current.applyChange(queryKey, record, "delete");
|
|
2013
|
-
}
|
|
2014
|
-
} catch {
|
|
2046
|
+
const { type, payload } = msg;
|
|
2047
|
+
switch (type) {
|
|
2048
|
+
case MSG.USER_INFO: {
|
|
2049
|
+
const { role } = payload;
|
|
2050
|
+
setRoomRole(role);
|
|
2051
|
+
setReady(true);
|
|
2052
|
+
reconnectAttemptRef.current = 0;
|
|
2053
|
+
const ws = wsRef.current;
|
|
2054
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
2055
|
+
ws.send(JSON.stringify({ type: MSG.USER_LIST, payload: {} }));
|
|
2015
2056
|
}
|
|
2057
|
+
break;
|
|
2016
2058
|
}
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2059
|
+
case MSG.USER_LIST:
|
|
2060
|
+
setAllUsers(payload.users);
|
|
2061
|
+
setUsersLoaded(true);
|
|
2062
|
+
break;
|
|
2063
|
+
case MSG.QUERY_RESULT: {
|
|
2064
|
+
const { subscriptionId, records } = payload;
|
|
2022
2065
|
for (const [subId, queryKey] of subscriptionMapRef.current) {
|
|
2023
2066
|
if (subId === subscriptionId) {
|
|
2024
|
-
storeRef.current.
|
|
2067
|
+
storeRef.current.setQueryResult(queryKey, records);
|
|
2025
2068
|
break;
|
|
2026
2069
|
}
|
|
2027
2070
|
}
|
|
2028
|
-
|
|
2029
|
-
const parsed = parseServerError(error);
|
|
2030
|
-
if (parsed.isPermissionError) {
|
|
2031
|
-
authCallbacksRef.current.onPermissionError?.(parsed.title, parsed.detail);
|
|
2032
|
-
} else {
|
|
2033
|
-
authCallbacksRef.current.onValidationError?.(parsed.title, parsed.detail);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
break;
|
|
2037
|
-
}
|
|
2038
|
-
case MSG.YJS_JOIN: {
|
|
2039
|
-
const { collection, recordId, fieldName, canWrite } = payload;
|
|
2040
|
-
const docKey = `${collection}:${recordId}:${fieldName}`;
|
|
2041
|
-
yjsJoinHandlersRef.current.get(docKey)?.forEach((h) => h(canWrite));
|
|
2042
|
-
break;
|
|
2043
|
-
}
|
|
2044
|
-
case MSG.ACK: {
|
|
2045
|
-
const { requestId, success, error, ...rest } = payload;
|
|
2046
|
-
const pending = pendingRequestsRef.current.get(requestId);
|
|
2047
|
-
if (pending) {
|
|
2048
|
-
clearTimeout(pending.timer);
|
|
2049
|
-
pendingRequestsRef.current.delete(requestId);
|
|
2050
|
-
success ? pending.resolve(rest) : pending.reject(new Error(error || "Mutation rejected"));
|
|
2071
|
+
break;
|
|
2051
2072
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
const { schemas: list } = payload;
|
|
2056
|
-
setDiscoveredSchemas(list ?? []);
|
|
2057
|
-
break;
|
|
2058
|
-
}
|
|
2059
|
-
case MSG.RESUBSCRIBE: {
|
|
2060
|
-
const ws = wsRef.current;
|
|
2061
|
-
if (ws?.readyState === WebSocket.OPEN) {
|
|
2062
|
-
for (const [subscriptionId, queryKey] of subscriptionMapRef.current) {
|
|
2073
|
+
case MSG.RECORD_CHANGE: {
|
|
2074
|
+
const { collection, record, changeType } = payload;
|
|
2075
|
+
for (const [, queryKey] of subscriptionMapRef.current) {
|
|
2063
2076
|
try {
|
|
2064
2077
|
const query = JSON.parse(queryKey);
|
|
2065
|
-
|
|
2078
|
+
if (query.collection !== collection) continue;
|
|
2079
|
+
const matches = recordMatchesWhere2(record, query.where);
|
|
2080
|
+
const exists = storeRef.current.hasRecord(queryKey, record.recordId);
|
|
2081
|
+
if (changeType === "delete") {
|
|
2082
|
+
if (exists) storeRef.current.applyChange(queryKey, record, "delete");
|
|
2083
|
+
} else if (changeType === "create") {
|
|
2084
|
+
if (matches) storeRef.current.applyChange(queryKey, record, "create");
|
|
2085
|
+
} else {
|
|
2086
|
+
if (matches && exists) storeRef.current.applyChange(queryKey, record, "update");
|
|
2087
|
+
else if (matches && !exists)
|
|
2088
|
+
storeRef.current.applyChange(queryKey, record, "create");
|
|
2089
|
+
else if (!matches && exists)
|
|
2090
|
+
storeRef.current.applyChange(queryKey, record, "delete");
|
|
2091
|
+
}
|
|
2066
2092
|
} catch {
|
|
2067
2093
|
}
|
|
2068
2094
|
}
|
|
2095
|
+
break;
|
|
2096
|
+
}
|
|
2097
|
+
case MSG.ERROR: {
|
|
2098
|
+
const { subscriptionId, error } = payload;
|
|
2099
|
+
if (subscriptionId) {
|
|
2100
|
+
for (const [subId, queryKey] of subscriptionMapRef.current) {
|
|
2101
|
+
if (subId === subscriptionId) {
|
|
2102
|
+
storeRef.current.setError(queryKey, error);
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
} else {
|
|
2107
|
+
const parsed = parseServerError(error);
|
|
2108
|
+
if (parsed.isPermissionError) {
|
|
2109
|
+
authCallbacksRef.current.onPermissionError?.(parsed.title, parsed.detail);
|
|
2110
|
+
} else {
|
|
2111
|
+
authCallbacksRef.current.onValidationError?.(parsed.title, parsed.detail);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
break;
|
|
2115
|
+
}
|
|
2116
|
+
case MSG.YJS_JOIN: {
|
|
2117
|
+
const { collection, recordId, fieldName, canWrite } = payload;
|
|
2118
|
+
const docKey = `${collection}:${recordId}:${fieldName}`;
|
|
2119
|
+
yjsJoinHandlersRef.current.get(docKey)?.forEach((h) => h(canWrite));
|
|
2120
|
+
break;
|
|
2121
|
+
}
|
|
2122
|
+
case MSG.ACK: {
|
|
2123
|
+
const { requestId, success, error, ...rest } = payload;
|
|
2124
|
+
const pending = pendingRequestsRef.current.get(requestId);
|
|
2125
|
+
if (pending) {
|
|
2126
|
+
clearTimeout(pending.timer);
|
|
2127
|
+
pendingRequestsRef.current.delete(requestId);
|
|
2128
|
+
if (success) {
|
|
2129
|
+
pending.resolve(rest);
|
|
2130
|
+
} else {
|
|
2131
|
+
pending.reject(new Error(error || "Mutation rejected"));
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
break;
|
|
2135
|
+
}
|
|
2136
|
+
case MSG.LIST_SCHEMAS: {
|
|
2137
|
+
const { schemas: list } = payload;
|
|
2138
|
+
setDiscoveredSchemas(list ?? []);
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
case MSG.RESUBSCRIBE: {
|
|
2142
|
+
const ws = wsRef.current;
|
|
2143
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
2144
|
+
for (const [subscriptionId, queryKey] of subscriptionMapRef.current) {
|
|
2145
|
+
try {
|
|
2146
|
+
const query = JSON.parse(queryKey);
|
|
2147
|
+
ws.send(JSON.stringify({ type: MSG.SUBSCRIBE, payload: { subscriptionId, query } }));
|
|
2148
|
+
} catch {
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
break;
|
|
2069
2153
|
}
|
|
2070
|
-
break;
|
|
2071
2154
|
}
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2155
|
+
},
|
|
2156
|
+
// Body reads only refs/setters, never roomId; keeping deps empty makes the
|
|
2157
|
+
// handler stable and avoids needlessly re-triggering the connect effect
|
|
2158
|
+
// (which lists roomId itself).
|
|
2159
|
+
[]
|
|
2160
|
+
);
|
|
2074
2161
|
const connect = useCallback3(async () => {
|
|
2075
2162
|
const profile = userProfileRef.current;
|
|
2076
2163
|
const hasTokenProvider = !!getAuthTokenRef.current;
|
|
2077
2164
|
if (!profile && !hasTokenProvider && !allowAnonymous) return;
|
|
2078
|
-
if (wsRef.current?.readyState === WebSocket.OPEN || wsRef.current?.readyState === WebSocket.CONNECTING)
|
|
2165
|
+
if (wsRef.current?.readyState === WebSocket.OPEN || wsRef.current?.readyState === WebSocket.CONNECTING)
|
|
2166
|
+
return;
|
|
2079
2167
|
if (reconnectTimeoutRef.current) {
|
|
2080
2168
|
clearTimeout(reconnectTimeoutRef.current);
|
|
2081
2169
|
reconnectTimeoutRef.current = null;
|
|
2082
2170
|
}
|
|
2171
|
+
const myToken = ++connectTokenRef.current;
|
|
2083
2172
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
2084
2173
|
const baseUrl = wsUrl?.replace(/^http/, "ws") ?? `${protocol}//${window.location.host}`;
|
|
2085
2174
|
const params = new URLSearchParams();
|
|
@@ -2089,6 +2178,7 @@ function ScopeConnection({
|
|
|
2089
2178
|
if (token) params.set("token", token);
|
|
2090
2179
|
} catch {
|
|
2091
2180
|
}
|
|
2181
|
+
if (connectTokenRef.current !== myToken) return;
|
|
2092
2182
|
params.set("appId", appId);
|
|
2093
2183
|
const url = `${baseUrl}${wsPathPrefix}/${roomId}?${params.toString()}`;
|
|
2094
2184
|
wsLog("connecting", roomId);
|
|
@@ -2112,7 +2202,8 @@ function ScopeConnection({
|
|
|
2112
2202
|
setStatus("disconnected");
|
|
2113
2203
|
setReady(false);
|
|
2114
2204
|
wsRef.current = null;
|
|
2115
|
-
for (const [, queryKey] of subscriptionMapRef.current)
|
|
2205
|
+
for (const [, queryKey] of subscriptionMapRef.current)
|
|
2206
|
+
storeRef.current.resetToLoading(queryKey);
|
|
2116
2207
|
for (const [, pending] of pendingRequestsRef.current) {
|
|
2117
2208
|
clearTimeout(pending.timer);
|
|
2118
2209
|
pending.reject(new Error("WebSocket disconnected"));
|
|
@@ -2127,6 +2218,7 @@ function ScopeConnection({
|
|
|
2127
2218
|
};
|
|
2128
2219
|
}, [roomId, wsUrl, wsPathPrefix, handleMessage, allowAnonymous, appId]);
|
|
2129
2220
|
const disconnect = useCallback3(() => {
|
|
2221
|
+
connectTokenRef.current++;
|
|
2130
2222
|
if (reconnectTimeoutRef.current) {
|
|
2131
2223
|
clearTimeout(reconnectTimeoutRef.current);
|
|
2132
2224
|
reconnectTimeoutRef.current = null;
|
|
@@ -2178,33 +2270,43 @@ function ScopeConnection({
|
|
|
2178
2270
|
binaryHandlersRef.current.delete(handler);
|
|
2179
2271
|
};
|
|
2180
2272
|
}, []);
|
|
2181
|
-
const registerYjsJoinHandler = useCallback3(
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
handlers
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
reject(new Error("
|
|
2200
|
-
},
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2273
|
+
const registerYjsJoinHandler = useCallback3(
|
|
2274
|
+
(docKey, handler) => {
|
|
2275
|
+
if (!yjsJoinHandlersRef.current.has(docKey)) yjsJoinHandlersRef.current.set(docKey, /* @__PURE__ */ new Set());
|
|
2276
|
+
yjsJoinHandlersRef.current.get(docKey).add(handler);
|
|
2277
|
+
return () => {
|
|
2278
|
+
const handlers = yjsJoinHandlersRef.current.get(docKey);
|
|
2279
|
+
if (handlers) {
|
|
2280
|
+
handlers.delete(handler);
|
|
2281
|
+
if (handlers.size === 0) yjsJoinHandlersRef.current.delete(docKey);
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
},
|
|
2285
|
+
[]
|
|
2286
|
+
);
|
|
2287
|
+
const sendConfirmed = useCallback3(
|
|
2288
|
+
(message, timeoutMs = 1e4) => {
|
|
2289
|
+
const ws = wsRef.current;
|
|
2290
|
+
if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
2291
|
+
return Promise.reject(new Error("WebSocket not connected"));
|
|
2292
|
+
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2293
|
+
return new Promise((resolve, reject) => {
|
|
2294
|
+
const timer = setTimeout(() => {
|
|
2295
|
+
pendingRequestsRef.current.delete(requestId);
|
|
2296
|
+
reject(new Error("Mutation confirmation timed out"));
|
|
2297
|
+
}, timeoutMs);
|
|
2298
|
+
pendingRequestsRef.current.set(requestId, { resolve, reject, timer });
|
|
2299
|
+
ws.send(JSON.stringify({ ...message, payload: { ...message.payload, requestId } }));
|
|
2300
|
+
});
|
|
2301
|
+
},
|
|
2302
|
+
[]
|
|
2303
|
+
);
|
|
2304
|
+
const setUserRole = useCallback3(
|
|
2305
|
+
(userId, role) => {
|
|
2306
|
+
sendMessage({ type: MSG.SET_ROLE, payload: { userId, role } });
|
|
2307
|
+
},
|
|
2308
|
+
[sendMessage]
|
|
2309
|
+
);
|
|
2208
2310
|
const requestUserList = useCallback3(() => {
|
|
2209
2311
|
sendMessage({ type: MSG.USER_LIST, payload: {} });
|
|
2210
2312
|
}, [sendMessage]);
|
|
@@ -2237,7 +2339,10 @@ function ScopeConnection({
|
|
|
2237
2339
|
const registeredSchemasRef = useRef5(null);
|
|
2238
2340
|
if (!isolated && registry && schemas !== registeredSchemasRef.current) {
|
|
2239
2341
|
if (registeredSchemasRef.current) {
|
|
2240
|
-
registry.unregister(
|
|
2342
|
+
registry.unregister(
|
|
2343
|
+
scopeIdRef.current,
|
|
2344
|
+
registeredSchemasRef.current.map((s) => s.name)
|
|
2345
|
+
);
|
|
2241
2346
|
}
|
|
2242
2347
|
const names = schemas.map((s) => s.name);
|
|
2243
2348
|
if (names.length > 0) registry.register(scopeIdRef.current, names, scopeEntryRef.current);
|
|
@@ -2245,57 +2350,61 @@ function ScopeConnection({
|
|
|
2245
2350
|
}
|
|
2246
2351
|
useEffect5(() => {
|
|
2247
2352
|
if (isolated || !registry) return;
|
|
2353
|
+
const scopeId = scopeIdRef.current;
|
|
2248
2354
|
return () => {
|
|
2249
2355
|
const names = (registeredSchemasRef.current ?? []).map((s) => s.name);
|
|
2250
|
-
if (names.length > 0) registry.unregister(
|
|
2356
|
+
if (names.length > 0) registry.unregister(scopeId, names);
|
|
2251
2357
|
};
|
|
2252
2358
|
}, [isolated, registry, roomId]);
|
|
2253
2359
|
const registeredCollections = useMemo3(() => new Set(schemas.map((s) => s.name)), [schemas]);
|
|
2254
|
-
const value = useMemo3(
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2360
|
+
const value = useMemo3(
|
|
2361
|
+
() => ({
|
|
2362
|
+
store: storeRef.current,
|
|
2363
|
+
roomId,
|
|
2364
|
+
registeredCollections,
|
|
2365
|
+
userProfile: auth?.userProfile ?? null,
|
|
2366
|
+
userProfileLoading: auth?.userProfileLoading ?? false,
|
|
2367
|
+
refetchUserProfile: auth?.refetchUserProfile ?? (async () => {
|
|
2368
|
+
}),
|
|
2369
|
+
roomRole,
|
|
2370
|
+
allUsers,
|
|
2371
|
+
usersLoaded,
|
|
2372
|
+
status,
|
|
2373
|
+
ready,
|
|
2374
|
+
discoveredSchemas,
|
|
2375
|
+
setUserRole,
|
|
2376
|
+
requestUserList,
|
|
2377
|
+
registerSubscription,
|
|
2378
|
+
unregisterSubscription,
|
|
2379
|
+
sendMessage,
|
|
2380
|
+
sendBinary,
|
|
2381
|
+
onBinaryMessage,
|
|
2382
|
+
registerYjsJoinHandler,
|
|
2383
|
+
sendConfirmed
|
|
2261
2384
|
}),
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
usersLoaded,
|
|
2286
|
-
status,
|
|
2287
|
-
ready,
|
|
2288
|
-
discoveredSchemas,
|
|
2289
|
-
setUserRole,
|
|
2290
|
-
requestUserList,
|
|
2291
|
-
registerSubscription,
|
|
2292
|
-
unregisterSubscription,
|
|
2293
|
-
sendMessage,
|
|
2294
|
-
sendBinary,
|
|
2295
|
-
onBinaryMessage,
|
|
2296
|
-
registerYjsJoinHandler,
|
|
2297
|
-
sendConfirmed
|
|
2298
|
-
]);
|
|
2385
|
+
[
|
|
2386
|
+
roomId,
|
|
2387
|
+
registeredCollections,
|
|
2388
|
+
auth?.userProfile,
|
|
2389
|
+
auth?.userProfileLoading,
|
|
2390
|
+
auth?.refetchUserProfile,
|
|
2391
|
+
roomRole,
|
|
2392
|
+
allUsers,
|
|
2393
|
+
usersLoaded,
|
|
2394
|
+
status,
|
|
2395
|
+
ready,
|
|
2396
|
+
discoveredSchemas,
|
|
2397
|
+
setUserRole,
|
|
2398
|
+
requestUserList,
|
|
2399
|
+
registerSubscription,
|
|
2400
|
+
unregisterSubscription,
|
|
2401
|
+
sendMessage,
|
|
2402
|
+
sendBinary,
|
|
2403
|
+
onBinaryMessage,
|
|
2404
|
+
registerYjsJoinHandler,
|
|
2405
|
+
sendConfirmed
|
|
2406
|
+
]
|
|
2407
|
+
);
|
|
2299
2408
|
if (!children) return null;
|
|
2300
2409
|
return /* @__PURE__ */ jsx7(RecordContext.Provider, { value, children });
|
|
2301
2410
|
}
|
|
@@ -2375,8 +2484,11 @@ function useQuery(collection, options) {
|
|
|
2375
2484
|
orderBy: options?.orderBy,
|
|
2376
2485
|
orderDir: options?.orderDir,
|
|
2377
2486
|
limit: options?.limit
|
|
2378
|
-
//
|
|
2487
|
+
// Keyed by the serialized optionsKey rather than the raw options object
|
|
2488
|
+
// (a fresh object each render) so the query — and its WS subscription —
|
|
2489
|
+
// is only rebuilt when the options actually change.
|
|
2379
2490
|
}),
|
|
2491
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on serialized optionsKey, not raw options.where/orderBy/orderDir/limit objects, to avoid rebuilding the WS subscription every render
|
|
2380
2492
|
[collection, optionsKey]
|
|
2381
2493
|
);
|
|
2382
2494
|
const queryKey = useMemo5(() => JSON.stringify(query), [query]);
|
|
@@ -2404,13 +2516,10 @@ function useQuery(collection, options) {
|
|
|
2404
2516
|
(onStoreChange) => store.subscribe(queryKey, onStoreChange),
|
|
2405
2517
|
[store, queryKey]
|
|
2406
2518
|
);
|
|
2407
|
-
const getSnapshot = useCallback4(
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
const records = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2412
|
-
const status = store.getStatus(queryKey);
|
|
2413
|
-
const error = store.getError(queryKey);
|
|
2519
|
+
const getSnapshot = useCallback4(() => store.getSnapshot(queryKey), [store, queryKey]);
|
|
2520
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2521
|
+
const records = snapshot.records;
|
|
2522
|
+
const { status, error } = snapshot;
|
|
2414
2523
|
return useMemo5(() => ({ records, status, error }), [records, status, error]);
|
|
2415
2524
|
}
|
|
2416
2525
|
|
|
@@ -2560,7 +2669,11 @@ function useYjsField(collection, recordId, fieldName) {
|
|
|
2560
2669
|
}, [doc, awareness, docKey, sendBinary, onBinaryMessage]);
|
|
2561
2670
|
useEffect8(() => {
|
|
2562
2671
|
if (!ready) return;
|
|
2563
|
-
const handleAwarenessUpdate = ({
|
|
2672
|
+
const handleAwarenessUpdate = ({
|
|
2673
|
+
added,
|
|
2674
|
+
updated,
|
|
2675
|
+
removed
|
|
2676
|
+
}) => {
|
|
2564
2677
|
const changedClients = added.concat(updated).concat(removed);
|
|
2565
2678
|
if (changedClients.length === 0) return;
|
|
2566
2679
|
const encoder = createEncoder();
|
|
@@ -2576,16 +2689,16 @@ function useYjsField(collection, recordId, fieldName) {
|
|
|
2576
2689
|
}, [awareness, docKey, sendBinary, ready]);
|
|
2577
2690
|
useEffect8(() => {
|
|
2578
2691
|
if (!ready) {
|
|
2579
|
-
|
|
2692
|
+
debugLog("[YJS] Not ready, resetting:", docKey);
|
|
2580
2693
|
setSynced(false);
|
|
2581
2694
|
setCanWrite(false);
|
|
2582
2695
|
return;
|
|
2583
2696
|
}
|
|
2584
|
-
|
|
2697
|
+
debugLog("[YJS] Joining:", docKey);
|
|
2585
2698
|
sendMessage({ type: MSG.YJS_JOIN, payload: { collection, recordId, fieldName } });
|
|
2586
2699
|
return () => {
|
|
2587
2700
|
awareness.setLocalState(null);
|
|
2588
|
-
|
|
2701
|
+
debugLog("[YJS] Leaving:", docKey);
|
|
2589
2702
|
sendMessage({ type: MSG.YJS_LEAVE, payload: { collection, recordId, fieldName } });
|
|
2590
2703
|
};
|
|
2591
2704
|
}, [ready, collection, recordId, fieldName, sendMessage, docKey, awareness]);
|
|
@@ -2621,17 +2734,20 @@ function useYjsText(collection, recordId, fieldName) {
|
|
|
2621
2734
|
}
|
|
2622
2735
|
isLocalUpdateRef.current = false;
|
|
2623
2736
|
}, [synced, updateCount]);
|
|
2624
|
-
const setText = useCallback6(
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2737
|
+
const setText = useCallback6(
|
|
2738
|
+
(value) => {
|
|
2739
|
+
setTextState(value);
|
|
2740
|
+
if (yTextRef.current && canWrite) {
|
|
2741
|
+
isLocalUpdateRef.current = true;
|
|
2742
|
+
doc.transact(() => {
|
|
2743
|
+
const yText = yTextRef.current;
|
|
2744
|
+
yText.delete(0, yText.length);
|
|
2745
|
+
yText.insert(0, value);
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
},
|
|
2749
|
+
[doc, canWrite]
|
|
2750
|
+
);
|
|
2635
2751
|
return { text, setText, synced, canWrite };
|
|
2636
2752
|
}
|
|
2637
2753
|
|
|
@@ -2753,6 +2869,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2753
2869
|
const connect = async () => {
|
|
2754
2870
|
if (!alive) return;
|
|
2755
2871
|
const token = await getAuthToken();
|
|
2872
|
+
if (!alive) return;
|
|
2756
2873
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
2757
2874
|
const baseUrl = `${protocol}//${window.location.host}`;
|
|
2758
2875
|
const url = new URL(`/ws/yjs/${encodeURIComponent(docId)}`, baseUrl);
|
|
@@ -2837,7 +2954,11 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2837
2954
|
};
|
|
2838
2955
|
}, [doc, awareness, docId, yText]);
|
|
2839
2956
|
useEffect10(() => {
|
|
2840
|
-
const handler = ({
|
|
2957
|
+
const handler = ({
|
|
2958
|
+
added,
|
|
2959
|
+
updated,
|
|
2960
|
+
removed
|
|
2961
|
+
}) => {
|
|
2841
2962
|
if (applyingRemoteAwarenessRef.current) return;
|
|
2842
2963
|
const changedClients = added.concat(updated).concat(removed);
|
|
2843
2964
|
if (changedClients.length === 0) return;
|
|
@@ -2867,16 +2988,19 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2867
2988
|
doc.off("update", handler);
|
|
2868
2989
|
};
|
|
2869
2990
|
}, [doc]);
|
|
2870
|
-
const setText = useCallback9(
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2991
|
+
const setText = useCallback9(
|
|
2992
|
+
(value) => {
|
|
2993
|
+
setTextState(value);
|
|
2994
|
+
if (!canWrite) return;
|
|
2995
|
+
isLocalRef.current = true;
|
|
2996
|
+
doc.transact(() => {
|
|
2997
|
+
yText.delete(0, yText.length);
|
|
2998
|
+
yText.insert(0, value);
|
|
2999
|
+
});
|
|
3000
|
+
isLocalRef.current = false;
|
|
3001
|
+
},
|
|
3002
|
+
[doc, yText, canWrite]
|
|
3003
|
+
);
|
|
2880
3004
|
return { doc, awareness, text, setText, synced, canWrite };
|
|
2881
3005
|
}
|
|
2882
3006
|
|
|
@@ -3609,7 +3733,7 @@ function useMessages(channelId, options) {
|
|
|
3609
3733
|
if (parentMessageId) data.parentMessageId = parentMessageId;
|
|
3610
3734
|
return create(data);
|
|
3611
3735
|
},
|
|
3612
|
-
[channelId, create]
|
|
3736
|
+
[channelId, user, create]
|
|
3613
3737
|
);
|
|
3614
3738
|
const edit = useCallback16(
|
|
3615
3739
|
(messageId, newContent) => {
|
|
@@ -3629,7 +3753,15 @@ function useMessages(channelId, options) {
|
|
|
3629
3753
|
},
|
|
3630
3754
|
[put]
|
|
3631
3755
|
);
|
|
3632
|
-
return {
|
|
3756
|
+
return {
|
|
3757
|
+
messages: records,
|
|
3758
|
+
status,
|
|
3759
|
+
error,
|
|
3760
|
+
send,
|
|
3761
|
+
edit,
|
|
3762
|
+
remove,
|
|
3763
|
+
softDelete
|
|
3764
|
+
};
|
|
3633
3765
|
}
|
|
3634
3766
|
|
|
3635
3767
|
// src/client/messaging/useChannels.ts
|
|
@@ -3651,7 +3783,7 @@ function useChannels() {
|
|
|
3651
3783
|
archived: false
|
|
3652
3784
|
});
|
|
3653
3785
|
},
|
|
3654
|
-
[createMutation]
|
|
3786
|
+
[user, createMutation]
|
|
3655
3787
|
);
|
|
3656
3788
|
const archive = useCallback17(
|
|
3657
3789
|
(channelId) => {
|
|
@@ -3665,7 +3797,15 @@ function useChannels() {
|
|
|
3665
3797
|
},
|
|
3666
3798
|
[put]
|
|
3667
3799
|
);
|
|
3668
|
-
return {
|
|
3800
|
+
return {
|
|
3801
|
+
channels: records,
|
|
3802
|
+
status,
|
|
3803
|
+
error,
|
|
3804
|
+
create,
|
|
3805
|
+
archive,
|
|
3806
|
+
update,
|
|
3807
|
+
remove
|
|
3808
|
+
};
|
|
3669
3809
|
}
|
|
3670
3810
|
|
|
3671
3811
|
// src/client/messaging/useReactions.ts
|
|
@@ -3788,6 +3928,7 @@ function useConversation(opts) {
|
|
|
3788
3928
|
});
|
|
3789
3929
|
opts?.onMessageSent?.(content, parentMessageId);
|
|
3790
3930
|
},
|
|
3931
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- depend on the precise opts?.onMessageSent callback, not the whole opts object, to avoid recreating send when unrelated opts fields change
|
|
3791
3932
|
[messageMut, user?.id, opts?.onMessageSent]
|
|
3792
3933
|
);
|
|
3793
3934
|
const edit = useCallback21(
|
|
@@ -3886,7 +4027,7 @@ function formatFullTimestamp(dateStr) {
|
|
|
3886
4027
|
|
|
3887
4028
|
// src/client/messaging/conversation-utils.ts
|
|
3888
4029
|
function getConversationDisplayName(conversation, currentUserId, getUser) {
|
|
3889
|
-
const { Type, Name
|
|
4030
|
+
const { Type, Name } = conversation.data;
|
|
3890
4031
|
if (Type !== "dm" && Type !== "group") {
|
|
3891
4032
|
return Name || "Untitled";
|
|
3892
4033
|
}
|
|
@@ -3945,6 +4086,13 @@ function parseMessageMetadata(raw) {
|
|
|
3945
4086
|
|
|
3946
4087
|
// src/client/directory/useConversations.ts
|
|
3947
4088
|
import { useCallback as useCallback22, useMemo as useMemo13, useRef as useRef14 } from "react";
|
|
4089
|
+
var defaultConvFields = {
|
|
4090
|
+
Visibility: "public",
|
|
4091
|
+
Status: "active",
|
|
4092
|
+
AssigneeId: "",
|
|
4093
|
+
LinkedRef: "",
|
|
4094
|
+
MessageCount: 0
|
|
4095
|
+
};
|
|
3948
4096
|
function useConversations() {
|
|
3949
4097
|
const { records: conversations, status } = useQuery("conversations");
|
|
3950
4098
|
const { create, put } = useMutations("conversations");
|
|
@@ -3980,13 +4128,6 @@ function useConversations() {
|
|
|
3980
4128
|
},
|
|
3981
4129
|
[stateRecords]
|
|
3982
4130
|
);
|
|
3983
|
-
const defaultConvFields = {
|
|
3984
|
-
Visibility: "public",
|
|
3985
|
-
Status: "active",
|
|
3986
|
-
AssigneeId: "",
|
|
3987
|
-
LinkedRef: "",
|
|
3988
|
-
MessageCount: 0
|
|
3989
|
-
};
|
|
3990
4131
|
const createChannel = useCallback22(
|
|
3991
4132
|
async (name, description) => {
|
|
3992
4133
|
const existing = conversations.find((r) => r.data.Name === name && r.data.Type !== "dm");
|
|
@@ -4748,10 +4889,13 @@ function PlatformProvider({ children }) {
|
|
|
4748
4889
|
}
|
|
4749
4890
|
};
|
|
4750
4891
|
ws.onclose = () => {
|
|
4892
|
+
clearInterval(pingInterval);
|
|
4893
|
+
pingInterval = void 0;
|
|
4751
4894
|
if (mounted) {
|
|
4752
4895
|
retryTimer = setTimeout(connect, 3e3);
|
|
4753
4896
|
}
|
|
4754
4897
|
};
|
|
4898
|
+
clearInterval(pingInterval);
|
|
4755
4899
|
pingInterval = setInterval(() => {
|
|
4756
4900
|
if (ws?.readyState === WebSocket.OPEN) ws.send("ping");
|
|
4757
4901
|
}, 3e4);
|
|
@@ -4915,7 +5059,8 @@ async function request(method, endpoint, body, options) {
|
|
|
4915
5059
|
if ("data" in payload) {
|
|
4916
5060
|
return { success: true, data: payload.data };
|
|
4917
5061
|
}
|
|
4918
|
-
const
|
|
5062
|
+
const rest = { ...payload };
|
|
5063
|
+
delete rest.success;
|
|
4919
5064
|
return { success: true, data: rest };
|
|
4920
5065
|
} catch (err) {
|
|
4921
5066
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
@@ -5556,7 +5701,7 @@ function usePagedResource(fetchPage, deps, options = {}) {
|
|
|
5556
5701
|
controllerRef.current = null;
|
|
5557
5702
|
}
|
|
5558
5703
|
}
|
|
5559
|
-
}, [autoRetryOnError, clearAutoRetryTimer, enabled, fetchPage, keepPreviousData, pageSize, resetCooldown, setFailureCooldown]);
|
|
5704
|
+
}, [autoRetryOnError, clearAutoRetryTimer, enabled, fetchPage, keepPreviousData, maxItemsPerPage, pageSize, resetCooldown, setFailureCooldown]);
|
|
5560
5705
|
const loadMore = useCallback27(() => {
|
|
5561
5706
|
if (!state.hasMore || state.isLoadingInitial || state.isLoadingMore || state.isRefreshing) return;
|
|
5562
5707
|
void startLoad(nextPageRef.current, "auto");
|