@tldraw/editor 5.6.0-canary.e9e8ec003426 → 5.6.0-canary.f790b44c29fc

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.
Files changed (30) hide show
  1. package/dist-cjs/index.js +1 -1
  2. package/dist-cjs/lib/components/LiveCollaborators.js +3 -3
  3. package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +12 -10
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +3 -5
  7. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  8. package/dist-cjs/lib/license/LicenseManager.js +62 -4
  9. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  10. package/dist-cjs/version.js +3 -3
  11. package/dist-cjs/version.js.map +1 -1
  12. package/dist-esm/index.mjs +1 -1
  13. package/dist-esm/lib/components/LiveCollaborators.mjs +3 -3
  14. package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
  15. package/dist-esm/lib/editor/Editor.mjs +12 -10
  16. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  17. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +3 -5
  18. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  19. package/dist-esm/lib/license/LicenseManager.mjs +62 -4
  20. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  21. package/dist-esm/version.mjs +3 -3
  22. package/dist-esm/version.mjs.map +1 -1
  23. package/editor.css +2 -1
  24. package/package.json +7 -7
  25. package/src/lib/components/LiveCollaborators.tsx +3 -3
  26. package/src/lib/editor/Editor.ts +17 -15
  27. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +4 -6
  28. package/src/lib/license/LicenseManager.test.ts +204 -0
  29. package/src/lib/license/LicenseManager.ts +88 -4
  30. package/src/version.ts +3 -3
@@ -347,11 +347,9 @@ class ScribbleManager {
347
347
  }
348
348
  }
349
349
  if (session.options.fadeMode === "individual") {
350
- for (let i = session.items.length - 1; i >= 0; i--) {
351
- if (session.items[i].scribble.points.length === 0) {
352
- session.items.splice(i, 1);
353
- }
354
- }
350
+ session.items = session.items.filter(
351
+ ({ scribble }) => scribble.points.length > 0 || scribble.state === "starting"
352
+ );
355
353
  }
356
354
  }
357
355
  tickPersistentItem(item) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/ScribbleManager/ScribbleManager.ts"],
