@tldraw/editor 5.3.0-internal.d205573d66cb → 5.3.0-next.299378752aaf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +0 -6
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +0 -3
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +7 -62
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js +0 -7
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +0 -6
- package/dist-esm/index.mjs +2 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +0 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +8 -63
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs +0 -7
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +0 -10
- package/package.json +7 -7
- package/src/index.ts +1 -3
- package/src/lib/editor/managers/TextManager/TextManager.ts +0 -3
- package/src/lib/license/LicenseManager.test.ts +29 -220
- package/src/lib/license/LicenseManager.ts +6 -107
- package/src/lib/license/useLicenseManagerState.ts +1 -17
- package/src/version.ts +3 -3
|
@@ -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 { 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\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\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\tconsole.error('License validation failed:', error)\n\t\t\t\tthis.state.set('unlicensed')\n\t\t\t})\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\t// If we are using https on a non-loopback domain we assume it's a production env and a development one otherwise\n\t\treturn (\n\t\t\t!['https:', 'vscode-webview:'].includes(window.location.protocol) ||\n\t\t\tthis.isLoopbackHost(window.location.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// eslint-disable-next-line no-restricted-globals\n\t\tfetch(url.toString())\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(host.replace(/\\*/g, '.*?'))\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\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": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { atom } from '@tldraw/state'\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}\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/** @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\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\tpublic verbose = true\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\tthis.getLicenseFromKey(licenseKey)\n\t\t\t.then((result) => {\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\tthis.state.set(licenseState)\n\t\t\t})\n\t\t\t.catch((error) => {\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\tprivate getIsDevelopment() {\n\t\t// If we are using https on a non-loopback domain we assume it's a production env and a development one otherwise\n\t\treturn (\n\t\t\t!['https:', 'vscode-webview:'].includes(window.location.protocol) ||\n\t\t\tthis.isLoopbackHost(window.location.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// eslint-disable-next-line no-restricted-globals\n\t\tfetch(url.toString())\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// 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\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(host.replace(/\\*/g, '.*?'))\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\treturn new Date(expiryDate.getFullYear(), expiryDate.getMonth(), expiryDate.getDate())\n\t}\n\n\tprivate getExpirationDateWithGracePeriod(expiryDate: Date) {\n\t\treturn new Date(\n\t\t\texpiryDate.getFullYear(),\n\t\t\texpiryDate.getMonth(),\n\t\t\texpiryDate.getDate() + GRACE_PERIOD_DAYS + 1 // Add 1 day to include the expiration day\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\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
|
}
|
|
@@ -2,14 +2,7 @@ import { useValue } from "@tldraw/state-react";
|
|
|
2
2
|
function useLicenseManagerState(licenseManager) {
|
|
3
3
|
return useValue("watermarkState", () => licenseManager.state.get(), [licenseManager]);
|
|
4
4
|
}
|
|
5
|
-
function useLicenseFeatureFlag(licenseManager, feature) {
|
|
6
|
-
return useValue("licenseFeature", () => licenseManager.isFeatureEnabled(feature), [
|
|
7
|
-
licenseManager,
|
|
8
|
-
feature
|
|
9
|
-
]);
|
|
10
|
-
}
|
|
11
5
|
export {
|
|
12
|
-
useLicenseFeatureFlag,
|
|
13
6
|
useLicenseManagerState
|
|
14
7
|
};
|
|
15
8
|
//# sourceMappingURL=useLicenseManagerState.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/license/useLicenseManagerState.ts"],
|
|
4
|
-
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AAIlB,SAAS,uBAAuB,gBAA8C;AACpF,SAAO,SAAS,kBAAkB,MAAM,eAAe,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;AACrF;
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { LicenseManager, LicenseState } from './LicenseManager'\n\n/** @internal */\nexport function useLicenseManagerState(licenseManager: LicenseManager): LicenseState {\n\treturn useValue('watermarkState', () => licenseManager.state.get(), [licenseManager])\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AAIlB,SAAS,uBAAuB,gBAA8C;AACpF,SAAO,SAAS,kBAAkB,MAAM,eAAe,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;AACrF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "5.3.0-
|
|
1
|
+
const version = "5.3.0-next.299378752aaf";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2026-05-06T16:28:18.473Z",
|
|
4
|
-
minor: "2026-07-
|
|
5
|
-
patch: "2026-07-
|
|
4
|
+
minor: "2026-07-13T13:51:04.172Z",
|
|
5
|
+
patch: "2026-07-13T13:51:04.172Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -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.3.0-
|
|
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.3.0-next.299378752aaf'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-13T13:51:04.172Z',\n\tpatch: '2026-07-13T13:51:04.172Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/editor.css
CHANGED
|
@@ -785,16 +785,6 @@ input,
|
|
|
785
785
|
.tl-rich-text h6 {
|
|
786
786
|
margin-top: 5px;
|
|
787
787
|
margin-bottom: 10px;
|
|
788
|
-
/**
|
|
789
|
-
* Headings render at a larger font-size than the body text (via the browser's
|
|
790
|
-
* default h1–h6 sizing), so they can't inherit the container's resolved
|
|
791
|
-
* whole-pixel line-height — that value is derived from the base font size and
|
|
792
|
-
* would cram the taller lines together. Instead, scale line-height with each
|
|
793
|
-
* heading's own font-size using tldraw's unitless line-height multiplier, which
|
|
794
|
-
* the rich-text render/measure paths expose here. The 1.35 fallback mirrors the
|
|
795
|
-
* default theme line-height (see defaultThemes.ts).
|
|
796
|
-
*/
|
|
797
|
-
line-height: var(--tl-rich-text-heading-line-height, 1.35);
|
|
798
788
|
}
|
|
799
789
|
|
|
800
790
|
.tl-rich-text a {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "5.3.0-
|
|
4
|
+
"version": "5.3.0-next.299378752aaf",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"@tiptap/core": "^3.12.1",
|
|
50
50
|
"@tiptap/pm": "^3.12.1",
|
|
51
51
|
"@tiptap/react": "^3.12.1",
|
|
52
|
-
"@tldraw/state": "5.3.0-
|
|
53
|
-
"@tldraw/state-react": "5.3.0-
|
|
54
|
-
"@tldraw/store": "5.3.0-
|
|
55
|
-
"@tldraw/tlschema": "5.3.0-
|
|
56
|
-
"@tldraw/utils": "5.3.0-
|
|
57
|
-
"@tldraw/validate": "5.3.0-
|
|
52
|
+
"@tldraw/state": "5.3.0-next.299378752aaf",
|
|
53
|
+
"@tldraw/state-react": "5.3.0-next.299378752aaf",
|
|
54
|
+
"@tldraw/store": "5.3.0-next.299378752aaf",
|
|
55
|
+
"@tldraw/tlschema": "5.3.0-next.299378752aaf",
|
|
56
|
+
"@tldraw/utils": "5.3.0-next.299378752aaf",
|
|
57
|
+
"@tldraw/validate": "5.3.0-next.299378752aaf",
|
|
58
58
|
"classnames": "^2.5.1",
|
|
59
59
|
"eventemitter3": "^4.0.7",
|
|
60
60
|
"idb": "^7.1.1",
|
package/src/index.ts
CHANGED
|
@@ -324,14 +324,12 @@ export {
|
|
|
324
324
|
LicenseManager,
|
|
325
325
|
type InvalidLicenseKeyResult,
|
|
326
326
|
type InvalidLicenseReason,
|
|
327
|
-
type LicenseFeatureName,
|
|
328
327
|
type LicenseFromKeyResult,
|
|
329
328
|
type LicenseInfo,
|
|
330
329
|
type LicenseState,
|
|
331
330
|
type ValidLicenseKeyResult,
|
|
332
331
|
} from './lib/license/LicenseManager'
|
|
333
|
-
export { LICENSE_TIMEOUT
|
|
334
|
-
export { useLicenseFeatureFlag } from './lib/license/useLicenseManagerState'
|
|
332
|
+
export { LICENSE_TIMEOUT } from './lib/license/LicenseProvider'
|
|
335
333
|
export {
|
|
336
334
|
defaultTldrawOptions,
|
|
337
335
|
type TLClipboardPasteRawInfo,
|
|
@@ -183,9 +183,6 @@ export class TextManager extends EditorManager {
|
|
|
183
183
|
'font-weight': opts.fontWeight,
|
|
184
184
|
'font-size': opts.fontSize + 'px',
|
|
185
185
|
'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,
|
|
186
|
-
// Unitless multiplier consumed by the .tl-rich-text h1–h6 rule (see editor.css),
|
|
187
|
-
// so heading measurement matches on-canvas rendering.
|
|
188
|
-
'--tl-rich-text-heading-line-height': `${opts.lineHeight}`,
|
|
189
186
|
padding: opts.padding,
|
|
190
187
|
'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,
|
|
191
188
|
'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,
|
|
@@ -4,7 +4,6 @@ import { publishDates } from '../../version'
|
|
|
4
4
|
import { str2ab } from '../utils/licensing'
|
|
5
5
|
import {
|
|
6
6
|
FLAGS,
|
|
7
|
-
getEnabledFeatures,
|
|
8
7
|
getLicenseState,
|
|
9
8
|
LicenseManager,
|
|
10
9
|
PROPERTIES,
|
|
@@ -509,116 +508,6 @@ describe('LicenseManager', () => {
|
|
|
509
508
|
expect(result.isEvaluationLicense).toBe(true)
|
|
510
509
|
expect(result.isEvaluationLicenseExpired).toBe(true)
|
|
511
510
|
})
|
|
512
|
-
|
|
513
|
-
it('Checks for the commenting feature flag', async () => {
|
|
514
|
-
const commentingLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
515
|
-
commentingLicenseInfo[PROPERTIES.FLAGS] |= FLAGS.FEAT_COMMENTING
|
|
516
|
-
const commentingLicenseKey = await generateLicenseKey(
|
|
517
|
-
JSON.stringify(commentingLicenseInfo),
|
|
518
|
-
keyPair
|
|
519
|
-
)
|
|
520
|
-
const result = (await licenseManager.getLicenseFromKey(
|
|
521
|
-
commentingLicenseKey
|
|
522
|
-
)) as ValidLicenseKeyResult
|
|
523
|
-
expect(result.isCommentingEnabled).toBe(true)
|
|
524
|
-
expect(result.isCollaborationEnabled).toBe(false)
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
it('Checks for the collaboration feature flag', async () => {
|
|
528
|
-
const collaborationLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
529
|
-
collaborationLicenseInfo[PROPERTIES.FLAGS] |= FLAGS.FEAT_COLLABORATION
|
|
530
|
-
const collaborationLicenseKey = await generateLicenseKey(
|
|
531
|
-
JSON.stringify(collaborationLicenseInfo),
|
|
532
|
-
keyPair
|
|
533
|
-
)
|
|
534
|
-
const result = (await licenseManager.getLicenseFromKey(
|
|
535
|
-
collaborationLicenseKey
|
|
536
|
-
)) as ValidLicenseKeyResult
|
|
537
|
-
expect(result.isCollaborationEnabled).toBe(true)
|
|
538
|
-
// The collaboration umbrella grants the commenting sub-feature.
|
|
539
|
-
expect(result.isCommentingEnabled).toBe(true)
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
it('Leaves feature flags off when no feature bits are set', async () => {
|
|
543
|
-
const standardLicenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
544
|
-
const result = (await licenseManager.getLicenseFromKey(
|
|
545
|
-
standardLicenseKey
|
|
546
|
-
)) as ValidLicenseKeyResult
|
|
547
|
-
expect(result.isCollaborationEnabled).toBe(false)
|
|
548
|
-
expect(result.isCommentingEnabled).toBe(false)
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
it('Enables features synchronously in development, before validation resolves', () => {
|
|
552
|
-
process.env.NODE_ENV = 'development'
|
|
553
|
-
// @ts-ignore
|
|
554
|
-
delete window.location
|
|
555
|
-
// @ts-ignore
|
|
556
|
-
window.location = new URL('https://www.example.com')
|
|
557
|
-
|
|
558
|
-
try {
|
|
559
|
-
// No await: the feature flags should already reflect development (all enabled) rather
|
|
560
|
-
// than the fail-closed default, which only lifts once async validation resolves.
|
|
561
|
-
const devLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
562
|
-
expect(devLicenseManager.isFeatureEnabled('commenting')).toBe(true)
|
|
563
|
-
expect(devLicenseManager.isFeatureEnabled('collaboration')).toBe(true)
|
|
564
|
-
} finally {
|
|
565
|
-
process.env.NODE_ENV = 'test'
|
|
566
|
-
}
|
|
567
|
-
})
|
|
568
|
-
})
|
|
569
|
-
|
|
570
|
-
describe('Expiry date timezone handling', () => {
|
|
571
|
-
afterEach(() => {
|
|
572
|
-
vi.useRealTimers()
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
// The named expiry date is minted as a date-only string and should mean "the license
|
|
576
|
-
// is fully usable through the end of that day in UTC", giving every user the same
|
|
577
|
-
// cutoff regardless of their local timezone.
|
|
578
|
-
async function makeEvaluationLicenseWithExpiry(expiry: string) {
|
|
579
|
-
const licenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
580
|
-
licenseInfo[PROPERTIES.FLAGS] = FLAGS.EVALUATION_LICENSE
|
|
581
|
-
licenseInfo[PROPERTIES.EXPIRY_DATE] = expiry
|
|
582
|
-
return generateLicenseKey(JSON.stringify(licenseInfo), keyPair)
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
it('Keeps the license valid through the end of the named expiry day (UTC)', async () => {
|
|
586
|
-
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
587
|
-
|
|
588
|
-
// The final moment of the named day is still valid.
|
|
589
|
-
vi.useFakeTimers()
|
|
590
|
-
vi.setSystemTime(new Date('2026-06-26T23:59:59.000Z'))
|
|
591
|
-
expect(
|
|
592
|
-
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
593
|
-
.isEvaluationLicenseExpired
|
|
594
|
-
).toBe(false)
|
|
595
|
-
})
|
|
596
|
-
|
|
597
|
-
it('Expires the license at the start of the day after the named expiry day (UTC)', async () => {
|
|
598
|
-
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
599
|
-
|
|
600
|
-
vi.useFakeTimers()
|
|
601
|
-
vi.setSystemTime(new Date('2026-06-27T00:00:01.000Z'))
|
|
602
|
-
expect(
|
|
603
|
-
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
604
|
-
.isEvaluationLicenseExpired
|
|
605
|
-
).toBe(true)
|
|
606
|
-
})
|
|
607
|
-
|
|
608
|
-
it('Does not expire early for users west of UTC who are still on the named day locally', async () => {
|
|
609
|
-
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
610
|
-
|
|
611
|
-
// Under the old logic a user west of UTC parsed the date one calendar day early and
|
|
612
|
-
// expired at the *start* of that day, losing most of a day. With the uniform UTC
|
|
613
|
-
// cutoff the license stays valid all through June 26 for everyone, no matter their
|
|
614
|
-
// local offset. 1pm UTC on June 26 is well before the cutoff, so it is still valid.
|
|
615
|
-
vi.useFakeTimers()
|
|
616
|
-
vi.setSystemTime(new Date('2026-06-26T13:00:00.000Z'))
|
|
617
|
-
expect(
|
|
618
|
-
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
619
|
-
.isEvaluationLicenseExpired
|
|
620
|
-
).toBe(false)
|
|
621
|
-
})
|
|
622
511
|
})
|
|
623
512
|
|
|
624
513
|
describe('License expiry and grace period', () => {
|
|
@@ -651,54 +540,38 @@ describe('LicenseManager', () => {
|
|
|
651
540
|
})
|
|
652
541
|
|
|
653
542
|
it('Handles grace period correctly - 20 days expired should still be within grace period', async () => {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
expiredLicenseKey
|
|
671
|
-
)) as ValidLicenseKeyResult
|
|
672
|
-
expect(result.isAnnualLicense).toBe(true)
|
|
673
|
-
expect(result.isAnnualLicenseExpired).toBe(false) // Within 30-day grace period
|
|
674
|
-
expect(result.daysSinceExpiry).toBe(19)
|
|
675
|
-
} finally {
|
|
676
|
-
vi.useRealTimers()
|
|
677
|
-
}
|
|
543
|
+
const expiredLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
544
|
+
const expiredDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 20) // 20 days ago
|
|
545
|
+
expiredLicenseInfo[PROPERTIES.EXPIRY_DATE] = expiredDate.toISOString()
|
|
546
|
+
|
|
547
|
+
const expiredLicenseKey = await generateLicenseKey(
|
|
548
|
+
JSON.stringify(expiredLicenseInfo),
|
|
549
|
+
keyPair
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
// Test the getLicenseFromKey method to verify grace period calculation
|
|
553
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
554
|
+
expiredLicenseKey
|
|
555
|
+
)) as ValidLicenseKeyResult
|
|
556
|
+
expect(result.isAnnualLicense).toBe(true)
|
|
557
|
+
expect(result.isAnnualLicenseExpired).toBe(false) // Within 30-day grace period
|
|
558
|
+
expect(result.daysSinceExpiry).toBe(20)
|
|
678
559
|
})
|
|
679
560
|
|
|
680
561
|
it('Calculates days since expiry correctly', async () => {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const result = (await licenseManager.getLicenseFromKey(
|
|
696
|
-
expiredLicenseKey
|
|
697
|
-
)) as ValidLicenseKeyResult
|
|
698
|
-
expect(result.daysSinceExpiry).toBe(14)
|
|
699
|
-
} finally {
|
|
700
|
-
vi.useRealTimers()
|
|
701
|
-
}
|
|
562
|
+
const expiredLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
563
|
+
const expiredDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 15) // 15 days ago
|
|
564
|
+
expiredLicenseInfo[PROPERTIES.EXPIRY_DATE] = expiredDate.toISOString()
|
|
565
|
+
|
|
566
|
+
const expiredLicenseKey = await generateLicenseKey(
|
|
567
|
+
JSON.stringify(expiredLicenseInfo),
|
|
568
|
+
keyPair
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
572
|
+
expiredLicenseKey
|
|
573
|
+
)) as ValidLicenseKeyResult
|
|
574
|
+
expect(result.daysSinceExpiry).toBe(15)
|
|
702
575
|
})
|
|
703
576
|
})
|
|
704
577
|
|
|
@@ -850,8 +723,6 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
|
|
|
850
723
|
isLicensedWithWatermark: false,
|
|
851
724
|
isEvaluationLicense: false,
|
|
852
725
|
isEvaluationLicenseExpired: false,
|
|
853
|
-
isCollaborationEnabled: false,
|
|
854
|
-
isCommentingEnabled: false,
|
|
855
726
|
daysSinceExpiry: 0,
|
|
856
727
|
// WatermarkManager does not check these fields, it relies on the calculated values like isAnnualLicenseExpired
|
|
857
728
|
license: {
|
|
@@ -1153,65 +1024,3 @@ describe('getLicenseState', () => {
|
|
|
1153
1024
|
})
|
|
1154
1025
|
})
|
|
1155
1026
|
})
|
|
1156
|
-
|
|
1157
|
-
describe('getEnabledFeatures', () => {
|
|
1158
|
-
it('enables every feature in development regardless of flags', () => {
|
|
1159
|
-
const result = getDefaultLicenseResult({
|
|
1160
|
-
isCollaborationEnabled: false,
|
|
1161
|
-
isCommentingEnabled: false,
|
|
1162
|
-
})
|
|
1163
|
-
expect(getEnabledFeatures(result, 'unlicensed', true)).toEqual({
|
|
1164
|
-
collaboration: true,
|
|
1165
|
-
commenting: true,
|
|
1166
|
-
})
|
|
1167
|
-
})
|
|
1168
|
-
|
|
1169
|
-
it('reflects the license flags for a valid licensed state', () => {
|
|
1170
|
-
const result = getDefaultLicenseResult({
|
|
1171
|
-
isCollaborationEnabled: false,
|
|
1172
|
-
isCommentingEnabled: true,
|
|
1173
|
-
})
|
|
1174
|
-
expect(getEnabledFeatures(result, 'licensed', false)).toEqual({
|
|
1175
|
-
collaboration: false,
|
|
1176
|
-
commenting: true,
|
|
1177
|
-
})
|
|
1178
|
-
})
|
|
1179
|
-
|
|
1180
|
-
it('still grants features while showing a watermark', () => {
|
|
1181
|
-
const result = getDefaultLicenseResult({
|
|
1182
|
-
isCollaborationEnabled: true,
|
|
1183
|
-
isCommentingEnabled: true,
|
|
1184
|
-
})
|
|
1185
|
-
expect(getEnabledFeatures(result, 'licensed-with-watermark', false)).toEqual({
|
|
1186
|
-
collaboration: true,
|
|
1187
|
-
commenting: true,
|
|
1188
|
-
})
|
|
1189
|
-
})
|
|
1190
|
-
|
|
1191
|
-
it.each(['unlicensed', 'unlicensed-production', 'expired', 'pending'] as const)(
|
|
1192
|
-
'disables all features for the %s state in production',
|
|
1193
|
-
(state) => {
|
|
1194
|
-
const result = getDefaultLicenseResult({
|
|
1195
|
-
isCollaborationEnabled: true,
|
|
1196
|
-
isCommentingEnabled: true,
|
|
1197
|
-
})
|
|
1198
|
-
expect(getEnabledFeatures(result, state, false)).toEqual({
|
|
1199
|
-
collaboration: false,
|
|
1200
|
-
commenting: false,
|
|
1201
|
-
})
|
|
1202
|
-
}
|
|
1203
|
-
)
|
|
1204
|
-
|
|
1205
|
-
it('disables all features when the license is not parseable', () => {
|
|
1206
|
-
expect(
|
|
1207
|
-
getEnabledFeatures(
|
|
1208
|
-
{ isLicenseParseable: false, reason: 'no-key-provided' },
|
|
1209
|
-
'licensed',
|
|
1210
|
-
false
|
|
1211
|
-
)
|
|
1212
|
-
).toEqual({
|
|
1213
|
-
collaboration: false,
|
|
1214
|
-
commenting: false,
|
|
1215
|
-
})
|
|
1216
|
-
})
|
|
1217
|
-
})
|