deepspace 0.7.0 → 0.8.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 +30 -0
- package/README.md +22 -8
- package/dist/cli.js +10306 -5256
- package/dist/cli.js.map +1 -1
- package/dist/feature-installer.js +638 -0
- package/dist/feature-installer.js.map +1 -0
- package/dist/index.d.ts +107 -117
- package/dist/index.js +610 -779
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +403 -167
- package/dist/server.js +498 -367
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +115 -0
- package/dist/testing.js +3 -3
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +397 -160
- package/dist/worker.js +506 -367
- package/dist/worker.js.map +1 -1
- package/features/ai-chat/feature.json +2 -0
- package/features/ai-chat/src/AiChatPage.tsx +2 -3
- package/features/ai-chat/src/ChatPanel.messages.tsx +344 -0
- package/features/ai-chat/src/ChatPanel.stream.ts +397 -0
- package/features/ai-chat/src/ChatPanel.tsx +158 -862
- package/features/ai-chat/src/__tests__/ChatPanel.stream.test.ts +163 -0
- package/features/canvas/src/CanvasListPage.tsx +1 -1
- package/features/docs/feature.json +4 -8
- package/features/docs/src/DocsEditorPage.tsx +33 -326
- package/features/docs/src/DocsListPage.tsx +33 -29
- package/features/docs/src/InviteDialog.tsx +11 -40
- package/features/docs/src/__tests__/docs-presence-access.test.ts +71 -0
- package/features/docs/src/docs-library-types.ts +28 -3
- package/features/docs/src/docs-ui.css +1 -4
- package/features/docs/src/use-docs-presence-access.ts +330 -0
- package/features/file-attachments/feature.json +2 -2
- package/features/file-attachments/src/AttachmentPreview.tsx +428 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +100 -462
- package/features/file-attachments/src/useFilePreview.ts +54 -68
- package/features/file-manager/src/FileManagerPage.tsx +1 -1
- package/features/integration-test/src/integration.spec.ts +1 -1
- package/features/items/feature.json +1 -5
- package/features/items/src/ItemsPage.tsx +207 -135
- package/features/landing/feature.json +3 -0
- package/features/landing/src/LandingPage.tsx +4 -18
- package/features/leaderboard/feature.json +1 -5
- package/features/leaderboard/src/LeaderboardPage.tsx +333 -198
- package/features/messaging/feature.json +0 -4
- package/features/messaging/src/ChatMultiPage.tsx +1 -3
- package/features/messaging/src/ChatPage.tsx +1 -3
- package/features/messaging/src/components/chat/MessageActionSheet.tsx +40 -16
- package/features/search-bar/feature.json +8 -3
- package/features/search-bar/src/SearchOverlay.tsx +108 -252
- package/features/search-bar/src/__tests__/search-model.test.ts +40 -0
- package/features/search-bar/src/search-model.ts +223 -0
- package/features/tasks/src/TasksPage.tsx +3 -3
- package/features/topbar/src/Topbar.tsx +0 -17
- package/package.json +1 -2
- package/features/docs/src/docs-favorites.ts +0 -18
- package/features/docs/src/use-yjs-room-with-awareness.ts +0 -289
- package/features/file-attachments/src/attachments-constants.ts +0 -9
- package/features/items/src/items-constants.ts +0 -10
- package/features/leaderboard/src/leaderboard-constants.ts +0 -21
- package/features/messaging/src/components/chat/ReactionPicker.tsx +0 -2
- package/scripts/add-feature.cjs +0 -807
package/dist/index.js
CHANGED
|
@@ -9,6 +9,16 @@ var DEV_CONFIG = {
|
|
|
9
9
|
mainAppUrl: "http://localhost:5173",
|
|
10
10
|
dashboardUrl: "http://localhost:5174"
|
|
11
11
|
};
|
|
12
|
+
var STAGING_CONFIG = {
|
|
13
|
+
name: "staging",
|
|
14
|
+
apiUrl: "https://api.deepspacesites.com",
|
|
15
|
+
platformWorkerUrl: "https://platform.deepspacesites.com",
|
|
16
|
+
authUrl: "https://auth.deepspacesites.com",
|
|
17
|
+
authSignInUrl: "https://auth.deepspacesites.com/login/social",
|
|
18
|
+
authSignUpUrl: "https://auth.deepspacesites.com/login/social",
|
|
19
|
+
mainAppUrl: "https://spacestest.com",
|
|
20
|
+
dashboardUrl: "https://dashboard.deepspacesites.com"
|
|
21
|
+
};
|
|
12
22
|
var PROD_CONFIG = {
|
|
13
23
|
name: "prod",
|
|
14
24
|
apiUrl: "https://api-worker.deep.space",
|
|
@@ -24,6 +34,7 @@ function parseEnv(value) {
|
|
|
24
34
|
if (!value) return null;
|
|
25
35
|
const v = value.toLowerCase();
|
|
26
36
|
if (v === "dev" || v === "development") return "dev";
|
|
37
|
+
if (v === "staging") return "staging";
|
|
27
38
|
if (v === "prod" || v === "production") return "prod";
|
|
28
39
|
return null;
|
|
29
40
|
}
|
|
@@ -64,6 +75,10 @@ function detectEnvironment() {
|
|
|
64
75
|
cachedEnvironment = "dev";
|
|
65
76
|
return "dev";
|
|
66
77
|
}
|
|
78
|
+
if (hostname === "spacestest.com" || hostname.endsWith(".spacestest.com") || hostname === "deepspacesites.com" || hostname.endsWith(".deepspacesites.com")) {
|
|
79
|
+
cachedEnvironment = "staging";
|
|
80
|
+
return "staging";
|
|
81
|
+
}
|
|
67
82
|
if (hostname === "app.space" || hostname.endsWith(".app.space") || hostname === "deep.space" || hostname.endsWith(".deep.space")) {
|
|
68
83
|
cachedEnvironment = "prod";
|
|
69
84
|
return "prod";
|
|
@@ -74,7 +89,7 @@ function detectEnvironment() {
|
|
|
74
89
|
}
|
|
75
90
|
function getEnvironmentConfig() {
|
|
76
91
|
const env = detectEnvironment();
|
|
77
|
-
return env === "dev" ? DEV_CONFIG : PROD_CONFIG;
|
|
92
|
+
return env === "dev" ? DEV_CONFIG : env === "staging" ? STAGING_CONFIG : PROD_CONFIG;
|
|
78
93
|
}
|
|
79
94
|
function getApiUrl() {
|
|
80
95
|
return getEnvironmentConfig().apiUrl;
|
|
@@ -371,6 +386,14 @@ var Awareness = class {
|
|
|
371
386
|
this.emit({ added: [], updated: [], removed });
|
|
372
387
|
}
|
|
373
388
|
}
|
|
389
|
+
/** Release all state and listeners owned by this awareness instance. */
|
|
390
|
+
destroy() {
|
|
391
|
+
this.setLocalState(null);
|
|
392
|
+
this.states.clear();
|
|
393
|
+
this.meta.clear();
|
|
394
|
+
this.updateListeners.clear();
|
|
395
|
+
this.changeListeners.clear();
|
|
396
|
+
}
|
|
374
397
|
};
|
|
375
398
|
function encodeAwarenessMessage(awareness, clientIds) {
|
|
376
399
|
const encoder = createEncoder();
|
|
@@ -1156,6 +1179,229 @@ import {
|
|
|
1156
1179
|
useMemo as useMemo2
|
|
1157
1180
|
} from "react";
|
|
1158
1181
|
|
|
1182
|
+
// src/client/storage/ScopeRegistry.tsx
|
|
1183
|
+
import { createContext, useContext, useCallback, useRef as useRef3, useMemo } from "react";
|
|
1184
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1185
|
+
var ScopeRegistryContext = createContext(null);
|
|
1186
|
+
function useScopeRegistry() {
|
|
1187
|
+
return useContext(ScopeRegistryContext);
|
|
1188
|
+
}
|
|
1189
|
+
function ScopeRegistryProvider({ children }) {
|
|
1190
|
+
const mapRef = useRef3(/* @__PURE__ */ new Map());
|
|
1191
|
+
const register = useCallback((scopeId, collections, entry) => {
|
|
1192
|
+
for (const name of collections) {
|
|
1193
|
+
mapRef.current.set(name, { scopeId, entry });
|
|
1194
|
+
}
|
|
1195
|
+
}, []);
|
|
1196
|
+
const unregister = useCallback((scopeId, collections) => {
|
|
1197
|
+
for (const name of collections) {
|
|
1198
|
+
const existing = mapRef.current.get(name);
|
|
1199
|
+
if (existing?.scopeId === scopeId) {
|
|
1200
|
+
mapRef.current.delete(name);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}, []);
|
|
1204
|
+
const resolve = useCallback((collection) => {
|
|
1205
|
+
return mapRef.current.get(collection)?.entry ?? null;
|
|
1206
|
+
}, []);
|
|
1207
|
+
const value = useMemo(() => ({ register, unregister, resolve }), [register, unregister, resolve]);
|
|
1208
|
+
return /* @__PURE__ */ jsx5(ScopeRegistryContext.Provider, { value, children });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/client/storage/context.tsx
|
|
1212
|
+
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1213
|
+
var RecordContext = createContext2(null);
|
|
1214
|
+
function useRecordContext() {
|
|
1215
|
+
const ctx = useContext2(RecordContext);
|
|
1216
|
+
if (!ctx) {
|
|
1217
|
+
throw new Error("useRecordContext must be used within a RecordScope");
|
|
1218
|
+
}
|
|
1219
|
+
return ctx;
|
|
1220
|
+
}
|
|
1221
|
+
var RecordAuthContext = createContext2(null);
|
|
1222
|
+
function useRecordAuth() {
|
|
1223
|
+
return useContext2(RecordAuthContext);
|
|
1224
|
+
}
|
|
1225
|
+
function RecordProviderState({
|
|
1226
|
+
children,
|
|
1227
|
+
fetchUser,
|
|
1228
|
+
allowAnonymous = false,
|
|
1229
|
+
getAuthToken: getAuthTokenProp,
|
|
1230
|
+
onPermissionError: onPermissionErrorProp,
|
|
1231
|
+
onValidationError: onValidationErrorProp
|
|
1232
|
+
}) {
|
|
1233
|
+
const [userProfile, setUserProfile] = useState3(null);
|
|
1234
|
+
const [userProfileLoading, setUserProfileLoading] = useState3(true);
|
|
1235
|
+
const onPermissionErrorRef = useRef4(onPermissionErrorProp);
|
|
1236
|
+
onPermissionErrorRef.current = onPermissionErrorProp;
|
|
1237
|
+
const onValidationErrorRef = useRef4(onValidationErrorProp);
|
|
1238
|
+
onValidationErrorRef.current = onValidationErrorProp;
|
|
1239
|
+
const onPermissionError = useCallback2((title, detail) => {
|
|
1240
|
+
onPermissionErrorRef.current?.(title, detail);
|
|
1241
|
+
}, []);
|
|
1242
|
+
const onValidationError = useCallback2((title, detail) => {
|
|
1243
|
+
onValidationErrorRef.current?.(title, detail);
|
|
1244
|
+
}, []);
|
|
1245
|
+
const refetchUserProfile = useCallback2(async () => {
|
|
1246
|
+
try {
|
|
1247
|
+
const profile = await fetchUser();
|
|
1248
|
+
setUserProfile(profile);
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
console.error("[RecordProvider] Failed to refetch user profile:", err);
|
|
1251
|
+
}
|
|
1252
|
+
}, [fetchUser]);
|
|
1253
|
+
useEffect4(() => {
|
|
1254
|
+
let mounted = true;
|
|
1255
|
+
setUserProfileLoading(true);
|
|
1256
|
+
fetchUser().then((profile) => {
|
|
1257
|
+
if (mounted) setUserProfile(profile);
|
|
1258
|
+
}).finally(() => {
|
|
1259
|
+
if (mounted) setUserProfileLoading(false);
|
|
1260
|
+
});
|
|
1261
|
+
const interval = setInterval(() => {
|
|
1262
|
+
if (mounted) {
|
|
1263
|
+
fetchUser().then((p) => {
|
|
1264
|
+
if (mounted) setUserProfile(p);
|
|
1265
|
+
}).catch(() => {
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}, 3e4);
|
|
1269
|
+
return () => {
|
|
1270
|
+
mounted = false;
|
|
1271
|
+
clearInterval(interval);
|
|
1272
|
+
};
|
|
1273
|
+
}, [fetchUser]);
|
|
1274
|
+
const authValue = useMemo2(
|
|
1275
|
+
() => ({
|
|
1276
|
+
userProfile,
|
|
1277
|
+
userProfileLoading,
|
|
1278
|
+
refetchUserProfile,
|
|
1279
|
+
allowAnonymous,
|
|
1280
|
+
getAuthToken: getAuthTokenProp,
|
|
1281
|
+
onPermissionError,
|
|
1282
|
+
onValidationError
|
|
1283
|
+
}),
|
|
1284
|
+
[
|
|
1285
|
+
userProfile,
|
|
1286
|
+
userProfileLoading,
|
|
1287
|
+
refetchUserProfile,
|
|
1288
|
+
allowAnonymous,
|
|
1289
|
+
getAuthTokenProp,
|
|
1290
|
+
onPermissionError,
|
|
1291
|
+
onValidationError
|
|
1292
|
+
]
|
|
1293
|
+
);
|
|
1294
|
+
return /* @__PURE__ */ jsx6(RecordAuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx6(ScopeRegistryProvider, { children }) });
|
|
1295
|
+
}
|
|
1296
|
+
var seenWriteErrors = /* @__PURE__ */ new Set();
|
|
1297
|
+
function defaultOnWriteError(error) {
|
|
1298
|
+
const key = JSON.stringify([error.kind, error.title, error.detail]);
|
|
1299
|
+
if (seenWriteErrors.has(key)) return;
|
|
1300
|
+
if (seenWriteErrors.size >= 100) seenWriteErrors.clear();
|
|
1301
|
+
seenWriteErrors.add(key);
|
|
1302
|
+
console.error(
|
|
1303
|
+
`[deepspace] Write rejected \u2014 ${error.title}: ${error.detail}
|
|
1304
|
+
This error reached no UI. Pass onWriteError to <RecordProvider> (e.g. wire it to a toast) so users see rejected writes. (Further repeats of this error are suppressed.)`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
var warnedSignedOutNull = false;
|
|
1308
|
+
function SignedOutDiagnostic() {
|
|
1309
|
+
return /* @__PURE__ */ jsxs4(
|
|
1310
|
+
"div",
|
|
1311
|
+
{
|
|
1312
|
+
role: "alert",
|
|
1313
|
+
"data-deepspace-diagnostic": "signed-out",
|
|
1314
|
+
style: {
|
|
1315
|
+
margin: "24px auto",
|
|
1316
|
+
maxWidth: 560,
|
|
1317
|
+
padding: "16px 20px",
|
|
1318
|
+
border: "1px solid #d97706",
|
|
1319
|
+
borderRadius: 8,
|
|
1320
|
+
background: "#fffbeb",
|
|
1321
|
+
color: "#78350f",
|
|
1322
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
1323
|
+
fontSize: 13,
|
|
1324
|
+
lineHeight: 1.5
|
|
1325
|
+
},
|
|
1326
|
+
children: [
|
|
1327
|
+
/* @__PURE__ */ jsx6("strong", { children: "DeepSpace: nothing rendered because you're signed out." }),
|
|
1328
|
+
/* @__PURE__ */ jsxs4("p", { style: { margin: "8px 0 0" }, children: [
|
|
1329
|
+
"This page is inside a ",
|
|
1330
|
+
"<RecordProvider>",
|
|
1331
|
+
" without ",
|
|
1332
|
+
/* @__PURE__ */ jsx6("code", { children: "allowAnonymous" }),
|
|
1333
|
+
", which renders nothing for signed-out visitors. Either add",
|
|
1334
|
+
" ",
|
|
1335
|
+
/* @__PURE__ */ jsx6("code", { children: "<RecordProvider allowAnonymous>" }),
|
|
1336
|
+
" to show the page publicly, or require sign-in before this point (e.g. a protected route / AuthGate)."
|
|
1337
|
+
] }),
|
|
1338
|
+
/* @__PURE__ */ jsx6("p", { style: { margin: "8px 0 0", opacity: 0.8 }, children: "This notice only appears on localhost \u2014 a deployed app renders nothing here." })
|
|
1339
|
+
]
|
|
1340
|
+
}
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
function RecordProvider({
|
|
1344
|
+
children,
|
|
1345
|
+
allowAnonymous = false,
|
|
1346
|
+
getAuthToken: getAuthTokenProp,
|
|
1347
|
+
onWriteError = defaultOnWriteError
|
|
1348
|
+
}) {
|
|
1349
|
+
const { isLoaded, isSignedIn } = useAuth();
|
|
1350
|
+
const fetchUser = useCallback2(async () => {
|
|
1351
|
+
if (!isSignedIn) return null;
|
|
1352
|
+
const token = await getAuthToken();
|
|
1353
|
+
if (!token) return null;
|
|
1354
|
+
try {
|
|
1355
|
+
const parts = token.split(".");
|
|
1356
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
1357
|
+
return {
|
|
1358
|
+
id: payload.sub,
|
|
1359
|
+
name: payload.name ?? "",
|
|
1360
|
+
email: payload.email ?? "",
|
|
1361
|
+
imageUrl: payload.image ?? void 0
|
|
1362
|
+
};
|
|
1363
|
+
} catch {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
}, [isSignedIn]);
|
|
1367
|
+
const getAuthTokenFn = useCallback2(async () => {
|
|
1368
|
+
if (getAuthTokenProp) return getAuthTokenProp();
|
|
1369
|
+
if (!isSignedIn) return null;
|
|
1370
|
+
return getAuthToken();
|
|
1371
|
+
}, [getAuthTokenProp, isSignedIn]);
|
|
1372
|
+
if (!isLoaded) {
|
|
1373
|
+
return /* @__PURE__ */ jsx6(Fragment5, { children: null });
|
|
1374
|
+
}
|
|
1375
|
+
if (!isSignedIn && !allowAnonymous) {
|
|
1376
|
+
if (isLocalDevHost()) {
|
|
1377
|
+
if (!warnedSignedOutNull) {
|
|
1378
|
+
warnedSignedOutNull = true;
|
|
1379
|
+
console.warn(
|
|
1380
|
+
"[deepspace] RecordProvider rendered nothing: signed out and allowAnonymous is off. Add `allowAnonymous` to show this page to signed-out visitors, or require sign-in before this point."
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
return /* @__PURE__ */ jsx6(SignedOutDiagnostic, {});
|
|
1384
|
+
}
|
|
1385
|
+
return /* @__PURE__ */ jsx6(Fragment5, { children: null });
|
|
1386
|
+
}
|
|
1387
|
+
const onPermissionError = (title, detail) => onWriteError({ kind: "permission", title, detail });
|
|
1388
|
+
const onValidationError = (title, detail) => onWriteError({ kind: "validation", title, detail });
|
|
1389
|
+
return /* @__PURE__ */ jsx6(
|
|
1390
|
+
RecordProviderState,
|
|
1391
|
+
{
|
|
1392
|
+
fetchUser,
|
|
1393
|
+
allowAnonymous,
|
|
1394
|
+
getAuthToken: getAuthTokenFn,
|
|
1395
|
+
onPermissionError,
|
|
1396
|
+
onValidationError,
|
|
1397
|
+
children
|
|
1398
|
+
}
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/client/storage/RecordScope.tsx
|
|
1403
|
+
import { useEffect as useEffect5, useState as useState4, useCallback as useCallback3, useRef as useRef5, useMemo as useMemo3 } from "react";
|
|
1404
|
+
|
|
1159
1405
|
// src/client/storage/store.ts
|
|
1160
1406
|
var EMPTY_RECORDS = [];
|
|
1161
1407
|
var EMPTY_SNAPSHOT = { status: "loading", records: EMPTY_RECORDS };
|
|
@@ -1457,9 +1703,6 @@ var RecordSocket = class {
|
|
|
1457
1703
|
} catch {
|
|
1458
1704
|
}
|
|
1459
1705
|
if (this.connectToken !== myToken || this.destroyed) return;
|
|
1460
|
-
for (const [key, value] of Object.entries(config.extraParams ?? {})) {
|
|
1461
|
-
params.set(key, value);
|
|
1462
|
-
}
|
|
1463
1706
|
if (identityTag !== void 0) this.identityTag = identityTag;
|
|
1464
1707
|
const protocol = typeof window !== "undefined" && window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
1465
1708
|
const baseUrl = config.wsUrl?.replace(/^http/, "ws") ?? `${protocol}//${typeof window !== "undefined" ? window.location.host : ""}`;
|
|
@@ -1590,588 +1833,111 @@ var RecordSocket = class {
|
|
|
1590
1833
|
}
|
|
1591
1834
|
};
|
|
1592
1835
|
}
|
|
1593
|
-
// ── message dispatch ──────────────────────────────────────────────────────
|
|
1594
|
-
resubscribeAll(ws) {
|
|
1595
|
-
for (const [subscriptionId, queryKey] of this.subscriptions) {
|
|
1596
|
-
try {
|
|
1597
|
-
const query = JSON.parse(queryKey);
|
|
1598
|
-
ws.send(JSON.stringify({ type: MSG.SUBSCRIBE, payload: { subscriptionId, query } }));
|
|
1599
|
-
} catch {
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
handleMessage(event) {
|
|
1604
|
-
if (event.data instanceof ArrayBuffer) {
|
|
1605
|
-
this.binaryHandlers.forEach((h) => h(event.data));
|
|
1606
|
-
return;
|
|
1607
|
-
}
|
|
1608
|
-
let msg;
|
|
1609
|
-
try {
|
|
1610
|
-
msg = JSON.parse(event.data);
|
|
1611
|
-
} catch {
|
|
1612
|
-
console.error("[RecordSocket] Failed to parse message");
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
const { type, payload } = msg;
|
|
1616
|
-
const { store, listeners } = this.config;
|
|
1617
|
-
switch (type) {
|
|
1618
|
-
case MSG.USER_INFO: {
|
|
1619
|
-
const { role } = payload;
|
|
1620
|
-
listeners.onRole(role);
|
|
1621
|
-
listeners.onReady(true);
|
|
1622
|
-
this.reconnectAttempt = 0;
|
|
1623
|
-
this.sendMessage({ type: MSG.USER_LIST, payload: {} });
|
|
1624
|
-
break;
|
|
1625
|
-
}
|
|
1626
|
-
case MSG.USER_LIST:
|
|
1627
|
-
listeners.onUsers(payload.users);
|
|
1628
|
-
break;
|
|
1629
|
-
case MSG.QUERY_RESULT: {
|
|
1630
|
-
const { subscriptionId, records } = payload;
|
|
1631
|
-
const queryKey = this.subscriptions.get(subscriptionId);
|
|
1632
|
-
if (queryKey !== void 0) store.setQueryResult(queryKey, records);
|
|
1633
|
-
break;
|
|
1634
|
-
}
|
|
1635
|
-
case MSG.RECORD_CHANGE: {
|
|
1636
|
-
const { collection, record, changeType } = payload;
|
|
1637
|
-
for (const queryKey of this.subscriptions.values()) {
|
|
1638
|
-
try {
|
|
1639
|
-
const query = JSON.parse(queryKey);
|
|
1640
|
-
if (query.collection !== collection) continue;
|
|
1641
|
-
const matches = recordMatchesWhere(record, query.where);
|
|
1642
|
-
const exists = store.hasRecord(queryKey, record.recordId);
|
|
1643
|
-
if (changeType === "delete") {
|
|
1644
|
-
if (exists) store.applyChange(queryKey, record, "delete");
|
|
1645
|
-
} else if (changeType === "create") {
|
|
1646
|
-
if (matches) store.applyChange(queryKey, record, "create");
|
|
1647
|
-
} else {
|
|
1648
|
-
if (matches && exists) store.applyChange(queryKey, record, "update");
|
|
1649
|
-
else if (matches && !exists) store.applyChange(queryKey, record, "create");
|
|
1650
|
-
else if (!matches && exists) store.applyChange(queryKey, record, "delete");
|
|
1651
|
-
}
|
|
1652
|
-
} catch {
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
break;
|
|
1656
|
-
}
|
|
1657
|
-
case MSG.ERROR: {
|
|
1658
|
-
const { subscriptionId, error } = payload;
|
|
1659
|
-
if (subscriptionId) {
|
|
1660
|
-
const queryKey = this.subscriptions.get(subscriptionId);
|
|
1661
|
-
if (queryKey !== void 0) store.setError(queryKey, error);
|
|
1662
|
-
} else {
|
|
1663
|
-
const parsed = parseServerError(error);
|
|
1664
|
-
if (parsed.isPermissionError) listeners.onPermissionError?.(parsed.title, parsed.detail);
|
|
1665
|
-
else listeners.onValidationError?.(parsed.title, parsed.detail);
|
|
1666
|
-
}
|
|
1667
|
-
break;
|
|
1668
|
-
}
|
|
1669
|
-
case MSG.YJS_JOIN: {
|
|
1670
|
-
const { collection, recordId, fieldName, canWrite } = payload;
|
|
1671
|
-
const docKey = `${collection}:${recordId}:${fieldName}`;
|
|
1672
|
-
this.yjsJoinHandlers.get(docKey)?.forEach((h) => h(canWrite));
|
|
1673
|
-
break;
|
|
1674
|
-
}
|
|
1675
|
-
case MSG.ACK: {
|
|
1676
|
-
const { requestId, success, error, ...rest } = payload;
|
|
1677
|
-
const pending = this.pendingRequests.get(requestId);
|
|
1678
|
-
if (pending) {
|
|
1679
|
-
clearTimeout(pending.timer);
|
|
1680
|
-
this.pendingRequests.delete(requestId);
|
|
1681
|
-
if (success) pending.resolve(rest);
|
|
1682
|
-
else pending.reject(new Error(error || "Mutation rejected"));
|
|
1683
|
-
}
|
|
1684
|
-
break;
|
|
1685
|
-
}
|
|
1686
|
-
case MSG.LIST_SCHEMAS: {
|
|
1687
|
-
const { schemas } = payload;
|
|
1688
|
-
listeners.onSchemas?.(schemas ?? []);
|
|
1689
|
-
break;
|
|
1690
|
-
}
|
|
1691
|
-
case MSG.RESUBSCRIBE: {
|
|
1692
|
-
if (this.ws?.readyState === WS_OPEN) this.resubscribeAll(this.ws);
|
|
1693
|
-
break;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
};
|
|
1698
|
-
|
|
1699
|
-
// src/client/storage/ScopeRegistry.tsx
|
|
1700
|
-
import { createContext, useContext, useCallback, useRef as useRef3, useMemo } from "react";
|
|
1701
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1702
|
-
var ScopeRegistryContext = createContext(null);
|
|
1703
|
-
function useScopeRegistry() {
|
|
1704
|
-
return useContext(ScopeRegistryContext);
|
|
1705
|
-
}
|
|
1706
|
-
function ScopeRegistryProvider({ children }) {
|
|
1707
|
-
const mapRef = useRef3(/* @__PURE__ */ new Map());
|
|
1708
|
-
const register = useCallback((scopeId, collections, entry) => {
|
|
1709
|
-
for (const name of collections) {
|
|
1710
|
-
mapRef.current.set(name, { scopeId, entry });
|
|
1711
|
-
}
|
|
1712
|
-
}, []);
|
|
1713
|
-
const unregister = useCallback((scopeId, collections) => {
|
|
1714
|
-
for (const name of collections) {
|
|
1715
|
-
const existing = mapRef.current.get(name);
|
|
1716
|
-
if (existing?.scopeId === scopeId) {
|
|
1717
|
-
mapRef.current.delete(name);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}, []);
|
|
1721
|
-
const resolve = useCallback((collection) => {
|
|
1722
|
-
return mapRef.current.get(collection)?.entry ?? null;
|
|
1723
|
-
}, []);
|
|
1724
|
-
const value = useMemo(() => ({ register, unregister, resolve }), [register, unregister, resolve]);
|
|
1725
|
-
return /* @__PURE__ */ jsx5(ScopeRegistryContext.Provider, { value, children });
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
// src/client/storage/context.tsx
|
|
1729
|
-
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1730
|
-
var RecordContext = createContext2(null);
|
|
1731
|
-
function useRecordContext() {
|
|
1732
|
-
const ctx = useContext2(RecordContext);
|
|
1733
|
-
if (!ctx) {
|
|
1734
|
-
throw new Error("useRecordContext must be used within a RecordProvider");
|
|
1735
|
-
}
|
|
1736
|
-
return ctx;
|
|
1737
|
-
}
|
|
1738
|
-
var RecordAuthContext = createContext2(null);
|
|
1739
|
-
function useRecordAuth() {
|
|
1740
|
-
return useContext2(RecordAuthContext);
|
|
1741
|
-
}
|
|
1742
|
-
function RecordProviderCore({
|
|
1743
|
-
roomId,
|
|
1744
|
-
wsUrl,
|
|
1745
|
-
children,
|
|
1746
|
-
fetchUser,
|
|
1747
|
-
allowAnonymous = false,
|
|
1748
|
-
getAuthToken: getAuthTokenProp,
|
|
1749
|
-
onPermissionError: onPermissionErrorProp,
|
|
1750
|
-
onValidationError: onValidationErrorProp
|
|
1751
|
-
}) {
|
|
1752
|
-
const [userProfile, setUserProfile] = useState3(null);
|
|
1753
|
-
const [userProfileLoading, setUserProfileLoading] = useState3(true);
|
|
1754
|
-
const [roomRole, setRoomRole] = useState3(null);
|
|
1755
|
-
const [allUsers, setAllUsers] = useState3([]);
|
|
1756
|
-
const [usersLoaded, setUsersLoaded] = useState3(false);
|
|
1757
|
-
const [status, setStatus] = useState3("connecting");
|
|
1758
|
-
const [ready, setReady] = useState3(false);
|
|
1759
|
-
const onPermissionErrorRef = useRef4(
|
|
1760
|
-
void 0
|
|
1761
|
-
);
|
|
1762
|
-
const onValidationErrorRef = useRef4(
|
|
1763
|
-
void 0
|
|
1764
|
-
);
|
|
1765
|
-
onPermissionErrorRef.current = onPermissionErrorProp;
|
|
1766
|
-
onValidationErrorRef.current = onValidationErrorProp;
|
|
1767
|
-
const storeRef = useRef4(new RecordStore());
|
|
1768
|
-
const socketRef = useRef4(null);
|
|
1769
|
-
const subscriptionsRef = useRef4(/* @__PURE__ */ new Map());
|
|
1770
|
-
const binaryHandlersRef = useRef4(/* @__PURE__ */ new Set());
|
|
1771
|
-
const yjsJoinHandlersRef = useRef4(/* @__PURE__ */ new Map());
|
|
1772
|
-
const userProfileRef = useRef4(null);
|
|
1773
|
-
userProfileRef.current = userProfile;
|
|
1774
|
-
const getAuthTokenRef = useRef4(getAuthTokenProp);
|
|
1775
|
-
getAuthTokenRef.current = getAuthTokenProp;
|
|
1776
|
-
const refetchUserProfile = useCallback2(async () => {
|
|
1777
|
-
try {
|
|
1778
|
-
const profile = await fetchUser();
|
|
1779
|
-
setUserProfile(profile);
|
|
1780
|
-
} catch (err) {
|
|
1781
|
-
console.error("[RecordProvider] Failed to refetch user profile:", err);
|
|
1782
|
-
}
|
|
1783
|
-
}, [fetchUser]);
|
|
1784
|
-
useEffect4(() => {
|
|
1785
|
-
let mounted = true;
|
|
1786
|
-
setUserProfileLoading(true);
|
|
1787
|
-
fetchUser().then((profile) => {
|
|
1788
|
-
if (mounted) setUserProfile(profile);
|
|
1789
|
-
}).finally(() => {
|
|
1790
|
-
if (mounted) setUserProfileLoading(false);
|
|
1791
|
-
});
|
|
1792
|
-
const interval = setInterval(() => {
|
|
1793
|
-
if (mounted)
|
|
1794
|
-
fetchUser().then((p) => {
|
|
1795
|
-
if (mounted) setUserProfile(p);
|
|
1796
|
-
}).catch(() => {
|
|
1797
|
-
});
|
|
1798
|
-
}, 3e4);
|
|
1799
|
-
return () => {
|
|
1800
|
-
mounted = false;
|
|
1801
|
-
clearInterval(interval);
|
|
1802
|
-
};
|
|
1803
|
-
}, [fetchUser]);
|
|
1804
|
-
const userProfileId = userProfile?.id ?? null;
|
|
1805
|
-
useEffect4(() => {
|
|
1806
|
-
if (userProfileLoading) return;
|
|
1807
|
-
const socket = new RecordSocket({
|
|
1808
|
-
roomId,
|
|
1809
|
-
store: storeRef.current,
|
|
1810
|
-
subscriptions: subscriptionsRef.current,
|
|
1811
|
-
binaryHandlers: binaryHandlersRef.current,
|
|
1812
|
-
yjsJoinHandlers: yjsJoinHandlersRef.current,
|
|
1813
|
-
getToken: () => (getAuthTokenRef.current ?? getAuthToken)(),
|
|
1814
|
-
wsUrl,
|
|
1815
|
-
log: (event, detail) => debugLog(`[WS] ${event}`, detail),
|
|
1816
|
-
listeners: {
|
|
1817
|
-
onStatus: setStatus,
|
|
1818
|
-
onReady: setReady,
|
|
1819
|
-
onRole: setRoomRole,
|
|
1820
|
-
onUsers: (users) => {
|
|
1821
|
-
setAllUsers(users);
|
|
1822
|
-
setUsersLoaded(true);
|
|
1823
|
-
},
|
|
1824
|
-
onPermissionError: (title, detail) => onPermissionErrorRef.current?.(title, detail),
|
|
1825
|
-
onValidationError: (title, detail) => onValidationErrorRef.current?.(title, detail)
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
socketRef.current = socket;
|
|
1829
|
-
if (userProfileId || allowAnonymous) {
|
|
1830
|
-
void socket.connect(userProfileId ?? "");
|
|
1831
|
-
}
|
|
1832
|
-
return () => {
|
|
1833
|
-
socketRef.current = null;
|
|
1834
|
-
socket.destroy();
|
|
1835
|
-
setStatus("connecting");
|
|
1836
|
-
setReady(false);
|
|
1837
|
-
};
|
|
1838
|
-
}, [roomId, wsUrl, userProfileId, userProfileLoading, allowAnonymous]);
|
|
1839
|
-
useEffect4(() => {
|
|
1840
|
-
const onVisible = () => {
|
|
1841
|
-
if (document.visibilityState === "visible" && !socketRef.current?.isOpen) {
|
|
1842
|
-
debugLog("[WS] Reconnecting after visibility change");
|
|
1843
|
-
socketRef.current?.resetBackoff();
|
|
1844
|
-
void socketRef.current?.connect();
|
|
1845
|
-
}
|
|
1846
|
-
};
|
|
1847
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
1848
|
-
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
1849
|
-
}, []);
|
|
1850
|
-
const sendMessage = useCallback2((message) => {
|
|
1851
|
-
socketRef.current?.sendMessage(message);
|
|
1852
|
-
}, []);
|
|
1853
|
-
const setUserRole = useCallback2(
|
|
1854
|
-
(userId, role) => {
|
|
1855
|
-
sendMessage({ type: MSG.SET_ROLE, payload: { userId, role } });
|
|
1856
|
-
},
|
|
1857
|
-
[sendMessage]
|
|
1858
|
-
);
|
|
1859
|
-
const requestUserList = useCallback2(() => {
|
|
1860
|
-
sendMessage({ type: MSG.USER_LIST, payload: {} });
|
|
1861
|
-
}, [sendMessage]);
|
|
1862
|
-
const registerSubscription = useCallback2((subscriptionId, queryKey) => {
|
|
1863
|
-
subscriptionsRef.current.set(subscriptionId, queryKey);
|
|
1864
|
-
}, []);
|
|
1865
|
-
const unregisterSubscription = useCallback2((subscriptionId) => {
|
|
1866
|
-
subscriptionsRef.current.delete(subscriptionId);
|
|
1867
|
-
}, []);
|
|
1868
|
-
const sendBinary = useCallback2((data) => {
|
|
1869
|
-
socketRef.current?.sendBinary(data);
|
|
1870
|
-
}, []);
|
|
1871
|
-
const onBinaryMessage = useCallback2((handler) => {
|
|
1872
|
-
binaryHandlersRef.current.add(handler);
|
|
1873
|
-
return () => {
|
|
1874
|
-
binaryHandlersRef.current.delete(handler);
|
|
1875
|
-
};
|
|
1876
|
-
}, []);
|
|
1877
|
-
const registerYjsJoinHandler = useCallback2(
|
|
1878
|
-
(docKey, handler) => {
|
|
1879
|
-
const handlers = yjsJoinHandlersRef.current;
|
|
1880
|
-
if (!handlers.has(docKey)) handlers.set(docKey, /* @__PURE__ */ new Set());
|
|
1881
|
-
handlers.get(docKey).add(handler);
|
|
1882
|
-
return () => {
|
|
1883
|
-
const set = handlers.get(docKey);
|
|
1884
|
-
if (set) {
|
|
1885
|
-
set.delete(handler);
|
|
1886
|
-
if (set.size === 0) handlers.delete(docKey);
|
|
1887
|
-
}
|
|
1888
|
-
};
|
|
1889
|
-
},
|
|
1890
|
-
[]
|
|
1891
|
-
);
|
|
1892
|
-
const sendConfirmed = useCallback2(
|
|
1893
|
-
(message, timeoutMs = 1e4) => {
|
|
1894
|
-
const socket = socketRef.current;
|
|
1895
|
-
if (!socket) return Promise.reject(new Error("WebSocket not connected"));
|
|
1896
|
-
return socket.sendConfirmed(message, timeoutMs);
|
|
1897
|
-
},
|
|
1898
|
-
[]
|
|
1899
|
-
);
|
|
1900
|
-
const value = useMemo2(
|
|
1901
|
-
() => ({
|
|
1902
|
-
store: storeRef.current,
|
|
1903
|
-
roomId,
|
|
1904
|
-
userProfile,
|
|
1905
|
-
userProfileLoading,
|
|
1906
|
-
refetchUserProfile,
|
|
1907
|
-
roomRole,
|
|
1908
|
-
allUsers,
|
|
1909
|
-
usersLoaded,
|
|
1910
|
-
status,
|
|
1911
|
-
ready,
|
|
1912
|
-
setUserRole,
|
|
1913
|
-
requestUserList,
|
|
1914
|
-
registerSubscription,
|
|
1915
|
-
unregisterSubscription,
|
|
1916
|
-
sendMessage,
|
|
1917
|
-
sendBinary,
|
|
1918
|
-
onBinaryMessage,
|
|
1919
|
-
registerYjsJoinHandler,
|
|
1920
|
-
sendConfirmed
|
|
1921
|
-
}),
|
|
1922
|
-
[
|
|
1923
|
-
roomId,
|
|
1924
|
-
userProfile,
|
|
1925
|
-
userProfileLoading,
|
|
1926
|
-
refetchUserProfile,
|
|
1927
|
-
roomRole,
|
|
1928
|
-
allUsers,
|
|
1929
|
-
usersLoaded,
|
|
1930
|
-
status,
|
|
1931
|
-
ready,
|
|
1932
|
-
setUserRole,
|
|
1933
|
-
requestUserList,
|
|
1934
|
-
registerSubscription,
|
|
1935
|
-
unregisterSubscription,
|
|
1936
|
-
sendMessage,
|
|
1937
|
-
sendBinary,
|
|
1938
|
-
onBinaryMessage,
|
|
1939
|
-
registerYjsJoinHandler,
|
|
1940
|
-
sendConfirmed
|
|
1941
|
-
]
|
|
1942
|
-
);
|
|
1943
|
-
const onPermissionError = useCallback2((title, detail) => {
|
|
1944
|
-
onPermissionErrorRef.current?.(title, detail);
|
|
1945
|
-
}, []);
|
|
1946
|
-
const onValidationError = useCallback2((title, detail) => {
|
|
1947
|
-
onValidationErrorRef.current?.(title, detail);
|
|
1948
|
-
}, []);
|
|
1949
|
-
const authValue = useMemo2(
|
|
1950
|
-
() => ({
|
|
1951
|
-
userProfile,
|
|
1952
|
-
userProfileLoading,
|
|
1953
|
-
refetchUserProfile,
|
|
1954
|
-
allowAnonymous,
|
|
1955
|
-
getAuthToken: getAuthTokenProp,
|
|
1956
|
-
onPermissionError,
|
|
1957
|
-
onValidationError
|
|
1958
|
-
}),
|
|
1959
|
-
[
|
|
1960
|
-
userProfile,
|
|
1961
|
-
userProfileLoading,
|
|
1962
|
-
refetchUserProfile,
|
|
1963
|
-
allowAnonymous,
|
|
1964
|
-
getAuthTokenProp,
|
|
1965
|
-
onPermissionError,
|
|
1966
|
-
onValidationError
|
|
1967
|
-
]
|
|
1968
|
-
);
|
|
1969
|
-
return /* @__PURE__ */ jsx6(RecordAuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx6(ScopeRegistryProvider, { children: /* @__PURE__ */ jsx6(RecordContext.Provider, { value, children }) }) });
|
|
1970
|
-
}
|
|
1971
|
-
function RecordProviderAuthOnly({
|
|
1972
|
-
children,
|
|
1973
|
-
fetchUser,
|
|
1974
|
-
allowAnonymous = false,
|
|
1975
|
-
getAuthToken: getAuthTokenProp,
|
|
1976
|
-
onPermissionError: onPermissionErrorProp,
|
|
1977
|
-
onValidationError: onValidationErrorProp
|
|
1978
|
-
}) {
|
|
1979
|
-
const [userProfile, setUserProfile] = useState3(null);
|
|
1980
|
-
const [userProfileLoading, setUserProfileLoading] = useState3(true);
|
|
1981
|
-
const onPermissionErrorRef = useRef4(onPermissionErrorProp);
|
|
1982
|
-
onPermissionErrorRef.current = onPermissionErrorProp;
|
|
1983
|
-
const onValidationErrorRef = useRef4(onValidationErrorProp);
|
|
1984
|
-
onValidationErrorRef.current = onValidationErrorProp;
|
|
1985
|
-
const onPermissionError = useCallback2((title, detail) => {
|
|
1986
|
-
onPermissionErrorRef.current?.(title, detail);
|
|
1987
|
-
}, []);
|
|
1988
|
-
const onValidationError = useCallback2((title, detail) => {
|
|
1989
|
-
onValidationErrorRef.current?.(title, detail);
|
|
1990
|
-
}, []);
|
|
1991
|
-
const refetchUserProfile = useCallback2(async () => {
|
|
1992
|
-
try {
|
|
1993
|
-
const profile = await fetchUser();
|
|
1994
|
-
setUserProfile(profile);
|
|
1995
|
-
} catch (err) {
|
|
1996
|
-
console.error("[RecordProvider] Failed to refetch user profile:", err);
|
|
1997
|
-
}
|
|
1998
|
-
}, [fetchUser]);
|
|
1999
|
-
useEffect4(() => {
|
|
2000
|
-
let mounted = true;
|
|
2001
|
-
setUserProfileLoading(true);
|
|
2002
|
-
fetchUser().then((profile) => {
|
|
2003
|
-
if (mounted) setUserProfile(profile);
|
|
2004
|
-
}).finally(() => {
|
|
2005
|
-
if (mounted) setUserProfileLoading(false);
|
|
2006
|
-
});
|
|
2007
|
-
const interval = setInterval(() => {
|
|
2008
|
-
if (mounted) {
|
|
2009
|
-
fetchUser().then((p) => {
|
|
2010
|
-
if (mounted) setUserProfile(p);
|
|
2011
|
-
}).catch(() => {
|
|
2012
|
-
});
|
|
2013
|
-
}
|
|
2014
|
-
}, 3e4);
|
|
2015
|
-
return () => {
|
|
2016
|
-
mounted = false;
|
|
2017
|
-
clearInterval(interval);
|
|
2018
|
-
};
|
|
2019
|
-
}, [fetchUser]);
|
|
2020
|
-
const authValue = useMemo2(
|
|
2021
|
-
() => ({
|
|
2022
|
-
userProfile,
|
|
2023
|
-
userProfileLoading,
|
|
2024
|
-
refetchUserProfile,
|
|
2025
|
-
allowAnonymous,
|
|
2026
|
-
getAuthToken: getAuthTokenProp,
|
|
2027
|
-
onPermissionError,
|
|
2028
|
-
onValidationError
|
|
2029
|
-
}),
|
|
2030
|
-
[
|
|
2031
|
-
userProfile,
|
|
2032
|
-
userProfileLoading,
|
|
2033
|
-
refetchUserProfile,
|
|
2034
|
-
allowAnonymous,
|
|
2035
|
-
getAuthTokenProp,
|
|
2036
|
-
onPermissionError,
|
|
2037
|
-
onValidationError
|
|
2038
|
-
]
|
|
2039
|
-
);
|
|
2040
|
-
return /* @__PURE__ */ jsx6(RecordAuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx6(ScopeRegistryProvider, { children }) });
|
|
2041
|
-
}
|
|
2042
|
-
var seenWriteErrors = /* @__PURE__ */ new Set();
|
|
2043
|
-
function defaultOnWriteError(error) {
|
|
2044
|
-
const key = JSON.stringify([error.kind, error.title, error.detail]);
|
|
2045
|
-
if (seenWriteErrors.has(key)) return;
|
|
2046
|
-
if (seenWriteErrors.size >= 100) seenWriteErrors.clear();
|
|
2047
|
-
seenWriteErrors.add(key);
|
|
2048
|
-
console.error(
|
|
2049
|
-
`[deepspace] Write rejected \u2014 ${error.title}: ${error.detail}
|
|
2050
|
-
This error reached no UI. Pass onWriteError to <RecordProvider> (e.g. wire it to a toast) so users see rejected writes. (Further repeats of this error are suppressed.)`
|
|
2051
|
-
);
|
|
2052
|
-
}
|
|
2053
|
-
var warnedIgnoredSchemas = false;
|
|
2054
|
-
var warnedSignedOutNull = false;
|
|
2055
|
-
function SignedOutDiagnostic() {
|
|
2056
|
-
return /* @__PURE__ */ jsxs4(
|
|
2057
|
-
"div",
|
|
2058
|
-
{
|
|
2059
|
-
role: "alert",
|
|
2060
|
-
"data-deepspace-diagnostic": "signed-out",
|
|
2061
|
-
style: {
|
|
2062
|
-
margin: "24px auto",
|
|
2063
|
-
maxWidth: 560,
|
|
2064
|
-
padding: "16px 20px",
|
|
2065
|
-
border: "1px solid #d97706",
|
|
2066
|
-
borderRadius: 8,
|
|
2067
|
-
background: "#fffbeb",
|
|
2068
|
-
color: "#78350f",
|
|
2069
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2070
|
-
fontSize: 13,
|
|
2071
|
-
lineHeight: 1.5
|
|
2072
|
-
},
|
|
2073
|
-
children: [
|
|
2074
|
-
/* @__PURE__ */ jsx6("strong", { children: "DeepSpace: nothing rendered because you're signed out." }),
|
|
2075
|
-
/* @__PURE__ */ jsxs4("p", { style: { margin: "8px 0 0" }, children: [
|
|
2076
|
-
"This page is inside a ",
|
|
2077
|
-
"<RecordProvider>",
|
|
2078
|
-
" without ",
|
|
2079
|
-
/* @__PURE__ */ jsx6("code", { children: "allowAnonymous" }),
|
|
2080
|
-
", which renders nothing for signed-out visitors. Either add",
|
|
2081
|
-
" ",
|
|
2082
|
-
/* @__PURE__ */ jsx6("code", { children: "<RecordProvider allowAnonymous>" }),
|
|
2083
|
-
" to show the page publicly, or require sign-in before this point (e.g. a protected route / AuthGate)."
|
|
2084
|
-
] }),
|
|
2085
|
-
/* @__PURE__ */ jsx6("p", { style: { margin: "8px 0 0", opacity: 0.8 }, children: "This notice only appears on localhost \u2014 a deployed app renders nothing here." })
|
|
2086
|
-
]
|
|
1836
|
+
// ── message dispatch ──────────────────────────────────────────────────────
|
|
1837
|
+
resubscribeAll(ws) {
|
|
1838
|
+
for (const [subscriptionId, queryKey] of this.subscriptions) {
|
|
1839
|
+
try {
|
|
1840
|
+
const query = JSON.parse(queryKey);
|
|
1841
|
+
ws.send(JSON.stringify({ type: MSG.SUBSCRIBE, payload: { subscriptionId, query } }));
|
|
1842
|
+
} catch {
|
|
1843
|
+
}
|
|
2087
1844
|
}
|
|
2088
|
-
);
|
|
2089
|
-
}
|
|
2090
|
-
function RecordProvider({
|
|
2091
|
-
roomId,
|
|
2092
|
-
schemas,
|
|
2093
|
-
wsUrl,
|
|
2094
|
-
children,
|
|
2095
|
-
allowAnonymous = false,
|
|
2096
|
-
getAuthToken: getAuthTokenProp,
|
|
2097
|
-
onWriteError = defaultOnWriteError
|
|
2098
|
-
}) {
|
|
2099
|
-
const { isLoaded, isSignedIn } = useAuth();
|
|
2100
|
-
if (schemas && roomId && isLocalDevHost() && !warnedIgnoredSchemas) {
|
|
2101
|
-
warnedIgnoredSchemas = true;
|
|
2102
|
-
console.warn(
|
|
2103
|
-
"[deepspace] RecordProvider received a `schemas` prop alongside `roomId`, but the single-scope provider ignores it \u2014 schemas are resolved server-side in this mode. Remove the `schemas` prop, or adopt the multi-scope shape and pass schemas to <RecordScope>."
|
|
2104
|
-
);
|
|
2105
1845
|
}
|
|
2106
|
-
|
|
2107
|
-
if (
|
|
2108
|
-
|
|
2109
|
-
|
|
1846
|
+
handleMessage(event) {
|
|
1847
|
+
if (event.data instanceof ArrayBuffer) {
|
|
1848
|
+
this.binaryHandlers.forEach((h) => h(event.data));
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
let msg;
|
|
2110
1852
|
try {
|
|
2111
|
-
|
|
2112
|
-
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
2113
|
-
return {
|
|
2114
|
-
id: payload.sub,
|
|
2115
|
-
name: payload.name ?? "",
|
|
2116
|
-
email: payload.email ?? "",
|
|
2117
|
-
imageUrl: payload.image ?? void 0
|
|
2118
|
-
};
|
|
1853
|
+
msg = JSON.parse(event.data);
|
|
2119
1854
|
} catch {
|
|
2120
|
-
|
|
1855
|
+
console.error("[RecordSocket] Failed to parse message");
|
|
1856
|
+
return;
|
|
2121
1857
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
if (isLocalDevHost()) {
|
|
2133
|
-
if (!warnedSignedOutNull) {
|
|
2134
|
-
warnedSignedOutNull = true;
|
|
2135
|
-
console.warn(
|
|
2136
|
-
"[deepspace] RecordProvider rendered nothing: signed out and allowAnonymous is off. Add `allowAnonymous` to show this page to signed-out visitors, or require sign-in before this point."
|
|
2137
|
-
);
|
|
1858
|
+
const { type, payload } = msg;
|
|
1859
|
+
const { store, listeners } = this.config;
|
|
1860
|
+
switch (type) {
|
|
1861
|
+
case MSG.USER_INFO: {
|
|
1862
|
+
const { role } = payload;
|
|
1863
|
+
listeners.onRole(role);
|
|
1864
|
+
listeners.onReady(true);
|
|
1865
|
+
this.reconnectAttempt = 0;
|
|
1866
|
+
this.sendMessage({ type: MSG.USER_LIST, payload: {} });
|
|
1867
|
+
break;
|
|
2138
1868
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
{
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
1869
|
+
case MSG.USER_LIST:
|
|
1870
|
+
listeners.onUsers(payload.users);
|
|
1871
|
+
break;
|
|
1872
|
+
case MSG.QUERY_RESULT: {
|
|
1873
|
+
const { subscriptionId, records } = payload;
|
|
1874
|
+
const queryKey = this.subscriptions.get(subscriptionId);
|
|
1875
|
+
if (queryKey !== void 0) store.setQueryResult(queryKey, records);
|
|
1876
|
+
break;
|
|
1877
|
+
}
|
|
1878
|
+
case MSG.RECORD_CHANGE: {
|
|
1879
|
+
const { collection, record, changeType } = payload;
|
|
1880
|
+
for (const queryKey of this.subscriptions.values()) {
|
|
1881
|
+
try {
|
|
1882
|
+
const query = JSON.parse(queryKey);
|
|
1883
|
+
if (query.collection !== collection) continue;
|
|
1884
|
+
const matches = recordMatchesWhere(record, query.where);
|
|
1885
|
+
const exists = store.hasRecord(queryKey, record.recordId);
|
|
1886
|
+
if (changeType === "delete") {
|
|
1887
|
+
if (exists) store.applyChange(queryKey, record, "delete");
|
|
1888
|
+
} else if (changeType === "create") {
|
|
1889
|
+
if (matches) store.applyChange(queryKey, record, "create");
|
|
1890
|
+
} else {
|
|
1891
|
+
if (matches && exists) store.applyChange(queryKey, record, "update");
|
|
1892
|
+
else if (matches && !exists) store.applyChange(queryKey, record, "create");
|
|
1893
|
+
else if (!matches && exists) store.applyChange(queryKey, record, "delete");
|
|
1894
|
+
}
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
case MSG.ERROR: {
|
|
1901
|
+
const { subscriptionId, error } = payload;
|
|
1902
|
+
if (subscriptionId) {
|
|
1903
|
+
const queryKey = this.subscriptions.get(subscriptionId);
|
|
1904
|
+
if (queryKey !== void 0) store.setError(queryKey, error);
|
|
1905
|
+
} else {
|
|
1906
|
+
const parsed = parseServerError(error);
|
|
1907
|
+
if (parsed.isPermissionError) listeners.onPermissionError?.(parsed.title, parsed.detail);
|
|
1908
|
+
else listeners.onValidationError?.(parsed.title, parsed.detail);
|
|
1909
|
+
}
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
case MSG.YJS_JOIN: {
|
|
1913
|
+
const { collection, recordId, fieldName, canWrite } = payload;
|
|
1914
|
+
const docKey = `${collection}:${recordId}:${fieldName}`;
|
|
1915
|
+
this.yjsJoinHandlers.get(docKey)?.forEach((h) => h(canWrite));
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
case MSG.ACK: {
|
|
1919
|
+
const { requestId, success, error, ...rest } = payload;
|
|
1920
|
+
const pending = this.pendingRequests.get(requestId);
|
|
1921
|
+
if (pending) {
|
|
1922
|
+
clearTimeout(pending.timer);
|
|
1923
|
+
this.pendingRequests.delete(requestId);
|
|
1924
|
+
if (success) pending.resolve(rest);
|
|
1925
|
+
else pending.reject(new Error(error || "Mutation rejected"));
|
|
1926
|
+
}
|
|
1927
|
+
break;
|
|
1928
|
+
}
|
|
1929
|
+
case MSG.LIST_SCHEMAS: {
|
|
1930
|
+
const { schemas } = payload;
|
|
1931
|
+
listeners.onSchemas?.(schemas ?? []);
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
case MSG.RESUBSCRIBE: {
|
|
1935
|
+
if (this.ws?.readyState === WS_OPEN) this.resubscribeAll(this.ws);
|
|
1936
|
+
break;
|
|
2157
1937
|
}
|
|
2158
|
-
);
|
|
2159
|
-
}
|
|
2160
|
-
return /* @__PURE__ */ jsx6(
|
|
2161
|
-
RecordProviderAuthOnly,
|
|
2162
|
-
{
|
|
2163
|
-
fetchUser,
|
|
2164
|
-
allowAnonymous,
|
|
2165
|
-
getAuthToken: getAuthTokenFn,
|
|
2166
|
-
onPermissionError,
|
|
2167
|
-
onValidationError,
|
|
2168
|
-
children
|
|
2169
1938
|
}
|
|
2170
|
-
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
// src/client/storage/RecordScope.tsx
|
|
2174
|
-
import { useEffect as useEffect5, useState as useState4, useCallback as useCallback3, useRef as useRef5, useMemo as useMemo3 } from "react";
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
2175
1941
|
|
|
2176
1942
|
// src/client/storage/ws-log.ts
|
|
2177
1943
|
var activeCount = 0;
|
|
@@ -2186,25 +1952,14 @@ import { Fragment as Fragment6, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-run
|
|
|
2186
1952
|
function HeadlessScope({
|
|
2187
1953
|
roomId,
|
|
2188
1954
|
schemas,
|
|
2189
|
-
appId,
|
|
2190
1955
|
wsUrl,
|
|
2191
1956
|
wsPathPrefix
|
|
2192
1957
|
}) {
|
|
2193
|
-
return /* @__PURE__ */ jsx7(
|
|
2194
|
-
ScopeConnection,
|
|
2195
|
-
{
|
|
2196
|
-
roomId,
|
|
2197
|
-
schemas,
|
|
2198
|
-
appId,
|
|
2199
|
-
wsUrl,
|
|
2200
|
-
wsPathPrefix
|
|
2201
|
-
}
|
|
2202
|
-
);
|
|
1958
|
+
return /* @__PURE__ */ jsx7(ScopeConnection, { roomId, schemas, wsUrl, wsPathPrefix });
|
|
2203
1959
|
}
|
|
2204
1960
|
function ScopeConnection({
|
|
2205
1961
|
roomId,
|
|
2206
1962
|
schemas,
|
|
2207
|
-
appId,
|
|
2208
1963
|
children,
|
|
2209
1964
|
wsUrl,
|
|
2210
1965
|
wsPathPrefix = "/ws",
|
|
@@ -2248,7 +2003,6 @@ function ScopeConnection({
|
|
|
2248
2003
|
getToken: () => (getAuthTokenRef.current ?? getAuthToken)(),
|
|
2249
2004
|
wsUrl,
|
|
2250
2005
|
wsPathPrefix,
|
|
2251
|
-
extraParams: { appId },
|
|
2252
2006
|
log: (event) => wsLog(event, roomId),
|
|
2253
2007
|
listeners: {
|
|
2254
2008
|
onStatus: setStatus,
|
|
@@ -2273,7 +2027,7 @@ function ScopeConnection({
|
|
|
2273
2027
|
setStatus("connecting");
|
|
2274
2028
|
setReady(false);
|
|
2275
2029
|
};
|
|
2276
|
-
}, [roomId, wsUrl, wsPathPrefix,
|
|
2030
|
+
}, [roomId, wsUrl, wsPathPrefix, userProfileId, userProfileLoading, allowAnonymous]);
|
|
2277
2031
|
useEffect5(() => {
|
|
2278
2032
|
const onVisible = () => {
|
|
2279
2033
|
if (document.visibilityState === "visible" && !socketRef.current?.isOpen) {
|
|
@@ -2430,7 +2184,6 @@ function RecordScope({
|
|
|
2430
2184
|
roomId,
|
|
2431
2185
|
schemas,
|
|
2432
2186
|
children,
|
|
2433
|
-
appId,
|
|
2434
2187
|
sharedScopes,
|
|
2435
2188
|
wsUrl,
|
|
2436
2189
|
wsPathPrefix = "/ws",
|
|
@@ -2442,7 +2195,6 @@ function RecordScope({
|
|
|
2442
2195
|
{
|
|
2443
2196
|
roomId: shared.roomId,
|
|
2444
2197
|
schemas: shared.schemas,
|
|
2445
|
-
appId,
|
|
2446
2198
|
wsUrl,
|
|
2447
2199
|
wsPathPrefix
|
|
2448
2200
|
},
|
|
@@ -2453,7 +2205,6 @@ function RecordScope({
|
|
|
2453
2205
|
{
|
|
2454
2206
|
roomId,
|
|
2455
2207
|
schemas,
|
|
2456
|
-
appId,
|
|
2457
2208
|
wsUrl,
|
|
2458
2209
|
wsPathPrefix,
|
|
2459
2210
|
isolated,
|
|
@@ -2491,7 +2242,7 @@ function useQuery(collection, options) {
|
|
|
2491
2242
|
const unregisterSub = preferLocal ? recordCtx.unregisterSubscription : scopeEntry?.unregisterSubscription ?? recordCtx?.unregisterSubscription;
|
|
2492
2243
|
if (!store || !sendMessage || !registerSub || !unregisterSub) {
|
|
2493
2244
|
throw new Error(
|
|
2494
|
-
`useQuery('${collection}'): No scope found. Wrap
|
|
2245
|
+
`useQuery('${collection}'): No scope found. Wrap this hook in a RecordScope that registers the collection.`
|
|
2495
2246
|
);
|
|
2496
2247
|
}
|
|
2497
2248
|
const optionsKey = JSON.stringify(options ?? null);
|
|
@@ -2552,7 +2303,7 @@ function useMutations(collection) {
|
|
|
2552
2303
|
const sendConfirmed = preferLocal ? recordCtx.sendConfirmed : scopeEntry?.sendConfirmed ?? recordCtx?.sendConfirmed;
|
|
2553
2304
|
if (!sendMessage || !sendConfirmed) {
|
|
2554
2305
|
throw new Error(
|
|
2555
|
-
`useMutations('${collection}'): No scope found. Wrap
|
|
2306
|
+
`useMutations('${collection}'): No scope found. Wrap this hook in a RecordScope that registers the collection.`
|
|
2556
2307
|
);
|
|
2557
2308
|
}
|
|
2558
2309
|
const create = useCallback5(
|
|
@@ -2619,7 +2370,7 @@ function useUsers() {
|
|
|
2619
2370
|
}
|
|
2620
2371
|
|
|
2621
2372
|
// src/client/storage/hooks/useYjs.ts
|
|
2622
|
-
import { useState as useState5, useEffect as useEffect8, useCallback as useCallback6, useRef as useRef7 } from "react";
|
|
2373
|
+
import { useState as useState5, useEffect as useEffect8, useLayoutEffect, useCallback as useCallback6, useRef as useRef7, useMemo as useMemo8 } from "react";
|
|
2623
2374
|
import * as Y2 from "yjs";
|
|
2624
2375
|
function useYjsField(collection, recordId, fieldName) {
|
|
2625
2376
|
const { sendMessage, sendBinary, onBinaryMessage, registerYjsJoinHandler, ready } = useRecordContext();
|
|
@@ -2627,17 +2378,23 @@ function useYjsField(collection, recordId, fieldName) {
|
|
|
2627
2378
|
const [canWrite, setCanWrite] = useState5(false);
|
|
2628
2379
|
const [updateCount, setUpdateCount] = useState5(0);
|
|
2629
2380
|
const canWriteRef = useRef7(false);
|
|
2630
|
-
const docRef = useRef7(null);
|
|
2631
|
-
if (!docRef.current) {
|
|
2632
|
-
docRef.current = new Y2.Doc();
|
|
2633
|
-
}
|
|
2634
|
-
const doc = docRef.current;
|
|
2635
|
-
const awarenessRef = useRef7(null);
|
|
2636
|
-
if (!awarenessRef.current) {
|
|
2637
|
-
awarenessRef.current = new Awareness(doc);
|
|
2638
|
-
}
|
|
2639
|
-
const awareness = awarenessRef.current;
|
|
2640
2381
|
const docKey = `${collection}:${recordId}:${fieldName}`;
|
|
2382
|
+
const { doc, awareness } = useMemo8(() => {
|
|
2383
|
+
const nextDoc = new Y2.Doc();
|
|
2384
|
+
return { doc: nextDoc, awareness: new Awareness(nextDoc) };
|
|
2385
|
+
}, [docKey]);
|
|
2386
|
+
useEffect8(() => {
|
|
2387
|
+
return () => {
|
|
2388
|
+
awareness.destroy();
|
|
2389
|
+
doc.destroy();
|
|
2390
|
+
};
|
|
2391
|
+
}, [awareness, doc]);
|
|
2392
|
+
useLayoutEffect(() => {
|
|
2393
|
+
setSynced(false);
|
|
2394
|
+
setCanWrite(false);
|
|
2395
|
+
setUpdateCount(0);
|
|
2396
|
+
canWriteRef.current = false;
|
|
2397
|
+
}, [docKey]);
|
|
2641
2398
|
useEffect8(() => {
|
|
2642
2399
|
canWriteRef.current = canWrite;
|
|
2643
2400
|
}, [canWrite]);
|
|
@@ -2740,40 +2497,38 @@ function useYjsField(collection, recordId, fieldName) {
|
|
|
2740
2497
|
}
|
|
2741
2498
|
function useYjsText(collection, recordId, fieldName) {
|
|
2742
2499
|
const { doc, synced, canWrite, updateCount } = useYjsField(collection, recordId, fieldName);
|
|
2743
|
-
const
|
|
2744
|
-
const
|
|
2745
|
-
const
|
|
2500
|
+
const yText = useMemo8(() => doc.getText(fieldName), [doc, fieldName]);
|
|
2501
|
+
const [textState, setTextState] = useState5(() => ({ doc, value: "" }));
|
|
2502
|
+
const text = textState.doc === doc ? textState.value : "";
|
|
2746
2503
|
useEffect8(() => {
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
isLocalUpdateRef.current = false;
|
|
2754
|
-
}, [synced, updateCount]);
|
|
2504
|
+
if (!synced) return;
|
|
2505
|
+
const value = yText.toString();
|
|
2506
|
+
setTextState(
|
|
2507
|
+
(current) => current.doc === doc && current.value === value ? current : { doc, value }
|
|
2508
|
+
);
|
|
2509
|
+
}, [doc, yText, synced, updateCount]);
|
|
2755
2510
|
const setText = useCallback6(
|
|
2756
2511
|
(value) => {
|
|
2757
|
-
setTextState(
|
|
2758
|
-
|
|
2759
|
-
|
|
2512
|
+
setTextState(
|
|
2513
|
+
(current) => current.doc === doc && current.value === value ? current : { doc, value }
|
|
2514
|
+
);
|
|
2515
|
+
if (canWrite) {
|
|
2760
2516
|
doc.transact(() => {
|
|
2761
|
-
const yText = yTextRef.current;
|
|
2762
2517
|
yText.delete(0, yText.length);
|
|
2763
2518
|
yText.insert(0, value);
|
|
2764
2519
|
});
|
|
2765
2520
|
}
|
|
2766
2521
|
},
|
|
2767
|
-
[doc, canWrite]
|
|
2522
|
+
[doc, yText, canWrite]
|
|
2768
2523
|
);
|
|
2769
2524
|
return { text, setText, synced, canWrite };
|
|
2770
2525
|
}
|
|
2771
2526
|
|
|
2772
2527
|
// src/client/storage/hooks/useUserLookup.ts
|
|
2773
|
-
import { useMemo as
|
|
2528
|
+
import { useMemo as useMemo9, useCallback as useCallback7 } from "react";
|
|
2774
2529
|
function useUserLookup() {
|
|
2775
2530
|
const { users, usersLoaded } = useUsers();
|
|
2776
|
-
const userMap =
|
|
2531
|
+
const userMap = useMemo9(() => {
|
|
2777
2532
|
const map = /* @__PURE__ */ new Map();
|
|
2778
2533
|
for (const user of users) {
|
|
2779
2534
|
map.set(user.id, {
|
|
@@ -2855,23 +2610,50 @@ function usePresence(opts) {
|
|
|
2855
2610
|
}
|
|
2856
2611
|
|
|
2857
2612
|
// src/client/storage/hooks/useYjsRoom.ts
|
|
2858
|
-
import { useState as useState7, useEffect as useEffect10, useCallback as useCallback9, useRef as useRef8, useMemo as
|
|
2613
|
+
import { useState as useState7, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useCallback as useCallback9, useRef as useRef8, useMemo as useMemo10 } from "react";
|
|
2859
2614
|
import * as Y3 from "yjs";
|
|
2615
|
+
var UPDATE_BATCH_DELAY_MS = 16;
|
|
2860
2616
|
function useYjsRoom(docId, fieldName) {
|
|
2861
|
-
const [
|
|
2617
|
+
const [syncedDocId, setSyncedDocId] = useState7(null);
|
|
2862
2618
|
const [canWrite, setCanWrite] = useState7(false);
|
|
2619
|
+
const [writeAuthResolved, setWriteAuthResolved] = useState7(false);
|
|
2863
2620
|
const [text, setTextState] = useState7("");
|
|
2864
2621
|
const [, setUpdateCount] = useState7(0);
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
const awareness = awarenessRef.current;
|
|
2622
|
+
const synced = syncedDocId === docId;
|
|
2623
|
+
const { doc, awareness } = useMemo10(() => {
|
|
2624
|
+
const nextDoc = new Y3.Doc();
|
|
2625
|
+
return { doc: nextDoc, awareness: new Awareness(nextDoc) };
|
|
2626
|
+
}, [docId]);
|
|
2871
2627
|
const wsRef = useRef8(null);
|
|
2872
2628
|
const isLocalRef = useRef8(false);
|
|
2873
2629
|
const applyingRemoteAwarenessRef = useRef8(false);
|
|
2874
|
-
const
|
|
2630
|
+
const pendingUpdateRef = useRef8(null);
|
|
2631
|
+
const updateBatchTimerRef = useRef8(null);
|
|
2632
|
+
const yText = useMemo10(() => doc.getText(fieldName), [doc, fieldName]);
|
|
2633
|
+
const flushPendingUpdate = useCallback9((socket = wsRef.current) => {
|
|
2634
|
+
if (updateBatchTimerRef.current) clearTimeout(updateBatchTimerRef.current);
|
|
2635
|
+
updateBatchTimerRef.current = null;
|
|
2636
|
+
const update = pendingUpdateRef.current;
|
|
2637
|
+
pendingUpdateRef.current = null;
|
|
2638
|
+
if (!update || !socket || socket.readyState !== WebSocket.OPEN) return;
|
|
2639
|
+
const enc = createEncoder();
|
|
2640
|
+
writeVarUint(enc, MSG_SYNC);
|
|
2641
|
+
writeVarUint(enc, MSG_SYNC_UPDATE);
|
|
2642
|
+
writeVarUint8Array(enc, update);
|
|
2643
|
+
socket.send(toUint8Array(enc).buffer);
|
|
2644
|
+
}, []);
|
|
2645
|
+
useEffect10(() => {
|
|
2646
|
+
return () => {
|
|
2647
|
+
awareness.destroy();
|
|
2648
|
+
doc.destroy();
|
|
2649
|
+
};
|
|
2650
|
+
}, [awareness, doc]);
|
|
2651
|
+
useLayoutEffect2(() => {
|
|
2652
|
+
setSyncedDocId(null);
|
|
2653
|
+
setCanWrite(false);
|
|
2654
|
+
setWriteAuthResolved(false);
|
|
2655
|
+
setTextState("");
|
|
2656
|
+
}, [docId]);
|
|
2875
2657
|
useEffect10(() => {
|
|
2876
2658
|
const observer = () => {
|
|
2877
2659
|
if (isLocalRef.current) return;
|
|
@@ -2884,8 +2666,15 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2884
2666
|
let ws = null;
|
|
2885
2667
|
let reconnectTimer = null;
|
|
2886
2668
|
let alive = true;
|
|
2669
|
+
const renewLocalAwareness = () => {
|
|
2670
|
+
const localState = awareness.getLocalState();
|
|
2671
|
+
if (localState) awareness.setLocalState(localState);
|
|
2672
|
+
};
|
|
2887
2673
|
const connect = async () => {
|
|
2888
2674
|
if (!alive) return;
|
|
2675
|
+
setCanWrite(false);
|
|
2676
|
+
setWriteAuthResolved(false);
|
|
2677
|
+
renewLocalAwareness();
|
|
2889
2678
|
const token = await getAuthToken();
|
|
2890
2679
|
if (!alive) return;
|
|
2891
2680
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -2898,13 +2687,17 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2898
2687
|
wsRef.current = ws;
|
|
2899
2688
|
ws.onopen = () => {
|
|
2900
2689
|
wsLog("connected", `yjs:${docId}`);
|
|
2901
|
-
|
|
2690
|
+
setSyncedDocId(null);
|
|
2691
|
+
renewLocalAwareness();
|
|
2902
2692
|
};
|
|
2903
2693
|
ws.onmessage = (event) => {
|
|
2904
2694
|
if (typeof event.data === "string") {
|
|
2905
2695
|
try {
|
|
2906
2696
|
const msg = JSON.parse(event.data);
|
|
2907
|
-
if (msg.type === "auth" && typeof msg.canWrite === "boolean")
|
|
2697
|
+
if (msg.type === "auth" && typeof msg.canWrite === "boolean") {
|
|
2698
|
+
setCanWrite(msg.canWrite);
|
|
2699
|
+
setWriteAuthResolved(true);
|
|
2700
|
+
}
|
|
2908
2701
|
} catch {
|
|
2909
2702
|
}
|
|
2910
2703
|
return;
|
|
@@ -2923,13 +2716,13 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2923
2716
|
writeVarUint(enc, MSG_SYNC_STEP2);
|
|
2924
2717
|
writeVarUint8Array(enc, diff);
|
|
2925
2718
|
ws?.send(toUint8Array(enc).buffer);
|
|
2926
|
-
|
|
2719
|
+
setSyncedDocId(docId);
|
|
2927
2720
|
break;
|
|
2928
2721
|
}
|
|
2929
2722
|
case MSG_SYNC_STEP2: {
|
|
2930
2723
|
Y3.applyUpdate(doc, payload, "server");
|
|
2931
2724
|
setTextState(yText.toString());
|
|
2932
|
-
|
|
2725
|
+
setSyncedDocId(docId);
|
|
2933
2726
|
break;
|
|
2934
2727
|
}
|
|
2935
2728
|
case MSG_SYNC_UPDATE: {
|
|
@@ -2944,6 +2737,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2944
2737
|
applyingRemoteAwarenessRef.current = true;
|
|
2945
2738
|
try {
|
|
2946
2739
|
handleAwarenessMessage(awareness, data);
|
|
2740
|
+
} catch {
|
|
2947
2741
|
} finally {
|
|
2948
2742
|
applyingRemoteAwarenessRef.current = false;
|
|
2949
2743
|
}
|
|
@@ -2952,7 +2746,9 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2952
2746
|
ws.onclose = () => {
|
|
2953
2747
|
wsLog("disconnected", `yjs:${docId}`);
|
|
2954
2748
|
wsRef.current = null;
|
|
2955
|
-
|
|
2749
|
+
setSyncedDocId(null);
|
|
2750
|
+
setCanWrite(false);
|
|
2751
|
+
setWriteAuthResolved(false);
|
|
2956
2752
|
if (alive) reconnectTimer = setTimeout(connect, 1e3);
|
|
2957
2753
|
};
|
|
2958
2754
|
ws.onerror = () => ws?.close();
|
|
@@ -2962,6 +2758,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2962
2758
|
wsLog("closing", `yjs:${docId}`);
|
|
2963
2759
|
alive = false;
|
|
2964
2760
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2761
|
+
flushPendingUpdate(ws);
|
|
2965
2762
|
if (ws) {
|
|
2966
2763
|
ws.onclose = null;
|
|
2967
2764
|
ws.onmessage = null;
|
|
@@ -2970,7 +2767,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2970
2767
|
}
|
|
2971
2768
|
wsRef.current = null;
|
|
2972
2769
|
};
|
|
2973
|
-
}, [doc, awareness, docId, yText]);
|
|
2770
|
+
}, [doc, awareness, docId, flushPendingUpdate, yText]);
|
|
2974
2771
|
useEffect10(() => {
|
|
2975
2772
|
const handler = ({
|
|
2976
2773
|
added,
|
|
@@ -2995,17 +2792,15 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2995
2792
|
if (origin === "server") return;
|
|
2996
2793
|
const ws = wsRef.current;
|
|
2997
2794
|
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
writeVarUint(enc, MSG_SYNC_UPDATE);
|
|
3001
|
-
writeVarUint8Array(enc, update);
|
|
3002
|
-
ws.send(toUint8Array(enc).buffer);
|
|
2795
|
+
pendingUpdateRef.current = pendingUpdateRef.current ? Y3.mergeUpdates([pendingUpdateRef.current, update]) : update;
|
|
2796
|
+
updateBatchTimerRef.current ??= setTimeout(flushPendingUpdate, UPDATE_BATCH_DELAY_MS);
|
|
3003
2797
|
};
|
|
3004
2798
|
doc.on("update", handler);
|
|
3005
2799
|
return () => {
|
|
3006
2800
|
doc.off("update", handler);
|
|
2801
|
+
flushPendingUpdate();
|
|
3007
2802
|
};
|
|
3008
|
-
}, [doc]);
|
|
2803
|
+
}, [doc, flushPendingUpdate]);
|
|
3009
2804
|
const setText = useCallback9(
|
|
3010
2805
|
(value) => {
|
|
3011
2806
|
setTextState(value);
|
|
@@ -3019,7 +2814,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
3019
2814
|
},
|
|
3020
2815
|
[doc, yText, canWrite]
|
|
3021
2816
|
);
|
|
3022
|
-
return { doc, awareness, text, setText, synced, canWrite };
|
|
2817
|
+
return { doc, awareness, text, setText, synced, canWrite, writeAuthResolved };
|
|
3023
2818
|
}
|
|
3024
2819
|
|
|
3025
2820
|
// src/client/storage/hooks/useGameRoom.ts
|
|
@@ -3568,17 +3363,20 @@ function toConnectionStatus(queryStatus) {
|
|
|
3568
3363
|
}
|
|
3569
3364
|
|
|
3570
3365
|
// src/client/storage/useR2Files.ts
|
|
3571
|
-
import { useCallback as useCallback15, useState as useState13, useMemo as
|
|
3366
|
+
import { useCallback as useCallback15, useState as useState13, useMemo as useMemo11 } from "react";
|
|
3572
3367
|
function resolveFile(fileOrKey) {
|
|
3573
3368
|
if (typeof fileOrKey === "string") {
|
|
3574
3369
|
return { key: fileOrKey, name: void 0 };
|
|
3575
3370
|
}
|
|
3576
3371
|
return { key: fileOrKey.key, name: fileOrKey.originalName };
|
|
3577
3372
|
}
|
|
3373
|
+
function encodeKeyPath(key) {
|
|
3374
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
3375
|
+
}
|
|
3578
3376
|
function useR2Files(options) {
|
|
3579
3377
|
const [isUploading, setIsUploading] = useState13(false);
|
|
3580
3378
|
const scope = options?.scope ?? "self";
|
|
3581
|
-
const scopeParams =
|
|
3379
|
+
const scopeParams = useMemo11(() => {
|
|
3582
3380
|
const params = new URLSearchParams();
|
|
3583
3381
|
params.set("scope", scope);
|
|
3584
3382
|
return params.toString();
|
|
@@ -3590,104 +3388,128 @@ function useR2Files(options) {
|
|
|
3590
3388
|
}
|
|
3591
3389
|
return {};
|
|
3592
3390
|
}, []);
|
|
3593
|
-
const upload = useCallback15(
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3391
|
+
const upload = useCallback15(
|
|
3392
|
+
async (file, name) => {
|
|
3393
|
+
setIsUploading(true);
|
|
3394
|
+
try {
|
|
3395
|
+
const headers = await authHeaders();
|
|
3396
|
+
const formData = new FormData();
|
|
3397
|
+
formData.append("file", file);
|
|
3398
|
+
if (name) formData.append("name", name);
|
|
3399
|
+
const response = await fetch(`/api/files/upload?${scopeParams}`, {
|
|
3400
|
+
method: "POST",
|
|
3401
|
+
headers,
|
|
3402
|
+
body: formData
|
|
3403
|
+
});
|
|
3404
|
+
return await response.json();
|
|
3405
|
+
} catch (err) {
|
|
3406
|
+
return { success: false, error: err instanceof Error ? err.message : "Upload failed" };
|
|
3407
|
+
} finally {
|
|
3408
|
+
setIsUploading(false);
|
|
3409
|
+
}
|
|
3410
|
+
},
|
|
3411
|
+
[scopeParams, authHeaders]
|
|
3412
|
+
);
|
|
3413
|
+
const uploadBase64 = useCallback15(
|
|
3414
|
+
async (base64Data, name, mimeType) => {
|
|
3415
|
+
setIsUploading(true);
|
|
3416
|
+
try {
|
|
3417
|
+
const headers = await authHeaders();
|
|
3418
|
+
const response = await fetch(`/api/files/upload?${scopeParams}`, {
|
|
3419
|
+
method: "POST",
|
|
3420
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
3421
|
+
body: JSON.stringify({ data: base64Data, name, mimeType })
|
|
3422
|
+
});
|
|
3423
|
+
return await response.json();
|
|
3424
|
+
} catch (err) {
|
|
3425
|
+
return { success: false, error: err instanceof Error ? err.message : "Upload failed" };
|
|
3426
|
+
} finally {
|
|
3427
|
+
setIsUploading(false);
|
|
3428
|
+
}
|
|
3429
|
+
},
|
|
3430
|
+
[scopeParams, authHeaders]
|
|
3431
|
+
);
|
|
3432
|
+
const deleteFile = useCallback15(
|
|
3433
|
+
async (fileOrKey) => {
|
|
3434
|
+
const { key } = resolveFile(fileOrKey);
|
|
3435
|
+
try {
|
|
3436
|
+
const headers = await authHeaders();
|
|
3437
|
+
const response = await fetch(`/api/files/${encodeKeyPath(key)}?${scopeParams}`, {
|
|
3438
|
+
method: "DELETE",
|
|
3439
|
+
headers
|
|
3440
|
+
});
|
|
3441
|
+
return await response.json();
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
return { success: false, error: err instanceof Error ? err.message : "Delete failed" };
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
[scopeParams, authHeaders]
|
|
3447
|
+
);
|
|
3448
|
+
const list = useCallback15(
|
|
3449
|
+
async (prefix) => {
|
|
3450
|
+
try {
|
|
3451
|
+
const headers = await authHeaders();
|
|
3452
|
+
const params = new URLSearchParams(scopeParams);
|
|
3453
|
+
if (prefix) params.set("prefix", prefix);
|
|
3454
|
+
const response = await fetch(`/api/files?${params.toString()}`, { headers });
|
|
3455
|
+
const result = await response.json();
|
|
3456
|
+
return result.files || [];
|
|
3457
|
+
} catch {
|
|
3458
|
+
return [];
|
|
3459
|
+
}
|
|
3460
|
+
},
|
|
3461
|
+
[scopeParams, authHeaders]
|
|
3462
|
+
);
|
|
3463
|
+
const downloadFile = useCallback15(
|
|
3464
|
+
async (fileOrKey, fileName) => {
|
|
3465
|
+
const { key, name: infoName } = resolveFile(fileOrKey);
|
|
3466
|
+
try {
|
|
3467
|
+
const headers = await authHeaders();
|
|
3468
|
+
const response = await fetch(`/api/files/${encodeKeyPath(key)}?${scopeParams}`, { headers });
|
|
3469
|
+
if (!response.ok) {
|
|
3470
|
+
const body = await response.json().catch(() => null);
|
|
3471
|
+
return { success: false, error: body?.error ?? `Download failed (${response.status})` };
|
|
3472
|
+
}
|
|
3473
|
+
const disposition = response.headers.get("Content-Disposition");
|
|
3474
|
+
const dispositionName = disposition?.match(/filename="(.+?)"/)?.[1];
|
|
3475
|
+
const fallbackName = key.split("/").pop() ?? "download";
|
|
3476
|
+
const resolvedName = fileName ?? infoName ?? dispositionName ?? fallbackName;
|
|
3477
|
+
const blob = await response.blob();
|
|
3478
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3479
|
+
const anchor = document.createElement("a");
|
|
3480
|
+
anchor.href = blobUrl;
|
|
3481
|
+
anchor.download = resolvedName;
|
|
3482
|
+
document.body.appendChild(anchor);
|
|
3483
|
+
anchor.click();
|
|
3484
|
+
document.body.removeChild(anchor);
|
|
3485
|
+
URL.revokeObjectURL(blobUrl);
|
|
3486
|
+
return { success: true };
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
return { success: false, error: err instanceof Error ? err.message : "Download failed" };
|
|
3489
|
+
}
|
|
3490
|
+
},
|
|
3491
|
+
[scopeParams, authHeaders]
|
|
3492
|
+
);
|
|
3493
|
+
const readFile = useCallback15(
|
|
3494
|
+
async (fileOrKey) => {
|
|
3495
|
+
const { key } = resolveFile(fileOrKey);
|
|
3653
3496
|
const headers = await authHeaders();
|
|
3654
|
-
const response = await fetch(`/api/files/${key}?${scopeParams}`, { headers });
|
|
3497
|
+
const response = await fetch(`/api/files/${encodeKeyPath(key)}?${scopeParams}`, { headers });
|
|
3655
3498
|
if (!response.ok) {
|
|
3656
|
-
const body = await response.
|
|
3657
|
-
|
|
3499
|
+
const body = await response.text().catch(() => "");
|
|
3500
|
+
throw new Error(`Failed to read file (${response.status}): ${body}`);
|
|
3658
3501
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
const
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
document.body.removeChild(anchor);
|
|
3671
|
-
URL.revokeObjectURL(blobUrl);
|
|
3672
|
-
return { success: true };
|
|
3673
|
-
} catch (err) {
|
|
3674
|
-
return { success: false, error: err instanceof Error ? err.message : "Download failed" };
|
|
3675
|
-
}
|
|
3676
|
-
}, [scopeParams, authHeaders]);
|
|
3677
|
-
const readFile = useCallback15(async (fileOrKey) => {
|
|
3678
|
-
const { key } = resolveFile(fileOrKey);
|
|
3679
|
-
const headers = await authHeaders();
|
|
3680
|
-
const response = await fetch(`/api/files/${key}?${scopeParams}`, { headers });
|
|
3681
|
-
if (!response.ok) {
|
|
3682
|
-
const body = await response.text().catch(() => "");
|
|
3683
|
-
throw new Error(`Failed to read file (${response.status}): ${body}`);
|
|
3684
|
-
}
|
|
3685
|
-
return response;
|
|
3686
|
-
}, [scopeParams, authHeaders]);
|
|
3687
|
-
const getUrl = useCallback15((fileOrKey) => {
|
|
3688
|
-
const { key } = resolveFile(fileOrKey);
|
|
3689
|
-
return `/api/files/${key}?${scopeParams}`;
|
|
3690
|
-
}, [scopeParams]);
|
|
3502
|
+
return response;
|
|
3503
|
+
},
|
|
3504
|
+
[scopeParams, authHeaders]
|
|
3505
|
+
);
|
|
3506
|
+
const getUrl = useCallback15(
|
|
3507
|
+
(fileOrKey) => {
|
|
3508
|
+
const { key } = resolveFile(fileOrKey);
|
|
3509
|
+
return `/api/files/${encodeKeyPath(key)}?${scopeParams}`;
|
|
3510
|
+
},
|
|
3511
|
+
[scopeParams]
|
|
3512
|
+
);
|
|
3691
3513
|
return { upload, uploadBase64, deleteFile, downloadFile, readFile, list, getUrl, isUploading };
|
|
3692
3514
|
}
|
|
3693
3515
|
|
|
@@ -3727,9 +3549,9 @@ function getUserColor(userId, palette = DEFAULT_USER_COLORS) {
|
|
|
3727
3549
|
}
|
|
3728
3550
|
|
|
3729
3551
|
// src/client/messaging/useMessages.ts
|
|
3730
|
-
import { useCallback as useCallback16, useMemo as
|
|
3552
|
+
import { useCallback as useCallback16, useMemo as useMemo12 } from "react";
|
|
3731
3553
|
function useMessages(channelId, options) {
|
|
3732
|
-
const where =
|
|
3554
|
+
const where = useMemo12(() => {
|
|
3733
3555
|
if (!channelId) return { channelId: "__none__" };
|
|
3734
3556
|
const w = { channelId };
|
|
3735
3557
|
if (options?.parentMessageId !== void 0) {
|
|
@@ -3925,7 +3747,7 @@ function useReadReceipts() {
|
|
|
3925
3747
|
}
|
|
3926
3748
|
|
|
3927
3749
|
// src/client/messaging/useConversation.ts
|
|
3928
|
-
import { useCallback as useCallback21, useMemo as
|
|
3750
|
+
import { useCallback as useCallback21, useMemo as useMemo13 } from "react";
|
|
3929
3751
|
function useConversation(opts) {
|
|
3930
3752
|
const { user } = useUser();
|
|
3931
3753
|
const { ready } = useRecordContext();
|
|
@@ -3983,7 +3805,7 @@ function useConversation(opts) {
|
|
|
3983
3805
|
},
|
|
3984
3806
|
[reactionMut, allReactions, user?.id]
|
|
3985
3807
|
);
|
|
3986
|
-
return
|
|
3808
|
+
return useMemo13(
|
|
3987
3809
|
() => ({
|
|
3988
3810
|
messages: allMessages,
|
|
3989
3811
|
reactions: allReactions,
|
|
@@ -4103,7 +3925,7 @@ function parseMessageMetadata(raw) {
|
|
|
4103
3925
|
}
|
|
4104
3926
|
|
|
4105
3927
|
// src/client/directory/useConversations.ts
|
|
4106
|
-
import { useCallback as useCallback22, useMemo as
|
|
3928
|
+
import { useCallback as useCallback22, useMemo as useMemo14, useRef as useRef14 } from "react";
|
|
4107
3929
|
var defaultConvFields = {
|
|
4108
3930
|
Visibility: "public",
|
|
4109
3931
|
Status: "active",
|
|
@@ -4124,19 +3946,19 @@ function useConversations() {
|
|
|
4124
3946
|
recentCreationsRef.current.delete(id);
|
|
4125
3947
|
}
|
|
4126
3948
|
}
|
|
4127
|
-
const readStateMap =
|
|
3949
|
+
const readStateMap = useMemo14(
|
|
4128
3950
|
() => new Map(stateRecords.map((r) => [r.data.ConversationId, r.data.LastReadAt])),
|
|
4129
3951
|
[stateRecords]
|
|
4130
3952
|
);
|
|
4131
|
-
const readMessageCountMap =
|
|
3953
|
+
const readMessageCountMap = useMemo14(
|
|
4132
3954
|
() => new Map(stateRecords.map((r) => [r.data.ConversationId, r.data.LastReadMessageCount ?? 0])),
|
|
4133
3955
|
[stateRecords]
|
|
4134
3956
|
);
|
|
4135
|
-
const starredSet =
|
|
3957
|
+
const starredSet = useMemo14(
|
|
4136
3958
|
() => new Set(stateRecords.filter((r) => r.data.Starred).map((r) => r.data.ConversationId)),
|
|
4137
3959
|
[stateRecords]
|
|
4138
3960
|
);
|
|
4139
|
-
const archivedSet =
|
|
3961
|
+
const archivedSet = useMemo14(
|
|
4140
3962
|
() => new Set(stateRecords.filter((r) => r.data.Archived).map((r) => r.data.ConversationId)),
|
|
4141
3963
|
[stateRecords]
|
|
4142
3964
|
);
|
|
@@ -4338,7 +4160,7 @@ function useConversations() {
|
|
|
4338
4160
|
}
|
|
4339
4161
|
|
|
4340
4162
|
// src/client/directory/useCommunities.ts
|
|
4341
|
-
import { useCallback as useCallback23, useMemo as
|
|
4163
|
+
import { useCallback as useCallback23, useMemo as useMemo15 } from "react";
|
|
4342
4164
|
function useCommunities() {
|
|
4343
4165
|
const { records: communities, status } = useQuery("communities");
|
|
4344
4166
|
const communityMut = useMutations("communities");
|
|
@@ -4346,7 +4168,7 @@ function useCommunities() {
|
|
|
4346
4168
|
const membershipMut = useMutations("memberships");
|
|
4347
4169
|
const { user } = useUser();
|
|
4348
4170
|
const ready = status === "ready";
|
|
4349
|
-
const membershipMap =
|
|
4171
|
+
const membershipMap = useMemo15(() => {
|
|
4350
4172
|
const map = /* @__PURE__ */ new Map();
|
|
4351
4173
|
for (const m of memberships) {
|
|
4352
4174
|
const list = map.get(m.data.CommunityId) ?? [];
|
|
@@ -4355,7 +4177,7 @@ function useCommunities() {
|
|
|
4355
4177
|
}
|
|
4356
4178
|
return map;
|
|
4357
4179
|
}, [memberships]);
|
|
4358
|
-
const myMemberships =
|
|
4180
|
+
const myMemberships = useMemo15(
|
|
4359
4181
|
() => new Set(
|
|
4360
4182
|
memberships.filter((m) => m.data.UserId === user?.id).map((m) => m.data.CommunityId)
|
|
4361
4183
|
),
|
|
@@ -5024,22 +4846,29 @@ function usePlatformWS(options) {
|
|
|
5024
4846
|
return { state, send };
|
|
5025
4847
|
}
|
|
5026
4848
|
|
|
5027
|
-
// src/client/integration
|
|
4849
|
+
// src/client/integration.ts
|
|
5028
4850
|
var ENDPOINT_PREFIX = "/api/integrations";
|
|
5029
4851
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
4852
|
+
var ENDPOINT_RE = /^[a-z0-9][a-z0-9._~-]*(?:\/[a-z0-9][a-z0-9._~-]*)*$/i;
|
|
5030
4853
|
function resolveUrl(endpoint) {
|
|
5031
|
-
if (endpoint
|
|
5032
|
-
|
|
4854
|
+
if (endpoint !== "" && !ENDPOINT_RE.test(endpoint)) {
|
|
4855
|
+
throw new TypeError(
|
|
4856
|
+
'Integration endpoints must be relative route segments, for example "openai/chat-completion".'
|
|
4857
|
+
);
|
|
5033
4858
|
}
|
|
5034
|
-
const path = endpoint
|
|
4859
|
+
const path = endpoint ? `${ENDPOINT_PREFIX}/${endpoint}` : ENDPOINT_PREFIX;
|
|
5035
4860
|
if (typeof window !== "undefined") {
|
|
5036
4861
|
return `${window.location.origin}${path}`;
|
|
5037
4862
|
}
|
|
5038
4863
|
return path;
|
|
5039
4864
|
}
|
|
5040
|
-
async function request(method, endpoint, body, options) {
|
|
5041
|
-
const
|
|
4865
|
+
async function request(method, endpoint, body, options, query) {
|
|
4866
|
+
const queryString = query?.toString();
|
|
4867
|
+
const url = `${resolveUrl(endpoint)}${queryString ? `?${queryString}` : ""}`;
|
|
5042
4868
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
4869
|
+
if (options?.signal?.aborted) {
|
|
4870
|
+
return { success: false, error: "Request cancelled" };
|
|
4871
|
+
}
|
|
5043
4872
|
const headers = {
|
|
5044
4873
|
"Content-Type": "application/json",
|
|
5045
4874
|
...options?.headers
|
|
@@ -5051,9 +4880,12 @@ async function request(method, endpoint, body, options) {
|
|
|
5051
4880
|
}
|
|
5052
4881
|
const controller = new AbortController();
|
|
5053
4882
|
const abortFromCaller = () => controller.abort();
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
const timeout = setTimeout(() =>
|
|
4883
|
+
options?.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
4884
|
+
let timedOut = false;
|
|
4885
|
+
const timeout = setTimeout(() => {
|
|
4886
|
+
timedOut = true;
|
|
4887
|
+
controller.abort();
|
|
4888
|
+
}, timeoutMs);
|
|
5057
4889
|
try {
|
|
5058
4890
|
const res = await fetch(url, {
|
|
5059
4891
|
method,
|
|
@@ -5082,7 +4914,10 @@ async function request(method, endpoint, body, options) {
|
|
|
5082
4914
|
return { success: true, data: rest };
|
|
5083
4915
|
} catch (err) {
|
|
5084
4916
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
5085
|
-
return {
|
|
4917
|
+
return {
|
|
4918
|
+
success: false,
|
|
4919
|
+
error: timedOut ? `Request timed out after ${timeoutMs}ms` : "Request cancelled"
|
|
4920
|
+
};
|
|
5086
4921
|
}
|
|
5087
4922
|
return {
|
|
5088
4923
|
success: false,
|
|
@@ -5098,16 +4933,14 @@ var integration = {
|
|
|
5098
4933
|
return request("POST", endpoint, data, options);
|
|
5099
4934
|
},
|
|
5100
4935
|
get(endpoint, params, options) {
|
|
5101
|
-
let
|
|
4936
|
+
let searchParams;
|
|
5102
4937
|
if (params) {
|
|
5103
|
-
|
|
4938
|
+
searchParams = new URLSearchParams();
|
|
5104
4939
|
for (const [k, v] of Object.entries(params)) {
|
|
5105
4940
|
if (v != null) searchParams.set(k, String(v));
|
|
5106
4941
|
}
|
|
5107
|
-
const qs = searchParams.toString();
|
|
5108
|
-
if (qs) url = `${endpoint}?${qs}`;
|
|
5109
4942
|
}
|
|
5110
|
-
return request("GET",
|
|
4943
|
+
return request("GET", endpoint, void 0, options, searchParams);
|
|
5111
4944
|
},
|
|
5112
4945
|
put(endpoint, data, options) {
|
|
5113
4946
|
return request("PUT", endpoint, data, options);
|
|
@@ -5415,7 +5248,7 @@ function decodeAiStreamChunk(chunk) {
|
|
|
5415
5248
|
}
|
|
5416
5249
|
|
|
5417
5250
|
// src/client/status.ts
|
|
5418
|
-
import { useCallback as useCallback27, useEffect as useEffect21, useMemo as
|
|
5251
|
+
import { useCallback as useCallback27, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef18, useState as useState18 } from "react";
|
|
5419
5252
|
|
|
5420
5253
|
// src/client/status-core.ts
|
|
5421
5254
|
var ASYNC_RESOURCE_DEFAULTS = {
|
|
@@ -5493,7 +5326,6 @@ function useAuthProfileReady(options = {}) {
|
|
|
5493
5326
|
isReady: status === "ready"
|
|
5494
5327
|
};
|
|
5495
5328
|
}
|
|
5496
|
-
var useAuthReady = useAuthProfileReady;
|
|
5497
5329
|
function useAsyncResource(fetcher, deps, options = {}) {
|
|
5498
5330
|
const enabled = options.enabled ?? true;
|
|
5499
5331
|
const keepPreviousData = options.keepPreviousData ?? true;
|
|
@@ -5590,7 +5422,7 @@ function useAsyncResource(fetcher, deps, options = {}) {
|
|
|
5590
5422
|
controller.abort();
|
|
5591
5423
|
};
|
|
5592
5424
|
}, [enabled, keepPreviousData, retry, retryDelayMs, slowAfterMs, reloadTick, ...deps]);
|
|
5593
|
-
return
|
|
5425
|
+
return useMemo16(() => ({ ...state, reload }), [state, reload]);
|
|
5594
5426
|
}
|
|
5595
5427
|
function usePagedResource(fetchPage, deps, options = {}) {
|
|
5596
5428
|
const enabled = options.enabled ?? true;
|
|
@@ -5762,7 +5594,7 @@ function usePagedResource(fetchPage, deps, options = {}) {
|
|
|
5762
5594
|
loadingRef.current = false;
|
|
5763
5595
|
};
|
|
5764
5596
|
}, [enabled, pageSize, maxItemsPerPage, keepPreviousData, autoRetryOnError, retryDelayMs, maxRetryDelayMs, ...deps]);
|
|
5765
|
-
return
|
|
5597
|
+
return useMemo16(() => ({
|
|
5766
5598
|
...state,
|
|
5767
5599
|
loadMore,
|
|
5768
5600
|
retry,
|
|
@@ -5940,7 +5772,7 @@ function PricingTable(props) {
|
|
|
5940
5772
|
}
|
|
5941
5773
|
|
|
5942
5774
|
// src/client/charges/useCheckout.ts
|
|
5943
|
-
import { useCallback as useCallback29, useEffect as useEffect23, useMemo as
|
|
5775
|
+
import { useCallback as useCallback29, useEffect as useEffect23, useMemo as useMemo17, useState as useState20 } from "react";
|
|
5944
5776
|
async function authedJson2(path, init) {
|
|
5945
5777
|
const token = await getAuthToken();
|
|
5946
5778
|
const res = await fetch(path, {
|
|
@@ -6017,7 +5849,7 @@ function useCheckout(opts) {
|
|
|
6017
5849
|
(productId) => purchases.some((p) => p.productId === productId),
|
|
6018
5850
|
[purchases]
|
|
6019
5851
|
);
|
|
6020
|
-
const owned =
|
|
5852
|
+
const owned = useMemo17(
|
|
6021
5853
|
() => !!opts?.productId && purchases.some((p) => p.productId === opts.productId),
|
|
6022
5854
|
[opts?.productId, purchases]
|
|
6023
5855
|
);
|
|
@@ -6270,7 +6102,6 @@ export {
|
|
|
6270
6102
|
useAsyncResource,
|
|
6271
6103
|
useAuth,
|
|
6272
6104
|
useAuthProfileReady,
|
|
6273
|
-
useAuthReady,
|
|
6274
6105
|
useAuthStatus,
|
|
6275
6106
|
useAuthUser,
|
|
6276
6107
|
useCanvas,
|