4
- "sourcesContent": ["import { TLPageId, TLScribble, VecModel } from '@tldraw/tlschema'\nimport { uniqueId } from '@tldraw/utils'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n/** @public */\nexport interface ScribbleItem {\n\tid: string\n\tscribble: TLScribble\n\ttimeoutMs: number\n\tdelayRemaining: number\n\tprev: null | VecModel\n\tnext: null | VecModel\n}\n\n/** @public */\nexport interface ScribbleSessionOptions {\n\t/** Session id. Auto-generated if not provided. */\n\tid?: string\n\t/**\n\t * Whether scribbles self-consume (shrink from start) while drawing.\n\t * - true: scribbles eat their own tail as you draw (default, used for eraser/select)\n\t * - false: scribbles persist until session stops (used for laser)\n\t */\n\tselfConsume?: boolean\n\t/**\n\t * How long to wait after last activity before auto-stopping the session.\n\t * Only applies when selfConsume is false.\n\t */\n\tidleTimeoutMs?: number\n\t/**\n\t * How scribbles fade when stopping.\n\t * - 'individual': each scribble fades on its own (default)\n\t * - 'grouped': all scribbles fade together as one sequence\n\t */\n\tfadeMode?: 'individual' | 'grouped'\n\t/**\n\t * Easing for grouped fade.\n\t */\n\tfadeEasing?: 'linear' | 'ease-in'\n\t/**\n\t * Duration of the fade in milliseconds.\n\t */\n\tfadeDurationMs?: number\n}\n\n// Internal session state (not exported)\ninterface Session {\n\tid: string\n\tpageId: TLPageId\n\titems: ScribbleItem[]\n\tstate: 'active' | 'stopping' | 'complete'\n\toptions: Required<Omit<ScribbleSessionOptions, 'id'>>\n\tidleTimeoutHandle?: number\n\tfadeElapsed: number\n\ttotalPointsAtFadeStart: number\n}\n\n/** @public */\nexport class ScribbleManager {\n\tprivate sessions = new Map<string, Session>()\n\n\tconstructor(private editor: Editor) {}\n\n\t// ==================== SESSION API ====================\n\n\t/**\n\t * Start a new session for grouping scribbles.\n\t * Returns a session ID that can be used with other session methods.\n\t *\n\t * @param options - Session configuration\n\t * @returns Session ID\n\t * @public\n\t */\n\tstartSession(options: ScribbleSessionOptions = {}): string {\n\t\tconst id = options.id ?? uniqueId()\n\t\tconst session: Session = {\n\t\t\tid,\n\t\t\tpageId: this.editor.getCurrentPageId(),\n\t\t\titems: [],\n\t\t\tstate: 'active',\n\t\t\toptions: {\n\t\t\t\tselfConsume: options.selfConsume ?? true,\n\t\t\t\tidleTimeoutMs: options.idleTimeoutMs ?? 0,\n\t\t\t\tfadeMode: options.fadeMode ?? 'individual',\n\t\t\t\tfadeEasing: options.fadeEasing ?? (options.fadeMode === 'grouped' ? 'ease-in' : 'linear'),\n\t\t\t\tfadeDurationMs: options.fadeDurationMs ?? this.editor.options.laserFadeoutMs,\n\t\t\t},\n\t\t\tfadeElapsed: 0,\n\t\t\ttotalPointsAtFadeStart: 0,\n\t\t}\n\n\t\tthis.sessions.set(id, session)\n\n\t\t// Set up idle timeout if configured\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn id\n\t}\n\n\t/**\n\t * Add a scribble to a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param scribble - Partial scribble properties\n\t * @param scribbleId - Optional scribble ID\n\t * @public\n\t */\n\taddScribbleToSession(\n\t\tsessionId: string,\n\t\tscribble: Partial<TLScribble>,\n\t\tscribbleId = uniqueId()\n\t): ScribbleItem {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) throw Error(`Session ${sessionId} not found`)\n\n\t\tconst item: ScribbleItem = {\n\t\t\tid: scribbleId,\n\t\t\tscribble: {\n\t\t\t\tid: scribbleId,\n\t\t\t\tsize: 20,\n\t\t\t\tcolor: 'accent',\n\t\t\t\topacity: 0.8,\n\t\t\t\tdelay: 0,\n\t\t\t\tpoints: [],\n\t\t\t\tshrink: 0.1,\n\t\t\t\ttaper: true,\n\t\t\t\t...scribble,\n\t\t\t\tstate: 'starting',\n\t\t\t},\n\t\t\ttimeoutMs: 0,\n\t\t\tdelayRemaining: scribble.delay ?? 0,\n\t\t\tprev: null,\n\t\t\tnext: null,\n\t\t}\n\n\t\tsession.items.push(item)\n\n\t\t// Reset idle timeout on activity\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn item\n\t}\n\n\t/**\n\t * Add a point to a scribble in a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param scribbleId - The scribble ID\n\t * @param x - X coordinate\n\t * @param y - Y coordinate\n\t * @param z - Z coordinate (pressure)\n\t * @public\n\t */\n\taddPointToSession(\n\t\tsessionId: string,\n\t\tscribbleId: string,\n\t\tx: number,\n\t\ty: number,\n\t\tz = 0.5\n\t): ScribbleItem {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) throw Error(`Session ${sessionId} not found`)\n\n\t\tconst item = session.items.find((i) => i.id === scribbleId)\n\t\tif (!item) throw Error(`Scribble ${scribbleId} not found in session ${sessionId}`)\n\n\t\tconst point = { x, y, z }\n\t\tif (!item.prev || Vec.Dist(item.prev, point) >= 1) {\n\t\t\titem.next = point\n\t\t}\n\n\t\t// Reset idle timeout on activity\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn item\n\t}\n\n\t/**\n\t * Extend a session, resetting its idle timeout.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\textendSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\t}\n\n\t/**\n\t * Stop a session, triggering fade-out.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tstopSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session || session.state !== 'active') return\n\n\t\tthis.clearIdleTimeout(session)\n\t\tsession.state = 'stopping'\n\n\t\tif (session.options.fadeMode === 'grouped') {\n\t\t\tsession.totalPointsAtFadeStart = session.items.reduce(\n\t\t\t\t(sum, item) => sum + item.scribble.points.length,\n\t\t\t\t0\n\t\t\t)\n\t\t\tsession.fadeElapsed = 0\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.delayRemaining = Math.min(item.delayRemaining, 200)\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear all scribbles in a session immediately.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tclearSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\n\t\tthis.clearIdleTimeout(session)\n\t\tfor (const item of session.items) {\n\t\t\titem.scribble.points.length = 0\n\t\t}\n\t\tsession.state = 'complete'\n\t}\n\n\t/**\n\t * Check if a session is active.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tisSessionActive(sessionId: string): boolean {\n\t\tconst session = this.sessions.get(sessionId)\n\t\treturn session?.state === 'active'\n\t}\n\n\t// ==================== SIMPLE API (for eraser, select, etc.) ====================\n\n\t/**\n\t * Add a scribble using the default self-consuming behavior.\n\t * Creates an implicit session for the scribble.\n\t *\n\t * @param scribble - Partial scribble properties\n\t * @param id - Optional scribble id\n\t * @returns The created scribble item\n\t * @public\n\t */\n\taddScribble(scribble: Partial<TLScribble>, id = uniqueId()): ScribbleItem {\n\t\tconst sessionId = this.startSession()\n\t\treturn this.addScribbleToSession(sessionId, scribble, id)\n\t}\n\n\t/**\n\t * Add a point to a scribble. Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @param x - X coordinate\n\t * @param y - Y coordinate\n\t * @param z - Z coordinate (pressure)\n\t * @public\n\t */\n\taddPoint(id: string, x: number, y: number, z = 0.5): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\tconst point = { x, y, z }\n\t\t\t\tif (!item.prev || Vec.Dist(item.prev, point) >= 1) {\n\t\t\t\t\titem.next = point\n\t\t\t\t}\n\t\t\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\t\t\tthis.resetIdleTimeout(session)\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Mark a scribble as complete (done being drawn but not yet fading).\n\t * Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @public\n\t */\n\tcomplete(id: string): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\tif (item.scribble.state === 'starting' || item.scribble.state === 'active') {\n\t\t\t\t\titem.scribble.state = 'complete'\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Stop a scribble. Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @public\n\t */\n\tstop(id: string): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\titem.delayRemaining = Math.min(item.delayRemaining, 200)\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Stop and remove all sessions.\n\t *\n\t * @public\n\t */\n\treset(): void {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tthis.clearIdleTimeout(session)\n\t\t}\n\t\tthis.sessions.clear()\n\t\tthis.editor.updateInstanceState({ scribbles: [] })\n\t}\n\n\t/**\n\t * Update on each animation frame.\n\t *\n\t * @param elapsed - The number of milliseconds since the last tick.\n\t * @public\n\t */\n\ttick(elapsed: number): void {\n\t\tconst currentScribbles = this.editor.getInstanceState().scribbles\n\t\tif (this.sessions.size === 0 && currentScribbles.length === 0) return\n\n\t\tthis.editor.run(() => {\n\t\t\t// Drop sessions from other pages, otherwise a still-fading laser trail\n\t\t\t// keeps playing out over the page the user switched to (#10449).\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tif (session.pageId !== currentPageId) {\n\t\t\t\t\tthis.clearSession(session.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tick all sessions\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tthis.tickSession(session, elapsed)\n\t\t\t}\n\n\t\t\t// Remove completed sessions\n\t\t\tfor (const [id, session] of this.sessions) {\n\t\t\t\tif (session.state === 'complete') {\n\t\t\t\t\tthis.clearIdleTimeout(session)\n\t\t\t\t\tthis.sessions.delete(id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Collect scribbles from all sessions\n\t\t\tconst scribbles: TLScribble[] = []\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tfor (const item of session.items) {\n\t\t\t\t\tif (item.scribble.points.length > 0) {\n\t\t\t\t\t\tscribbles.push({\n\t\t\t\t\t\t\t...item.scribble,\n\t\t\t\t\t\t\tpoints: [...item.scribble.points],\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.editor.updateInstanceState({ scribbles })\n\t\t})\n\t}\n\n\t// ==================== PRIVATE HELPERS ====================\n\n\tprivate resetIdleTimeout(session: Session): void {\n\t\tthis.clearIdleTimeout(session)\n\t\tsession.idleTimeoutHandle = this.editor.timers.setTimeout(() => {\n\t\t\tthis.stopSession(session.id)\n\t\t}, session.options.idleTimeoutMs)\n\t}\n\n\tprivate clearIdleTimeout(session: Session): void {\n\t\tif (session.idleTimeoutHandle !== undefined) {\n\t\t\tthis.editor.timers.clearTimeout(session.idleTimeoutHandle)\n\t\t\tsession.idleTimeoutHandle = undefined\n\t\t}\n\t}\n\n\tprivate tickSession(session: Session, elapsed: number): void {\n\t\tif (session.state === 'complete') return\n\n\t\tif (session.state === 'stopping' && session.options.fadeMode === 'grouped') {\n\t\t\tthis.tickGroupedFade(session, elapsed)\n\t\t} else {\n\t\t\tthis.tickSessionItems(session, elapsed)\n\t\t}\n\n\t\t// Check if session is complete\n\t\tconst hasContent = session.items.some((item) => item.scribble.points.length > 0)\n\t\tif (!hasContent && (session.state === 'stopping' || session.items.length === 0)) {\n\t\t\tsession.state = 'complete'\n\t\t}\n\t}\n\n\tprivate tickSessionItems(session: Session, elapsed: number): void {\n\t\tfor (const item of session.items) {\n\t\t\tconst shouldSelfConsume =\n\t\t\t\tsession.options.selfConsume ||\n\t\t\t\tsession.state === 'stopping' ||\n\t\t\t\titem.scribble.state === 'stopping'\n\n\t\t\tif (shouldSelfConsume) {\n\t\t\t\tthis.tickSelfConsumingItem(item, elapsed)\n\t\t\t} else {\n\t\t\t\tthis.tickPersistentItem(item)\n\t\t\t}\n\t\t}\n\n\t\t// Remove completed items in individual fade mode\n\t\tif (session.options.fadeMode === 'individual') {\n\t\t\tfor (let i = session.items.length - 1; i >= 0; i--) {\n\t\t\t\tif (session.items[i].scribble.points.length === 0) {\n\t\t\t\t\tsession.items.splice(i, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate tickPersistentItem(item: ScribbleItem): void {\n\t\tconst { scribble } = item\n\n\t\tif (scribble.state === 'starting') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t\tif (scribble.points.length > 8) {\n\t\t\t\tscribble.state = 'active'\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif (scribble.state === 'active') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate tickSelfConsumingItem(item: ScribbleItem, elapsed: number): void {\n\t\tconst { scribble } = item\n\n\t\tif (scribble.state === 'starting') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t\tif (scribble.points.length > 8) {\n\t\t\t\tscribble.state = 'active'\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif (item.delayRemaining > 0) {\n\t\t\titem.delayRemaining = Math.max(0, item.delayRemaining - elapsed)\n\t\t}\n\n\t\titem.timeoutMs += elapsed\n\t\tif (item.timeoutMs >= 16) {\n\t\t\titem.timeoutMs = 0\n\t\t}\n\n\t\tconst { delayRemaining, timeoutMs, prev, next } = item\n\n\t\tswitch (scribble.state) {\n\t\t\tcase 'active': {\n\t\t\t\tif (next && next !== prev) {\n\t\t\t\t\titem.prev = next\n\t\t\t\t\tscribble.points.push(next)\n\t\t\t\t\tif (delayRemaining === 0 && scribble.points.length > 8) {\n\t\t\t\t\t\tscribble.points.shift()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (timeoutMs === 0) {\n\t\t\t\t\t\tif (scribble.points.length > 1) {\n\t\t\t\t\t\t\tscribble.points.shift()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\titem.delayRemaining = scribble.delay\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'stopping': {\n\t\t\t\tif (delayRemaining === 0 && timeoutMs === 0) {\n\t\t\t\t\tif (scribble.points.length <= 1) {\n\t\t\t\t\t\tscribble.points.length = 0\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (scribble.shrink) {\n\t\t\t\t\t\tscribble.size = Math.max(1, scribble.size * (1 - scribble.shrink))\n\t\t\t\t\t}\n\t\t\t\t\tscribble.points.shift()\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'paused': {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate tickGroupedFade(session: Session, elapsed: number): void {\n\t\tsession.fadeElapsed += elapsed\n\n\t\tlet remainingPoints = 0\n\t\tfor (const item of session.items) {\n\t\t\tremainingPoints += item.scribble.points.length\n\t\t}\n\n\t\tif (remainingPoints === 0) return\n\n\t\tif (session.fadeElapsed >= session.options.fadeDurationMs) {\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.scribble.points.length = 0\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tconst progress = session.fadeElapsed / session.options.fadeDurationMs\n\t\tconst easedProgress = session.options.fadeEasing === 'ease-in' ? progress * progress : progress\n\n\t\tconst targetRemoved = Math.floor(easedProgress * session.totalPointsAtFadeStart)\n\t\tconst actuallyRemoved = session.totalPointsAtFadeStart - remainingPoints\n\t\tconst pointsToRemove = Math.max(1, targetRemoved - actuallyRemoved)\n\n\t\tlet removed = 0\n\t\tlet itemIndex = 0\n\t\twhile (removed < pointsToRemove && itemIndex < session.items.length) {\n\t\t\tconst item = session.items[itemIndex]\n\t\t\tif (item.scribble.points.length > 0) {\n\t\t\t\titem.scribble.points.shift()\n\t\t\t\tremoved++\n\t\t\t} else {\n\t\t\t\titemIndex++\n\t\t\t}\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { TLPageId, TLScribble, VecModel } from '@tldraw/tlschema'\nimport { uniqueId } from '@tldraw/utils'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n/** @public */\nexport interface ScribbleItem {\n\tid: string\n\tscribble: TLScribble\n\ttimeoutMs: number\n\tdelayRemaining: number\n\tprev: null | VecModel\n\tnext: null | VecModel\n}\n\n/** @public */\nexport interface ScribbleSessionOptions {\n\t/** Session id. Auto-generated if not provided. */\n\tid?: string\n\t/**\n\t * Whether scribbles self-consume (shrink from start) while drawing.\n\t * - true: scribbles eat their own tail as you draw (default, used for eraser/select)\n\t * - false: scribbles persist until session stops (used for laser)\n\t */\n\tselfConsume?: boolean\n\t/**\n\t * How long to wait after last activity before auto-stopping the session.\n\t * Only applies when selfConsume is false.\n\t */\n\tidleTimeoutMs?: number\n\t/**\n\t * How scribbles fade when stopping.\n\t * - 'individual': each scribble fades on its own (default)\n\t * - 'grouped': all scribbles fade together as one sequence\n\t */\n\tfadeMode?: 'individual' | 'grouped'\n\t/**\n\t * Easing for grouped fade.\n\t */\n\tfadeEasing?: 'linear' | 'ease-in'\n\t/**\n\t * Duration of the fade in milliseconds.\n\t */\n\tfadeDurationMs?: number\n}\n\n// Internal session state (not exported)\ninterface Session {\n\tid: string\n\tpageId: TLPageId\n\titems: ScribbleItem[]\n\tstate: 'active' | 'stopping' | 'complete'\n\toptions: Required<Omit<ScribbleSessionOptions, 'id'>>\n\tidleTimeoutHandle?: number\n\tfadeElapsed: number\n\ttotalPointsAtFadeStart: number\n}\n\n/** @public */\nexport class ScribbleManager {\n\tprivate sessions = new Map<string, Session>()\n\n\tconstructor(private editor: Editor) {}\n\n\t// ==================== SESSION API ====================\n\n\t/**\n\t * Start a new session for grouping scribbles.\n\t * Returns a session ID that can be used with other session methods.\n\t *\n\t * @param options - Session configuration\n\t * @returns Session ID\n\t * @public\n\t */\n\tstartSession(options: ScribbleSessionOptions = {}): string {\n\t\tconst id = options.id ?? uniqueId()\n\t\tconst session: Session = {\n\t\t\tid,\n\t\t\tpageId: this.editor.getCurrentPageId(),\n\t\t\titems: [],\n\t\t\tstate: 'active',\n\t\t\toptions: {\n\t\t\t\tselfConsume: options.selfConsume ?? true,\n\t\t\t\tidleTimeoutMs: options.idleTimeoutMs ?? 0,\n\t\t\t\tfadeMode: options.fadeMode ?? 'individual',\n\t\t\t\tfadeEasing: options.fadeEasing ?? (options.fadeMode === 'grouped' ? 'ease-in' : 'linear'),\n\t\t\t\tfadeDurationMs: options.fadeDurationMs ?? this.editor.options.laserFadeoutMs,\n\t\t\t},\n\t\t\tfadeElapsed: 0,\n\t\t\ttotalPointsAtFadeStart: 0,\n\t\t}\n\n\t\tthis.sessions.set(id, session)\n\n\t\t// Set up idle timeout if configured\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn id\n\t}\n\n\t/**\n\t * Add a scribble to a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param scribble - Partial scribble properties\n\t * @param scribbleId - Optional scribble ID\n\t * @public\n\t */\n\taddScribbleToSession(\n\t\tsessionId: string,\n\t\tscribble: Partial<TLScribble>,\n\t\tscribbleId = uniqueId()\n\t): ScribbleItem {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) throw Error(`Session ${sessionId} not found`)\n\n\t\tconst item: ScribbleItem = {\n\t\t\tid: scribbleId,\n\t\t\tscribble: {\n\t\t\t\tid: scribbleId,\n\t\t\t\tsize: 20,\n\t\t\t\tcolor: 'accent',\n\t\t\t\topacity: 0.8,\n\t\t\t\tdelay: 0,\n\t\t\t\tpoints: [],\n\t\t\t\tshrink: 0.1,\n\t\t\t\ttaper: true,\n\t\t\t\t...scribble,\n\t\t\t\tstate: 'starting',\n\t\t\t},\n\t\t\ttimeoutMs: 0,\n\t\t\tdelayRemaining: scribble.delay ?? 0,\n\t\t\tprev: null,\n\t\t\tnext: null,\n\t\t}\n\n\t\tsession.items.push(item)\n\n\t\t// Reset idle timeout on activity\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn item\n\t}\n\n\t/**\n\t * Add a point to a scribble in a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param scribbleId - The scribble ID\n\t * @param x - X coordinate\n\t * @param y - Y coordinate\n\t * @param z - Z coordinate (pressure)\n\t * @public\n\t */\n\taddPointToSession(\n\t\tsessionId: string,\n\t\tscribbleId: string,\n\t\tx: number,\n\t\ty: number,\n\t\tz = 0.5\n\t): ScribbleItem {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) throw Error(`Session ${sessionId} not found`)\n\n\t\tconst item = session.items.find((i) => i.id === scribbleId)\n\t\tif (!item) throw Error(`Scribble ${scribbleId} not found in session ${sessionId}`)\n\n\t\tconst point = { x, y, z }\n\t\tif (!item.prev || Vec.Dist(item.prev, point) >= 1) {\n\t\t\titem.next = point\n\t\t}\n\n\t\t// Reset idle timeout on activity\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\n\t\treturn item\n\t}\n\n\t/**\n\t * Extend a session, resetting its idle timeout.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\textendSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\n\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\tthis.resetIdleTimeout(session)\n\t\t}\n\t}\n\n\t/**\n\t * Stop a session, triggering fade-out.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tstopSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session || session.state !== 'active') return\n\n\t\tthis.clearIdleTimeout(session)\n\t\tsession.state = 'stopping'\n\n\t\tif (session.options.fadeMode === 'grouped') {\n\t\t\tsession.totalPointsAtFadeStart = session.items.reduce(\n\t\t\t\t(sum, item) => sum + item.scribble.points.length,\n\t\t\t\t0\n\t\t\t)\n\t\t\tsession.fadeElapsed = 0\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.delayRemaining = Math.min(item.delayRemaining, 200)\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear all scribbles in a session immediately.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tclearSession(sessionId: string): void {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\n\t\tthis.clearIdleTimeout(session)\n\t\tfor (const item of session.items) {\n\t\t\titem.scribble.points.length = 0\n\t\t}\n\t\tsession.state = 'complete'\n\t}\n\n\t/**\n\t * Check if a session is active.\n\t *\n\t * @param sessionId - The session ID\n\t * @public\n\t */\n\tisSessionActive(sessionId: string): boolean {\n\t\tconst session = this.sessions.get(sessionId)\n\t\treturn session?.state === 'active'\n\t}\n\n\t// ==================== SIMPLE API (for eraser, select, etc.) ====================\n\n\t/**\n\t * Add a scribble using the default self-consuming behavior.\n\t * Creates an implicit session for the scribble.\n\t *\n\t * @param scribble - Partial scribble properties\n\t * @param id - Optional scribble id\n\t * @returns The created scribble item\n\t * @public\n\t */\n\taddScribble(scribble: Partial<TLScribble>, id = uniqueId()): ScribbleItem {\n\t\tconst sessionId = this.startSession()\n\t\treturn this.addScribbleToSession(sessionId, scribble, id)\n\t}\n\n\t/**\n\t * Add a point to a scribble. Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @param x - X coordinate\n\t * @param y - Y coordinate\n\t * @param z - Z coordinate (pressure)\n\t * @public\n\t */\n\taddPoint(id: string, x: number, y: number, z = 0.5): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\tconst point = { x, y, z }\n\t\t\t\tif (!item.prev || Vec.Dist(item.prev, point) >= 1) {\n\t\t\t\t\titem.next = point\n\t\t\t\t}\n\t\t\t\tif (session.options.idleTimeoutMs > 0) {\n\t\t\t\t\tthis.resetIdleTimeout(session)\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Mark a scribble as complete (done being drawn but not yet fading).\n\t * Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @public\n\t */\n\tcomplete(id: string): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\tif (item.scribble.state === 'starting' || item.scribble.state === 'active') {\n\t\t\t\t\titem.scribble.state = 'complete'\n\t\t\t\t}\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Stop a scribble. Searches all sessions.\n\t *\n\t * @param id - The scribble id\n\t * @public\n\t */\n\tstop(id: string): ScribbleItem {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tconst item = session.items.find((i) => i.id === id)\n\t\t\tif (item) {\n\t\t\t\titem.delayRemaining = Math.min(item.delayRemaining, 200)\n\t\t\t\titem.scribble.state = 'stopping'\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\tthrow Error(`Scribble with id ${id} not found`)\n\t}\n\n\t/**\n\t * Stop and remove all sessions.\n\t *\n\t * @public\n\t */\n\treset(): void {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tthis.clearIdleTimeout(session)\n\t\t}\n\t\tthis.sessions.clear()\n\t\tthis.editor.updateInstanceState({ scribbles: [] })\n\t}\n\n\t/**\n\t * Update on each animation frame.\n\t *\n\t * @param elapsed - The number of milliseconds since the last tick.\n\t * @public\n\t */\n\ttick(elapsed: number): void {\n\t\tconst currentScribbles = this.editor.getInstanceState().scribbles\n\t\tif (this.sessions.size === 0 && currentScribbles.length === 0) return\n\n\t\tthis.editor.run(() => {\n\t\t\t// Drop sessions from other pages, otherwise a still-fading laser trail\n\t\t\t// keeps playing out over the page the user switched to (#10449).\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tif (session.pageId !== currentPageId) {\n\t\t\t\t\tthis.clearSession(session.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tick all sessions\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tthis.tickSession(session, elapsed)\n\t\t\t}\n\n\t\t\t// Remove completed sessions\n\t\t\tfor (const [id, session] of this.sessions) {\n\t\t\t\tif (session.state === 'complete') {\n\t\t\t\t\tthis.clearIdleTimeout(session)\n\t\t\t\t\tthis.sessions.delete(id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Collect scribbles from all sessions\n\t\t\tconst scribbles: TLScribble[] = []\n\t\t\tfor (const session of this.sessions.values()) {\n\t\t\t\tfor (const item of session.items) {\n\t\t\t\t\tif (item.scribble.points.length > 0) {\n\t\t\t\t\t\tscribbles.push({\n\t\t\t\t\t\t\t...item.scribble,\n\t\t\t\t\t\t\tpoints: [...item.scribble.points],\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.editor.updateInstanceState({ scribbles })\n\t\t})\n\t}\n\n\t// ==================== PRIVATE HELPERS ====================\n\n\tprivate resetIdleTimeout(session: Session): void {\n\t\tthis.clearIdleTimeout(session)\n\t\tsession.idleTimeoutHandle = this.editor.timers.setTimeout(() => {\n\t\t\tthis.stopSession(session.id)\n\t\t}, session.options.idleTimeoutMs)\n\t}\n\n\tprivate clearIdleTimeout(session: Session): void {\n\t\tif (session.idleTimeoutHandle !== undefined) {\n\t\t\tthis.editor.timers.clearTimeout(session.idleTimeoutHandle)\n\t\t\tsession.idleTimeoutHandle = undefined\n\t\t}\n\t}\n\n\tprivate tickSession(session: Session, elapsed: number): void {\n\t\tif (session.state === 'complete') return\n\n\t\tif (session.state === 'stopping' && session.options.fadeMode === 'grouped') {\n\t\t\tthis.tickGroupedFade(session, elapsed)\n\t\t} else {\n\t\t\tthis.tickSessionItems(session, elapsed)\n\t\t}\n\n\t\t// Check if session is complete\n\t\tconst hasContent = session.items.some((item) => item.scribble.points.length > 0)\n\t\tif (!hasContent && (session.state === 'stopping' || session.items.length === 0)) {\n\t\t\tsession.state = 'complete'\n\t\t}\n\t}\n\n\tprivate tickSessionItems(session: Session, elapsed: number): void {\n\t\tfor (const item of session.items) {\n\t\t\tconst shouldSelfConsume =\n\t\t\t\tsession.options.selfConsume ||\n\t\t\t\tsession.state === 'stopping' ||\n\t\t\t\titem.scribble.state === 'stopping'\n\n\t\t\tif (shouldSelfConsume) {\n\t\t\t\tthis.tickSelfConsumingItem(item, elapsed)\n\t\t\t} else {\n\t\t\t\tthis.tickPersistentItem(item)\n\t\t\t}\n\t\t}\n\n\t\t// Keep starting scribbles with no point yet, or addPoint can't find them (#7681).\n\t\tif (session.options.fadeMode === 'individual') {\n\t\t\tsession.items = session.items.filter(\n\t\t\t\t({ scribble }) => scribble.points.length > 0 || scribble.state === 'starting'\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate tickPersistentItem(item: ScribbleItem): void {\n\t\tconst { scribble } = item\n\n\t\tif (scribble.state === 'starting') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t\tif (scribble.points.length > 8) {\n\t\t\t\tscribble.state = 'active'\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif (scribble.state === 'active') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate tickSelfConsumingItem(item: ScribbleItem, elapsed: number): void {\n\t\tconst { scribble } = item\n\n\t\tif (scribble.state === 'starting') {\n\t\t\tconst { next, prev } = item\n\t\t\tif (next && next !== prev) {\n\t\t\t\titem.prev = next\n\t\t\t\tscribble.points.push(next)\n\t\t\t}\n\t\t\tif (scribble.points.length > 8) {\n\t\t\t\tscribble.state = 'active'\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif (item.delayRemaining > 0) {\n\t\t\titem.delayRemaining = Math.max(0, item.delayRemaining - elapsed)\n\t\t}\n\n\t\titem.timeoutMs += elapsed\n\t\tif (item.timeoutMs >= 16) {\n\t\t\titem.timeoutMs = 0\n\t\t}\n\n\t\tconst { delayRemaining, timeoutMs, prev, next } = item\n\n\t\tswitch (scribble.state) {\n\t\t\tcase 'active': {\n\t\t\t\tif (next && next !== prev) {\n\t\t\t\t\titem.prev = next\n\t\t\t\t\tscribble.points.push(next)\n\t\t\t\t\tif (delayRemaining === 0 && scribble.points.length > 8) {\n\t\t\t\t\t\tscribble.points.shift()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (timeoutMs === 0) {\n\t\t\t\t\t\tif (scribble.points.length > 1) {\n\t\t\t\t\t\t\tscribble.points.shift()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\titem.delayRemaining = scribble.delay\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'stopping': {\n\t\t\t\tif (delayRemaining === 0 && timeoutMs === 0) {\n\t\t\t\t\tif (scribble.points.length <= 1) {\n\t\t\t\t\t\tscribble.points.length = 0\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (scribble.shrink) {\n\t\t\t\t\t\tscribble.size = Math.max(1, scribble.size * (1 - scribble.shrink))\n\t\t\t\t\t}\n\t\t\t\t\tscribble.points.shift()\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'paused': {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate tickGroupedFade(session: Session, elapsed: number): void {\n\t\tsession.fadeElapsed += elapsed\n\n\t\tlet remainingPoints = 0\n\t\tfor (const item of session.items) {\n\t\t\tremainingPoints += item.scribble.points.length\n\t\t}\n\n\t\tif (remainingPoints === 0) return\n\n\t\tif (session.fadeElapsed >= session.options.fadeDurationMs) {\n\t\t\tfor (const item of session.items) {\n\t\t\t\titem.scribble.points.length = 0\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tconst progress = session.fadeElapsed / session.options.fadeDurationMs\n\t\tconst easedProgress = session.options.fadeEasing === 'ease-in' ? progress * progress : progress\n\n\t\tconst targetRemoved = Math.floor(easedProgress * session.totalPointsAtFadeStart)\n\t\tconst actuallyRemoved = session.totalPointsAtFadeStart - remainingPoints\n\t\tconst pointsToRemove = Math.max(1, targetRemoved - actuallyRemoved)\n\n\t\tlet removed = 0\n\t\tlet itemIndex = 0\n\t\twhile (removed < pointsToRemove && itemIndex < session.items.length) {\n\t\t\tconst item = session.items[itemIndex]\n\t\t\tif (item.scribble.points.length > 0) {\n\t\t\t\titem.scribble.points.shift()\n\t\t\t\tremoved++\n\t\t\t} else {\n\t\t\t\titemIndex++\n\t\t\t}\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["next", "prev"]
7
7
  }
