deepspace 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +21 -0
- package/dist/cli.js +889 -323
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +26 -12
- package/dist/index.js +100 -27
- package/dist/index.js.map +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +51 -15
- package/dist/server.js +212 -61
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +28 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +69 -10
- package/dist/worker.js +251 -61
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +4 -4
- package/features/documentation/feature.json +2 -1
- package/features/documents/feature.json +3 -2
- package/features/documents/src/DocumentsEditorPage.tsx +47 -62
- package/features/documents/src/DocumentsListPage.tsx +59 -26
- package/features/documents/src/InviteDialog.tsx +49 -14
- package/features/documents/src/LibrarySidebar.tsx +92 -84
- package/features/documents/src/__tests__/documents-presence-access.test.ts +15 -2
- package/features/documents/src/documents-library-types.ts +6 -0
- package/features/documents/src/editor/useDocEditor.ts +1 -1
- package/features/documents/src/use-documents-presence-access.ts +1 -3
- package/features/tasks/src/TasksPage.tsx +6 -3
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1361,7 +1361,7 @@ declare function encode<M extends BaseMessage<string, unknown>>(message: M): str
|
|
|
1361
1361
|
* Provides:
|
|
1362
1362
|
* - WebSocket upgrade with Cloudflare hibernation API
|
|
1363
1363
|
* - Connection tracking (WebSocket -> UserAttachment)
|
|
1364
|
-
* - Auth: parse JWT-verified user info from
|
|
1364
|
+
* - Auth: parse JWT-verified user info from internal request headers
|
|
1365
1365
|
* - Presence: connected users list, awareness on connect/disconnect
|
|
1366
1366
|
* - Message routing: JSON parse -> dispatch by `type` field, binary hook
|
|
1367
1367
|
* - Raw SQLite access via this.sql
|
|
@@ -1396,8 +1396,8 @@ interface ConnectionAttachment extends UserAttachment {
|
|
|
1396
1396
|
subscriptions: Subscription[];
|
|
1397
1397
|
/** Yjs docs this connection is editing */
|
|
1398
1398
|
yjsSubscriptions: YjsSubscription[];
|
|
1399
|
-
/** Client-side Yjs awareness clientId
|
|
1400
|
-
|
|
1399
|
+
/** Client-side Yjs awareness clientId for each subscribed document. */
|
|
1400
|
+
awarenessClientIds?: Partial<Record<YjsDocKey, number>>;
|
|
1401
1401
|
}
|
|
1402
1402
|
/**
|
|
1403
1403
|
* Context passed to handlers for accessing shared resources.
|
|
@@ -1418,6 +1418,8 @@ interface HandlerContext {
|
|
|
1418
1418
|
/** Asset-layer misses became real 404s, and the worker owns the fallback. The
|
|
1419
1419
|
* config and the worker's fallback path have to move together. */
|
|
1420
1420
|
declare const WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
1421
|
+
/** Room identity moved to internal headers and privileged room routes gained app-role checks. */
|
|
1422
|
+
declare const SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
1421
1423
|
type AppMigrationIdsValidation = {
|
|
1422
1424
|
valid: true;
|
|
1423
1425
|
ids: string[];
|
|
@@ -2764,12 +2766,15 @@ interface User extends UserProfile {
|
|
|
2764
2766
|
*/
|
|
2765
2767
|
interface RoomUser {
|
|
2766
2768
|
id: string;
|
|
2767
|
-
|
|
2769
|
+
/** Present only in the admin user-management view. */
|
|
2770
|
+
email?: string;
|
|
2768
2771
|
name: string;
|
|
2769
2772
|
imageUrl?: string;
|
|
2770
2773
|
role: string;
|
|
2771
|
-
|
|
2772
|
-
|
|
2774
|
+
/** Present only in the admin user-management view. */
|
|
2775
|
+
createdAt?: string;
|
|
2776
|
+
/** Present only in the admin user-management view. */
|
|
2777
|
+
lastSeenAt?: string;
|
|
2773
2778
|
}
|
|
2774
2779
|
type RoomConnectionState = 'connecting' | 'connected' | 'disconnected';
|
|
2775
2780
|
/** A server-rejected optimistic write. */
|
|
@@ -3065,6 +3070,8 @@ declare function useQuery<T = unknown>(collection: string, options?: {
|
|
|
3065
3070
|
* ```
|
|
3066
3071
|
*/
|
|
3067
3072
|
declare function useMutations<T = unknown>(collection: string): {
|
|
3073
|
+
/** True once the collection's RecordRoom can accept writes. */
|
|
3074
|
+
ready: boolean;
|
|
3068
3075
|
create: (data: T) => Promise<string>;
|
|
3069
3076
|
put: (recordId: string, data: Partial<T>) => Promise<void>;
|
|
3070
3077
|
remove: (recordId: string) => Promise<void>;
|
|
@@ -3073,6 +3080,12 @@ declare function useMutations<T = unknown>(collection: string): {
|
|
|
3073
3080
|
removeConfirmed: (recordId: string) => Promise<void>;
|
|
3074
3081
|
};
|
|
3075
3082
|
|
|
3083
|
+
/** A write was attempted before its RecordRoom connection became ready. */
|
|
3084
|
+
declare class RecordRoomNotReadyError extends Error {
|
|
3085
|
+
readonly code = "not_ready";
|
|
3086
|
+
constructor(collection?: string);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3076
3089
|
/**
|
|
3077
3090
|
* useUsers Hook
|
|
3078
3091
|
*
|
|
@@ -3158,7 +3171,8 @@ declare function useYjsText(collection: string, recordId: string, fieldName: str
|
|
|
3158
3171
|
*/
|
|
3159
3172
|
interface UserInfo {
|
|
3160
3173
|
id: string;
|
|
3161
|
-
|
|
3174
|
+
/** Available to admins; ordinary collaborators receive public identity only. */
|
|
3175
|
+
email?: string;
|
|
3162
3176
|
name: string;
|
|
3163
3177
|
imageUrl?: string;
|
|
3164
3178
|
role: string;
|
|
@@ -3220,7 +3234,7 @@ declare function usePresence(opts?: UsePresenceOptions): {
|
|
|
3220
3234
|
* Uses the shared yjs-protocol.ts encoding — no duplication.
|
|
3221
3235
|
*
|
|
3222
3236
|
* @example
|
|
3223
|
-
* const { doc, text, setText, synced, canWrite } = useYjsRoom(docId, 'content')
|
|
3237
|
+
* const { doc, text, setText, connected, synced, canWrite } = useYjsRoom(docId, 'content')
|
|
3224
3238
|
*/
|
|
3225
3239
|
|
|
3226
3240
|
interface UseYjsRoomResult {
|
|
@@ -3232,6 +3246,8 @@ interface UseYjsRoomResult {
|
|
|
3232
3246
|
text: string;
|
|
3233
3247
|
/** Set text (replaces full content) */
|
|
3234
3248
|
setText: (value: string) => void;
|
|
3249
|
+
/** Whether the current WebSocket transport is open */
|
|
3250
|
+
connected: boolean;
|
|
3235
3251
|
/** Whether initial sync is complete */
|
|
3236
3252
|
synced: boolean;
|
|
3237
3253
|
/** Whether user has write access */
|
|
@@ -3432,7 +3448,7 @@ declare function useJobs<P = unknown, R = unknown>(roomId: string): UseJobsResul
|
|
|
3432
3448
|
* Opens a WebSocket to /ws/presence/:scopeId for real-time presence tracking.
|
|
3433
3449
|
* Use with any scope: canvas, doc, thread, page, etc.
|
|
3434
3450
|
*
|
|
3435
|
-
* Peers can share
|
|
3451
|
+
* Peers can share small ephemeral state (cursor position, typing indicator,
|
|
3436
3452
|
* viewport, selection) via updateState().
|
|
3437
3453
|
*
|
|
3438
3454
|
* @example
|
|
@@ -3451,8 +3467,6 @@ declare function useJobs<P = unknown, R = unknown>(roomId: string): UseJobsResul
|
|
|
3451
3467
|
interface PresencePeerClient {
|
|
3452
3468
|
userId: string;
|
|
3453
3469
|
userName: string;
|
|
3454
|
-
userEmail: string;
|
|
3455
|
-
userImageUrl?: string;
|
|
3456
3470
|
joinedAt: string;
|
|
3457
3471
|
state: Record<string, unknown>;
|
|
3458
3472
|
}
|
|
@@ -4839,4 +4853,4 @@ declare const ACCOUNT_STORAGE_LIMIT_BYTES: {
|
|
|
4839
4853
|
declare function storageLimitForTier(tier: string | null | undefined): number;
|
|
4840
4854
|
declare function formatBytes(bytes: number): string;
|
|
4841
4855
|
|
|
4842
|
-
export { ACCOUNT_STORAGE_LIMIT_BYTES, APP_LOG_EVENT_KEYS, ASYNC_RESOURCE_DEFAULTS, type AdHocChargeOpts, type AiStreamAction, type AiStreamChunk, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AsyncResourceDefaults, type AsyncResourceState, type AsyncResourceStatus, AuthGate, type AuthGateProps, AuthOverlay, Awareness, type AwarenessState, type AwarenessStates, type BaseMessage, type CanvasShapeClient, type Channel, type ChannelInvitation, type ChannelMember, type ChargeOnceOpts, type ChargeOnceResult, type ClientErrorKind, type ClientErrorReport, type ClientErrorReporterOptions, type ClientMessage, type CollectionSchema, type ConnectionAttachment, type ConnectionStatus, type ContentSegment, type ConversationObject, type CronHistoryEntry, type CronTaskState, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEEPSPACE_THEME_PROPERTIES, DEFAULT_USER_COLORS, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIProvider, type DeepSpaceAgentProfile, type DeepSpaceAgentProfileId, type DeepSpaceAgentSupport, DeepSpaceAuthProvider, type DeepSpaceThemeConfig, DeepSpaceThemeProvider, type DeepSpaceThemeProviderProps, type DeriveLoadStateInput, ENV, type Environment, type FileAttachment, type GroupedReaction, GuestBanner, type HandlerContext, type InboxEntry, type IntegrationResponse, type JobStatusView, type JobView, LOG_LEVELS, type LinkPreviewData, type LoadState, type LoadStateAuthInput, type LoadStateStatusInput, type LogLevel, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MemberRecord, type Message, type MessageHandlers, type MessageRecord, type MsgType, type PagedResourceFetchArgs, type PagedResourcePage, type PagedResourceState, type PagedResourceStatus, type PlanInfo, PlatformContext, type PlatformContextValue, PlatformProvider, type PlatformWSOptions, type PlatformWSResult, type PresencePeerClient, PricingTable, type PricingTablePlan, type PricingTablePrice, type ProductChargeOpts, type ProtocolMessage, type Purchase, type Query, type R2FileInfo, type R2Scope, type R2UploadResult, ROLES, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_CONFIG, ROLE_DEFAULT, type Reaction, type ReactionRecord, type ReadCursorRecord, type ReadReceipt, type RecordData, RecordProvider, type RecordProviderProps, RecordScope, type RequestOptions, type ResolvedDeepSpaceAgentModel, type Role, type RoomUser, type ScopeEntry, ScopeRegistryProvider, type ServerMessage, SignedIn, SignedOut, type SubscribeOpts, type SubscribeResult, type SubscriptionState, type SubscriptionStatus, type SyncResult, type UseAsyncResourceOptions, type UseAuthProfileReadyOptions, type UseAuthStatusOptions, type UseCanvasResult, type UseCheckoutOpts, type UseCronMonitorResult, type UseJobsResult, type UsePagedResourceOptions, type UsePresenceRoomResult, type UseR2FilesReturn, type UseVoiceAgentOptions, type UseVoiceAgentResult, type UseYjsFieldResult, type UseYjsRoomResult, type UseYjsTextResult, type User, type UserCredits, type UserInfo, type UserKarma, type UserProfile, type ViewportClient, type VoiceStatus, type VoiceTranscriptEntry, WORKER_OWNED_NOT_FOUND_MIGRATION_ID, type WriteError, applyDeepSpaceTheme, applyUIThemeTokens, authClient, canAutoLoadPagedResource, clampPagedResourceItems, clearAuthToken, clearDeepSpaceTheme, clientBuild, createDecoder, createEncoder, decodeAiStreamChunk, deriveLoadState, detectEnvironment, dispatch, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, formatBytes, formatFileSize, formatFullTimestamp, formatMessageTime, getApiUrl, getAuthToken, getAuthUrl, getConversationDisplayName, getConversationParticipantIds, getDeepSpaceAIModel, getEnvironmentConfig, getMessageType, getPagedResourceCooldownMs, getPlatformWorkerUrl, getThreadCounts, getUserColor, groupReactionsForMessage, handleAwarenessMessage, handleSyncMessage, hasContent, installClientErrorReporter, integration, isAsyncResourceSlow, isDMConversation, isDarkColor, isImageFile, isLocalDev, isProduction, listDeepSpaceAgentModels, parseMessageMetadata, parseSseLine, readThemeFromDOM, readVarUint, readVarUint8Array, reportClientError, resetEnvironmentCache, resolveDeepSpaceAgentModel, serverBuild, shouldGroupMessages, signIn, signOut, storageLimitForTier, toConnectionStatus, toUint8Array, useAsyncResource, useAuth, useAuthProfileReady, useAuthStatus, useAuthUser, useCanvas, useChannelMembers, useChannels, useCheckout, useCommunities, useConversation, useConversations, useCronMonitor, useDisplayName, useInbox, useIsDarkTheme, useJobs, useMessages, useMutations, usePagedResource, usePlatform, usePlatformWS, usePosts, usePresence, usePresenceRoom, useQuery, useR2Files, useReactions, useReadReceipts, useRecordContext, useSession, useSubscription, useUser, useUserLookup, useUsers, useVoiceAgent, useYjsField, useYjsRoom, useYjsText, validateAppMigrationIds, writeVarUint, writeVarUint8Array };
|
|
4856
|
+
export { ACCOUNT_STORAGE_LIMIT_BYTES, APP_LOG_EVENT_KEYS, ASYNC_RESOURCE_DEFAULTS, type AdHocChargeOpts, type AiStreamAction, type AiStreamChunk, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AsyncResourceDefaults, type AsyncResourceState, type AsyncResourceStatus, AuthGate, type AuthGateProps, AuthOverlay, Awareness, type AwarenessState, type AwarenessStates, type BaseMessage, type CanvasShapeClient, type Channel, type ChannelInvitation, type ChannelMember, type ChargeOnceOpts, type ChargeOnceResult, type ClientErrorKind, type ClientErrorReport, type ClientErrorReporterOptions, type ClientMessage, type CollectionSchema, type ConnectionAttachment, type ConnectionStatus, type ContentSegment, type ConversationObject, type CronHistoryEntry, type CronTaskState, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEEPSPACE_THEME_PROPERTIES, DEFAULT_USER_COLORS, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIProvider, type DeepSpaceAgentProfile, type DeepSpaceAgentProfileId, type DeepSpaceAgentSupport, DeepSpaceAuthProvider, type DeepSpaceThemeConfig, DeepSpaceThemeProvider, type DeepSpaceThemeProviderProps, type DeriveLoadStateInput, ENV, type Environment, type FileAttachment, type GroupedReaction, GuestBanner, type HandlerContext, type InboxEntry, type IntegrationResponse, type JobStatusView, type JobView, LOG_LEVELS, type LinkPreviewData, type LoadState, type LoadStateAuthInput, type LoadStateStatusInput, type LogLevel, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MemberRecord, type Message, type MessageHandlers, type MessageRecord, type MsgType, type PagedResourceFetchArgs, type PagedResourcePage, type PagedResourceState, type PagedResourceStatus, type PlanInfo, PlatformContext, type PlatformContextValue, PlatformProvider, type PlatformWSOptions, type PlatformWSResult, type PresencePeerClient, PricingTable, type PricingTablePlan, type PricingTablePrice, type ProductChargeOpts, type ProtocolMessage, type Purchase, type Query, type R2FileInfo, type R2Scope, type R2UploadResult, ROLES, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_CONFIG, ROLE_DEFAULT, type Reaction, type ReactionRecord, type ReadCursorRecord, type ReadReceipt, type RecordData, RecordProvider, type RecordProviderProps, RecordRoomNotReadyError, RecordScope, type RequestOptions, type ResolvedDeepSpaceAgentModel, type Role, type RoomUser, SECURE_ROOM_BOUNDARIES_MIGRATION_ID, type ScopeEntry, ScopeRegistryProvider, type ServerMessage, SignedIn, SignedOut, type SubscribeOpts, type SubscribeResult, type SubscriptionState, type SubscriptionStatus, type SyncResult, type UseAsyncResourceOptions, type UseAuthProfileReadyOptions, type UseAuthStatusOptions, type UseCanvasResult, type UseCheckoutOpts, type UseCronMonitorResult, type UseJobsResult, type UsePagedResourceOptions, type UsePresenceRoomResult, type UseR2FilesReturn, type UseVoiceAgentOptions, type UseVoiceAgentResult, type UseYjsFieldResult, type UseYjsRoomResult, type UseYjsTextResult, type User, type UserCredits, type UserInfo, type UserKarma, type UserProfile, type ViewportClient, type VoiceStatus, type VoiceTranscriptEntry, WORKER_OWNED_NOT_FOUND_MIGRATION_ID, type WriteError, applyDeepSpaceTheme, applyUIThemeTokens, authClient, canAutoLoadPagedResource, clampPagedResourceItems, clearAuthToken, clearDeepSpaceTheme, clientBuild, createDecoder, createEncoder, decodeAiStreamChunk, deriveLoadState, detectEnvironment, dispatch, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, formatBytes, formatFileSize, formatFullTimestamp, formatMessageTime, getApiUrl, getAuthToken, getAuthUrl, getConversationDisplayName, getConversationParticipantIds, getDeepSpaceAIModel, getEnvironmentConfig, getMessageType, getPagedResourceCooldownMs, getPlatformWorkerUrl, getThreadCounts, getUserColor, groupReactionsForMessage, handleAwarenessMessage, handleSyncMessage, hasContent, installClientErrorReporter, integration, isAsyncResourceSlow, isDMConversation, isDarkColor, isImageFile, isLocalDev, isProduction, listDeepSpaceAgentModels, parseMessageMetadata, parseSseLine, readThemeFromDOM, readVarUint, readVarUint8Array, reportClientError, resetEnvironmentCache, resolveDeepSpaceAgentModel, serverBuild, shouldGroupMessages, signIn, signOut, storageLimitForTier, toConnectionStatus, toUint8Array, useAsyncResource, useAuth, useAuthProfileReady, useAuthStatus, useAuthUser, useCanvas, useChannelMembers, useChannels, useCheckout, useCommunities, useConversation, useConversations, useCronMonitor, useDisplayName, useInbox, useIsDarkTheme, useJobs, useMessages, useMutations, usePagedResource, usePlatform, usePlatformWS, usePosts, usePresence, usePresenceRoom, useQuery, useR2Files, useReactions, useReadReceipts, useRecordContext, useSession, useSubscription, useUser, useUserLookup, useUsers, useVoiceAgent, useYjsField, useYjsRoom, useYjsText, validateAppMigrationIds, writeVarUint, writeVarUint8Array };
|
package/dist/index.js
CHANGED
|
@@ -749,6 +749,7 @@ function encode(message) {
|
|
|
749
749
|
var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
750
750
|
var MAX_APP_MIGRATION_IDS = 256;
|
|
751
751
|
var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
|
|
752
|
+
var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
|
|
752
753
|
function validateAppMigrationIds(value) {
|
|
753
754
|
if (!Array.isArray(value)) {
|
|
754
755
|
return { valid: false, reason: "app migrations must be a JSON string array" };
|
|
@@ -1688,6 +1689,17 @@ function reconnectDelayMs(attempt) {
|
|
|
1688
1689
|
return Math.min(1e3 * Math.pow(2, attempt), 3e4);
|
|
1689
1690
|
}
|
|
1690
1691
|
|
|
1692
|
+
// src/client/storage/errors.ts
|
|
1693
|
+
var RecordRoomNotReadyError = class extends Error {
|
|
1694
|
+
code = "not_ready";
|
|
1695
|
+
constructor(collection) {
|
|
1696
|
+
super(
|
|
1697
|
+
collection ? `RecordRoom is not ready for "${collection}" writes.` : "RecordRoom is not ready for writes."
|
|
1698
|
+
);
|
|
1699
|
+
this.name = "RecordRoomNotReadyError";
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1691
1703
|
// src/client/storage/serverErrors.ts
|
|
1692
1704
|
function humanizeCollection(slug) {
|
|
1693
1705
|
return slug.replace(/^rbac-/, "").split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
@@ -1982,7 +1994,7 @@ var RecordSocket = class {
|
|
|
1982
1994
|
sendConfirmed(message, timeoutMs = 1e4) {
|
|
1983
1995
|
const ws = this.ws;
|
|
1984
1996
|
if (!ws || ws.readyState !== WS_OPEN) {
|
|
1985
|
-
return Promise.reject(new
|
|
1997
|
+
return Promise.reject(new RecordRoomNotReadyError());
|
|
1986
1998
|
}
|
|
1987
1999
|
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1988
2000
|
return new Promise((resolve, reject) => {
|
|
@@ -2065,10 +2077,17 @@ var RecordSocket = class {
|
|
|
2065
2077
|
}
|
|
2066
2078
|
case MSG.RECORD_CHANGE: {
|
|
2067
2079
|
const { collection, record, changeType } = payload;
|
|
2068
|
-
for (const queryKey of this.subscriptions
|
|
2080
|
+
for (const [subscriptionId, queryKey] of this.subscriptions) {
|
|
2069
2081
|
try {
|
|
2070
2082
|
const query = JSON.parse(queryKey);
|
|
2071
2083
|
if (query.collection !== collection) continue;
|
|
2084
|
+
if (query.orderBy !== void 0 || query.limit !== void 0) {
|
|
2085
|
+
this.sendMessage({
|
|
2086
|
+
type: MSG.SUBSCRIBE,
|
|
2087
|
+
payload: { subscriptionId, query }
|
|
2088
|
+
});
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2072
2091
|
const matches = recordMatchesWhere(record, query.where);
|
|
2073
2092
|
const exists = store.hasRecord(queryKey, record.recordId);
|
|
2074
2093
|
if (changeType === "delete") {
|
|
@@ -2256,7 +2275,7 @@ function ScopeConnection({
|
|
|
2256
2275
|
const sendConfirmed = useCallback3(
|
|
2257
2276
|
(message, timeoutMs = 1e4) => {
|
|
2258
2277
|
const socket = socketRef.current;
|
|
2259
|
-
if (!socket) return Promise.reject(new
|
|
2278
|
+
if (!socket) return Promise.reject(new RecordRoomNotReadyError());
|
|
2260
2279
|
return socket.sendConfirmed(message, timeoutMs);
|
|
2261
2280
|
},
|
|
2262
2281
|
[]
|
|
@@ -2489,33 +2508,41 @@ function useMutations(collection) {
|
|
|
2489
2508
|
const preferLocal = recordCtx?.registeredCollections?.has(collection) ?? false;
|
|
2490
2509
|
const sendMessage = preferLocal ? recordCtx.sendMessage : scopeEntry?.sendMessage ?? recordCtx?.sendMessage;
|
|
2491
2510
|
const sendConfirmed = preferLocal ? recordCtx.sendConfirmed : scopeEntry?.sendConfirmed ?? recordCtx?.sendConfirmed;
|
|
2511
|
+
const ready = preferLocal ? recordCtx.ready : scopeEntry?.ready ?? recordCtx?.ready ?? false;
|
|
2492
2512
|
if (!sendMessage || !sendConfirmed) {
|
|
2493
2513
|
throw new Error(
|
|
2494
2514
|
`useMutations('${collection}'): No scope found. Wrap this hook in a RecordScope that registers the collection.`
|
|
2495
2515
|
);
|
|
2496
2516
|
}
|
|
2517
|
+
const assertReady = useCallback5(() => {
|
|
2518
|
+
if (!ready) throw new RecordRoomNotReadyError(collection);
|
|
2519
|
+
}, [ready, collection]);
|
|
2497
2520
|
const create = useCallback5(
|
|
2498
2521
|
async (data) => {
|
|
2522
|
+
assertReady();
|
|
2499
2523
|
const recordId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
2500
2524
|
sendMessage({ type: MSG.PUT, payload: { collection, recordId, data } });
|
|
2501
2525
|
return recordId;
|
|
2502
2526
|
},
|
|
2503
|
-
[sendMessage, collection]
|
|
2527
|
+
[assertReady, sendMessage, collection]
|
|
2504
2528
|
);
|
|
2505
2529
|
const put = useCallback5(
|
|
2506
2530
|
async (recordId, data) => {
|
|
2531
|
+
assertReady();
|
|
2507
2532
|
sendMessage({ type: MSG.PUT, payload: { collection, recordId, data } });
|
|
2508
2533
|
},
|
|
2509
|
-
[sendMessage, collection]
|
|
2534
|
+
[assertReady, sendMessage, collection]
|
|
2510
2535
|
);
|
|
2511
2536
|
const remove = useCallback5(
|
|
2512
2537
|
async (recordId) => {
|
|
2538
|
+
assertReady();
|
|
2513
2539
|
sendMessage({ type: MSG.DELETE, payload: { collection, recordId } });
|
|
2514
2540
|
},
|
|
2515
|
-
[sendMessage, collection]
|
|
2541
|
+
[assertReady, sendMessage, collection]
|
|
2516
2542
|
);
|
|
2517
2543
|
const createConfirmed = useCallback5(
|
|
2518
2544
|
async (data) => {
|
|
2545
|
+
assertReady();
|
|
2519
2546
|
const recordId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
2520
2547
|
await sendConfirmed({
|
|
2521
2548
|
type: MSG.PUT,
|
|
@@ -2523,26 +2550,28 @@ function useMutations(collection) {
|
|
|
2523
2550
|
});
|
|
2524
2551
|
return recordId;
|
|
2525
2552
|
},
|
|
2526
|
-
[sendConfirmed, collection]
|
|
2553
|
+
[assertReady, sendConfirmed, collection]
|
|
2527
2554
|
);
|
|
2528
2555
|
const putConfirmed = useCallback5(
|
|
2529
2556
|
async (recordId, data) => {
|
|
2557
|
+
assertReady();
|
|
2530
2558
|
await sendConfirmed({
|
|
2531
2559
|
type: MSG.PUT,
|
|
2532
2560
|
payload: { collection, recordId, data }
|
|
2533
2561
|
});
|
|
2534
2562
|
},
|
|
2535
|
-
[sendConfirmed, collection]
|
|
2563
|
+
[assertReady, sendConfirmed, collection]
|
|
2536
2564
|
);
|
|
2537
2565
|
const removeConfirmed = useCallback5(
|
|
2538
2566
|
async (recordId) => {
|
|
2567
|
+
assertReady();
|
|
2539
2568
|
await sendConfirmed({ type: MSG.DELETE, payload: { collection, recordId } });
|
|
2540
2569
|
},
|
|
2541
|
-
[sendConfirmed, collection]
|
|
2570
|
+
[assertReady, sendConfirmed, collection]
|
|
2542
2571
|
);
|
|
2543
2572
|
return useMemo6(
|
|
2544
|
-
() => ({ create, put, remove, createConfirmed, putConfirmed, removeConfirmed }),
|
|
2545
|
-
[create, put, remove, createConfirmed, putConfirmed, removeConfirmed]
|
|
2573
|
+
() => ({ ready, create, put, remove, createConfirmed, putConfirmed, removeConfirmed }),
|
|
2574
|
+
[ready, create, put, remove, createConfirmed, putConfirmed, removeConfirmed]
|
|
2546
2575
|
);
|
|
2547
2576
|
}
|
|
2548
2577
|
|
|
@@ -2802,6 +2831,7 @@ import { useState as useState7, useEffect as useEffect10, useLayoutEffect as use
|
|
|
2802
2831
|
import * as Y3 from "yjs";
|
|
2803
2832
|
var UPDATE_BATCH_DELAY_MS = 16;
|
|
2804
2833
|
function useYjsRoom(docId, fieldName) {
|
|
2834
|
+
const [connected, setConnected] = useState7(false);
|
|
2805
2835
|
const [syncedDocId, setSyncedDocId] = useState7(null);
|
|
2806
2836
|
const [canWrite, setCanWrite] = useState7(false);
|
|
2807
2837
|
const [writeAuthResolved, setWriteAuthResolved] = useState7(false);
|
|
@@ -2837,6 +2867,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2837
2867
|
};
|
|
2838
2868
|
}, [awareness, doc]);
|
|
2839
2869
|
useLayoutEffect2(() => {
|
|
2870
|
+
setConnected(false);
|
|
2840
2871
|
setSyncedDocId(null);
|
|
2841
2872
|
setCanWrite(false);
|
|
2842
2873
|
setWriteAuthResolved(false);
|
|
@@ -2854,31 +2885,44 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2854
2885
|
let ws = null;
|
|
2855
2886
|
let reconnectTimer = null;
|
|
2856
2887
|
let alive = true;
|
|
2888
|
+
let browserOnline = typeof navigator === "undefined" || navigator.onLine;
|
|
2889
|
+
let connecting = false;
|
|
2857
2890
|
const renewLocalAwareness = () => {
|
|
2858
2891
|
const localState = awareness.getLocalState();
|
|
2859
2892
|
if (localState) awareness.setLocalState(localState);
|
|
2860
2893
|
};
|
|
2861
2894
|
const connect = async () => {
|
|
2862
|
-
if (!alive) return;
|
|
2895
|
+
if (!alive || !browserOnline) return;
|
|
2896
|
+
if (connecting) return;
|
|
2897
|
+
if (ws?.readyState === WebSocket.CONNECTING || ws?.readyState === WebSocket.OPEN) return;
|
|
2898
|
+
connecting = true;
|
|
2863
2899
|
setCanWrite(false);
|
|
2864
2900
|
setWriteAuthResolved(false);
|
|
2865
2901
|
renewLocalAwareness();
|
|
2866
|
-
|
|
2867
|
-
|
|
2902
|
+
let token;
|
|
2903
|
+
try {
|
|
2904
|
+
token = await getAuthToken();
|
|
2905
|
+
} finally {
|
|
2906
|
+
connecting = false;
|
|
2907
|
+
}
|
|
2908
|
+
if (!alive || !browserOnline) return;
|
|
2868
2909
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
2869
2910
|
const baseUrl = `${protocol}//${window.location.host}`;
|
|
2870
2911
|
const url = new URL(`/ws/yjs/${encodeURIComponent(docId)}`, baseUrl);
|
|
2871
2912
|
if (token) url.searchParams.set("token", token);
|
|
2872
2913
|
wsLog("connecting", `yjs:${docId}`);
|
|
2873
|
-
|
|
2874
|
-
ws
|
|
2875
|
-
|
|
2876
|
-
|
|
2914
|
+
const socket = new WebSocket(url.toString());
|
|
2915
|
+
ws = socket;
|
|
2916
|
+
socket.binaryType = "arraybuffer";
|
|
2917
|
+
wsRef.current = socket;
|
|
2918
|
+
socket.onopen = () => {
|
|
2919
|
+
if (ws !== socket) return socket.close();
|
|
2877
2920
|
wsLog("connected", `yjs:${docId}`);
|
|
2921
|
+
setConnected(true);
|
|
2878
2922
|
setSyncedDocId(null);
|
|
2879
2923
|
renewLocalAwareness();
|
|
2880
2924
|
};
|
|
2881
|
-
|
|
2925
|
+
socket.onmessage = (event) => {
|
|
2882
2926
|
if (typeof event.data === "string") {
|
|
2883
2927
|
try {
|
|
2884
2928
|
const msg = JSON.parse(event.data);
|
|
@@ -2903,7 +2947,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2903
2947
|
writeVarUint(enc, MSG_SYNC);
|
|
2904
2948
|
writeVarUint(enc, MSG_SYNC_STEP2);
|
|
2905
2949
|
writeVarUint8Array(enc, diff);
|
|
2906
|
-
|
|
2950
|
+
socket.send(toUint8Array(enc).buffer);
|
|
2907
2951
|
setSyncedDocId(docId);
|
|
2908
2952
|
break;
|
|
2909
2953
|
}
|
|
@@ -2931,21 +2975,40 @@ function useYjsRoom(docId, fieldName) {
|
|
|
2931
2975
|
}
|
|
2932
2976
|
}
|
|
2933
2977
|
};
|
|
2934
|
-
|
|
2978
|
+
socket.onclose = () => {
|
|
2979
|
+
if (ws !== socket) return;
|
|
2935
2980
|
wsLog("disconnected", `yjs:${docId}`);
|
|
2981
|
+
ws = null;
|
|
2936
2982
|
wsRef.current = null;
|
|
2983
|
+
setConnected(false);
|
|
2937
2984
|
setSyncedDocId(null);
|
|
2938
2985
|
setCanWrite(false);
|
|
2939
2986
|
setWriteAuthResolved(false);
|
|
2940
|
-
if (alive) reconnectTimer = setTimeout(connect, 1e3);
|
|
2987
|
+
if (alive && browserOnline) reconnectTimer = setTimeout(connect, 1e3);
|
|
2941
2988
|
};
|
|
2942
|
-
|
|
2989
|
+
socket.onerror = () => socket.close();
|
|
2943
2990
|
};
|
|
2944
|
-
|
|
2991
|
+
const handleOffline = () => {
|
|
2992
|
+
browserOnline = false;
|
|
2993
|
+
setConnected(false);
|
|
2994
|
+
setSyncedDocId(null);
|
|
2995
|
+
setCanWrite(false);
|
|
2996
|
+
setWriteAuthResolved(false);
|
|
2997
|
+
ws?.close();
|
|
2998
|
+
};
|
|
2999
|
+
const handleOnline = () => {
|
|
3000
|
+
browserOnline = true;
|
|
3001
|
+
void connect();
|
|
3002
|
+
};
|
|
3003
|
+
window.addEventListener("offline", handleOffline);
|
|
3004
|
+
window.addEventListener("online", handleOnline);
|
|
3005
|
+
void connect();
|
|
2945
3006
|
return () => {
|
|
2946
3007
|
wsLog("closing", `yjs:${docId}`);
|
|
2947
3008
|
alive = false;
|
|
2948
3009
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
3010
|
+
window.removeEventListener("offline", handleOffline);
|
|
3011
|
+
window.removeEventListener("online", handleOnline);
|
|
2949
3012
|
flushPendingUpdate(ws);
|
|
2950
3013
|
if (ws) {
|
|
2951
3014
|
ws.onclose = null;
|
|
@@ -3002,7 +3065,7 @@ function useYjsRoom(docId, fieldName) {
|
|
|
3002
3065
|
},
|
|
3003
3066
|
[doc, yText, canWrite]
|
|
3004
3067
|
);
|
|
3005
|
-
return { doc, awareness, text, setText, synced, canWrite, writeAuthResolved };
|
|
3068
|
+
return { doc, awareness, text, setText, connected, synced, canWrite, writeAuthResolved };
|
|
3006
3069
|
}
|
|
3007
3070
|
|
|
3008
3071
|
// src/client/storage/hooks/useCanvas.ts
|
|
@@ -3270,6 +3333,9 @@ function useJobs(roomId) {
|
|
|
3270
3333
|
ws.onopen = () => setConnected(true);
|
|
3271
3334
|
ws.onmessage = (event) => {
|
|
3272
3335
|
dispatch(event.data, {
|
|
3336
|
+
[MSG.AUTH]: (p) => {
|
|
3337
|
+
if (!p.canWrite) rejectAllPending(new Error("JobRoom write access denied"));
|
|
3338
|
+
},
|
|
3273
3339
|
[MSG.JOB_UPDATE]: (p) => {
|
|
3274
3340
|
if (p.kind === "snapshot" && Array.isArray(p.jobs)) {
|
|
3275
3341
|
setJobs(p.jobs);
|
|
@@ -3363,16 +3429,21 @@ function useJobs(roomId) {
|
|
|
3363
3429
|
// src/client/storage/hooks/usePresenceRoom.ts
|
|
3364
3430
|
import { useState as useState11, useEffect as useEffect14, useCallback as useCallback13, useRef as useRef12 } from "react";
|
|
3365
3431
|
function usePresenceRoom(scopeId) {
|
|
3432
|
+
const auth = useRecordAuth();
|
|
3433
|
+
const tokenProvider = auth?.getAuthToken ?? getAuthToken;
|
|
3366
3434
|
const [peers, setPeers] = useState11([]);
|
|
3367
3435
|
const [connected, setConnected] = useState11(false);
|
|
3368
3436
|
const wsRef = useRef12(null);
|
|
3369
3437
|
useEffect14(() => {
|
|
3438
|
+
setConnected(false);
|
|
3439
|
+
setPeers([]);
|
|
3370
3440
|
let ws = null;
|
|
3371
3441
|
let reconnectTimer = null;
|
|
3372
3442
|
let alive = true;
|
|
3373
3443
|
const connect = async () => {
|
|
3374
3444
|
if (!alive) return;
|
|
3375
|
-
const token = await
|
|
3445
|
+
const token = await tokenProvider();
|
|
3446
|
+
if (!alive) return;
|
|
3376
3447
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
3377
3448
|
const baseUrl = `${protocol}//${window.location.host}`;
|
|
3378
3449
|
const url = new URL(`/ws/presence/${encodeURIComponent(scopeId)}`, baseUrl);
|
|
@@ -3426,7 +3497,7 @@ function usePresenceRoom(scopeId) {
|
|
|
3426
3497
|
}
|
|
3427
3498
|
wsRef.current = null;
|
|
3428
3499
|
};
|
|
3429
|
-
}, [scopeId]);
|
|
3500
|
+
}, [scopeId, tokenProvider]);
|
|
3430
3501
|
const updateState = useCallback13((state) => {
|
|
3431
3502
|
const ws = wsRef.current;
|
|
3432
3503
|
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -6263,7 +6334,9 @@ export {
|
|
|
6263
6334
|
ROLE_CONFIG,
|
|
6264
6335
|
ROLE_DEFAULT,
|
|
6265
6336
|
RecordProvider,
|
|
6337
|
+
RecordRoomNotReadyError,
|
|
6266
6338
|
RecordScope,
|
|
6339
|
+
SECURE_ROOM_BOUNDARIES_MIGRATION_ID,
|
|
6267
6340
|
ScopeRegistryProvider,
|
|
6268
6341
|
SignedIn,
|
|
6269
6342
|
SignedOut,
|