diff --git a/apps/examples/src/misc/develop.tsx b/apps/examples/src/misc/develop.tsx index db14ab8cd..dd1c01ee1 100644 --- a/apps/examples/src/misc/develop.tsx +++ b/apps/examples/src/misc/develop.tsx @@ -6,6 +6,7 @@ export default function Develop() {
{ ;(window as any).app = editor ;(window as any).editor = editor diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index a462cd59a..9bff17307 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -570,7 +570,7 @@ export class Edge2d extends Geometry2d { // @public (undocumented) export class Editor extends EventEmitter { - constructor({ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, }: TLEditorOptions); + constructor({ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, licenseKey, }: TLEditorOptions); addOpenMenu(id: string): this; alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this; animateShape(partial: null | TLShapePartial | undefined, animationOptions?: TLAnimationOptions): this; @@ -2032,6 +2032,7 @@ export interface TldrawEditorBaseProps { components?: TLEditorComponents; inferDarkMode?: boolean; initialState?: string; + licenseKey?: string; onMount?: TLOnMountHandler; shapeUtils?: readonly TLAnyShapeUtilConstructor[]; tools?: readonly TLStateNodeConstructor[]; diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 42b8d5496..8d5be0e7a 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -7447,7 +7447,7 @@ "excerptTokens": [ { "kind": "Content", - "text": "constructor({ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, }: " + "text": "constructor({ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, licenseKey, }: " }, { "kind": "Reference", @@ -7464,7 +7464,7 @@ "overloadIndex": 1, "parameters": [ { - "parameterName": "{ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, }", + "parameterName": "{ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, licenseKey, }", "parameterTypeTokenRange": { "startIndex": 1, "endIndex": 2 @@ -36588,6 +36588,33 @@ "endIndex": 2 } }, + { + "kind": "PropertySignature", + "canonicalReference": "@tldraw/editor!TldrawEditorBaseProps#licenseKey:member", + "docComment": "/**\n * License key for the editor.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "licenseKey?: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "licenseKey", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "PropertySignature", "canonicalReference": "@tldraw/editor!TldrawEditorBaseProps#onMount:member", diff --git a/packages/editor/src/lib/TldrawEditor.tsx b/packages/editor/src/lib/TldrawEditor.tsx index 976415fa5..6753312c3 100644 --- a/packages/editor/src/lib/TldrawEditor.tsx +++ b/packages/editor/src/lib/TldrawEditor.tsx @@ -113,6 +113,11 @@ export interface TldrawEditorBaseProps { * Whether to infer dark mode from the user's OS. Defaults to false. */ inferDarkMode?: boolean + + /** + * License key for the editor. + */ + licenseKey?: string } /** @@ -263,6 +268,7 @@ function TldrawEditorWithReadyStore({ shapeUtils, user, initialState, + licenseKey, autoFocus = true, inferDarkMode, }: Required< @@ -285,6 +291,7 @@ function TldrawEditorWithReadyStore({ user, initialState, inferDarkMode, + licenseKey, }) setEditor(editor) diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index cb624c6ee..75b5ce8fd 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -200,6 +200,7 @@ export class Editor extends EventEmitter { getContainer, initialState, inferDarkMode, + licenseKey, }: TLEditorOptions) { super() @@ -211,7 +212,7 @@ export class Editor extends EventEmitter { this.getContainer = getContainer ?? (() => document.body) - this.licenseManager = new LicenseManager(this) + this.licenseManager = new LicenseManager(this, licenseKey) this.textMeasure = new TextManager(this) this._tickManager = new TickManager(this) diff --git a/packages/editor/src/lib/editor/managers/LicenseManager.ts b/packages/editor/src/lib/editor/managers/LicenseManager.ts index a7f08153e..ce5d74c46 100644 --- a/packages/editor/src/lib/editor/managers/LicenseManager.ts +++ b/packages/editor/src/lib/editor/managers/LicenseManager.ts @@ -20,13 +20,44 @@ export class LicenseManager { ) { const key = licenseKey ?? process?.env?.TLDRAW_LICENSE_KEY if (key) { - const isValid = this.validateLicenseKey(key) - if (!isValid) throw Error('Invalid license key') - const dataFromKey = this.getDataFromKey(key) - const now = Date.now() - if (now > dataFromKey.expiration) throw Error('License key expired') - return + try { + // Get the license data from the license key + // todo: replace with actual get data from key + const dataFromKey = { + customer: 'Example', + sku: 'commercial', + origins: ['http://localhost:3000'], + expiration: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).getTime(), + } + + // Check that the license is valid + // todo: replace with actual validation + const isValid = true + if (!isValid) throw Error('Invalid license key') + + // Check expiration + const now = Date.now() + if (now > dataFromKey.expiration) throw Error('License key expired') + + if (dataFromKey.sku === 'grandfathered') { + // We don't check origins for grandfathered licenses + } else { + // Check that the current origin is allowed by the license + if (typeof window !== 'undefined' && window.location.origin) { + const allowedOrigins = dataFromKey.origins + const origin = window.location.origin + if (!allowedOrigins.includes(origin)) { + throw Error('License key is not valid for this origin') + } + } + } + } catch (e: any) { + console.error( + `Could not validate the license key. Reason: ${e.message}. See more at tldraw.dev/license` + ) + } } + // No license key provided, time to show the watermark const link = document.createElement('a') const id = this.getLinkId() @@ -95,19 +126,6 @@ export class LicenseManager { isDisposed = false - validateLicenseKey(key: string) { - return key === 'valid' - } - - getDataFromKey(key: string) { - return { - customer: 'Example', - sku: 'commercial', - origins: ['http://localhost:3000'], - expiration: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).getTime(), - } - } - getLinkId() { return `removing-invalidates-license-${getHashForObject({ version })}` }