@@ -62,6 +62,15 @@ const PROPERTIES = {
62
62
  };
63
63
  const NUMBER_OF_KNOWN_PROPERTIES = Object.keys(PROPERTIES).length;
64
64
  const LICENSE_EMAIL = "sales@tldraw.com";
65
+ const CANONICAL_TLDRAW_HOSTS = [
66
+ "tldraw.com",
67
+ "tldraw.dev",
68
+ "tldraw.club",
69
+ "tldraw.workers.dev",
70
+ "tldraw.xyz"
71
+ ];
72
+ const CANONICAL_HOST_LICENSE_ID = "tldraw-canonical-host";
73
+ const CANONICAL_HOST_NEVER_EXPIRES = /* @__PURE__ */ new Date(864e13);
65
74
  const WATERMARK_TRACK_SRC = `${(0, import_assets.getDefaultCdnBaseUrl)()}/watermarks/watermark-track.svg`;
66
75
  const NO_FEATURES = {
67
76
  collaboration: false,
@@ -120,18 +129,64 @@ class LicenseManager {
120
129
  return this.featureFlags.get()[feature];
121
130
  }
122
131
  getIsDevelopment() {
123
- const protocol = window.location.protocol;
124
- const hostname = window.location.hostname;
125
- if (hostname.toLowerCase().endsWith(".localhost")) {
132
+ const { protocol, hostname } = this.getOriginLocation();
133
+ if (hostname.endsWith(".localhost")) {
126
134
  return process.env.NODE_ENV !== "production";
127
135
  }
128
136
  return protocol === "http:" || protocol === "https:" && this.isLoopbackHost(hostname) || process.env.NODE_ENV !== "production";
129
137
  }
138
+ getOriginLocation() {
139
+ const { origin, protocol, hostname } = window.location;
140
+ const source = origin && origin !== "null" ? new URL(origin) : { protocol, hostname };
141
+ return { protocol: source.protocol, hostname: source.hostname.toLowerCase() };
142
+ }
130
143
  isLoopbackHost(hostname) {
131
144
  const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
132
145
  return host === "localhost" || host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host);
133
146
  }
147
+ /** Whether the page is served from one of tldraw's own domains, or a subdomain of one. */
148
+ isCanonicalTldrawHost() {
149
+ const hostname = window.location.hostname.toLowerCase();
150
+ return CANONICAL_TLDRAW_HOSTS.some((host) => hostname === host || hostname.endsWith(`.${host}`));
151
+ }
152
+ /**
153
+ * The license tldraw's own domains get without a key. It keeps the watermark — our properties
154
+ * advertise the SDK the same way any other watermarked deployment does — and grants the
155
+ * collaboration features our apps are built on.
156
+ *
157
+ * Every expiry-derived field is a literal rather than something computed from a date, because
158
+ * there is no key here to expire: the grant lasts as long as the host is ours.
159
+ */
160
+ getCanonicalHostLicense() {
161
+ return {
162
+ isLicenseParseable: true,
163
+ license: {
164
+ id: CANONICAL_HOST_LICENSE_ID,
165
+ hosts: [window.location.hostname.toLowerCase()],
166
+ flags: FLAGS.WITH_WATERMARK | FLAGS.FEAT_COLLABORATION | FLAGS.FEAT_COMMENTING,
167
+ expiryDate: CANONICAL_HOST_NEVER_EXPIRES.toISOString()
168
+ },
169
+ isDevelopment: this.isDevelopment,
170
+ isDomainValid: true,
171
+ expiryDate: CANONICAL_HOST_NEVER_EXPIRES,
172
+ isAnnualLicense: false,
173
+ isAnnualLicenseExpired: false,
174
+ isPerpetualLicense: false,
175
+ isPerpetualLicenseExpired: false,
176
+ isInternalLicense: false,
177
+ isNativeLicense: false,
178
+ isLicensedWithWatermark: true,
179
+ isEvaluationLicense: false,
180
+ isEvaluationLicenseExpired: false,
181
+ isCollaborationEnabled: true,
182
+ isCommentingEnabled: true,
183
+ daysSinceExpiry: 0
184
+ };
185
+ }
134
186
  getTrackType(result, licenseState) {
187
+ if ("license" in result && result.license.id === CANONICAL_HOST_LICENSE_ID) {
188
+ return null;
189
+ }
135
190
  if (licenseState === "unlicensed-production") {
136
191
  return "unlicensed";
137
192
  }
@@ -213,6 +268,9 @@ class LicenseManager {
213
268
  };
214
269
  }
215
270
  async getLicenseFromKey(licenseKey) {
271
+ if (!licenseKey && this.isCanonicalTldrawHost()) {
272
+ return this.getCanonicalHostLicense();
273
+ }
216
274
  if (!licenseKey) {
217
275
  if (!this.isDevelopment) {
218
276
  this.outputNoLicenseKeyProvided();
@@ -268,7 +326,7 @@ class LicenseManager {
268
326
  }
269
327
  }
270
328
  isDomainValid(licenseInfo) {
271
- const currentHostname = window.location.hostname.toLowerCase();
329
+ const currentHostname = this.getOriginLocation().hostname;
272
330
  return licenseInfo.hosts.some((host) => {
273
331
  const normalizedHostOrUrlRegex = host.toLowerCase().trim();
274
332
  if (normalizedHostOrUrlRegex === currentHostname || `www.${normalizedHostOrUrlRegex}` === currentHostname || normalizedHostOrUrlRegex === `www.${currentHostname}`) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/license/LicenseManager.ts"],
4
- "sourcesContent": ["import { atom, transact } from '@tldraw/state'\nimport { noop } from '@tldraw/utils'\nimport { publishDates, version } from '../../version'\nimport { getDefaultCdnBaseUrl } from '../utils/assets'\nimport { importPublicKey, str2ab } from '../utils/licensing'\n\nconst GRACE_PERIOD_DAYS = 30\n\nexport const FLAGS = {\n\t// -- MUTUALLY EXCLUSIVE FLAGS\n\t// Annual means the license expires after a time period, usually 1 year.\n\tANNUAL_LICENSE: 1,\n\t// Perpetual means the license never expires up to the max supported version.\n\tPERPETUAL_LICENSE: 1 << 1,\n\n\t// -- ADDITIVE FLAGS --\n\t// Internal means the license is for internal use only.\n\tINTERNAL_LICENSE: 1 << 2,\n\t// Watermark means the product is watermarked.\n\tWITH_WATERMARK: 1 << 3,\n\t// Evaluation means the license is for evaluation purposes only.\n\tEVALUATION_LICENSE: 1 << 4,\n\t// Native means the license is for native apps which switches\n\t// on special-case logic.\n\tNATIVE_LICENSE: 1 << 5,\n\n\t// -- FEATURE FLAGS --\n\t// Collaboration is the umbrella flag for collaboration features; it grants all sub-features.\n\tFEAT_COLLABORATION: 1 << 6,\n\t// Commenting is the first sub-feature of collaboration.\n\tFEAT_COMMENTING: 1 << 7,\n}\nconst HIGHEST_FLAG = Math.max(...Object.values(FLAGS))\n\nexport const PROPERTIES = {\n\tID: 0,\n\tHOSTS: 1,\n\tFLAGS: 2,\n\tEXPIRY_DATE: 3,\n}\nconst NUMBER_OF_KNOWN_PROPERTIES = Object.keys(PROPERTIES).length\n\nconst LICENSE_EMAIL = 'sales@tldraw.com'\n\nconst WATERMARK_TRACK_SRC = `${getDefaultCdnBaseUrl()}/watermarks/watermark-track.svg`\n\n/** @internal */\nexport interface LicenseInfo {\n\tid: string\n\thosts: string[]\n\tflags: number\n\texpiryDate: string\n}\n\n/**\n * Names of the licensable product features gated by the license. `collaboration` is an umbrella\n * that also grants all of its sub-features (currently just `commenting`).\n *\n * @internal\n */\nexport type LicenseFeatureName = 'collaboration' | 'commenting'\n\nconst NO_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {\n\tcollaboration: false,\n\tcommenting: false,\n}\nconst ALL_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {\n\tcollaboration: true,\n\tcommenting: true,\n}\n\n/** @internal */\nexport type LicenseState =\n\t| 'pending' // License validation is in progress\n\t| 'licensed' // License is valid and active (no restrictions)\n\t| 'licensed-with-watermark' // License is valid but shows watermark (evaluation licenses, WITH_WATERMARK licenses)\n\t| 'unlicensed' // No valid license found or license is invalid (development)\n\t| 'unlicensed-production' // No valid license in production deployment (missing, invalid, or wrong domain)\n\t| 'expired' // License has been expired (30 days past expiration for regular licenses, immediately for evaluation licenses)\n/** @internal */\nexport type InvalidLicenseReason =\n\t| 'invalid-license-key'\n\t| 'no-key-provided'\n\t| 'has-key-development-mode'\n\n/** @internal */\nexport type LicenseFromKeyResult = InvalidLicenseKeyResult | ValidLicenseKeyResult\n\n/** @internal */\nexport interface InvalidLicenseKeyResult {\n\tisLicenseParseable: false\n\treason: InvalidLicenseReason\n}\n\n/** @internal */\nexport interface ValidLicenseKeyResult {\n\tisLicenseParseable: true\n\tlicense: LicenseInfo\n\tisDevelopment: boolean\n\tisDomainValid: boolean\n\texpiryDate: Date\n\tisAnnualLicense: boolean\n\tisAnnualLicenseExpired: boolean\n\tisPerpetualLicense: boolean\n\tisPerpetualLicenseExpired: boolean\n\tisInternalLicense: boolean\n\tisNativeLicense: boolean\n\tisLicensedWithWatermark: boolean\n\tisEvaluationLicense: boolean\n\tisEvaluationLicenseExpired: boolean\n\tisCollaborationEnabled: boolean\n\tisCommentingEnabled: boolean\n\tdaysSinceExpiry: number\n}\n\n/** @internal */\nexport type TrackType = 'unlicensed' | 'with_watermark' | 'evaluation' | null\n\n/** @internal */\nexport class LicenseManager {\n\tprivate publicKey =\n\t\t'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHJh0uUfxHtCGyerXmmatE368Hd9rI6LH9oPDQihnaCryRFWEVeOvf9U/SPbyxX74LFyJs5tYeAHq5Nc0Ax25LQ'\n\tpublic isDevelopment: boolean\n\tpublic isTest: boolean\n\tpublic isCryptoAvailable: boolean\n\tstate = atom<LicenseState>('license state', 'pending')\n\tfeatureFlags = atom<Record<LicenseFeatureName, boolean>>('license feature flags', {\n\t\t...NO_FEATURES,\n\t})\n\tpublic verbose = true\n\tprivate isDisposed = false\n\n\tconstructor(licenseKey: string | undefined, testPublicKey?: string) {\n\t\tthis.isTest = process.env.NODE_ENV === 'test'\n\t\tthis.isDevelopment = this.getIsDevelopment()\n\t\tthis.publicKey = testPublicKey || this.publicKey\n\t\tthis.isCryptoAvailable = !!crypto.subtle\n\n\t\t// In development every feature is enabled (see `getEnabledFeatures`), and that doesn't depend\n\t\t// on the async validation result. Reflect it eagerly so features aren't reported as disabled\n\t\t// during the validation window \u2014 or left disabled if validation rejects (the `.catch` below\n\t\t// never sets `featureFlags`). In production the fail-closed default stands until validation.\n\t\tif (this.isDevelopment) {\n\t\t\tthis.featureFlags.set({ ...ALL_FEATURES })\n\t\t}\n\n\t\tthis.getLicenseFromKey(licenseKey)\n\t\t\t.then((result) => {\n\t\t\t\t// replaced by a manager for a newer key: this one's messages, tracking ping and state\n\t\t\t\t// would describe a key the app no longer uses\n\t\t\t\tif (this.isDisposed) return\n\t\t\t\tconst licenseState = getLicenseState(\n\t\t\t\t\tresult,\n\t\t\t\t\t(messages: string[]) => this.outputMessages(messages),\n\t\t\t\t\tthis.isDevelopment\n\t\t\t\t)\n\n\t\t\t\tthis.maybeTrack(result, licenseState)\n\n\t\t\t\t// Update both atoms atomically so dependents never observe the license state and the\n\t\t\t\t// feature flags out of sync mid-update.\n\t\t\t\ttransact(() => {\n\t\t\t\t\tthis.state.set(licenseState)\n\t\t\t\t\tthis.featureFlags.set(getEnabledFeatures(result, licenseState, this.isDevelopment))\n\t\t\t\t})\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tif (this.isDisposed) return\n\t\t\t\tconsole.error('License validation failed:', error)\n\t\t\t\tthis.state.set('unlicensed')\n\t\t\t})\n\t}\n\n\t/** Ignore the pending validation: nothing is logged, tracked or set once the manager is replaced. */\n\tdispose() {\n\t\tthis.isDisposed = true\n\t}\n\n\t/**\n\t * Returns whether a given licensable feature is enabled. Reactive: reading this inside a signal\n\t * recomputes when license validation resolves.\n\t */\n\tisFeatureEnabled(feature: LicenseFeatureName): boolean {\n\t\treturn this.featureFlags.get()[feature]\n\t}\n\n\tprivate getIsDevelopment() {\n\t\tconst protocol = window.location.protocol\n\t\tconst hostname = window.location.hostname\n\n\t\t// Tauri uses `tauri://localhost` on macOS and Linux and `http://tauri.localhost` on Windows.\n\t\tif (hostname.toLowerCase().endsWith('.localhost')) {\n\t\t\treturn process.env.NODE_ENV !== 'production'\n\t\t}\n\n\t\treturn (\n\t\t\tprotocol === 'http:' ||\n\t\t\t(protocol === 'https:' && this.isLoopbackHost(hostname)) ||\n\t\t\tprocess.env.NODE_ENV !== 'production'\n\t\t)\n\t}\n\n\tprivate isLoopbackHost(hostname: string) {\n\t\t// localhost, IPv4 loopback (127.0.0.0/8) and IPv6 loopback (::1) are all local development hosts\n\t\tconst host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '')\n\t\treturn host === 'localhost' || host === '::1' || /^127(?:\\.\\d{1,3}){3}$/.test(host)\n\t}\n\n\tprivate getTrackType(result: LicenseFromKeyResult, licenseState: LicenseState): TrackType {\n\t\t// Track watermark for unlicensed production deployments\n\t\tif (licenseState === 'unlicensed-production') {\n\t\t\treturn 'unlicensed'\n\t\t}\n\n\t\tif (this.isDevelopment) {\n\t\t\treturn null\n\t\t}\n\n\t\tif (!result.isLicenseParseable) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Track evaluation licenses (for analytics, even though no watermark is shown)\n\t\tif (result.isEvaluationLicense) {\n\t\t\treturn 'evaluation'\n\t\t}\n\n\t\t// Track licenses that show watermarks\n\t\tif (licenseState === 'licensed-with-watermark') {\n\t\t\treturn 'with_watermark'\n\t\t}\n\n\t\treturn null\n\t}\n\n\tprivate maybeTrack(result: LicenseFromKeyResult, licenseState: LicenseState): void {\n\t\tconst trackType = this.getTrackType(result, licenseState)\n\t\tif (!trackType) {\n\t\t\treturn\n\t\t}\n\n\t\tconst url = new URL(WATERMARK_TRACK_SRC)\n\t\turl.searchParams.set('version', version)\n\t\turl.searchParams.set('license_type', trackType)\n\t\tif ('license' in result) {\n\t\t\turl.searchParams.set('license_id', result.license.id)\n\t\t\tconst sku = this.isFlagEnabled(result.license.flags, FLAGS.EVALUATION_LICENSE)\n\t\t\t\t? 'evaluation'\n\t\t\t\t: this.isFlagEnabled(result.license.flags, FLAGS.ANNUAL_LICENSE)\n\t\t\t\t\t? 'annual'\n\t\t\t\t\t: this.isFlagEnabled(result.license.flags, FLAGS.PERPETUAL_LICENSE)\n\t\t\t\t\t\t? 'perpetual'\n\t\t\t\t\t\t: 'unknown'\n\t\t\turl.searchParams.set('sku', sku)\n\t\t}\n\t\turl.searchParams.set('url', window.location.href)\n\t\tif (process.env.NODE_ENV) {\n\t\t\turl.searchParams.set('environment', process.env.NODE_ENV)\n\t\t}\n\n\t\t// best-effort: a blocked or offline request must not surface as an unhandled rejection\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tfetch(url.toString()).catch(noop)\n\t}\n\n\tprivate async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {\n\t\tconst [data, signature] = licenseKey.split('.')\n\t\tconst [prefix, encodedData] = data.split('/')\n\n\t\tif (!prefix.startsWith('tldraw-')) {\n\t\t\tthrow new Error(`Unsupported prefix '${prefix}'`)\n\t\t}\n\n\t\tconst publicCryptoKey = await importPublicKey(this.publicKey)\n\n\t\tlet isVerified\n\t\ttry {\n\t\t\tisVerified = await crypto.subtle.verify(\n\t\t\t\t{\n\t\t\t\t\tname: 'ECDSA',\n\t\t\t\t\thash: { name: 'SHA-256' },\n\t\t\t\t},\n\t\t\t\tpublicCryptoKey,\n\t\t\t\tnew Uint8Array(str2ab(atob(signature))),\n\t\t\t\tnew Uint8Array(str2ab(atob(encodedData)))\n\t\t\t)\n\t\t} catch (e) {\n\t\t\tconsole.error(e)\n\t\t\tthrow new Error('Could not perform signature validation')\n\t\t}\n\n\t\tif (!isVerified) {\n\t\t\tthrow new Error('Invalid signature')\n\t\t}\n\n\t\tlet decodedData: any\n\t\ttry {\n\t\t\tdecodedData = JSON.parse(atob(encodedData))\n\t\t} catch {\n\t\t\tthrow new Error('Could not parse object')\n\t\t}\n\t\tif (decodedData.length > NUMBER_OF_KNOWN_PROPERTIES) {\n\t\t\tthis.outputMessages([\n\t\t\t\t'License key contains some unknown properties.',\n\t\t\t\t'You may want to update tldraw packages to a newer version to get access to new functionality.',\n\t\t\t])\n\t\t}\n\n\t\treturn {\n\t\t\tid: decodedData[PROPERTIES.ID],\n\t\t\thosts: decodedData[PROPERTIES.HOSTS],\n\t\t\tflags: decodedData[PROPERTIES.FLAGS],\n\t\t\texpiryDate: decodedData[PROPERTIES.EXPIRY_DATE],\n\t\t}\n\t}\n\n\tasync getLicenseFromKey(licenseKey?: string): Promise<LicenseFromKeyResult> {\n\t\tif (!licenseKey) {\n\t\t\tif (!this.isDevelopment) {\n\t\t\t\tthis.outputNoLicenseKeyProvided()\n\t\t\t}\n\n\t\t\treturn { isLicenseParseable: false, reason: 'no-key-provided' }\n\t\t}\n\n\t\tif (this.isDevelopment && !this.isCryptoAvailable) {\n\t\t\tif (this.verbose) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log(\n\t\t\t\t\t'tldraw: you seem to be in a development environment that does not support crypto. License not verified.'\n\t\t\t\t)\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log('You should check that this works in production separately.')\n\t\t\t}\n\t\t\t// We can't parse the license if we are in development mode since crypto\n\t\t\t// is not available on http\n\t\t\treturn { isLicenseParseable: false, reason: 'has-key-development-mode' }\n\t\t}\n\n\t\t// Borrowed idea from AG Grid:\n\t\t// Copying from various sources (like PDFs) can include zero-width characters.\n\t\t// This helps makes sure the key validation doesn't fail.\n\t\tlet cleanedLicenseKey = licenseKey.replace(/[\\u200B-\\u200D\\uFEFF]/g, '')\n\t\tcleanedLicenseKey = cleanedLicenseKey.replace(/\\r?\\n|\\r/g, '')\n\n\t\ttry {\n\t\t\tconst licenseInfo = await this.extractLicenseKey(cleanedLicenseKey)\n\t\t\tconst expiryDate = new Date(licenseInfo.expiryDate)\n\t\t\tconst isAnnualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.ANNUAL_LICENSE)\n\t\t\tconst isPerpetualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.PERPETUAL_LICENSE)\n\t\t\tconst isEvaluationLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.EVALUATION_LICENSE)\n\n\t\t\tconst isAnnualLicenseExpired = isAnnualLicense && this.isAnnualLicenseExpired(expiryDate)\n\t\t\tconst isPerpetualLicenseExpired =\n\t\t\t\tisPerpetualLicense && this.isPerpetualLicenseExpired(expiryDate)\n\n\t\t\t// The collaboration umbrella grants all of its sub-features, so commenting is enabled\n\t\t\t// by either the commenting flag or the collaboration flag.\n\t\t\tconst isCollaborationEnabled = this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COLLABORATION)\n\t\t\tconst isCommentingEnabled =\n\t\t\t\tisCollaborationEnabled || this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COMMENTING)\n\n\t\t\t// For perpetual licenses, the calendar expiry date only gates access to future\n\t\t\t// major/minor releases; it does not \"expire\" the license itself. While the user\n\t\t\t// is still on a covered version we report `daysSinceExpiry` as 0 so consumers\n\t\t\t// (and the grace-period warning) don't treat them as past expiry.\n\t\t\tconst daysSinceExpiry =\n\t\t\t\tisPerpetualLicense && !isPerpetualLicenseExpired ? 0 : this.getDaysSinceExpiry(expiryDate)\n\n\t\t\tconst result: ValidLicenseKeyResult = {\n\t\t\t\tlicense: licenseInfo,\n\t\t\t\tisLicenseParseable: true,\n\t\t\t\tisDevelopment: this.isDevelopment,\n\t\t\t\tisDomainValid: this.isDomainValid(licenseInfo),\n\t\t\t\texpiryDate,\n\t\t\t\tisAnnualLicense,\n\t\t\t\tisAnnualLicenseExpired,\n\t\t\t\tisPerpetualLicense,\n\t\t\t\tisPerpetualLicenseExpired,\n\t\t\t\tisInternalLicense: this.isFlagEnabled(licenseInfo.flags, FLAGS.INTERNAL_LICENSE),\n\t\t\t\tisNativeLicense: this.isNativeLicense(licenseInfo),\n\t\t\t\tisLicensedWithWatermark: this.isFlagEnabled(licenseInfo.flags, FLAGS.WITH_WATERMARK),\n\t\t\t\tisEvaluationLicense,\n\t\t\t\tisEvaluationLicenseExpired:\n\t\t\t\t\tisEvaluationLicense && this.isEvaluationLicenseExpired(expiryDate),\n\t\t\t\tisCollaborationEnabled,\n\t\t\t\tisCommentingEnabled,\n\t\t\t\tdaysSinceExpiry,\n\t\t\t}\n\t\t\tthis.outputLicenseInfoIfNeeded(result)\n\n\t\t\treturn result\n\t\t} catch (e: any) {\n\t\t\tthis.outputInvalidLicenseKey(e.message)\n\t\t\t// If the license can't be parsed, it's invalid\n\t\t\treturn { isLicenseParseable: false, reason: 'invalid-license-key' }\n\t\t}\n\t}\n\n\tprivate isDomainValid(licenseInfo: LicenseInfo) {\n\t\tconst currentHostname = window.location.hostname.toLowerCase()\n\n\t\treturn licenseInfo.hosts.some((host) => {\n\t\t\tconst normalizedHostOrUrlRegex = host.toLowerCase().trim()\n\n\t\t\t// Allow the domain if listed and www variations, 'example.com' allows 'example.com' and 'www.example.com'\n\t\t\tif (\n\t\t\t\tnormalizedHostOrUrlRegex === currentHostname ||\n\t\t\t\t`www.${normalizedHostOrUrlRegex}` === currentHostname ||\n\t\t\t\tnormalizedHostOrUrlRegex === `www.${currentHostname}`\n\t\t\t) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// If host is '*', we allow all domains.\n\t\t\tif (host === '*') {\n\t\t\t\t// All domains allowed.\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Native license support\n\t\t\t// In this case, `normalizedHost` is actually a protocol, e.g. `app-bundle:`\n\t\t\tif (this.isNativeLicense(licenseInfo)) {\n\t\t\t\treturn new RegExp(normalizedHostOrUrlRegex).test(window.location.href)\n\t\t\t}\n\n\t\t\t// Glob testing, we only support '*.somedomain.com' right now.\n\t\t\tif (host.includes('*')) {\n\t\t\t\tconst globToRegex = new RegExp(\n\t\t\t\t\tnormalizedHostOrUrlRegex.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*?') + '$'\n\t\t\t\t)\n\t\t\t\treturn globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)\n\t\t\t}\n\n\t\t\t// VSCode support\n\t\t\tif (window.location.protocol === 'vscode-webview:') {\n\t\t\t\tconst currentUrl = new URL(window.location.href)\n\t\t\t\tconst extensionId = currentUrl.searchParams.get('extensionId')\n\t\t\t\tif (normalizedHostOrUrlRegex === extensionId) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false\n\t\t})\n\t}\n\n\tprivate isNativeLicense(licenseInfo: LicenseInfo) {\n\t\treturn this.isFlagEnabled(licenseInfo.flags, FLAGS.NATIVE_LICENSE)\n\t}\n\n\tprivate getExpirationDateWithoutGracePeriod(expiryDate: Date) {\n\t\t// The named expiry date is the last day the license is fully usable, so the license\n\t\t// expires at the end of that day, i.e. the start of the following day. We work in UTC\n\t\t// (the expiry date is minted and parsed as a UTC date-only string) so the cutoff is a\n\t\t// single predictable instant for every user regardless of their local timezone.\n\t\treturn new Date(\n\t\t\tDate.UTC(\n\t\t\t\texpiryDate.getUTCFullYear(),\n\t\t\t\texpiryDate.getUTCMonth(),\n\t\t\t\texpiryDate.getUTCDate() + 1 // Add 1 day so the named date is fully usable\n\t\t\t)\n\t\t)\n\t}\n\n\tprivate getExpirationDateWithGracePeriod(expiryDate: Date) {\n\t\treturn new Date(\n\t\t\tDate.UTC(\n\t\t\t\texpiryDate.getUTCFullYear(),\n\t\t\t\texpiryDate.getUTCMonth(),\n\t\t\t\texpiryDate.getUTCDate() + GRACE_PERIOD_DAYS + 1 // Add 1 day to include the expiration day\n\t\t\t)\n\t\t)\n\t}\n\n\tprivate isAnnualLicenseExpired(expiryDate: Date) {\n\t\tconst expiration = this.getExpirationDateWithGracePeriod(expiryDate)\n\t\treturn new Date() >= expiration\n\t}\n\n\tprivate isPerpetualLicenseExpired(expiryDate: Date) {\n\t\tconst expiration = this.getExpirationDateWithGracePeriod(expiryDate)\n\t\tconst dates = {\n\t\t\tmajor: new Date(publishDates.major),\n\t\t\tminor: new Date(publishDates.minor),\n\t\t}\n\t\t// We allow patch releases, but the major and minor releases should be within the expiration date\n\t\treturn dates.major >= expiration || dates.minor >= expiration\n\t}\n\n\tprivate getDaysSinceExpiry(expiryDate: Date): number {\n\t\tconst now = new Date()\n\t\tconst expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)\n\t\tconst diffTime = now.getTime() - expiration.getTime()\n\t\tconst diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))\n\t\treturn Math.max(0, diffDays)\n\t}\n\n\tprivate isEvaluationLicenseExpired(expiryDate: Date): boolean {\n\t\t// Evaluation licenses have no grace period - they expire immediately\n\t\tconst now = new Date()\n\t\tconst expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)\n\t\treturn now >= expiration\n\t}\n\n\tprivate isFlagEnabled(flags: number, flag: number) {\n\t\treturn (flags & flag) === flag\n\t}\n\n\tprivate outputNoLicenseKeyProvided() {\n\t\t// Noop, we don't need to show this message.\n\t\t// this.outputMessages([\n\t\t// \t'No tldraw license key provided!',\n\t\t// \t`Please reach out to ${LICENSE_EMAIL} if you would like to license tldraw or if you'd like a trial.`,\n\t\t// ])\n\t}\n\n\tprivate outputInvalidLicenseKey(msg: string) {\n\t\tthis.outputMessages(['Invalid tldraw license key', `Reason: ${msg}`])\n\t}\n\n\tprivate outputLicenseInfoIfNeeded(result: ValidLicenseKeyResult) {\n\t\t// If we added a new flag it will be twice the value of the currently highest flag.\n\t\t// And if all the current flags are on we would get the `HIGHEST_FLAG * 2 - 1`, so anything higher than that means there are new flags.\n\t\tif (result.license.flags >= HIGHEST_FLAG * 2) {\n\t\t\tthis.outputMessages(\n\t\t\t\t[\n\t\t\t\t\t'Warning: This tldraw license contains some unknown flags.',\n\t\t\t\t\t'This will still work, however, you may want to update tldraw packages to a newer version to get access to new functionality.',\n\t\t\t\t],\n\t\t\t\t'warning'\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate outputMessages(messages: string[], type: 'warning' | 'error' = 'error') {\n\t\tif (this.isTest) return\n\t\tif (this.verbose) {\n\t\t\tthis.outputDelimiter(type)\n\t\t\tfor (const message of messages) {\n\t\t\t\tconst bgColor = type === 'warning' ? 'orange' : 'crimson'\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log(\n\t\t\t\t\t`%c${message}`,\n\t\t\t\t\t`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`\n\t\t\t\t)\n\t\t\t}\n\t\t\tthis.outputDelimiter(type)\n\t\t}\n\t}\n\n\tprivate outputDelimiter(type: 'warning' | 'error' = 'error') {\n\t\tconst bgColor = type === 'warning' ? 'orange' : 'crimson'\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(\n\t\t\t'%c-------------------------------------------------------------------',\n\t\t\t`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`\n\t\t)\n\t}\n\n\tstatic className = 'tl-watermark_SEE-LICENSE'\n}\n\n/**\n * Derives which licensable features are enabled from the parse result and the derived license\n * state. In development every feature is enabled so SDK developers can build against them; in\n * production a feature requires both an active, valid license and the corresponding flag.\n *\n * @internal\n */\nexport function getEnabledFeatures(\n\tresult: LicenseFromKeyResult,\n\tlicenseState: LicenseState,\n\tisDevelopment: boolean\n): Record<LicenseFeatureName, boolean> {\n\t// Development gets all features so SDK developers can build against them.\n\tif (isDevelopment) {\n\t\treturn { ...ALL_FEATURES }\n\t}\n\n\t// Features require an active, valid license. Both the 30-day grace-period 'licensed' state and\n\t// the watermark state count as valid; unlicensed/expired/pending do not.\n\tif (\n\t\t!result.isLicenseParseable ||\n\t\t(licenseState !== 'licensed' && licenseState !== 'licensed-with-watermark')\n\t) {\n\t\treturn { ...NO_FEATURES }\n\t}\n\n\t// Evaluation licenses get every feature so prospects can trial the whole product without us\n\t// having to mint per-feature evaluation keys. Expired evaluation licenses are already excluded\n\t// above: they resolve to the 'expired' state.\n\tif (result.isEvaluationLicense) {\n\t\treturn { ...ALL_FEATURES }\n\t}\n\n\treturn {\n\t\tcollaboration: result.isCollaborationEnabled,\n\t\tcommenting: result.isCommentingEnabled,\n\t}\n}\n\nexport function getLicenseState(\n\tresult: LicenseFromKeyResult,\n\toutputMessages: (messages: string[]) => void,\n\tisDevelopment: boolean\n): LicenseState {\n\tif (!result.isLicenseParseable) {\n\t\tif (isDevelopment) {\n\t\t\treturn 'unlicensed'\n\t\t}\n\n\t\t// All unlicensed scenarios should not work in production\n\t\tif (result.reason === 'no-key-provided') {\n\t\t\toutputMessages([\n\t\t\t\t'No tldraw license key provided!',\n\t\t\t\t'A license is required for production deployments.',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t\t])\n\t\t} else {\n\t\t\toutputMessages([\n\t\t\t\t'Invalid license key. tldraw requires a valid license for production use.',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t\t])\n\t\t}\n\t\treturn 'unlicensed-production'\n\t}\n\n\tif (!result.isDomainValid && !result.isDevelopment) {\n\t\toutputMessages([\n\t\t\t'License key is not valid for this domain.',\n\t\t\t'A license is required for production deployments.',\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t])\n\t\treturn 'unlicensed-production'\n\t}\n\n\t// Handle evaluation licenses - they expire immediately with no grace period\n\tif (result.isEvaluationLicense) {\n\t\tif (result.isEvaluationLicenseExpired) {\n\t\t\toutputMessages([\n\t\t\t\t'Your tldraw evaluation license has expired!',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a full license.`,\n\t\t\t])\n\t\t\treturn 'expired'\n\t\t} else {\n\t\t\t// Valid evaluation license - tracked but no watermark shown\n\t\t\treturn 'licensed'\n\t\t}\n\t}\n\n\t// Handle expired regular licenses (both annual and perpetual)\n\tif (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {\n\t\toutputMessages([\n\t\t\t'Your tldraw license has been expired for more than 30 days!',\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to renew your license.`,\n\t\t])\n\t\treturn 'expired'\n\t}\n\n\t// Check if license is past expiry date but within grace period\n\tconst daysSinceExpiry = result.daysSinceExpiry\n\tif (daysSinceExpiry > 0 && !result.isEvaluationLicense) {\n\t\toutputMessages([\n\t\t\t'Your tldraw license has expired.',\n\t\t\t`License expired ${daysSinceExpiry} days ago.`,\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to renew your license.`,\n\t\t])\n\t\t// Within 30-day grace period: still licensed (no watermark)\n\t\treturn 'licensed'\n\t}\n\n\t// License is valid, determine if it has watermark\n\tif (result.isLicensedWithWatermark) {\n\t\treturn 'licensed-with-watermark'\n\t}\n\n\treturn 'licensed'\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { atom, transact } from '@tldraw/state'\nimport { noop } from '@tldraw/utils'\nimport { publishDates, version } from '../../version'\nimport { getDefaultCdnBaseUrl } from '../utils/assets'\nimport { importPublicKey, str2ab } from '../utils/licensing'\n\nconst GRACE_PERIOD_DAYS = 30\n\nexport const FLAGS = {\n\t// -- MUTUALLY EXCLUSIVE FLAGS\n\t// Annual means the license expires after a time period, usually 1 year.\n\tANNUAL_LICENSE: 1,\n\t// Perpetual means the license never expires up to the max supported version.\n\tPERPETUAL_LICENSE: 1 << 1,\n\n\t// -- ADDITIVE FLAGS --\n\t// Internal means the license is for internal use only.\n\tINTERNAL_LICENSE: 1 << 2,\n\t// Watermark means the product is watermarked.\n\tWITH_WATERMARK: 1 << 3,\n\t// Evaluation means the license is for evaluation purposes only.\n\tEVALUATION_LICENSE: 1 << 4,\n\t// Native means the license is for native apps which switches\n\t// on special-case logic.\n\tNATIVE_LICENSE: 1 << 5,\n\n\t// -- FEATURE FLAGS --\n\t// Collaboration is the umbrella flag for collaboration features; it grants all sub-features.\n\tFEAT_COLLABORATION: 1 << 6,\n\t// Commenting is the first sub-feature of collaboration.\n\tFEAT_COMMENTING: 1 << 7,\n}\nconst HIGHEST_FLAG = Math.max(...Object.values(FLAGS))\n\nexport const PROPERTIES = {\n\tID: 0,\n\tHOSTS: 1,\n\tFLAGS: 2,\n\tEXPIRY_DATE: 3,\n}\nconst NUMBER_OF_KNOWN_PROPERTIES = Object.keys(PROPERTIES).length\n\nconst LICENSE_EMAIL = 'sales@tldraw.com'\n\n/**\n * tldraw's own public domains. A page served from one of these hosts \u2014 or any subdomain of one \u2014 is\n * licensed without a key, because we own them: minting and rotating keys for our own properties was\n * a recurring source of expired and wrong-domain deployments.\n *\n * This list ships in the bundle, so it holds only domains we already serve from publicly. tldraw's\n * private domains are deliberately absent and keep using a real license key.\n */\nconst CANONICAL_TLDRAW_HOSTS = [\n\t'tldraw.com',\n\t'tldraw.dev',\n\t'tldraw.club',\n\t'tldraw.workers.dev',\n\t'tldraw.xyz',\n]\n\n/** Marks a license as the keyless canonical-host grant, which is never tracked. */\nconst CANONICAL_HOST_LICENSE_ID = 'tldraw-canonical-host'\n\n/** The canonical-host grant has no key behind it, and so no expiry date to fall due. */\nconst CANONICAL_HOST_NEVER_EXPIRES = new Date(8640000000000000)\n\nconst WATERMARK_TRACK_SRC = `${getDefaultCdnBaseUrl()}/watermarks/watermark-track.svg`\n\n/** @internal */\nexport interface LicenseInfo {\n\tid: string\n\thosts: string[]\n\tflags: number\n\texpiryDate: string\n}\n\n/**\n * Names of the licensable product features gated by the license. `collaboration` is an umbrella\n * that also grants all of its sub-features (currently just `commenting`).\n *\n * @internal\n */\nexport type LicenseFeatureName = 'collaboration' | 'commenting'\n\nconst NO_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {\n\tcollaboration: false,\n\tcommenting: false,\n}\nconst ALL_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {\n\tcollaboration: true,\n\tcommenting: true,\n}\n\n/** @internal */\nexport type LicenseState =\n\t| 'pending' // License validation is in progress\n\t| 'licensed' // License is valid and active (no restrictions)\n\t| 'licensed-with-watermark' // License is valid but shows watermark (evaluation licenses, WITH_WATERMARK licenses)\n\t| 'unlicensed' // No valid license found or license is invalid (development)\n\t| 'unlicensed-production' // No valid license in production deployment (missing, invalid, or wrong domain)\n\t| 'expired' // License has been expired (30 days past expiration for regular licenses, immediately for evaluation licenses)\n/** @internal */\nexport type InvalidLicenseReason =\n\t| 'invalid-license-key'\n\t| 'no-key-provided'\n\t| 'has-key-development-mode'\n\n/** @internal */\nexport type LicenseFromKeyResult = InvalidLicenseKeyResult | ValidLicenseKeyResult\n\n/** @internal */\nexport interface InvalidLicenseKeyResult {\n\tisLicenseParseable: false\n\treason: InvalidLicenseReason\n}\n\n/** @internal */\nexport interface ValidLicenseKeyResult {\n\tisLicenseParseable: true\n\tlicense: LicenseInfo\n\tisDevelopment: boolean\n\tisDomainValid: boolean\n\texpiryDate: Date\n\tisAnnualLicense: boolean\n\tisAnnualLicenseExpired: boolean\n\tisPerpetualLicense: boolean\n\tisPerpetualLicenseExpired: boolean\n\tisInternalLicense: boolean\n\tisNativeLicense: boolean\n\tisLicensedWithWatermark: boolean\n\tisEvaluationLicense: boolean\n\tisEvaluationLicenseExpired: boolean\n\tisCollaborationEnabled: boolean\n\tisCommentingEnabled: boolean\n\tdaysSinceExpiry: number\n}\n\n/** @internal */\nexport type TrackType = 'unlicensed' | 'with_watermark' | 'evaluation' | null\n\n/** @internal */\nexport class LicenseManager {\n\tprivate publicKey =\n\t\t'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHJh0uUfxHtCGyerXmmatE368Hd9rI6LH9oPDQihnaCryRFWEVeOvf9U/SPbyxX74LFyJs5tYeAHq5Nc0Ax25LQ'\n\tpublic isDevelopment: boolean\n\tpublic isTest: boolean\n\tpublic isCryptoAvailable: boolean\n\tstate = atom<LicenseState>('license state', 'pending')\n\tfeatureFlags = atom<Record<LicenseFeatureName, boolean>>('license feature flags', {\n\t\t...NO_FEATURES,\n\t})\n\tpublic verbose = true\n\tprivate isDisposed = false\n\n\tconstructor(licenseKey: string | undefined, testPublicKey?: string) {\n\t\tthis.isTest = process.env.NODE_ENV === 'test'\n\t\tthis.isDevelopment = this.getIsDevelopment()\n\t\tthis.publicKey = testPublicKey || this.publicKey\n\t\tthis.isCryptoAvailable = !!crypto.subtle\n\n\t\t// In development every feature is enabled (see `getEnabledFeatures`), and that doesn't depend\n\t\t// on the async validation result. Reflect it eagerly so features aren't reported as disabled\n\t\t// during the validation window \u2014 or left disabled if validation rejects (the `.catch` below\n\t\t// never sets `featureFlags`). In production the fail-closed default stands until validation.\n\t\tif (this.isDevelopment) {\n\t\t\tthis.featureFlags.set({ ...ALL_FEATURES })\n\t\t}\n\n\t\tthis.getLicenseFromKey(licenseKey)\n\t\t\t.then((result) => {\n\t\t\t\t// replaced by a manager for a newer key: this one's messages, tracking ping and state\n\t\t\t\t// would describe a key the app no longer uses\n\t\t\t\tif (this.isDisposed) return\n\t\t\t\tconst licenseState = getLicenseState(\n\t\t\t\t\tresult,\n\t\t\t\t\t(messages: string[]) => this.outputMessages(messages),\n\t\t\t\t\tthis.isDevelopment\n\t\t\t\t)\n\n\t\t\t\tthis.maybeTrack(result, licenseState)\n\n\t\t\t\t// Update both atoms atomically so dependents never observe the license state and the\n\t\t\t\t// feature flags out of sync mid-update.\n\t\t\t\ttransact(() => {\n\t\t\t\t\tthis.state.set(licenseState)\n\t\t\t\t\tthis.featureFlags.set(getEnabledFeatures(result, licenseState, this.isDevelopment))\n\t\t\t\t})\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tif (this.isDisposed) return\n\t\t\t\tconsole.error('License validation failed:', error)\n\t\t\t\tthis.state.set('unlicensed')\n\t\t\t})\n\t}\n\n\t/** Ignore the pending validation: nothing is logged, tracked or set once the manager is replaced. */\n\tdispose() {\n\t\tthis.isDisposed = true\n\t}\n\n\t/**\n\t * Returns whether a given licensable feature is enabled. Reactive: reading this inside a signal\n\t * recomputes when license validation resolves.\n\t */\n\tisFeatureEnabled(feature: LicenseFeatureName): boolean {\n\t\treturn this.featureFlags.get()[feature]\n\t}\n\n\tprivate getIsDevelopment() {\n\t\tconst { protocol, hostname } = this.getOriginLocation()\n\n\t\t// Tauri uses `tauri://localhost` on macOS and Linux and `http://tauri.localhost` on Windows.\n\t\tif (hostname.endsWith('.localhost')) {\n\t\t\treturn process.env.NODE_ENV !== 'production'\n\t\t}\n\n\t\treturn (\n\t\t\tprotocol === 'http:' ||\n\t\t\t(protocol === 'https:' && this.isLoopbackHost(hostname)) ||\n\t\t\tprocess.env.NODE_ENV !== 'production'\n\t\t)\n\t}\n\n\tprivate getOriginLocation() {\n\t\t// A document loaded from `blob:https://example.com/<uuid>` has protocol `blob:` and an empty\n\t\t// hostname, but its origin is the page that created the blob. Opaque origins ('null': custom\n\t\t// schemes, file:, about:) keep using the location.\n\t\tconst { origin, protocol, hostname } = window.location\n\t\tconst source = origin && origin !== 'null' ? new URL(origin) : { protocol, hostname }\n\t\treturn { protocol: source.protocol, hostname: source.hostname.toLowerCase() }\n\t}\n\n\tprivate isLoopbackHost(hostname: string) {\n\t\t// localhost, IPv4 loopback (127.0.0.0/8) and IPv6 loopback (::1) are all local development hosts\n\t\tconst host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '')\n\t\treturn host === 'localhost' || host === '::1' || /^127(?:\\.\\d{1,3}){3}$/.test(host)\n\t}\n\n\t/** Whether the page is served from one of tldraw's own domains, or a subdomain of one. */\n\tprivate isCanonicalTldrawHost() {\n\t\tconst hostname = window.location.hostname.toLowerCase()\n\t\treturn CANONICAL_TLDRAW_HOSTS.some((host) => hostname === host || hostname.endsWith(`.${host}`))\n\t}\n\n\t/**\n\t * The license tldraw's own domains get without a key. It keeps the watermark \u2014 our properties\n\t * advertise the SDK the same way any other watermarked deployment does \u2014 and grants the\n\t * collaboration features our apps are built on.\n\t *\n\t * Every expiry-derived field is a literal rather than something computed from a date, because\n\t * there is no key here to expire: the grant lasts as long as the host is ours.\n\t */\n\tprivate getCanonicalHostLicense(): ValidLicenseKeyResult {\n\t\treturn {\n\t\t\tisLicenseParseable: true,\n\t\t\tlicense: {\n\t\t\t\tid: CANONICAL_HOST_LICENSE_ID,\n\t\t\t\thosts: [window.location.hostname.toLowerCase()],\n\t\t\t\tflags: FLAGS.WITH_WATERMARK | FLAGS.FEAT_COLLABORATION | FLAGS.FEAT_COMMENTING,\n\t\t\t\texpiryDate: CANONICAL_HOST_NEVER_EXPIRES.toISOString(),\n\t\t\t},\n\t\t\tisDevelopment: this.isDevelopment,\n\t\t\tisDomainValid: true,\n\t\t\texpiryDate: CANONICAL_HOST_NEVER_EXPIRES,\n\t\t\tisAnnualLicense: false,\n\t\t\tisAnnualLicenseExpired: false,\n\t\t\tisPerpetualLicense: false,\n\t\t\tisPerpetualLicenseExpired: false,\n\t\t\tisInternalLicense: false,\n\t\t\tisNativeLicense: false,\n\t\t\tisLicensedWithWatermark: true,\n\t\t\tisEvaluationLicense: false,\n\t\t\tisEvaluationLicenseExpired: false,\n\t\t\tisCollaborationEnabled: true,\n\t\t\tisCommentingEnabled: true,\n\t\t\tdaysSinceExpiry: 0,\n\t\t}\n\t}\n\n\tprivate getTrackType(result: LicenseFromKeyResult, licenseState: LicenseState): TrackType {\n\t\t// Our own domains are licensed without a key, and the watermark ping exists to tell us about\n\t\t// deployments we don't already know about. Reporting our own apps back to ourselves would only\n\t\t// add noise to the numbers we read, so the canonical-host grant is never tracked.\n\t\tif ('license' in result && result.license.id === CANONICAL_HOST_LICENSE_ID) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Track watermark for unlicensed production deployments\n\t\tif (licenseState === 'unlicensed-production') {\n\t\t\treturn 'unlicensed'\n\t\t}\n\n\t\tif (this.isDevelopment) {\n\t\t\treturn null\n\t\t}\n\n\t\tif (!result.isLicenseParseable) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Track evaluation licenses (for analytics, even though no watermark is shown)\n\t\tif (result.isEvaluationLicense) {\n\t\t\treturn 'evaluation'\n\t\t}\n\n\t\t// Track licenses that show watermarks\n\t\tif (licenseState === 'licensed-with-watermark') {\n\t\t\treturn 'with_watermark'\n\t\t}\n\n\t\treturn null\n\t}\n\n\tprivate maybeTrack(result: LicenseFromKeyResult, licenseState: LicenseState): void {\n\t\tconst trackType = this.getTrackType(result, licenseState)\n\t\tif (!trackType) {\n\t\t\treturn\n\t\t}\n\n\t\tconst url = new URL(WATERMARK_TRACK_SRC)\n\t\turl.searchParams.set('version', version)\n\t\turl.searchParams.set('license_type', trackType)\n\t\tif ('license' in result) {\n\t\t\turl.searchParams.set('license_id', result.license.id)\n\t\t\tconst sku = this.isFlagEnabled(result.license.flags, FLAGS.EVALUATION_LICENSE)\n\t\t\t\t? 'evaluation'\n\t\t\t\t: this.isFlagEnabled(result.license.flags, FLAGS.ANNUAL_LICENSE)\n\t\t\t\t\t? 'annual'\n\t\t\t\t\t: this.isFlagEnabled(result.license.flags, FLAGS.PERPETUAL_LICENSE)\n\t\t\t\t\t\t? 'perpetual'\n\t\t\t\t\t\t: 'unknown'\n\t\t\turl.searchParams.set('sku', sku)\n\t\t}\n\t\turl.searchParams.set('url', window.location.href)\n\t\tif (process.env.NODE_ENV) {\n\t\t\turl.searchParams.set('environment', process.env.NODE_ENV)\n\t\t}\n\n\t\t// best-effort: a blocked or offline request must not surface as an unhandled rejection\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tfetch(url.toString()).catch(noop)\n\t}\n\n\tprivate async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {\n\t\tconst [data, signature] = licenseKey.split('.')\n\t\tconst [prefix, encodedData] = data.split('/')\n\n\t\tif (!prefix.startsWith('tldraw-')) {\n\t\t\tthrow new Error(`Unsupported prefix '${prefix}'`)\n\t\t}\n\n\t\tconst publicCryptoKey = await importPublicKey(this.publicKey)\n\n\t\tlet isVerified\n\t\ttry {\n\t\t\tisVerified = await crypto.subtle.verify(\n\t\t\t\t{\n\t\t\t\t\tname: 'ECDSA',\n\t\t\t\t\thash: { name: 'SHA-256' },\n\t\t\t\t},\n\t\t\t\tpublicCryptoKey,\n\t\t\t\tnew Uint8Array(str2ab(atob(signature))),\n\t\t\t\tnew Uint8Array(str2ab(atob(encodedData)))\n\t\t\t)\n\t\t} catch (e) {\n\t\t\tconsole.error(e)\n\t\t\tthrow new Error('Could not perform signature validation')\n\t\t}\n\n\t\tif (!isVerified) {\n\t\t\tthrow new Error('Invalid signature')\n\t\t}\n\n\t\tlet decodedData: any\n\t\ttry {\n\t\t\tdecodedData = JSON.parse(atob(encodedData))\n\t\t} catch {\n\t\t\tthrow new Error('Could not parse object')\n\t\t}\n\t\tif (decodedData.length > NUMBER_OF_KNOWN_PROPERTIES) {\n\t\t\tthis.outputMessages([\n\t\t\t\t'License key contains some unknown properties.',\n\t\t\t\t'You may want to update tldraw packages to a newer version to get access to new functionality.',\n\t\t\t])\n\t\t}\n\n\t\treturn {\n\t\t\tid: decodedData[PROPERTIES.ID],\n\t\t\thosts: decodedData[PROPERTIES.HOSTS],\n\t\t\tflags: decodedData[PROPERTIES.FLAGS],\n\t\t\texpiryDate: decodedData[PROPERTIES.EXPIRY_DATE],\n\t\t}\n\t}\n\n\tasync getLicenseFromKey(licenseKey?: string): Promise<LicenseFromKeyResult> {\n\t\t// A key that is provided still wins, so an app on one of our hosts can opt into a different\n\t\t// license \u2014 a native or feature-specific key \u2014 just by passing it.\n\t\tif (!licenseKey && this.isCanonicalTldrawHost()) {\n\t\t\treturn this.getCanonicalHostLicense()\n\t\t}\n\n\t\tif (!licenseKey) {\n\t\t\tif (!this.isDevelopment) {\n\t\t\t\tthis.outputNoLicenseKeyProvided()\n\t\t\t}\n\n\t\t\treturn { isLicenseParseable: false, reason: 'no-key-provided' }\n\t\t}\n\n\t\tif (this.isDevelopment && !this.isCryptoAvailable) {\n\t\t\tif (this.verbose) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log(\n\t\t\t\t\t'tldraw: you seem to be in a development environment that does not support crypto. License not verified.'\n\t\t\t\t)\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log('You should check that this works in production separately.')\n\t\t\t}\n\t\t\t// We can't parse the license if we are in development mode since crypto\n\t\t\t// is not available on http\n\t\t\treturn { isLicenseParseable: false, reason: 'has-key-development-mode' }\n\t\t}\n\n\t\t// Borrowed idea from AG Grid:\n\t\t// Copying from various sources (like PDFs) can include zero-width characters.\n\t\t// This helps makes sure the key validation doesn't fail.\n\t\tlet cleanedLicenseKey = licenseKey.replace(/[\\u200B-\\u200D\\uFEFF]/g, '')\n\t\tcleanedLicenseKey = cleanedLicenseKey.replace(/\\r?\\n|\\r/g, '')\n\n\t\ttry {\n\t\t\tconst licenseInfo = await this.extractLicenseKey(cleanedLicenseKey)\n\t\t\tconst expiryDate = new Date(licenseInfo.expiryDate)\n\t\t\tconst isAnnualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.ANNUAL_LICENSE)\n\t\t\tconst isPerpetualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.PERPETUAL_LICENSE)\n\t\t\tconst isEvaluationLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.EVALUATION_LICENSE)\n\n\t\t\tconst isAnnualLicenseExpired = isAnnualLicense && this.isAnnualLicenseExpired(expiryDate)\n\t\t\tconst isPerpetualLicenseExpired =\n\t\t\t\tisPerpetualLicense && this.isPerpetualLicenseExpired(expiryDate)\n\n\t\t\t// The collaboration umbrella grants all of its sub-features, so commenting is enabled\n\t\t\t// by either the commenting flag or the collaboration flag.\n\t\t\tconst isCollaborationEnabled = this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COLLABORATION)\n\t\t\tconst isCommentingEnabled =\n\t\t\t\tisCollaborationEnabled || this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COMMENTING)\n\n\t\t\t// For perpetual licenses, the calendar expiry date only gates access to future\n\t\t\t// major/minor releases; it does not \"expire\" the license itself. While the user\n\t\t\t// is still on a covered version we report `daysSinceExpiry` as 0 so consumers\n\t\t\t// (and the grace-period warning) don't treat them as past expiry.\n\t\t\tconst daysSinceExpiry =\n\t\t\t\tisPerpetualLicense && !isPerpetualLicenseExpired ? 0 : this.getDaysSinceExpiry(expiryDate)\n\n\t\t\tconst result: ValidLicenseKeyResult = {\n\t\t\t\tlicense: licenseInfo,\n\t\t\t\tisLicenseParseable: true,\n\t\t\t\tisDevelopment: this.isDevelopment,\n\t\t\t\tisDomainValid: this.isDomainValid(licenseInfo),\n\t\t\t\texpiryDate,\n\t\t\t\tisAnnualLicense,\n\t\t\t\tisAnnualLicenseExpired,\n\t\t\t\tisPerpetualLicense,\n\t\t\t\tisPerpetualLicenseExpired,\n\t\t\t\tisInternalLicense: this.isFlagEnabled(licenseInfo.flags, FLAGS.INTERNAL_LICENSE),\n\t\t\t\tisNativeLicense: this.isNativeLicense(licenseInfo),\n\t\t\t\tisLicensedWithWatermark: this.isFlagEnabled(licenseInfo.flags, FLAGS.WITH_WATERMARK),\n\t\t\t\tisEvaluationLicense,\n\t\t\t\tisEvaluationLicenseExpired:\n\t\t\t\t\tisEvaluationLicense && this.isEvaluationLicenseExpired(expiryDate),\n\t\t\t\tisCollaborationEnabled,\n\t\t\t\tisCommentingEnabled,\n\t\t\t\tdaysSinceExpiry,\n\t\t\t}\n\t\t\tthis.outputLicenseInfoIfNeeded(result)\n\n\t\t\treturn result\n\t\t} catch (e: any) {\n\t\t\tthis.outputInvalidLicenseKey(e.message)\n\t\t\t// If the license can't be parsed, it's invalid\n\t\t\treturn { isLicenseParseable: false, reason: 'invalid-license-key' }\n\t\t}\n\t}\n\n\tprivate isDomainValid(licenseInfo: LicenseInfo) {\n\t\tconst currentHostname = this.getOriginLocation().hostname\n\n\t\treturn licenseInfo.hosts.some((host) => {\n\t\t\tconst normalizedHostOrUrlRegex = host.toLowerCase().trim()\n\n\t\t\t// Allow the domain if listed and www variations, 'example.com' allows 'example.com' and 'www.example.com'\n\t\t\tif (\n\t\t\t\tnormalizedHostOrUrlRegex === currentHostname ||\n\t\t\t\t`www.${normalizedHostOrUrlRegex}` === currentHostname ||\n\t\t\t\tnormalizedHostOrUrlRegex === `www.${currentHostname}`\n\t\t\t) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// If host is '*', we allow all domains.\n\t\t\tif (host === '*') {\n\t\t\t\t// All domains allowed.\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Native license support\n\t\t\t// In this case, `normalizedHost` is actually a protocol, e.g. `app-bundle:`\n\t\t\tif (this.isNativeLicense(licenseInfo)) {\n\t\t\t\treturn new RegExp(normalizedHostOrUrlRegex).test(window.location.href)\n\t\t\t}\n\n\t\t\t// Glob testing, we only support '*.somedomain.com' right now.\n\t\t\tif (host.includes('*')) {\n\t\t\t\tconst globToRegex = new RegExp(\n\t\t\t\t\tnormalizedHostOrUrlRegex.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*?') + '$'\n\t\t\t\t)\n\t\t\t\treturn globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)\n\t\t\t}\n\n\t\t\t// VSCode support\n\t\t\tif (window.location.protocol === 'vscode-webview:') {\n\t\t\t\tconst currentUrl = new URL(window.location.href)\n\t\t\t\tconst extensionId = currentUrl.searchParams.get('extensionId')\n\t\t\t\tif (normalizedHostOrUrlRegex === extensionId) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false\n\t\t})\n\t}\n\n\tprivate isNativeLicense(licenseInfo: LicenseInfo) {\n\t\treturn this.isFlagEnabled(licenseInfo.flags, FLAGS.NATIVE_LICENSE)\n\t}\n\n\tprivate getExpirationDateWithoutGracePeriod(expiryDate: Date) {\n\t\t// The named expiry date is the last day the license is fully usable, so the license\n\t\t// expires at the end of that day, i.e. the start of the following day. We work in UTC\n\t\t// (the expiry date is minted and parsed as a UTC date-only string) so the cutoff is a\n\t\t// single predictable instant for every user regardless of their local timezone.\n\t\treturn new Date(\n\t\t\tDate.UTC(\n\t\t\t\texpiryDate.getUTCFullYear(),\n\t\t\t\texpiryDate.getUTCMonth(),\n\t\t\t\texpiryDate.getUTCDate() + 1 // Add 1 day so the named date is fully usable\n\t\t\t)\n\t\t)\n\t}\n\n\tprivate getExpirationDateWithGracePeriod(expiryDate: Date) {\n\t\treturn new Date(\n\t\t\tDate.UTC(\n\t\t\t\texpiryDate.getUTCFullYear(),\n\t\t\t\texpiryDate.getUTCMonth(),\n\t\t\t\texpiryDate.getUTCDate() + GRACE_PERIOD_DAYS + 1 // Add 1 day to include the expiration day\n\t\t\t)\n\t\t)\n\t}\n\n\tprivate isAnnualLicenseExpired(expiryDate: Date) {\n\t\tconst expiration = this.getExpirationDateWithGracePeriod(expiryDate)\n\t\treturn new Date() >= expiration\n\t}\n\n\tprivate isPerpetualLicenseExpired(expiryDate: Date) {\n\t\tconst expiration = this.getExpirationDateWithGracePeriod(expiryDate)\n\t\tconst dates = {\n\t\t\tmajor: new Date(publishDates.major),\n\t\t\tminor: new Date(publishDates.minor),\n\t\t}\n\t\t// We allow patch releases, but the major and minor releases should be within the expiration date\n\t\treturn dates.major >= expiration || dates.minor >= expiration\n\t}\n\n\tprivate getDaysSinceExpiry(expiryDate: Date): number {\n\t\tconst now = new Date()\n\t\tconst expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)\n\t\tconst diffTime = now.getTime() - expiration.getTime()\n\t\tconst diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))\n\t\treturn Math.max(0, diffDays)\n\t}\n\n\tprivate isEvaluationLicenseExpired(expiryDate: Date): boolean {\n\t\t// Evaluation licenses have no grace period - they expire immediately\n\t\tconst now = new Date()\n\t\tconst expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)\n\t\treturn now >= expiration\n\t}\n\n\tprivate isFlagEnabled(flags: number, flag: number) {\n\t\treturn (flags & flag) === flag\n\t}\n\n\tprivate outputNoLicenseKeyProvided() {\n\t\t// Noop, we don't need to show this message.\n\t\t// this.outputMessages([\n\t\t// \t'No tldraw license key provided!',\n\t\t// \t`Please reach out to ${LICENSE_EMAIL} if you would like to license tldraw or if you'd like a trial.`,\n\t\t// ])\n\t}\n\n\tprivate outputInvalidLicenseKey(msg: string) {\n\t\tthis.outputMessages(['Invalid tldraw license key', `Reason: ${msg}`])\n\t}\n\n\tprivate outputLicenseInfoIfNeeded(result: ValidLicenseKeyResult) {\n\t\t// If we added a new flag it will be twice the value of the currently highest flag.\n\t\t// And if all the current flags are on we would get the `HIGHEST_FLAG * 2 - 1`, so anything higher than that means there are new flags.\n\t\tif (result.license.flags >= HIGHEST_FLAG * 2) {\n\t\t\tthis.outputMessages(\n\t\t\t\t[\n\t\t\t\t\t'Warning: This tldraw license contains some unknown flags.',\n\t\t\t\t\t'This will still work, however, you may want to update tldraw packages to a newer version to get access to new functionality.',\n\t\t\t\t],\n\t\t\t\t'warning'\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate outputMessages(messages: string[], type: 'warning' | 'error' = 'error') {\n\t\tif (this.isTest) return\n\t\tif (this.verbose) {\n\t\t\tthis.outputDelimiter(type)\n\t\t\tfor (const message of messages) {\n\t\t\t\tconst bgColor = type === 'warning' ? 'orange' : 'crimson'\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.log(\n\t\t\t\t\t`%c${message}`,\n\t\t\t\t\t`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`\n\t\t\t\t)\n\t\t\t}\n\t\t\tthis.outputDelimiter(type)\n\t\t}\n\t}\n\n\tprivate outputDelimiter(type: 'warning' | 'error' = 'error') {\n\t\tconst bgColor = type === 'warning' ? 'orange' : 'crimson'\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(\n\t\t\t'%c-------------------------------------------------------------------',\n\t\t\t`color: white; background: ${bgColor}; padding: 2px; border-radius: 3px;`\n\t\t)\n\t}\n\n\tstatic className = 'tl-watermark_SEE-LICENSE'\n}\n\n/**\n * Derives which licensable features are enabled from the parse result and the derived license\n * state. In development every feature is enabled so SDK developers can build against them; in\n * production a feature requires both an active, valid license and the corresponding flag.\n *\n * @internal\n */\nexport function getEnabledFeatures(\n\tresult: LicenseFromKeyResult,\n\tlicenseState: LicenseState,\n\tisDevelopment: boolean\n): Record<LicenseFeatureName, boolean> {\n\t// Development gets all features so SDK developers can build against them.\n\tif (isDevelopment) {\n\t\treturn { ...ALL_FEATURES }\n\t}\n\n\t// Features require an active, valid license. Both the 30-day grace-period 'licensed' state and\n\t// the watermark state count as valid; unlicensed/expired/pending do not.\n\tif (\n\t\t!result.isLicenseParseable ||\n\t\t(licenseState !== 'licensed' && licenseState !== 'licensed-with-watermark')\n\t) {\n\t\treturn { ...NO_FEATURES }\n\t}\n\n\t// Evaluation licenses get every feature so prospects can trial the whole product without us\n\t// having to mint per-feature evaluation keys. Expired evaluation licenses are already excluded\n\t// above: they resolve to the 'expired' state.\n\tif (result.isEvaluationLicense) {\n\t\treturn { ...ALL_FEATURES }\n\t}\n\n\treturn {\n\t\tcollaboration: result.isCollaborationEnabled,\n\t\tcommenting: result.isCommentingEnabled,\n\t}\n}\n\nexport function getLicenseState(\n\tresult: LicenseFromKeyResult,\n\toutputMessages: (messages: string[]) => void,\n\tisDevelopment: boolean\n): LicenseState {\n\tif (!result.isLicenseParseable) {\n\t\tif (isDevelopment) {\n\t\t\treturn 'unlicensed'\n\t\t}\n\n\t\t// All unlicensed scenarios should not work in production\n\t\tif (result.reason === 'no-key-provided') {\n\t\t\toutputMessages([\n\t\t\t\t'No tldraw license key provided!',\n\t\t\t\t'A license is required for production deployments.',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t\t])\n\t\t} else {\n\t\t\toutputMessages([\n\t\t\t\t'Invalid license key. tldraw requires a valid license for production use.',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t\t])\n\t\t}\n\t\treturn 'unlicensed-production'\n\t}\n\n\tif (!result.isDomainValid && !result.isDevelopment) {\n\t\toutputMessages([\n\t\t\t'License key is not valid for this domain.',\n\t\t\t'A license is required for production deployments.',\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,\n\t\t])\n\t\treturn 'unlicensed-production'\n\t}\n\n\t// Handle evaluation licenses - they expire immediately with no grace period\n\tif (result.isEvaluationLicense) {\n\t\tif (result.isEvaluationLicenseExpired) {\n\t\t\toutputMessages([\n\t\t\t\t'Your tldraw evaluation license has expired!',\n\t\t\t\t`Please reach out to ${LICENSE_EMAIL} to purchase a full license.`,\n\t\t\t])\n\t\t\treturn 'expired'\n\t\t} else {\n\t\t\t// Valid evaluation license - tracked but no watermark shown\n\t\t\treturn 'licensed'\n\t\t}\n\t}\n\n\t// Handle expired regular licenses (both annual and perpetual)\n\tif (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {\n\t\toutputMessages([\n\t\t\t'Your tldraw license has been expired for more than 30 days!',\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to renew your license.`,\n\t\t])\n\t\treturn 'expired'\n\t}\n\n\t// Check if license is past expiry date but within grace period\n\tconst daysSinceExpiry = result.daysSinceExpiry\n\tif (daysSinceExpiry > 0 && !result.isEvaluationLicense) {\n\t\toutputMessages([\n\t\t\t'Your tldraw license has expired.',\n\t\t\t`License expired ${daysSinceExpiry} days ago.`,\n\t\t\t`Please reach out to ${LICENSE_EMAIL} to renew your license.`,\n\t\t])\n\t\t// Within 30-day grace period: still licensed (no watermark)\n\t\treturn 'licensed'\n\t}\n\n\t// License is valid, determine if it has watermark\n\tif (result.isLicensedWithWatermark) {\n\t\treturn 'licensed-with-watermark'\n\t}\n\n\treturn 'licensed'\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "5.6.0-canary.e9e8ec003426";
25
+ const version = "5.6.0-canary.f790b44c29fc";
26
26
  const publishDates = {
27
27
  major: "2026-05-06T16:28:18.473Z",
28
- minor: "2026-10-01T10:49:00.293Z",
29
- patch: "2026-10-01T10:49:00.293Z"
28
+ minor: "2026-10-15T12:21:58.625Z",
29
+ patch: "2026-10-15T12:21:58.625Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.6.0-canary.e9e8ec003426'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-10-01T10:49:00.293Z',\n\tpatch: '2026-10-01T10:49:00.293Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.6.0-canary.f790b44c29fc'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-10-15T12:21:58.625Z',\n\tpatch: '2026-10-15T12:21:58.625Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -312,7 +312,7 @@ import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
312
312
  import { uniq } from "./lib/utils/uniq.mjs";
313
313
  registerTldrawLibraryVersion(
314
314
  "@tldraw/editor",
315
- "5.6.0-canary.e9e8ec003426",
315
+ "5.6.0-canary.f790b44c29fc",
316
316
  "esm"
317
317
  );
318
318
  import { getColorValue } from "./lib/editor/managers/ThemeManager/defaultThemes.mjs";
@@ -62,15 +62,15 @@ const Collaborator = track(function Collaborator2({
62
62
  });
63
63
  function CursorDef() {
64
64
  return /* @__PURE__ */ jsxs("g", { id: useSharedSafeId("cursor"), children: [
65
- /* @__PURE__ */ jsxs("g", { fill: "rgba(0,0,0,.2)", transform: "translate(-11,-11)", children: [
65
+ /* @__PURE__ */ jsxs("g", { fill: "rgba(0,0,0,.2)", transform: "translate(-11,-9)", children: [
66
66
  /* @__PURE__ */ jsx("path", { d: "m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" }),
67
67
  /* @__PURE__ */ jsx("path", { d: "m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" })
68
68
  ] }),
69
- /* @__PURE__ */ jsxs("g", { fill: "white", transform: "translate(-12,-12)", children: [
69
+ /* @__PURE__ */ jsxs("g", { fill: "white", transform: "translate(-12,-10)", children: [
70
70
  /* @__PURE__ */ jsx("path", { d: "m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" }),
71
71
  /* @__PURE__ */ jsx("path", { d: "m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" })
72
72
  ] }),
73
- /* @__PURE__ */ jsxs("g", { fill: "currentColor", transform: "translate(-12,-12)", children: [
73
+ /* @__PURE__ */ jsxs("g", { fill: "currentColor", transform: "translate(-12,-10)", children: [
74
74
  /* @__PURE__ */ jsx("path", { d: "m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z" }),
75
75
  /* @__PURE__ */ jsx("path", { d: "m13 10.814v11.188l2.969-2.866.428-.139h4.768z" })
76
76
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/components/LiveCollaborators.tsx"],
4
- "sourcesContent": ["import { track, useQuickReactor, useValue } from '@tldraw/state-react'\nimport { TLInstancePresence } from '@tldraw/tlschema'\nimport { useLayoutEffect, useRef } from 'react'\nimport { useEditorComponents } from '../hooks/EditorComponentsContext'\nimport { useEditor } from '../hooks/useEditor'\nimport { useSharedSafeId } from '../hooks/useSafeId'\nimport { isCursorInViewport } from '../utils/collaborators'\nimport { setStyleProperty } from '../utils/dom'\nimport { getHtmlLayerTransform } from '../utils/getHtmlLayerTransform'\n\n/**\n * The collaborator cursor layer: a DOM layer stacked as a sibling of the canvas \u2014 above all canvas\n * content, below the in-front layer and the UI panels \u2014 hosting each visible collaborator's cursor\n * (arrow, name tag, chat message). Off-viewport collaborators are the canvas-drawn hint arrows' job\n * (CollaboratorHintOverlayUtil), not this layer's.\n *\n * Cursors are DOM rather than canvas-drawn so their chrome styles and composes like the rest of\n * the UI. Re-render traffic is kept narrow: per-cursor positioning writes `transform` directly\n * (see `useTransform`), so a pointer move re-renders only the moved cursor; the camera transform\n * is written imperatively below, so a pure pan re-renders only cursors whose viewport visibility\n * flips (an equality-gated boolean per cursor); a zoom change re-renders every visible cursor,\n * because each one rescales by `1/zoom`.\n *\n * @public @react\n */\nexport const LiveCollaborators = track(function LiveCollaborators() {\n\tconst editor = useEditor()\n\tconst { CollaboratorCursor } = useEditorComponents()\n\n\t// The inner layer carries the camera transform, so the cursor components position in page\n\t// space exactly as canvas content does. Unlike the canvas's html layers this one unmounts\n\t// while no collaborators are visible, so the transform is written at two moments: on every\n\t// camera change (the reactor), and after every render (the layout effect, like useTransform\n\t// does per cursor) \u2014 the reactor's last run hit a null ref while the layer was unmounted,\n\t// and without the render-time write a reappearing layer would keep an identity transform\n\t// until the next camera move.\n\tconst rHtmlLayer = useRef<HTMLDivElement | null>(null)\n\tuseLayoutEffect(() => {\n\t\tconst elm = rHtmlLayer.current\n\t\tif (!elm) return\n\t\tsetStyleProperty(elm, 'transform', getHtmlLayerTransform(editor))\n\t})\n\tuseQuickReactor(\n\t\t'position collaborators layer',\n\t\tfunction positionCollaboratorsWhenCameraMoves() {\n\t\t\t// Reads the camera even while the layer is unmounted, keeping the subscription alive.\n\t\t\tconst transform = getHtmlLayerTransform(editor)\n\t\t\tsetStyleProperty(rHtmlLayer.current, 'transform', transform)\n\t\t},\n\t\t[editor]\n\t)\n\n\t// Visibility (activity state, following, highlighting) is handled by the editor.\n\tconst collaborators = editor.getVisibleCollaboratorsOnCurrentPage()\n\tif (collaborators.length === 0) return null\n\tif (!CollaboratorCursor) return null\n\n\treturn (\n\t\t<div className=\"tl-collaborators\">\n\t\t\t<svg className=\"tl-svg-context\" aria-hidden=\"true\">\n\t\t\t\t<defs>\n\t\t\t\t\t<CursorDef />\n\t\t\t\t</defs>\n\t\t\t</svg>\n\t\t\t<div ref={rHtmlLayer} className=\"tl-html-layer\">\n\t\t\t\t{collaborators.map((presence) => (\n\t\t\t\t\t<Collaborator key={presence.userId} latestPresence={presence} />\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n})\n\nconst Collaborator = track(function Collaborator({\n\tlatestPresence,\n}: {\n\tlatestPresence: TLInstancePresence\n}) {\n\tconst editor = useEditor()\n\tconst { CollaboratorCursor } = useEditorComponents()\n\n\tconst { userId, chatMessage, userName, cursor, color } = latestPresence\n\n\t// The viewport read lives inside this equality-gated computed rather than the tracked render:\n\t// pans move the viewport every frame, but this component only needs to know when the cursor\n\t// crosses the edge, so it subscribes to the boolean and re-renders only when it flips.\n\tconst cursorInViewport = useValue(\n\t\t'cursor in viewport',\n\t\t() =>\n\t\t\t!!cursor && isCursorInViewport(cursor, editor.getViewportPageBounds(), editor.getZoomLevel()),\n\t\t[editor, cursor]\n\t)\n\t// The zoom read is tracked directly: the cursor scales by 1/zoom, so zoom changes must\n\t// re-render it. Pure pans leave the zoom value unchanged, so they never invalidate this.\n\tconst zoomLevel = editor.getZoomLevel()\n\n\tif (!cursor) return null\n\n\t// Off-viewport collaborators show as the canvas-drawn hint arrows\n\t// (CollaboratorHintOverlayUtil), which shares this predicate \u2014 only the cursor itself is DOM.\n\tif (!cursorInViewport) return null\n\tif (!CollaboratorCursor) return null\n\treturn (\n\t\t<CollaboratorCursor\n\t\t\tclassName=\"tl-collaborator__cursor\"\n\t\t\tuserId={userId}\n\t\t\tpoint={cursor}\n\t\t\tcolor={color}\n\t\t\tzoom={zoomLevel}\n\t\t\tname={userName !== 'New User' ? userName : null}\n\t\t\tchatMessage={chatMessage ?? ''}\n\t\t/>\n\t)\n})\n\nfunction CursorDef() {\n\treturn (\n\t\t<g id={useSharedSafeId('cursor')}>\n\t\t\t<g fill=\"rgba(0,0,0,.2)\" transform=\"translate(-11,-11)\">\n\t\t\t\t<path d=\"m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z\" />\n\t\t\t\t<path d=\"m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z\" />\n\t\t\t</g>\n\t\t\t<g fill=\"white\" transform=\"translate(-12,-12)\">\n\t\t\t\t<path d=\"m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z\" />\n\t\t\t\t<path d=\"m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z\" />\n\t\t\t</g>\n\t\t\t<g fill=\"currentColor\" transform=\"translate(-12,-12)\">\n\t\t\t\t<path d=\"m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z\" />\n\t\t\t\t<path d=\"m13 10.814v11.188l2.969-2.866.428-.139h4.768z\" />\n\t\t\t</g>\n\t\t</g>\n\t)\n}\n"],
5
- "mappings": "AA0DE,SAGG,KAHH;AA1DF,SAAS,OAAO,iBAAiB,gBAAgB;AAEjD,SAAS,iBAAiB,cAAc;AACxC,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,6BAA6B;AAiB/B,MAAM,oBAAoB,MAAM,SAASA,qBAAoB;AACnE,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,mBAAmB,IAAI,oBAAoB;AASnD,QAAM,aAAa,OAA8B,IAAI;AACrD,kBAAgB,MAAM;AACrB,UAAM,MAAM,WAAW;AACvB,QAAI,CAAC,IAAK;AACV,qBAAiB,KAAK,aAAa,sBAAsB,MAAM,CAAC;AAAA,EACjE,CAAC;AACD;AAAA,IACC;AAAA,IACA,SAAS,uCAAuC;AAE/C,YAAM,YAAY,sBAAsB,MAAM;AAC9C,uBAAiB,WAAW,SAAS,aAAa,SAAS;AAAA,IAC5D;AAAA,IACA,CAAC,MAAM;AAAA,EACR;AAGA,QAAM,gBAAgB,OAAO,qCAAqC;AAClE,MAAI,cAAc,WAAW,EAAG,QAAO;AACvC,MAAI,CAAC,mBAAoB,QAAO;AAEhC,SACC,qBAAC,SAAI,WAAU,oBACd;AAAA,wBAAC,SAAI,WAAU,kBAAiB,eAAY,QAC3C,8BAAC,UACA,8BAAC,aAAU,GACZ,GACD;AAAA,IACA,oBAAC,SAAI,KAAK,YAAY,WAAU,iBAC9B,wBAAc,IAAI,CAAC,aACnB,oBAAC,gBAAmC,gBAAgB,YAAjC,SAAS,MAAkC,CAC9D,GACF;AAAA,KACD;AAEF,CAAC;AAED,MAAM,eAAe,MAAM,SAASC,cAAa;AAAA,EAChD;AACD,GAEG;AACF,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,mBAAmB,IAAI,oBAAoB;AAEnD,QAAM,EAAE,QAAQ,aAAa,UAAU,QAAQ,MAAM,IAAI;AAKzD,QAAM,mBAAmB;AAAA,IACxB;AAAA,IACA,MACC,CAAC,CAAC,UAAU,mBAAmB,QAAQ,OAAO,sBAAsB,GAAG,OAAO,aAAa,CAAC;AAAA,IAC7F,CAAC,QAAQ,MAAM;AAAA,EAChB;AAGA,QAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,OAAQ,QAAO;AAIpB,MAAI,CAAC,iBAAkB,QAAO;AAC9B,MAAI,CAAC,mBAAoB,QAAO;AAChC,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAU;AAAA,MACV;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,MAAM;AAAA,MACN,MAAM,aAAa,aAAa,WAAW;AAAA,MAC3C,aAAa,eAAe;AAAA;AAAA,EAC7B;AAEF,CAAC;AAED,SAAS,YAAY;AACpB,SACC,qBAAC,OAAE,IAAI,gBAAgB,QAAQ,GAC9B;AAAA,yBAAC,OAAE,MAAK,kBAAiB,WAAU,sBAClC;AAAA,0BAAC,UAAK,GAAE,uDAAsD;AAAA,MAC9D,oBAAC,UAAK,GAAE,0DAAyD;AAAA,OAClE;AAAA,IACA,qBAAC,OAAE,MAAK,SAAQ,WAAU,sBACzB;AAAA,0BAAC,UAAK,GAAE,uDAAsD;AAAA,MAC9D,oBAAC,UAAK,GAAE,0DAAyD;AAAA,OAClE;AAAA,IACA,qBAAC,OAAE,MAAK,gBAAe,WAAU,sBAChC;AAAA,0BAAC,UAAK,GAAE,mDAAkD;AAAA,MAC1D,oBAAC,UAAK,GAAE,iDAAgD;AAAA,OACzD;AAAA,KACD;AAEF;",
4
+ "sourcesContent": ["import { track, useQuickReactor, useValue } from '@tldraw/state-react'\nimport { TLInstancePresence } from '@tldraw/tlschema'\nimport { useLayoutEffect, useRef } from 'react'\nimport { useEditorComponents } from '../hooks/EditorComponentsContext'\nimport { useEditor } from '../hooks/useEditor'\nimport { useSharedSafeId } from '../hooks/useSafeId'\nimport { isCursorInViewport } from '../utils/collaborators'\nimport { setStyleProperty } from '../utils/dom'\nimport { getHtmlLayerTransform } from '../utils/getHtmlLayerTransform'\n\n/**\n * The collaborator cursor layer: a DOM layer stacked as a sibling of the canvas \u2014 above all canvas\n * content, below the in-front layer and the UI panels \u2014 hosting each visible collaborator's cursor\n * (arrow, name tag, chat message). Off-viewport collaborators are the canvas-drawn hint arrows' job\n * (CollaboratorHintOverlayUtil), not this layer's.\n *\n * Cursors are DOM rather than canvas-drawn so their chrome styles and composes like the rest of\n * the UI. Re-render traffic is kept narrow: per-cursor positioning writes `transform` directly\n * (see `useTransform`), so a pointer move re-renders only the moved cursor; the camera transform\n * is written imperatively below, so a pure pan re-renders only cursors whose viewport visibility\n * flips (an equality-gated boolean per cursor); a zoom change re-renders every visible cursor,\n * because each one rescales by `1/zoom`.\n *\n * @public @react\n */\nexport const LiveCollaborators = track(function LiveCollaborators() {\n\tconst editor = useEditor()\n\tconst { CollaboratorCursor } = useEditorComponents()\n\n\t// The inner layer carries the camera transform, so the cursor components position in page\n\t// space exactly as canvas content does. Unlike the canvas's html layers this one unmounts\n\t// while no collaborators are visible, so the transform is written at two moments: on every\n\t// camera change (the reactor), and after every render (the layout effect, like useTransform\n\t// does per cursor) \u2014 the reactor's last run hit a null ref while the layer was unmounted,\n\t// and without the render-time write a reappearing layer would keep an identity transform\n\t// until the next camera move.\n\tconst rHtmlLayer = useRef<HTMLDivElement | null>(null)\n\tuseLayoutEffect(() => {\n\t\tconst elm = rHtmlLayer.current\n\t\tif (!elm) return\n\t\tsetStyleProperty(elm, 'transform', getHtmlLayerTransform(editor))\n\t})\n\tuseQuickReactor(\n\t\t'position collaborators layer',\n\t\tfunction positionCollaboratorsWhenCameraMoves() {\n\t\t\t// Reads the camera even while the layer is unmounted, keeping the subscription alive.\n\t\t\tconst transform = getHtmlLayerTransform(editor)\n\t\t\tsetStyleProperty(rHtmlLayer.current, 'transform', transform)\n\t\t},\n\t\t[editor]\n\t)\n\n\t// Visibility (activity state, following, highlighting) is handled by the editor.\n\tconst collaborators = editor.getVisibleCollaboratorsOnCurrentPage()\n\tif (collaborators.length === 0) return null\n\tif (!CollaboratorCursor) return null\n\n\treturn (\n\t\t<div className=\"tl-collaborators\">\n\t\t\t<svg className=\"tl-svg-context\" aria-hidden=\"true\">\n\t\t\t\t<defs>\n\t\t\t\t\t<CursorDef />\n\t\t\t\t</defs>\n\t\t\t</svg>\n\t\t\t<div ref={rHtmlLayer} className=\"tl-html-layer\">\n\t\t\t\t{collaborators.map((presence) => (\n\t\t\t\t\t<Collaborator key={presence.userId} latestPresence={presence} />\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n})\n\nconst Collaborator = track(function Collaborator({\n\tlatestPresence,\n}: {\n\tlatestPresence: TLInstancePresence\n}) {\n\tconst editor = useEditor()\n\tconst { CollaboratorCursor } = useEditorComponents()\n\n\tconst { userId, chatMessage, userName, cursor, color } = latestPresence\n\n\t// The viewport read lives inside this equality-gated computed rather than the tracked render:\n\t// pans move the viewport every frame, but this component only needs to know when the cursor\n\t// crosses the edge, so it subscribes to the boolean and re-renders only when it flips.\n\tconst cursorInViewport = useValue(\n\t\t'cursor in viewport',\n\t\t() =>\n\t\t\t!!cursor && isCursorInViewport(cursor, editor.getViewportPageBounds(), editor.getZoomLevel()),\n\t\t[editor, cursor]\n\t)\n\t// The zoom read is tracked directly: the cursor scales by 1/zoom, so zoom changes must\n\t// re-render it. Pure pans leave the zoom value unchanged, so they never invalidate this.\n\tconst zoomLevel = editor.getZoomLevel()\n\n\tif (!cursor) return null\n\n\t// Off-viewport collaborators show as the canvas-drawn hint arrows\n\t// (CollaboratorHintOverlayUtil), which shares this predicate \u2014 only the cursor itself is DOM.\n\tif (!cursorInViewport) return null\n\tif (!CollaboratorCursor) return null\n\treturn (\n\t\t<CollaboratorCursor\n\t\t\tclassName=\"tl-collaborator__cursor\"\n\t\t\tuserId={userId}\n\t\t\tpoint={cursor}\n\t\t\tcolor={color}\n\t\t\tzoom={zoomLevel}\n\t\t\tname={userName !== 'New User' ? userName : null}\n\t\t\tchatMessage={chatMessage ?? ''}\n\t\t/>\n\t)\n})\n\nfunction CursorDef() {\n\treturn (\n\t\t<g id={useSharedSafeId('cursor')}>\n\t\t\t<g fill=\"rgba(0,0,0,.2)\" transform=\"translate(-11,-9)\">\n\t\t\t\t<path d=\"m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z\" />\n\t\t\t\t<path d=\"m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z\" />\n\t\t\t</g>\n\t\t\t<g fill=\"white\" transform=\"translate(-12,-10)\">\n\t\t\t\t<path d=\"m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z\" />\n\t\t\t\t<path d=\"m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z\" />\n\t\t\t</g>\n\t\t\t<g fill=\"currentColor\" transform=\"translate(-12,-10)\">\n\t\t\t\t<path d=\"m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z\" />\n\t\t\t\t<path d=\"m13 10.814v11.188l2.969-2.866.428-.139h4.768z\" />\n\t\t\t</g>\n\t\t</g>\n\t)\n}\n"],
5
+ "mappings": "AA0DE,SAGG,KAHH;AA1DF,SAAS,OAAO,iBAAiB,gBAAgB;AAEjD,SAAS,iBAAiB,cAAc;AACxC,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,6BAA6B;AAiB/B,MAAM,oBAAoB,MAAM,SAASA,qBAAoB;AACnE,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,mBAAmB,IAAI,oBAAoB;AASnD,QAAM,aAAa,OAA8B,IAAI;AACrD,kBAAgB,MAAM;AACrB,UAAM,MAAM,WAAW;AACvB,QAAI,CAAC,IAAK;AACV,qBAAiB,KAAK,aAAa,sBAAsB,MAAM,CAAC;AAAA,EACjE,CAAC;AACD;AAAA,IACC;AAAA,IACA,SAAS,uCAAuC;AAE/C,YAAM,YAAY,sBAAsB,MAAM;AAC9C,uBAAiB,WAAW,SAAS,aAAa,SAAS;AAAA,IAC5D;AAAA,IACA,CAAC,MAAM;AAAA,EACR;AAGA,QAAM,gBAAgB,OAAO,qCAAqC;AAClE,MAAI,cAAc,WAAW,EAAG,QAAO;AACvC,MAAI,CAAC,mBAAoB,QAAO;AAEhC,SACC,qBAAC,SAAI,WAAU,oBACd;AAAA,wBAAC,SAAI,WAAU,kBAAiB,eAAY,QAC3C,8BAAC,UACA,8BAAC,aAAU,GACZ,GACD;AAAA,IACA,oBAAC,SAAI,KAAK,YAAY,WAAU,iBAC9B,wBAAc,IAAI,CAAC,aACnB,oBAAC,gBAAmC,gBAAgB,YAAjC,SAAS,MAAkC,CAC9D,GACF;AAAA,KACD;AAEF,CAAC;AAED,MAAM,eAAe,MAAM,SAASC,cAAa;AAAA,EAChD;AACD,GAEG;AACF,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,mBAAmB,IAAI,oBAAoB;AAEnD,QAAM,EAAE,QAAQ,aAAa,UAAU,QAAQ,MAAM,IAAI;AAKzD,QAAM,mBAAmB;AAAA,IACxB;AAAA,IACA,MACC,CAAC,CAAC,UAAU,mBAAmB,QAAQ,OAAO,sBAAsB,GAAG,OAAO,aAAa,CAAC;AAAA,IAC7F,CAAC,QAAQ,MAAM;AAAA,EAChB;AAGA,QAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,CAAC,OAAQ,QAAO;AAIpB,MAAI,CAAC,iBAAkB,QAAO;AAC9B,MAAI,CAAC,mBAAoB,QAAO;AAChC,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAU;AAAA,MACV;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,MAAM;AAAA,MACN,MAAM,aAAa,aAAa,WAAW;AAAA,MAC3C,aAAa,eAAe;AAAA;AAAA,EAC7B;AAEF,CAAC;AAED,SAAS,YAAY;AACpB,SACC,qBAAC,OAAE,IAAI,gBAAgB,QAAQ,GAC9B;AAAA,yBAAC,OAAE,MAAK,kBAAiB,WAAU,qBAClC;AAAA,0BAAC,UAAK,GAAE,uDAAsD;AAAA,MAC9D,oBAAC,UAAK,GAAE,0DAAyD;AAAA,OAClE;AAAA,IACA,qBAAC,OAAE,MAAK,SAAQ,WAAU,sBACzB;AAAA,0BAAC,UAAK,GAAE,uDAAsD;AAAA,MAC9D,oBAAC,UAAK,GAAE,0DAAyD;AAAA,OAClE;AAAA,IACA,qBAAC,OAAE,MAAK,gBAAe,WAAU,sBAChC;AAAA,0BAAC,UAAK,GAAE,mDAAkD;AAAA,MAC1D,oBAAC,UAAK,GAAE,iDAAgD;AAAA,OACzD;AAAA,KACD;AAEF;",
6
6
  "names": ["LiveCollaborators", "Collaborator"]
7
7
  }
@@ -5970,9 +5970,10 @@ class Editor extends EventEmitter {
5970
5970
  }
5971
5971
  }
5972
5972
  let workingShape = null;
5973
+ const initialPagePoint = Mat.applyToPoint(pageTransform, new Vec());
5973
5974
  if (util.onResize && util.canResize(initialShape)) {
5974
5975
  const newPagePoint = this._scalePagePoint(
5975
- Mat.applyToPoint(pageTransform, new Vec(0, 0)),
5976
+ initialPagePoint,
5976
5977
  scaleOrigin,
5977
5978
  scale,
5978
5979
  scaleAxisRotation
@@ -5985,7 +5986,6 @@ class Editor extends EventEmitter {
5985
5986
  );
5986
5987
  myScale.x = areWidthAndHeightAlignedWithCorrectAxis ? scale.x : scale.y;
5987
5988
  myScale.y = areWidthAndHeightAlignedWithCorrectAxis ? scale.y : scale.x;
5988
- const initialPagePoint = Mat.applyToPoint(pageTransform, new Vec());
5989
5989
  const { x, y } = this.getPointInParentSpace(initialShape.id, initialPagePoint);
5990
5990
  workingShape = initialShape;
5991
5991
  if (!opts.skipStartAndEndCallbacks) {
@@ -6031,24 +6031,26 @@ class Editor extends EventEmitter {
6031
6031
  scale,
6032
6032
  scaleAxisRotation
6033
6033
  );
6034
- const initialPageCenterInParentSpace = this.getPointInParentSpace(
6034
+ const repositionedPagePoint = Vec.Add(
6035
+ newPageCenter,
6036
+ Vec.Sub(initialPagePoint, initialPageCenter)
6037
+ );
6038
+ const repositionedLocalPoint = this.getPointInParentSpace(
6035
6039
  initialShape.id,
6036
- initialPageCenter
6040
+ repositionedPagePoint
6037
6041
  );
6038
- const newPageCenterInParentSpace = this.getPointInParentSpace(initialShape.id, newPageCenter);
6039
- const delta = Vec.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace);
6040
6042
  if (workingShape) {
6041
6043
  return {
6042
6044
  ...workingShape,
6043
- x: initialShape.x + delta.x,
6044
- y: initialShape.y + delta.y
6045
+ x: repositionedLocalPoint.x,
6046
+ y: repositionedLocalPoint.y
6045
6047
  };
6046
6048
  }
6047
6049
  return {
6048
6050
  id,
6049
6051
  type: initialShape.type,
6050
- x: initialShape.x + delta.x,
6051
- y: initialShape.y + delta.y
6052
+ x: repositionedLocalPoint.x,
6053
+ y: repositionedLocalPoint.y
6052
6054
  };
6053
6055
  }
6054
6056
  /** @internal */