@tldraw/sync 3.16.0-internal.51e99e128bd4 → 3.16.0-internal.a478398270c6
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/dist-cjs/index.js +1 -1
- package/dist-cjs/useSync.js +1 -9
- package/dist-cjs/useSync.js.map +2 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/useSync.mjs +1 -9
- package/dist-esm/useSync.mjs.map +2 -2
- package/package.json +24 -13
- package/src/useSync.ts +0 -11
package/dist-cjs/index.js
CHANGED
|
@@ -29,7 +29,7 @@ var import_useSync = require("./useSync");
|
|
|
29
29
|
var import_useSyncDemo = require("./useSyncDemo");
|
|
30
30
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
31
31
|
"@tldraw/sync",
|
|
32
|
-
"3.16.0-internal.
|
|
32
|
+
"3.16.0-internal.a478398270c6",
|
|
33
33
|
"cjs"
|
|
34
34
|
);
|
|
35
35
|
//# sourceMappingURL=index.js.map
|
package/dist-cjs/useSync.js
CHANGED
|
@@ -105,13 +105,6 @@ function useSync(opts) {
|
|
|
105
105
|
id: import_tldraw.InstancePresenceRecordType.createId(store.id)
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
-
const otherUserPresences = store.query.ids("instance_presence", () => ({
|
|
109
|
-
userId: { neq: userPreferences.get().id }
|
|
110
|
-
}));
|
|
111
|
-
const presenceMode = (0, import_tldraw.computed)("presenceMode", () => {
|
|
112
|
-
if (otherUserPresences.get().size === 0) return "solo";
|
|
113
|
-
return "full";
|
|
114
|
-
});
|
|
115
108
|
const client = new import_sync_core.TLSyncClient({
|
|
116
109
|
store,
|
|
117
110
|
socket,
|
|
@@ -148,8 +141,7 @@ function useSync(opts) {
|
|
|
148
141
|
store.ensureStoreIsUsable();
|
|
149
142
|
});
|
|
150
143
|
},
|
|
151
|
-
presence
|
|
152
|
-
presenceMode
|
|
144
|
+
presence
|
|
153
145
|
});
|
|
154
146
|
return () => {
|
|
155
147
|
didCancel = true;
|
package/dist-cjs/useSync.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/useSync.ts"],
|
|
4
|
-
"sourcesContent": ["import { atom, isSignal, transact } from '@tldraw/state'\nimport { useAtom } from '@tldraw/state-react'\nimport {\n\tClientWebSocketAdapter,\n\tTLPresenceMode,\n\tTLRemoteSyncError,\n\tTLSyncClient,\n\tTLSyncErrorCloseEventReason,\n} from '@tldraw/sync-core'\nimport { useEffect } from 'react'\nimport {\n\tEditor,\n\tInstancePresenceRecordType,\n\tSignal,\n\tTAB_ID,\n\tTLAssetStore,\n\tTLPresenceStateInfo,\n\tTLPresenceUserInfo,\n\tTLRecord,\n\tTLStore,\n\tTLStoreSchemaOptions,\n\tTLStoreWithStatus,\n\tcomputed,\n\tcreateTLStore,\n\tdefaultUserPreferences,\n\tgetDefaultUserPresence,\n\tgetUserPreferences,\n\tuniqueId,\n\tuseReactiveEvent,\n\tuseRefState,\n\tuseShallowObjectIdentity,\n\tuseTLSchemaFromUtils,\n\tuseValue,\n} from 'tldraw'\n\nconst MULTIPLAYER_EVENT_NAME = 'multiplayer.client'\n\n/** @public */\nexport type RemoteTLStoreWithStatus = Exclude<\n\tTLStoreWithStatus,\n\t{ status: 'synced-local' } | { status: 'not-synced' }\n>\n\n/**\n * useSync creates a store that is synced with a multiplayer server.\n *\n * The store can be passed directly into the `<Tldraw />` component to enable multiplayer features.\n * It will handle loading states, and enable multiplayer UX like user cursors and following.\n *\n * To enable external blob storage, you should also pass in an `assets` object that implements the {@link tldraw#TLAssetStore} interface.\n * If you don't do this, adding large images and videos to rooms will cause performance issues at serialization boundaries.\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const store = useSync({\n * uri: 'wss://myapp.com/sync/my-test-room',\n * assets: myAssetStore\n * })\n * return <Tldraw store={store} />\n * }\n *\n * ```\n * @param opts - Options for the multiplayer sync store. See {@link UseSyncOptions} and {@link tldraw#TLStoreSchemaOptions}.\n *\n * @public\n */\nexport function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus {\n\tconst [state, setState] = useRefState<{\n\t\treadyClient?: TLSyncClient<TLRecord, TLStore>\n\t\terror?: Error\n\t} | null>(null)\n\tconst {\n\t\turi,\n\t\troomId = 'default',\n\t\tassets,\n\t\tonMount,\n\t\ttrackAnalyticsEvent: track,\n\t\tuserInfo,\n\t\tgetUserPresence: _getUserPresence,\n\t\t...schemaOpts\n\t} = opts\n\n\t// This line will throw a type error if we add any new options to the useSync hook but we don't destructure them\n\t// This is required because otherwise the useTLSchemaFromUtils might return a new schema on every render if the newly-added option\n\t// is allowed to be unstable (e.g. userInfo)\n\tconst __never__: never = 0 as any as keyof Omit<typeof schemaOpts, keyof TLStoreSchemaOptions>\n\n\tconst schema = useTLSchemaFromUtils(schemaOpts)\n\n\tconst prefs = useShallowObjectIdentity(userInfo)\n\tconst getUserPresence = useReactiveEvent(_getUserPresence ?? getDefaultUserPresence)\n\n\tconst userAtom = useAtom<TLPresenceUserInfo | Signal<TLPresenceUserInfo> | undefined>(\n\t\t'userAtom',\n\t\tprefs\n\t)\n\n\tuseEffect(() => {\n\t\tuserAtom.set(prefs)\n\t}, [prefs, userAtom])\n\n\tuseEffect(() => {\n\t\tconst storeId = uniqueId()\n\n\t\tconst userPreferences = computed<{ id: string; color: string; name: string }>(\n\t\t\t'userPreferences',\n\t\t\t() => {\n\t\t\t\tconst userStuff = userAtom.get()\n\t\t\t\tconst user = (isSignal(userStuff) ? userStuff.get() : userStuff) ?? getUserPreferences()\n\t\t\t\treturn {\n\t\t\t\t\tid: user.id,\n\t\t\t\t\tcolor: user.color ?? defaultUserPreferences.color,\n\t\t\t\t\tname: user.name ?? defaultUserPreferences.name,\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\tconst socket = new ClientWebSocketAdapter(async () => {\n\t\t\tconst uriString = typeof uri === 'string' ? uri : await uri()\n\n\t\t\t// set sessionId as a query param on the uri\n\t\t\tconst withParams = new URL(uriString)\n\t\t\tif (withParams.searchParams.has('sessionId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"sessionId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (withParams.searchParams.has('storeId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"storeId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\twithParams.searchParams.set('sessionId', TAB_ID)\n\t\t\twithParams.searchParams.set('storeId', storeId)\n\t\t\treturn withParams.toString()\n\t\t})\n\n\t\tlet didCancel = false\n\n\t\tconst collaborationStatusSignal = computed('collaboration status', () =>\n\t\t\tsocket.connectionStatus === 'error' ? 'offline' : socket.connectionStatus\n\t\t)\n\n\t\tconst syncMode = atom('sync mode', 'readwrite' as 'readonly' | 'readwrite')\n\n\t\tconst store = createTLStore({\n\t\t\tid: storeId,\n\t\t\tschema,\n\t\t\tassets,\n\t\t\tonMount,\n\t\t\tcollaboration: {\n\t\t\t\tstatus: collaborationStatusSignal,\n\t\t\t\tmode: syncMode,\n\t\t\t},\n\t\t})\n\n\t\tconst presence = computed('instancePresence', () => {\n\t\t\tconst presenceState = getUserPresence(store, userPreferences.get())\n\t\t\tif (!presenceState) return null\n\n\t\t\treturn InstancePresenceRecordType.create({\n\t\t\t\t...presenceState,\n\t\t\t\tid: InstancePresenceRecordType.createId(store.id),\n\t\t\t})\n\t\t})\n\n\t\tconst otherUserPresences = store.query.ids('instance_presence', () => ({\n\t\t\tuserId: { neq: userPreferences.get().id },\n\t\t}))\n\n\t\tconst presenceMode = computed<TLPresenceMode>('presenceMode', () => {\n\t\t\tif (otherUserPresences.get().size === 0) return 'solo'\n\t\t\treturn 'full'\n\t\t})\n\n\t\tconst client = new TLSyncClient({\n\t\t\tstore,\n\t\t\tsocket,\n\t\t\tdidCancel: () => didCancel,\n\t\t\tonLoad(client) {\n\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'load', roomId })\n\t\t\t\tsetState({ readyClient: client })\n\t\t\t},\n\t\t\tonSyncError(reason) {\n\t\t\t\tconsole.error('sync error', reason)\n\n\t\t\t\tswitch (reason) {\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_FOUND:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'room-not-found', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.FORBIDDEN:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'forbidden', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_AUTHENTICATED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'not-authenticated', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.RATE_LIMITED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'rate-limited', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'sync-error:' + reason, roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsetState({ error: new TLRemoteSyncError(reason) })\n\t\t\t\tsocket.close()\n\t\t\t},\n\t\t\tonAfterConnect(_, { isReadonly }) {\n\t\t\t\ttransact(() => {\n\t\t\t\t\tsyncMode.set(isReadonly ? 'readonly' : 'readwrite')\n\t\t\t\t\t// if the server crashes and loses all data it can return an empty document\n\t\t\t\t\t// when it comes back up. This is a safety check to make sure that if something like\n\t\t\t\t\t// that happens, it won't render the app broken and require a restart. The user will\n\t\t\t\t\t// most likely lose all their changes though since they'll have been working with pages\n\t\t\t\t\t// that won't exist. There's certainly something we can do to make this better.\n\t\t\t\t\t// but the likelihood of this happening is very low and maybe not worth caring about beyond this.\n\t\t\t\t\tstore.ensureStoreIsUsable()\n\t\t\t\t})\n\t\t\t},\n\t\t\tpresence,\n\t\t\tpresenceMode,\n\t\t})\n\n\t\treturn () => {\n\t\t\tdidCancel = true\n\t\t\tclient.close()\n\t\t\tsocket.close()\n\t\t\tsetState(null)\n\t\t}\n\t}, [assets, onMount, userAtom, roomId, schema, setState, track, uri, getUserPresence])\n\n\treturn useValue<RemoteTLStoreWithStatus>(\n\t\t'remote synced store',\n\t\t() => {\n\t\t\tif (!state) return { status: 'loading' }\n\t\t\tif (state.error) return { status: 'error', error: state.error }\n\t\t\tif (!state.readyClient) return { status: 'loading' }\n\t\t\tconst connectionStatus = state.readyClient.socket.connectionStatus\n\t\t\treturn {\n\t\t\t\tstatus: 'synced-remote',\n\t\t\t\tconnectionStatus: connectionStatus === 'error' ? 'offline' : connectionStatus,\n\t\t\t\tstore: state.readyClient.store,\n\t\t\t}\n\t\t},\n\t\t[state]\n\t)\n}\n\n/**\n * Options for the {@link useSync} hook.\n * @public\n */\nexport interface UseSyncOptions {\n\t/**\n\t * The URI of the multiplayer server. This must include the protocol,\n\t *\n\t * e.g. `wss://server.example.com/my-room` or `ws://localhost:5858/my-room`.\n\t *\n\t * Note that the protocol can also be `https` or `http` and it will upgrade to a websocket\n\t * connection.\n\t *\n\t * Optionally, you can pass a function which will be called each time a connection is\n\t * established to get the URI. This is useful if you need to include e.g. a short-lived session\n\t * token for authentication.\n\t */\n\turi: string | (() => string | Promise<string>)\n\t/**\n\t * A signal that contains the user information needed for multiplayer features.\n\t * This should be synchronized with the `userPreferences` configuration for the main `<Tldraw />` component.\n\t * If not provided, a default implementation based on localStorage will be used.\n\t */\n\tuserInfo?: TLPresenceUserInfo | Signal<TLPresenceUserInfo>\n\t/**\n\t * The asset store for blob storage. See {@link tldraw#TLAssetStore}.\n\t *\n\t * If you don't have time to implement blob storage and just want to get started, you can use the inline base64 asset store. {@link tldraw#inlineBase64AssetStore}\n\t * Note that storing base64 blobs inline in JSON is very inefficient and will cause performance issues quickly with large images and videos.\n\t */\n\tassets: TLAssetStore\n\n\t/** @internal */\n\tonMount?(editor: Editor): void\n\t/** @internal used for analytics only, we should refactor this away */\n\troomId?: string\n\t/** @internal */\n\ttrackAnalyticsEvent?(name: string, data: { [key: string]: any }): void\n\n\t/**\n\t * A reactive function that returns a {@link @tldraw/tlschema#TLInstancePresence} object. The\n\t * result of this function will be synchronized across all clients to display presence\n\t * indicators such as cursors. See {@link @tldraw/tlschema#getDefaultUserPresence} for\n\t * the default implementation of this function.\n\t */\n\tgetUserPresence?(store: TLStore, user: TLPresenceUserInfo): TLPresenceStateInfo | null\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAyC;AACzC,yBAAwB;AACxB,
|
|
4
|
+
"sourcesContent": ["import { atom, isSignal, transact } from '@tldraw/state'\nimport { useAtom } from '@tldraw/state-react'\nimport {\n\tClientWebSocketAdapter,\n\tTLRemoteSyncError,\n\tTLSyncClient,\n\tTLSyncErrorCloseEventReason,\n} from '@tldraw/sync-core'\nimport { useEffect } from 'react'\nimport {\n\tEditor,\n\tInstancePresenceRecordType,\n\tSignal,\n\tTAB_ID,\n\tTLAssetStore,\n\tTLPresenceStateInfo,\n\tTLPresenceUserInfo,\n\tTLRecord,\n\tTLStore,\n\tTLStoreSchemaOptions,\n\tTLStoreWithStatus,\n\tcomputed,\n\tcreateTLStore,\n\tdefaultUserPreferences,\n\tgetDefaultUserPresence,\n\tgetUserPreferences,\n\tuniqueId,\n\tuseReactiveEvent,\n\tuseRefState,\n\tuseShallowObjectIdentity,\n\tuseTLSchemaFromUtils,\n\tuseValue,\n} from 'tldraw'\n\nconst MULTIPLAYER_EVENT_NAME = 'multiplayer.client'\n\n/** @public */\nexport type RemoteTLStoreWithStatus = Exclude<\n\tTLStoreWithStatus,\n\t{ status: 'synced-local' } | { status: 'not-synced' }\n>\n\n/**\n * useSync creates a store that is synced with a multiplayer server.\n *\n * The store can be passed directly into the `<Tldraw />` component to enable multiplayer features.\n * It will handle loading states, and enable multiplayer UX like user cursors and following.\n *\n * To enable external blob storage, you should also pass in an `assets` object that implements the {@link tldraw#TLAssetStore} interface.\n * If you don't do this, adding large images and videos to rooms will cause performance issues at serialization boundaries.\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const store = useSync({\n * uri: 'wss://myapp.com/sync/my-test-room',\n * assets: myAssetStore\n * })\n * return <Tldraw store={store} />\n * }\n *\n * ```\n * @param opts - Options for the multiplayer sync store. See {@link UseSyncOptions} and {@link tldraw#TLStoreSchemaOptions}.\n *\n * @public\n */\nexport function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus {\n\tconst [state, setState] = useRefState<{\n\t\treadyClient?: TLSyncClient<TLRecord, TLStore>\n\t\terror?: Error\n\t} | null>(null)\n\tconst {\n\t\turi,\n\t\troomId = 'default',\n\t\tassets,\n\t\tonMount,\n\t\ttrackAnalyticsEvent: track,\n\t\tuserInfo,\n\t\tgetUserPresence: _getUserPresence,\n\t\t...schemaOpts\n\t} = opts\n\n\t// This line will throw a type error if we add any new options to the useSync hook but we don't destructure them\n\t// This is required because otherwise the useTLSchemaFromUtils might return a new schema on every render if the newly-added option\n\t// is allowed to be unstable (e.g. userInfo)\n\tconst __never__: never = 0 as any as keyof Omit<typeof schemaOpts, keyof TLStoreSchemaOptions>\n\n\tconst schema = useTLSchemaFromUtils(schemaOpts)\n\n\tconst prefs = useShallowObjectIdentity(userInfo)\n\tconst getUserPresence = useReactiveEvent(_getUserPresence ?? getDefaultUserPresence)\n\n\tconst userAtom = useAtom<TLPresenceUserInfo | Signal<TLPresenceUserInfo> | undefined>(\n\t\t'userAtom',\n\t\tprefs\n\t)\n\n\tuseEffect(() => {\n\t\tuserAtom.set(prefs)\n\t}, [prefs, userAtom])\n\n\tuseEffect(() => {\n\t\tconst storeId = uniqueId()\n\n\t\tconst userPreferences = computed<{ id: string; color: string; name: string }>(\n\t\t\t'userPreferences',\n\t\t\t() => {\n\t\t\t\tconst userStuff = userAtom.get()\n\t\t\t\tconst user = (isSignal(userStuff) ? userStuff.get() : userStuff) ?? getUserPreferences()\n\t\t\t\treturn {\n\t\t\t\t\tid: user.id,\n\t\t\t\t\tcolor: user.color ?? defaultUserPreferences.color,\n\t\t\t\t\tname: user.name ?? defaultUserPreferences.name,\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\tconst socket = new ClientWebSocketAdapter(async () => {\n\t\t\tconst uriString = typeof uri === 'string' ? uri : await uri()\n\n\t\t\t// set sessionId as a query param on the uri\n\t\t\tconst withParams = new URL(uriString)\n\t\t\tif (withParams.searchParams.has('sessionId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"sessionId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (withParams.searchParams.has('storeId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"storeId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\twithParams.searchParams.set('sessionId', TAB_ID)\n\t\t\twithParams.searchParams.set('storeId', storeId)\n\t\t\treturn withParams.toString()\n\t\t})\n\n\t\tlet didCancel = false\n\n\t\tconst collaborationStatusSignal = computed('collaboration status', () =>\n\t\t\tsocket.connectionStatus === 'error' ? 'offline' : socket.connectionStatus\n\t\t)\n\n\t\tconst syncMode = atom('sync mode', 'readwrite' as 'readonly' | 'readwrite')\n\n\t\tconst store = createTLStore({\n\t\t\tid: storeId,\n\t\t\tschema,\n\t\t\tassets,\n\t\t\tonMount,\n\t\t\tcollaboration: {\n\t\t\t\tstatus: collaborationStatusSignal,\n\t\t\t\tmode: syncMode,\n\t\t\t},\n\t\t})\n\n\t\tconst presence = computed('instancePresence', () => {\n\t\t\tconst presenceState = getUserPresence(store, userPreferences.get())\n\t\t\tif (!presenceState) return null\n\n\t\t\treturn InstancePresenceRecordType.create({\n\t\t\t\t...presenceState,\n\t\t\t\tid: InstancePresenceRecordType.createId(store.id),\n\t\t\t})\n\t\t})\n\n\t\tconst client = new TLSyncClient({\n\t\t\tstore,\n\t\t\tsocket,\n\t\t\tdidCancel: () => didCancel,\n\t\t\tonLoad(client) {\n\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'load', roomId })\n\t\t\t\tsetState({ readyClient: client })\n\t\t\t},\n\t\t\tonSyncError(reason) {\n\t\t\t\tconsole.error('sync error', reason)\n\n\t\t\t\tswitch (reason) {\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_FOUND:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'room-not-found', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.FORBIDDEN:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'forbidden', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_AUTHENTICATED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'not-authenticated', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.RATE_LIMITED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'rate-limited', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'sync-error:' + reason, roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsetState({ error: new TLRemoteSyncError(reason) })\n\t\t\t\tsocket.close()\n\t\t\t},\n\t\t\tonAfterConnect(_, { isReadonly }) {\n\t\t\t\ttransact(() => {\n\t\t\t\t\tsyncMode.set(isReadonly ? 'readonly' : 'readwrite')\n\t\t\t\t\t// if the server crashes and loses all data it can return an empty document\n\t\t\t\t\t// when it comes back up. This is a safety check to make sure that if something like\n\t\t\t\t\t// that happens, it won't render the app broken and require a restart. The user will\n\t\t\t\t\t// most likely lose all their changes though since they'll have been working with pages\n\t\t\t\t\t// that won't exist. There's certainly something we can do to make this better.\n\t\t\t\t\t// but the likelihood of this happening is very low and maybe not worth caring about beyond this.\n\t\t\t\t\tstore.ensureStoreIsUsable()\n\t\t\t\t})\n\t\t\t},\n\t\t\tpresence,\n\t\t})\n\n\t\treturn () => {\n\t\t\tdidCancel = true\n\t\t\tclient.close()\n\t\t\tsocket.close()\n\t\t\tsetState(null)\n\t\t}\n\t}, [assets, onMount, userAtom, roomId, schema, setState, track, uri, getUserPresence])\n\n\treturn useValue<RemoteTLStoreWithStatus>(\n\t\t'remote synced store',\n\t\t() => {\n\t\t\tif (!state) return { status: 'loading' }\n\t\t\tif (state.error) return { status: 'error', error: state.error }\n\t\t\tif (!state.readyClient) return { status: 'loading' }\n\t\t\tconst connectionStatus = state.readyClient.socket.connectionStatus\n\t\t\treturn {\n\t\t\t\tstatus: 'synced-remote',\n\t\t\t\tconnectionStatus: connectionStatus === 'error' ? 'offline' : connectionStatus,\n\t\t\t\tstore: state.readyClient.store,\n\t\t\t}\n\t\t},\n\t\t[state]\n\t)\n}\n\n/**\n * Options for the {@link useSync} hook.\n * @public\n */\nexport interface UseSyncOptions {\n\t/**\n\t * The URI of the multiplayer server. This must include the protocol,\n\t *\n\t * e.g. `wss://server.example.com/my-room` or `ws://localhost:5858/my-room`.\n\t *\n\t * Note that the protocol can also be `https` or `http` and it will upgrade to a websocket\n\t * connection.\n\t *\n\t * Optionally, you can pass a function which will be called each time a connection is\n\t * established to get the URI. This is useful if you need to include e.g. a short-lived session\n\t * token for authentication.\n\t */\n\turi: string | (() => string | Promise<string>)\n\t/**\n\t * A signal that contains the user information needed for multiplayer features.\n\t * This should be synchronized with the `userPreferences` configuration for the main `<Tldraw />` component.\n\t * If not provided, a default implementation based on localStorage will be used.\n\t */\n\tuserInfo?: TLPresenceUserInfo | Signal<TLPresenceUserInfo>\n\t/**\n\t * The asset store for blob storage. See {@link tldraw#TLAssetStore}.\n\t *\n\t * If you don't have time to implement blob storage and just want to get started, you can use the inline base64 asset store. {@link tldraw#inlineBase64AssetStore}\n\t * Note that storing base64 blobs inline in JSON is very inefficient and will cause performance issues quickly with large images and videos.\n\t */\n\tassets: TLAssetStore\n\n\t/** @internal */\n\tonMount?(editor: Editor): void\n\t/** @internal used for analytics only, we should refactor this away */\n\troomId?: string\n\t/** @internal */\n\ttrackAnalyticsEvent?(name: string, data: { [key: string]: any }): void\n\n\t/**\n\t * A reactive function that returns a {@link @tldraw/tlschema#TLInstancePresence} object. The\n\t * result of this function will be synchronized across all clients to display presence\n\t * indicators such as cursors. See {@link @tldraw/tlschema#getDefaultUserPresence} for\n\t * the default implementation of this function.\n\t */\n\tgetUserPresence?(store: TLStore, user: TLPresenceUserInfo): TLPresenceStateInfo | null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["client"]
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
package/dist-esm/useSync.mjs
CHANGED
|
@@ -101,13 +101,6 @@ function useSync(opts) {
|
|
|
101
101
|
id: InstancePresenceRecordType.createId(store.id)
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
|
-
const otherUserPresences = store.query.ids("instance_presence", () => ({
|
|
105
|
-
userId: { neq: userPreferences.get().id }
|
|
106
|
-
}));
|
|
107
|
-
const presenceMode = computed("presenceMode", () => {
|
|
108
|
-
if (otherUserPresences.get().size === 0) return "solo";
|
|
109
|
-
return "full";
|
|
110
|
-
});
|
|
111
104
|
const client = new TLSyncClient({
|
|
112
105
|
store,
|
|
113
106
|
socket,
|
|
@@ -144,8 +137,7 @@ function useSync(opts) {
|
|
|
144
137
|
store.ensureStoreIsUsable();
|
|
145
138
|
});
|
|
146
139
|
},
|
|
147
|
-
presence
|
|
148
|
-
presenceMode
|
|
140
|
+
presence
|
|
149
141
|
});
|
|
150
142
|
return () => {
|
|
151
143
|
didCancel = true;
|
package/dist-esm/useSync.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/useSync.ts"],
|
|
4
|
-
"sourcesContent": ["import { atom, isSignal, transact } from '@tldraw/state'\nimport { useAtom } from '@tldraw/state-react'\nimport {\n\tClientWebSocketAdapter,\n\tTLPresenceMode,\n\tTLRemoteSyncError,\n\tTLSyncClient,\n\tTLSyncErrorCloseEventReason,\n} from '@tldraw/sync-core'\nimport { useEffect } from 'react'\nimport {\n\tEditor,\n\tInstancePresenceRecordType,\n\tSignal,\n\tTAB_ID,\n\tTLAssetStore,\n\tTLPresenceStateInfo,\n\tTLPresenceUserInfo,\n\tTLRecord,\n\tTLStore,\n\tTLStoreSchemaOptions,\n\tTLStoreWithStatus,\n\tcomputed,\n\tcreateTLStore,\n\tdefaultUserPreferences,\n\tgetDefaultUserPresence,\n\tgetUserPreferences,\n\tuniqueId,\n\tuseReactiveEvent,\n\tuseRefState,\n\tuseShallowObjectIdentity,\n\tuseTLSchemaFromUtils,\n\tuseValue,\n} from 'tldraw'\n\nconst MULTIPLAYER_EVENT_NAME = 'multiplayer.client'\n\n/** @public */\nexport type RemoteTLStoreWithStatus = Exclude<\n\tTLStoreWithStatus,\n\t{ status: 'synced-local' } | { status: 'not-synced' }\n>\n\n/**\n * useSync creates a store that is synced with a multiplayer server.\n *\n * The store can be passed directly into the `<Tldraw />` component to enable multiplayer features.\n * It will handle loading states, and enable multiplayer UX like user cursors and following.\n *\n * To enable external blob storage, you should also pass in an `assets` object that implements the {@link tldraw#TLAssetStore} interface.\n * If you don't do this, adding large images and videos to rooms will cause performance issues at serialization boundaries.\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const store = useSync({\n * uri: 'wss://myapp.com/sync/my-test-room',\n * assets: myAssetStore\n * })\n * return <Tldraw store={store} />\n * }\n *\n * ```\n * @param opts - Options for the multiplayer sync store. See {@link UseSyncOptions} and {@link tldraw#TLStoreSchemaOptions}.\n *\n * @public\n */\nexport function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus {\n\tconst [state, setState] = useRefState<{\n\t\treadyClient?: TLSyncClient<TLRecord, TLStore>\n\t\terror?: Error\n\t} | null>(null)\n\tconst {\n\t\turi,\n\t\troomId = 'default',\n\t\tassets,\n\t\tonMount,\n\t\ttrackAnalyticsEvent: track,\n\t\tuserInfo,\n\t\tgetUserPresence: _getUserPresence,\n\t\t...schemaOpts\n\t} = opts\n\n\t// This line will throw a type error if we add any new options to the useSync hook but we don't destructure them\n\t// This is required because otherwise the useTLSchemaFromUtils might return a new schema on every render if the newly-added option\n\t// is allowed to be unstable (e.g. userInfo)\n\tconst __never__: never = 0 as any as keyof Omit<typeof schemaOpts, keyof TLStoreSchemaOptions>\n\n\tconst schema = useTLSchemaFromUtils(schemaOpts)\n\n\tconst prefs = useShallowObjectIdentity(userInfo)\n\tconst getUserPresence = useReactiveEvent(_getUserPresence ?? getDefaultUserPresence)\n\n\tconst userAtom = useAtom<TLPresenceUserInfo | Signal<TLPresenceUserInfo> | undefined>(\n\t\t'userAtom',\n\t\tprefs\n\t)\n\n\tuseEffect(() => {\n\t\tuserAtom.set(prefs)\n\t}, [prefs, userAtom])\n\n\tuseEffect(() => {\n\t\tconst storeId = uniqueId()\n\n\t\tconst userPreferences = computed<{ id: string; color: string; name: string }>(\n\t\t\t'userPreferences',\n\t\t\t() => {\n\t\t\t\tconst userStuff = userAtom.get()\n\t\t\t\tconst user = (isSignal(userStuff) ? userStuff.get() : userStuff) ?? getUserPreferences()\n\t\t\t\treturn {\n\t\t\t\t\tid: user.id,\n\t\t\t\t\tcolor: user.color ?? defaultUserPreferences.color,\n\t\t\t\t\tname: user.name ?? defaultUserPreferences.name,\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\tconst socket = new ClientWebSocketAdapter(async () => {\n\t\t\tconst uriString = typeof uri === 'string' ? uri : await uri()\n\n\t\t\t// set sessionId as a query param on the uri\n\t\t\tconst withParams = new URL(uriString)\n\t\t\tif (withParams.searchParams.has('sessionId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"sessionId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (withParams.searchParams.has('storeId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"storeId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\twithParams.searchParams.set('sessionId', TAB_ID)\n\t\t\twithParams.searchParams.set('storeId', storeId)\n\t\t\treturn withParams.toString()\n\t\t})\n\n\t\tlet didCancel = false\n\n\t\tconst collaborationStatusSignal = computed('collaboration status', () =>\n\t\t\tsocket.connectionStatus === 'error' ? 'offline' : socket.connectionStatus\n\t\t)\n\n\t\tconst syncMode = atom('sync mode', 'readwrite' as 'readonly' | 'readwrite')\n\n\t\tconst store = createTLStore({\n\t\t\tid: storeId,\n\t\t\tschema,\n\t\t\tassets,\n\t\t\tonMount,\n\t\t\tcollaboration: {\n\t\t\t\tstatus: collaborationStatusSignal,\n\t\t\t\tmode: syncMode,\n\t\t\t},\n\t\t})\n\n\t\tconst presence = computed('instancePresence', () => {\n\t\t\tconst presenceState = getUserPresence(store, userPreferences.get())\n\t\t\tif (!presenceState) return null\n\n\t\t\treturn InstancePresenceRecordType.create({\n\t\t\t\t...presenceState,\n\t\t\t\tid: InstancePresenceRecordType.createId(store.id),\n\t\t\t})\n\t\t})\n\n\t\tconst otherUserPresences = store.query.ids('instance_presence', () => ({\n\t\t\tuserId: { neq: userPreferences.get().id },\n\t\t}))\n\n\t\tconst presenceMode = computed<TLPresenceMode>('presenceMode', () => {\n\t\t\tif (otherUserPresences.get().size === 0) return 'solo'\n\t\t\treturn 'full'\n\t\t})\n\n\t\tconst client = new TLSyncClient({\n\t\t\tstore,\n\t\t\tsocket,\n\t\t\tdidCancel: () => didCancel,\n\t\t\tonLoad(client) {\n\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'load', roomId })\n\t\t\t\tsetState({ readyClient: client })\n\t\t\t},\n\t\t\tonSyncError(reason) {\n\t\t\t\tconsole.error('sync error', reason)\n\n\t\t\t\tswitch (reason) {\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_FOUND:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'room-not-found', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.FORBIDDEN:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'forbidden', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_AUTHENTICATED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'not-authenticated', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.RATE_LIMITED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'rate-limited', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'sync-error:' + reason, roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsetState({ error: new TLRemoteSyncError(reason) })\n\t\t\t\tsocket.close()\n\t\t\t},\n\t\t\tonAfterConnect(_, { isReadonly }) {\n\t\t\t\ttransact(() => {\n\t\t\t\t\tsyncMode.set(isReadonly ? 'readonly' : 'readwrite')\n\t\t\t\t\t// if the server crashes and loses all data it can return an empty document\n\t\t\t\t\t// when it comes back up. This is a safety check to make sure that if something like\n\t\t\t\t\t// that happens, it won't render the app broken and require a restart. The user will\n\t\t\t\t\t// most likely lose all their changes though since they'll have been working with pages\n\t\t\t\t\t// that won't exist. There's certainly something we can do to make this better.\n\t\t\t\t\t// but the likelihood of this happening is very low and maybe not worth caring about beyond this.\n\t\t\t\t\tstore.ensureStoreIsUsable()\n\t\t\t\t})\n\t\t\t},\n\t\t\tpresence,\n\t\t\tpresenceMode,\n\t\t})\n\n\t\treturn () => {\n\t\t\tdidCancel = true\n\t\t\tclient.close()\n\t\t\tsocket.close()\n\t\t\tsetState(null)\n\t\t}\n\t}, [assets, onMount, userAtom, roomId, schema, setState, track, uri, getUserPresence])\n\n\treturn useValue<RemoteTLStoreWithStatus>(\n\t\t'remote synced store',\n\t\t() => {\n\t\t\tif (!state) return { status: 'loading' }\n\t\t\tif (state.error) return { status: 'error', error: state.error }\n\t\t\tif (!state.readyClient) return { status: 'loading' }\n\t\t\tconst connectionStatus = state.readyClient.socket.connectionStatus\n\t\t\treturn {\n\t\t\t\tstatus: 'synced-remote',\n\t\t\t\tconnectionStatus: connectionStatus === 'error' ? 'offline' : connectionStatus,\n\t\t\t\tstore: state.readyClient.store,\n\t\t\t}\n\t\t},\n\t\t[state]\n\t)\n}\n\n/**\n * Options for the {@link useSync} hook.\n * @public\n */\nexport interface UseSyncOptions {\n\t/**\n\t * The URI of the multiplayer server. This must include the protocol,\n\t *\n\t * e.g. `wss://server.example.com/my-room` or `ws://localhost:5858/my-room`.\n\t *\n\t * Note that the protocol can also be `https` or `http` and it will upgrade to a websocket\n\t * connection.\n\t *\n\t * Optionally, you can pass a function which will be called each time a connection is\n\t * established to get the URI. This is useful if you need to include e.g. a short-lived session\n\t * token for authentication.\n\t */\n\turi: string | (() => string | Promise<string>)\n\t/**\n\t * A signal that contains the user information needed for multiplayer features.\n\t * This should be synchronized with the `userPreferences` configuration for the main `<Tldraw />` component.\n\t * If not provided, a default implementation based on localStorage will be used.\n\t */\n\tuserInfo?: TLPresenceUserInfo | Signal<TLPresenceUserInfo>\n\t/**\n\t * The asset store for blob storage. See {@link tldraw#TLAssetStore}.\n\t *\n\t * If you don't have time to implement blob storage and just want to get started, you can use the inline base64 asset store. {@link tldraw#inlineBase64AssetStore}\n\t * Note that storing base64 blobs inline in JSON is very inefficient and will cause performance issues quickly with large images and videos.\n\t */\n\tassets: TLAssetStore\n\n\t/** @internal */\n\tonMount?(editor: Editor): void\n\t/** @internal used for analytics only, we should refactor this away */\n\troomId?: string\n\t/** @internal */\n\ttrackAnalyticsEvent?(name: string, data: { [key: string]: any }): void\n\n\t/**\n\t * A reactive function that returns a {@link @tldraw/tlschema#TLInstancePresence} object. The\n\t * result of this function will be synchronized across all clients to display presence\n\t * indicators such as cursors. See {@link @tldraw/tlschema#getDefaultUserPresence} for\n\t * the default implementation of this function.\n\t */\n\tgetUserPresence?(store: TLStore, user: TLPresenceUserInfo): TLPresenceStateInfo | null\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,MAAM,UAAU,gBAAgB;AACzC,SAAS,eAAe;AACxB;AAAA,EACC;AAAA,
|
|
4
|
+
"sourcesContent": ["import { atom, isSignal, transact } from '@tldraw/state'\nimport { useAtom } from '@tldraw/state-react'\nimport {\n\tClientWebSocketAdapter,\n\tTLRemoteSyncError,\n\tTLSyncClient,\n\tTLSyncErrorCloseEventReason,\n} from '@tldraw/sync-core'\nimport { useEffect } from 'react'\nimport {\n\tEditor,\n\tInstancePresenceRecordType,\n\tSignal,\n\tTAB_ID,\n\tTLAssetStore,\n\tTLPresenceStateInfo,\n\tTLPresenceUserInfo,\n\tTLRecord,\n\tTLStore,\n\tTLStoreSchemaOptions,\n\tTLStoreWithStatus,\n\tcomputed,\n\tcreateTLStore,\n\tdefaultUserPreferences,\n\tgetDefaultUserPresence,\n\tgetUserPreferences,\n\tuniqueId,\n\tuseReactiveEvent,\n\tuseRefState,\n\tuseShallowObjectIdentity,\n\tuseTLSchemaFromUtils,\n\tuseValue,\n} from 'tldraw'\n\nconst MULTIPLAYER_EVENT_NAME = 'multiplayer.client'\n\n/** @public */\nexport type RemoteTLStoreWithStatus = Exclude<\n\tTLStoreWithStatus,\n\t{ status: 'synced-local' } | { status: 'not-synced' }\n>\n\n/**\n * useSync creates a store that is synced with a multiplayer server.\n *\n * The store can be passed directly into the `<Tldraw />` component to enable multiplayer features.\n * It will handle loading states, and enable multiplayer UX like user cursors and following.\n *\n * To enable external blob storage, you should also pass in an `assets` object that implements the {@link tldraw#TLAssetStore} interface.\n * If you don't do this, adding large images and videos to rooms will cause performance issues at serialization boundaries.\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const store = useSync({\n * uri: 'wss://myapp.com/sync/my-test-room',\n * assets: myAssetStore\n * })\n * return <Tldraw store={store} />\n * }\n *\n * ```\n * @param opts - Options for the multiplayer sync store. See {@link UseSyncOptions} and {@link tldraw#TLStoreSchemaOptions}.\n *\n * @public\n */\nexport function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLStoreWithStatus {\n\tconst [state, setState] = useRefState<{\n\t\treadyClient?: TLSyncClient<TLRecord, TLStore>\n\t\terror?: Error\n\t} | null>(null)\n\tconst {\n\t\turi,\n\t\troomId = 'default',\n\t\tassets,\n\t\tonMount,\n\t\ttrackAnalyticsEvent: track,\n\t\tuserInfo,\n\t\tgetUserPresence: _getUserPresence,\n\t\t...schemaOpts\n\t} = opts\n\n\t// This line will throw a type error if we add any new options to the useSync hook but we don't destructure them\n\t// This is required because otherwise the useTLSchemaFromUtils might return a new schema on every render if the newly-added option\n\t// is allowed to be unstable (e.g. userInfo)\n\tconst __never__: never = 0 as any as keyof Omit<typeof schemaOpts, keyof TLStoreSchemaOptions>\n\n\tconst schema = useTLSchemaFromUtils(schemaOpts)\n\n\tconst prefs = useShallowObjectIdentity(userInfo)\n\tconst getUserPresence = useReactiveEvent(_getUserPresence ?? getDefaultUserPresence)\n\n\tconst userAtom = useAtom<TLPresenceUserInfo | Signal<TLPresenceUserInfo> | undefined>(\n\t\t'userAtom',\n\t\tprefs\n\t)\n\n\tuseEffect(() => {\n\t\tuserAtom.set(prefs)\n\t}, [prefs, userAtom])\n\n\tuseEffect(() => {\n\t\tconst storeId = uniqueId()\n\n\t\tconst userPreferences = computed<{ id: string; color: string; name: string }>(\n\t\t\t'userPreferences',\n\t\t\t() => {\n\t\t\t\tconst userStuff = userAtom.get()\n\t\t\t\tconst user = (isSignal(userStuff) ? userStuff.get() : userStuff) ?? getUserPreferences()\n\t\t\t\treturn {\n\t\t\t\t\tid: user.id,\n\t\t\t\t\tcolor: user.color ?? defaultUserPreferences.color,\n\t\t\t\t\tname: user.name ?? defaultUserPreferences.name,\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\tconst socket = new ClientWebSocketAdapter(async () => {\n\t\t\tconst uriString = typeof uri === 'string' ? uri : await uri()\n\n\t\t\t// set sessionId as a query param on the uri\n\t\t\tconst withParams = new URL(uriString)\n\t\t\tif (withParams.searchParams.has('sessionId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"sessionId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (withParams.searchParams.has('storeId')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'useSync. \"storeId\" is a reserved query param name. Please use a different name'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\twithParams.searchParams.set('sessionId', TAB_ID)\n\t\t\twithParams.searchParams.set('storeId', storeId)\n\t\t\treturn withParams.toString()\n\t\t})\n\n\t\tlet didCancel = false\n\n\t\tconst collaborationStatusSignal = computed('collaboration status', () =>\n\t\t\tsocket.connectionStatus === 'error' ? 'offline' : socket.connectionStatus\n\t\t)\n\n\t\tconst syncMode = atom('sync mode', 'readwrite' as 'readonly' | 'readwrite')\n\n\t\tconst store = createTLStore({\n\t\t\tid: storeId,\n\t\t\tschema,\n\t\t\tassets,\n\t\t\tonMount,\n\t\t\tcollaboration: {\n\t\t\t\tstatus: collaborationStatusSignal,\n\t\t\t\tmode: syncMode,\n\t\t\t},\n\t\t})\n\n\t\tconst presence = computed('instancePresence', () => {\n\t\t\tconst presenceState = getUserPresence(store, userPreferences.get())\n\t\t\tif (!presenceState) return null\n\n\t\t\treturn InstancePresenceRecordType.create({\n\t\t\t\t...presenceState,\n\t\t\t\tid: InstancePresenceRecordType.createId(store.id),\n\t\t\t})\n\t\t})\n\n\t\tconst client = new TLSyncClient({\n\t\t\tstore,\n\t\t\tsocket,\n\t\t\tdidCancel: () => didCancel,\n\t\t\tonLoad(client) {\n\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'load', roomId })\n\t\t\t\tsetState({ readyClient: client })\n\t\t\t},\n\t\t\tonSyncError(reason) {\n\t\t\t\tconsole.error('sync error', reason)\n\n\t\t\t\tswitch (reason) {\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_FOUND:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'room-not-found', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.FORBIDDEN:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'forbidden', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.NOT_AUTHENTICATED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'not-authenticated', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase TLSyncErrorCloseEventReason.RATE_LIMITED:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'rate-limited', roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\ttrack?.(MULTIPLAYER_EVENT_NAME, { name: 'sync-error:' + reason, roomId })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tsetState({ error: new TLRemoteSyncError(reason) })\n\t\t\t\tsocket.close()\n\t\t\t},\n\t\t\tonAfterConnect(_, { isReadonly }) {\n\t\t\t\ttransact(() => {\n\t\t\t\t\tsyncMode.set(isReadonly ? 'readonly' : 'readwrite')\n\t\t\t\t\t// if the server crashes and loses all data it can return an empty document\n\t\t\t\t\t// when it comes back up. This is a safety check to make sure that if something like\n\t\t\t\t\t// that happens, it won't render the app broken and require a restart. The user will\n\t\t\t\t\t// most likely lose all their changes though since they'll have been working with pages\n\t\t\t\t\t// that won't exist. There's certainly something we can do to make this better.\n\t\t\t\t\t// but the likelihood of this happening is very low and maybe not worth caring about beyond this.\n\t\t\t\t\tstore.ensureStoreIsUsable()\n\t\t\t\t})\n\t\t\t},\n\t\t\tpresence,\n\t\t})\n\n\t\treturn () => {\n\t\t\tdidCancel = true\n\t\t\tclient.close()\n\t\t\tsocket.close()\n\t\t\tsetState(null)\n\t\t}\n\t}, [assets, onMount, userAtom, roomId, schema, setState, track, uri, getUserPresence])\n\n\treturn useValue<RemoteTLStoreWithStatus>(\n\t\t'remote synced store',\n\t\t() => {\n\t\t\tif (!state) return { status: 'loading' }\n\t\t\tif (state.error) return { status: 'error', error: state.error }\n\t\t\tif (!state.readyClient) return { status: 'loading' }\n\t\t\tconst connectionStatus = state.readyClient.socket.connectionStatus\n\t\t\treturn {\n\t\t\t\tstatus: 'synced-remote',\n\t\t\t\tconnectionStatus: connectionStatus === 'error' ? 'offline' : connectionStatus,\n\t\t\t\tstore: state.readyClient.store,\n\t\t\t}\n\t\t},\n\t\t[state]\n\t)\n}\n\n/**\n * Options for the {@link useSync} hook.\n * @public\n */\nexport interface UseSyncOptions {\n\t/**\n\t * The URI of the multiplayer server. This must include the protocol,\n\t *\n\t * e.g. `wss://server.example.com/my-room` or `ws://localhost:5858/my-room`.\n\t *\n\t * Note that the protocol can also be `https` or `http` and it will upgrade to a websocket\n\t * connection.\n\t *\n\t * Optionally, you can pass a function which will be called each time a connection is\n\t * established to get the URI. This is useful if you need to include e.g. a short-lived session\n\t * token for authentication.\n\t */\n\turi: string | (() => string | Promise<string>)\n\t/**\n\t * A signal that contains the user information needed for multiplayer features.\n\t * This should be synchronized with the `userPreferences` configuration for the main `<Tldraw />` component.\n\t * If not provided, a default implementation based on localStorage will be used.\n\t */\n\tuserInfo?: TLPresenceUserInfo | Signal<TLPresenceUserInfo>\n\t/**\n\t * The asset store for blob storage. See {@link tldraw#TLAssetStore}.\n\t *\n\t * If you don't have time to implement blob storage and just want to get started, you can use the inline base64 asset store. {@link tldraw#inlineBase64AssetStore}\n\t * Note that storing base64 blobs inline in JSON is very inefficient and will cause performance issues quickly with large images and videos.\n\t */\n\tassets: TLAssetStore\n\n\t/** @internal */\n\tonMount?(editor: Editor): void\n\t/** @internal used for analytics only, we should refactor this away */\n\troomId?: string\n\t/** @internal */\n\ttrackAnalyticsEvent?(name: string, data: { [key: string]: any }): void\n\n\t/**\n\t * A reactive function that returns a {@link @tldraw/tlschema#TLInstancePresence} object. The\n\t * result of this function will be synchronized across all clients to display presence\n\t * indicators such as cursors. See {@link @tldraw/tlschema#getDefaultUserPresence} for\n\t * the default implementation of this function.\n\t */\n\tgetUserPresence?(store: TLStore, user: TLPresenceUserInfo): TLPresenceStateInfo | null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["client"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/sync",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "3.16.0-internal.
|
|
3
|
+
"description": "A tiny little drawing app (multiplayer sync react bindings).",
|
|
4
|
+
"version": "3.16.0-internal.a478398270c6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw GB Ltd.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"tldraw",
|
|
20
|
-
"sdk",
|
|
21
20
|
"drawing",
|
|
22
21
|
"app",
|
|
23
22
|
"development",
|
|
@@ -32,16 +31,15 @@
|
|
|
32
31
|
"src"
|
|
33
32
|
],
|
|
34
33
|
"scripts": {
|
|
35
|
-
"test-ci": "
|
|
36
|
-
"test": "yarn run -T
|
|
37
|
-
"test-coverage": "
|
|
34
|
+
"test-ci": "lazy inherit",
|
|
35
|
+
"test": "yarn run -T jest",
|
|
36
|
+
"test-coverage": "lazy inherit",
|
|
38
37
|
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts",
|
|
39
38
|
"build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
|
|
40
39
|
"build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
|
|
41
40
|
"prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
|
|
42
41
|
"postpack": "../../internal/scripts/postpack.sh",
|
|
43
|
-
"pack-tarball": "yarn pack"
|
|
44
|
-
"context": "yarn run -T tsx ../../internal/scripts/context.ts"
|
|
42
|
+
"pack-tarball": "yarn pack"
|
|
45
43
|
},
|
|
46
44
|
"devDependencies": {
|
|
47
45
|
"@types/react": "^18.3.18",
|
|
@@ -51,13 +49,26 @@
|
|
|
51
49
|
"uuid-by-string": "^4.0.0",
|
|
52
50
|
"uuid-readable": "^0.0.2"
|
|
53
51
|
},
|
|
52
|
+
"jest": {
|
|
53
|
+
"preset": "../../internal/config/jest/node/jest-preset.js",
|
|
54
|
+
"testEnvironment": "../../../packages/utils/patchedJestJsDom.js",
|
|
55
|
+
"moduleNameMapper": {
|
|
56
|
+
"^~(.*)": "<rootDir>/src/$1"
|
|
57
|
+
},
|
|
58
|
+
"transformIgnorePatterns": [
|
|
59
|
+
"ignore everything. swc is fast enough to transform everything"
|
|
60
|
+
],
|
|
61
|
+
"setupFiles": [
|
|
62
|
+
"./setupJest.js"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
54
65
|
"dependencies": {
|
|
55
|
-
"@tldraw/state": "3.16.0-internal.
|
|
56
|
-
"@tldraw/state-react": "3.16.0-internal.
|
|
57
|
-
"@tldraw/sync-core": "3.16.0-internal.
|
|
58
|
-
"@tldraw/utils": "3.16.0-internal.
|
|
66
|
+
"@tldraw/state": "3.16.0-internal.a478398270c6",
|
|
67
|
+
"@tldraw/state-react": "3.16.0-internal.a478398270c6",
|
|
68
|
+
"@tldraw/sync-core": "3.16.0-internal.a478398270c6",
|
|
69
|
+
"@tldraw/utils": "3.16.0-internal.a478398270c6",
|
|
59
70
|
"nanoevents": "^7.0.1",
|
|
60
|
-
"tldraw": "3.16.0-internal.
|
|
71
|
+
"tldraw": "3.16.0-internal.a478398270c6",
|
|
61
72
|
"ws": "^8.18.0"
|
|
62
73
|
},
|
|
63
74
|
"peerDependencies": {
|
package/src/useSync.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { atom, isSignal, transact } from '@tldraw/state'
|
|
|
2
2
|
import { useAtom } from '@tldraw/state-react'
|
|
3
3
|
import {
|
|
4
4
|
ClientWebSocketAdapter,
|
|
5
|
-
TLPresenceMode,
|
|
6
5
|
TLRemoteSyncError,
|
|
7
6
|
TLSyncClient,
|
|
8
7
|
TLSyncErrorCloseEventReason,
|
|
@@ -166,15 +165,6 @@ export function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLSt
|
|
|
166
165
|
})
|
|
167
166
|
})
|
|
168
167
|
|
|
169
|
-
const otherUserPresences = store.query.ids('instance_presence', () => ({
|
|
170
|
-
userId: { neq: userPreferences.get().id },
|
|
171
|
-
}))
|
|
172
|
-
|
|
173
|
-
const presenceMode = computed<TLPresenceMode>('presenceMode', () => {
|
|
174
|
-
if (otherUserPresences.get().size === 0) return 'solo'
|
|
175
|
-
return 'full'
|
|
176
|
-
})
|
|
177
|
-
|
|
178
168
|
const client = new TLSyncClient({
|
|
179
169
|
store,
|
|
180
170
|
socket,
|
|
@@ -220,7 +210,6 @@ export function useSync(opts: UseSyncOptions & TLStoreSchemaOptions): RemoteTLSt
|
|
|
220
210
|
})
|
|
221
211
|
},
|
|
222
212
|
presence,
|
|
223
|
-
presenceMode,
|
|
224
213
|
})
|
|
225
214
|
|
|
226
215
|
return () => {
|