{
  "version": 3,
  "sources": ["../src/useSyncDemo.ts"],
  "sourcesContent": ["import { useCallback, useMemo } from 'react'\nimport {\n\tAssetRecordType,\n\tEditor,\n\tMediaHelpers,\n\tTLAsset,\n\tTLAssetStore,\n\tTLPresenceStateInfo,\n\tTLStore,\n\tTLStoreSchemaOptions,\n\tTLUser,\n\tTLUserStore,\n\tclamp,\n\tdefaultBindingUtils,\n\tdefaultShapeUtils,\n\tgetHashForString,\n\tuniqueId,\n\tuseShallowObjectIdentity,\n} from 'tldraw'\nimport { RemoteTLStoreWithStatus, useSync } from './useSync'\n\n/** @public */\nexport interface UseSyncDemoOptions {\n\t/**\n\t * The room ID to sync with. Make sure the room ID is unique. The namespace is shared by\n\t * everyone using the demo server. Consider prefixing it with your company or project name.\n\t */\n\troomId: string\n\n\t/**\n\t * User store for identity, presence and attribution.\n\t * If not provided, a default implementation based on localStorage will be used.\n\t */\n\tusers?: TLUserStore\n\n\t/** @internal */\n\thost?: string\n\n\t/**\n\t * {@inheritdoc UseSyncOptions.getUserPresence}\n\t * @public\n\t */\n\tgetUserPresence?(store: TLStore, user: TLUser): TLPresenceStateInfo | null\n}\n\n/**\n * Safely accesses environment variables across different bundling environments.\n *\n * Depending on the environment this package is used in, process.env may not be available. This function\n * wraps `process.env` accesses in a try/catch to prevent runtime errors in environments where process\n * is not defined.\n *\n * The reason that this is just a try/catch and not a dynamic check e.g. `process &&\n * process.env[key]` is that many bundlers implement `process.env.WHATEVER` using compile-time\n * string replacement, rather than actually creating a runtime implementation of a `process` object.\n *\n * @param cb - Callback function that accesses an environment variable\n * @returns The environment variable value if available, otherwise undefined\n * @internal\n */\nfunction getEnv(cb: () => string | undefined): string | undefined {\n\ttry {\n\t\treturn cb()\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nconst DEMO_WORKER = getEnv(() => process.env.TLDRAW_BEMO_URL) ?? 'https://demo.tldraw.xyz'\nconst IMAGE_WORKER = getEnv(() => process.env.TLDRAW_IMAGE_URL) ?? 'https://images.tldraw.xyz'\n\n/**\n * Creates a tldraw store synced with a multiplayer room hosted on tldraw's demo server `https://demo.tldraw.xyz`.\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 * All data on the demo server is\n *\n * - Deleted after a day or so.\n * - Publicly accessible to anyone who knows the room ID. Use your company name as a prefix to help avoid collisions, or generate UUIDs for maximum privacy.\n *\n * @example\n * ```tsx\n * function MyApp() {\n *     const store = useSyncDemo({roomId: 'my-app-test-room'})\n *     return <Tldraw store={store} />\n * }\n * ```\n *\n * @param options - Options for the multiplayer demo sync store. See {@link UseSyncDemoOptions} and {@link @tldraw/editor#TLStoreSchemaOptions}.\n *\n * @public\n */\nexport function useSyncDemo(\n\toptions: UseSyncDemoOptions & TLStoreSchemaOptions\n): RemoteTLStoreWithStatus {\n\tconst { roomId, host = DEMO_WORKER, ..._syncOpts } = options\n\tconst assets = useMemo(() => createDemoAssetStore(host), [host])\n\n\tconst syncOpts = useShallowObjectIdentity(_syncOpts)\n\tconst syncOptsWithDefaults = useMemo(() => {\n\t\tif ('schema' in syncOpts && syncOpts.schema) return syncOpts\n\n\t\treturn {\n\t\t\t...syncOpts,\n\t\t\tshapeUtils:\n\t\t\t\t'shapeUtils' in syncOpts\n\t\t\t\t\t? [...defaultShapeUtils, ...(syncOpts.shapeUtils ?? [])]\n\t\t\t\t\t: defaultShapeUtils,\n\t\t\tbindingUtils:\n\t\t\t\t'bindingUtils' in syncOpts\n\t\t\t\t\t? [...defaultBindingUtils, ...(syncOpts.bindingUtils ?? [])]\n\t\t\t\t\t: defaultBindingUtils,\n\t\t}\n\t}, [syncOpts])\n\n\treturn useSync({\n\t\turi: `${host}/connect/${encodeURIComponent(roomId)}`,\n\t\troomId,\n\t\tassets,\n\t\tonMount: useCallback(\n\t\t\t(editor: Editor) => {\n\t\t\t\teditor.registerExternalAssetHandler('url', ({ url }) =>\n\t\t\t\t\tcreateAssetFromUrlUsingDemoServer(host, url)\n\t\t\t\t)\n\t\t\t},\n\t\t\t[host]\n\t\t),\n\t\t...syncOptsWithDefaults,\n\t})\n}\n\n/**\n * Determines whether file uploads should be disabled for a given host.\n *\n * Uploads are disabled for production tldraw domains to prevent abuse of the demo server\n * infrastructure. This includes tldraw.com and tldraw.xyz domains and their subdomains.\n *\n * @param host - The host URL to check for upload restrictions\n * @returns True if uploads should be disabled, false otherwise\n * @internal\n */\nfunction shouldDisallowUploads(host: string) {\n\tconst disallowedHosts = ['tldraw.com', 'tldraw.xyz']\n\treturn disallowedHosts.some(\n\t\t(disallowedHost) => host === disallowedHost || host.endsWith(`.${disallowedHost}`)\n\t)\n}\n\n/**\n * Creates an asset store implementation optimized for the tldraw demo server.\n *\n * This asset store handles file uploads to the demo server and provides intelligent\n * asset resolution with automatic image optimization based on network conditions,\n * screen density, and display size. It includes safeguards to prevent uploads to\n * production domains and optimizes images through the tldraw image processing service.\n *\n * @param host - The demo server host URL for file uploads and asset resolution\n * @returns A TLAssetStore implementation with upload and resolve capabilities\n * @example\n * ```ts\n * const assetStore = createDemoAssetStore('https://demo.tldraw.xyz')\n *\n * // Upload a file\n * const result = await assetStore.upload(asset, file)\n * console.log('Uploaded to:', result.src)\n *\n * // Resolve optimized asset URL\n * const optimizedUrl = assetStore.resolve(imageAsset, {\n *   steppedScreenScale: 1.5,\n *   dpr: 2,\n *   networkEffectiveType: '4g'\n * })\n * ```\n * @internal\n */\nfunction createDemoAssetStore(host: string): TLAssetStore {\n\treturn {\n\t\tupload: async (_asset, file) => {\n\t\t\tif (shouldDisallowUploads(host)) {\n\t\t\t\talert('Uploading images is disabled in this demo.')\n\t\t\t\tthrow new Error('Uploading images is disabled in this demo.')\n\t\t\t}\n\t\t\tconst objectName = `${uniqueId()}-${file.name}`.replace(/\\W/g, '-')\n\t\t\tconst url = `${host}/uploads/${objectName}`\n\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: file,\n\t\t\t})\n\t\t\t// a rejected upload (invalid name, duplicate, server error) resolves the fetch; without\n\t\t\t// this the asset would point at a URL that was never stored\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to upload asset (${response.status})`)\n\t\t\t}\n\n\t\t\treturn { src: url }\n\t\t},\n\n\t\tresolve(asset, context) {\n\t\t\tconst { src } = asset.props\n\t\t\tif (!src) return null\n\n\t\t\t// We don't deal with videos at the moment.\n\t\t\tif (asset.type === 'video') return src\n\n\t\t\t// Assert it's an image to make TS happy.\n\t\t\tif (asset.type !== 'image') return null\n\n\t\t\t// Don't try to transform data: URLs, yikes.\n\t\t\tif (!src.startsWith('http:') && !src.startsWith('https:')) return src\n\n\t\t\tif (context.shouldResolveToOriginal) return src\n\n\t\t\t// Don't try to transform animated images.\n\t\t\tif (MediaHelpers.isAnimatedImageType(asset.props.mimeType) || asset.props.isAnimated)\n\t\t\t\treturn src\n\n\t\t\t// Don't try to transform vector images.\n\t\t\tif (MediaHelpers.isVectorImageType(asset.props.mimeType)) return src\n\n\t\t\tconst url = new URL(src)\n\n\t\t\t// we only transform images that are hosted on domains we control\n\t\t\tconst isTldrawImage =\n\t\t\t\turl.origin === host || /\\.tldraw\\.(?:com|xyz|dev|workers\\.dev)$/.test(url.host)\n\n\t\t\tif (!isTldrawImage) return src\n\n\t\t\t// Assets that are under a certain file size aren't worth transforming (and incurring cost).\n\t\t\t// We still send them through the image worker to get them optimized though.\n\t\t\tconst { fileSize = 0 } = asset.props\n\t\t\tconst isWorthResizing = fileSize >= 1024 * 1024 * 1.5\n\n\t\t\tif (isWorthResizing) {\n\t\t\t\t// N.B. navigator.connection is only available in certain browsers (mainly Blink-based browsers)\n\t\t\t\t// 4g is as high the 'effectiveType' goes and we can pick a lower effective image quality for slower connections.\n\t\t\t\tconst networkCompensation =\n\t\t\t\t\t!context.networkEffectiveType || context.networkEffectiveType === '4g' ? 1 : 0.5\n\n\t\t\t\tconst pixelRatio = asset.props.pixelRatio ?? 1\n\t\t\t\tconst trueWidth = asset.props.w * pixelRatio\n\t\t\t\tconst width = Math.ceil(\n\t\t\t\t\tMath.min(\n\t\t\t\t\t\ttrueWidth *\n\t\t\t\t\t\t\tclamp(context.steppedScreenScale, 1 / 32, 1) *\n\t\t\t\t\t\t\tnetworkCompensation *\n\t\t\t\t\t\t\tcontext.dpr,\n\t\t\t\t\t\ttrueWidth\n\t\t\t\t\t)\n\t\t\t\t)\n\n\t\t\t\turl.searchParams.set('w', width.toString())\n\t\t\t}\n\n\t\t\treturn `${IMAGE_WORKER}/${url.host}/${url.toString().slice(url.origin.length + 1)}`\n\t\t},\n\t}\n}\n\n/**\n * Creates a bookmark asset by fetching metadata from a URL using the demo server.\n *\n * This function uses the demo server's bookmark unfurling service to extract metadata\n * like title, description, favicon, and preview image from a given URL. If the metadata\n * fetch fails, it returns a blank bookmark asset with just the URL.\n *\n * @param host - The demo server host URL to use for bookmark unfurling\n * @param url - The URL to create a bookmark asset from\n * @returns A promise that resolves to a TLAsset of type 'bookmark' with extracted metadata\n * @example\n * ```ts\n * const asset = await createAssetFromUrlUsingDemoServer(\n *   'https://demo.tldraw.xyz',\n *   'https://example.com'\n * )\n *\n * console.log(asset.props.title) // \"Example Domain\"\n * console.log(asset.props.description) // \"This domain is for use in illustrative examples...\"\n * ```\n * @internal\n */\nasync function createAssetFromUrlUsingDemoServer(host: string, url: string): Promise<TLAsset> {\n\tlet meta: { description?: string; image?: string; favicon?: string; title?: string } | null = null\n\ttry {\n\t\t// First, try to get the meta data from our endpoint\n\t\tconst fetchUrl = new URL(`${host}/bookmarks/unfurl`)\n\t\tfetchUrl.searchParams.set('url', url)\n\t\tmeta = await (await fetch(fetchUrl, { method: 'POST' })).json()\n\t} catch (error) {\n\t\t// Otherwise, fall back to a blank bookmark\n\t\tconsole.error(error)\n\t}\n\n\treturn {\n\t\tid: AssetRecordType.createId(getHashForString(url)),\n\t\ttypeName: 'asset',\n\t\ttype: 'bookmark',\n\t\tprops: {\n\t\t\tsrc: url,\n\t\t\tdescription: meta?.description ?? '',\n\t\t\timage: meta?.image ?? '',\n\t\t\tfavicon: meta?.favicon ?? '',\n\t\t\ttitle: meta?.title ?? '',\n\t\t},\n\t\tmeta: {},\n\t}\n}\n"],
  "mappings": "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",
  "names": []
}
