@tldraw/editor 3.16.0-canary.bf9fb05eb8ef → 3.16.0-canary.c7d3f7d5729d

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 (35) hide show
  1. package/dist-cjs/index.d.ts +21 -0
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +11 -10
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  7. package/dist-cjs/lib/license/LicenseManager.js +17 -22
  8. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  9. package/dist-cjs/lib/license/LicenseProvider.js +5 -0
  10. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  11. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  12. package/dist-cjs/version.js +3 -3
  13. package/dist-cjs/version.js.map +1 -1
  14. package/dist-esm/index.d.mts +21 -0
  15. package/dist-esm/index.mjs +1 -1
  16. package/dist-esm/index.mjs.map +2 -2
  17. package/dist-esm/lib/editor/Editor.mjs +11 -10
  18. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  19. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  20. package/dist-esm/lib/license/LicenseManager.mjs +17 -22
  21. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  22. package/dist-esm/lib/license/LicenseProvider.mjs +5 -0
  23. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  24. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  25. package/dist-esm/version.mjs +3 -3
  26. package/dist-esm/version.mjs.map +1 -1
  27. package/package.json +7 -7
  28. package/src/index.ts +1 -0
  29. package/src/lib/editor/Editor.ts +19 -21
  30. package/src/lib/editor/shapes/ShapeUtil.ts +21 -0
  31. package/src/lib/license/LicenseManager.test.ts +58 -51
  32. package/src/lib/license/LicenseManager.ts +32 -24
  33. package/src/lib/license/LicenseProvider.tsx +8 -0
  34. package/src/lib/license/useLicenseManagerState.ts +2 -2
  35. package/src/version.ts +3 -3
@@ -4,7 +4,7 @@ import { publishDates } from '../../version'
4
4
  import { str2ab } from '../utils/licensing'
5
5
  import {
6
6
  FLAGS,
7
- isEditorUnlicensed,
7
+ getLicenseState,
8
8
  LicenseManager,
9
9
  PROPERTIES,
10
10
  ValidLicenseKeyResult,
@@ -487,115 +487,122 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
487
487
  }
488
488
  }
489
489
 
490
- describe(isEditorUnlicensed, () => {
491
- it('shows watermark when license is not parseable', () => {
490
+ describe('getLicenseState', () => {
491
+ it('returns "unlicensed" for unparseable license', () => {
492
492
  const licenseResult = getDefaultLicenseResult({
493
493
  // @ts-ignore
494
494
  isLicenseParseable: false,
495
495
  })
496
- expect(isEditorUnlicensed(licenseResult)).toBe(true)
496
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
497
497
  })
498
498
 
499
- it('shows watermark when domain is not valid', () => {
499
+ it('returns "unlicensed" for invalid domain in production', () => {
500
500
  const licenseResult = getDefaultLicenseResult({
501
501
  isDomainValid: false,
502
+ isDevelopment: false,
502
503
  })
503
- expect(isEditorUnlicensed(licenseResult)).toBe(true)
504
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
504
505
  })
505
506
 
506
- it('shows watermark when annual license has expired', () => {
507
+ it('returns "licensed" for invalid domain in development mode', () => {
508
+ const licenseResult = getDefaultLicenseResult({
509
+ isDomainValid: false,
510
+ isDevelopment: true,
511
+ })
512
+ expect(getLicenseState(licenseResult)).toBe('licensed')
513
+ })
514
+
515
+ it('returns "unlicensed" for expired annual license', () => {
507
516
  const licenseResult = getDefaultLicenseResult({
508
517
  isAnnualLicense: true,
509
518
  isAnnualLicenseExpired: true,
519
+ isInternalLicense: false,
510
520
  })
511
- expect(isEditorUnlicensed(licenseResult)).toBe(true)
521
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
512
522
  })
513
523
 
514
- it('shows watermark when annual license has expired, even if dev mode', () => {
524
+ it('returns "unlicensed" for expired annual license even in dev mode', () => {
515
525
  const licenseResult = getDefaultLicenseResult({
516
526
  isAnnualLicense: true,
517
527
  isAnnualLicenseExpired: true,
518
528
  isDevelopment: true,
529
+ isInternalLicense: false,
519
530
  })
520
- expect(isEditorUnlicensed(licenseResult)).toBe(true)
531
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
521
532
  })
522
533
 
523
- it('shows watermark when perpetual license has expired', () => {
534
+ it('returns "unlicensed" for expired perpetual license', () => {
524
535
  const licenseResult = getDefaultLicenseResult({
525
536
  isPerpetualLicense: true,
526
537
  isPerpetualLicenseExpired: true,
538
+ isInternalLicense: false,
527
539
  })
528
- expect(isEditorUnlicensed(licenseResult)).toBe(true)
540
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
529
541
  })
530
542
 
531
- it('does not show watermark when license is valid and not expired', () => {
543
+ it('returns "internal-expired" for expired internal annual license with valid domain', () => {
544
+ const expiryDate = new Date(2023, 1, 1)
532
545
  const licenseResult = getDefaultLicenseResult({
533
546
  isAnnualLicense: true,
534
- isAnnualLicenseExpired: false,
535
- isInternalLicense: false,
547
+ isAnnualLicenseExpired: true,
548
+ isInternalLicense: true,
549
+ isDomainValid: true,
550
+ expiryDate,
536
551
  })
537
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
552
+ expect(getLicenseState(licenseResult)).toBe('internal-expired')
538
553
  })
539
554
 
540
- it('does not show watermark when perpetual license is valid and not expired', () => {
555
+ it('returns "internal-expired" for expired internal perpetual license with valid domain', () => {
556
+ const expiryDate = new Date(2023, 1, 1)
541
557
  const licenseResult = getDefaultLicenseResult({
542
558
  isPerpetualLicense: true,
543
- isPerpetualLicenseExpired: false,
544
- isInternalLicense: false,
545
- })
546
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
547
- })
548
-
549
- it('does not show watermark when in development mode', () => {
550
- const licenseResult = getDefaultLicenseResult({
551
- isDevelopment: true,
559
+ isPerpetualLicenseExpired: true,
560
+ isInternalLicense: true,
561
+ isDomainValid: true,
562
+ expiryDate,
552
563
  })
553
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
564
+ expect(getLicenseState(licenseResult)).toBe('internal-expired')
554
565
  })
555
566
 
556
- it('does not show watermark when license is parseable and domain is valid', () => {
567
+ it('returns "unlicensed" for expired internal license with invalid domain', () => {
568
+ const expiryDate = new Date(2023, 1, 1)
557
569
  const licenseResult = getDefaultLicenseResult({
558
- isLicenseParseable: true,
559
- isDomainValid: true,
570
+ isAnnualLicense: true,
571
+ isAnnualLicenseExpired: true,
572
+ isInternalLicense: true,
573
+ isDomainValid: false,
574
+ expiryDate,
560
575
  })
561
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
576
+ expect(getLicenseState(licenseResult)).toBe('unlicensed')
562
577
  })
563
578
 
564
- it('does not show watermark when license is parseable and domain is not valid and dev mode', () => {
579
+ it('returns "licensed-with-watermark" for watermarked license', () => {
565
580
  const licenseResult = getDefaultLicenseResult({
566
- isLicenseParseable: true,
567
- isDomainValid: false,
568
- isDevelopment: true,
581
+ isLicensedWithWatermark: true,
569
582
  })
570
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
583
+ expect(getLicenseState(licenseResult)).toBe('licensed-with-watermark')
571
584
  })
572
585
 
573
- it('throws when an internal annual license has expired', () => {
574
- const expiryDate = new Date(2023, 1, 1)
586
+ it('returns "licensed" for valid annual license', () => {
575
587
  const licenseResult = getDefaultLicenseResult({
576
588
  isAnnualLicense: true,
577
- isAnnualLicenseExpired: true,
578
- isInternalLicense: true,
579
- expiryDate,
589
+ isAnnualLicenseExpired: false,
580
590
  })
581
- expect(() => isEditorUnlicensed(licenseResult)).toThrow(/License: Internal license expired/)
591
+ expect(getLicenseState(licenseResult)).toBe('licensed')
582
592
  })
583
593
 
584
- it('throws when an internal perpetual license has expired', () => {
585
- const expiryDate = new Date(2023, 1, 1)
594
+ it('returns "licensed" for valid perpetual license', () => {
586
595
  const licenseResult = getDefaultLicenseResult({
587
596
  isPerpetualLicense: true,
588
- isPerpetualLicenseExpired: true,
589
- isInternalLicense: true,
590
- expiryDate,
597
+ isPerpetualLicenseExpired: false,
591
598
  })
592
- expect(() => isEditorUnlicensed(licenseResult)).toThrow(/License: Internal license expired/)
599
+ expect(getLicenseState(licenseResult)).toBe('licensed')
593
600
  })
594
601
 
595
- it('shows watermark when license has that flag specified', () => {
602
+ it('returns "licensed" for valid license in development mode', () => {
596
603
  const licenseResult = getDefaultLicenseResult({
597
- isLicensedWithWatermark: true,
604
+ isDevelopment: true,
598
605
  })
599
- expect(isEditorUnlicensed(licenseResult)).toBe(false)
606
+ expect(getLicenseState(licenseResult)).toBe('licensed')
600
607
  })
601
608
  })
@@ -33,6 +33,14 @@ export interface LicenseInfo {
33
33
  flags: number
34
34
  expiryDate: string
35
35
  }
36
+
37
+ /** @internal */
38
+ export type LicenseState =
39
+ | 'pending'
40
+ | 'licensed'
41
+ | 'licensed-with-watermark'
42
+ | 'unlicensed'
43
+ | 'internal-expired'
36
44
  /** @internal */
37
45
  export type InvalidLicenseReason =
38
46
  | 'invalid-license-key'
@@ -73,10 +81,7 @@ export class LicenseManager {
73
81
  public isDevelopment: boolean
74
82
  public isTest: boolean
75
83
  public isCryptoAvailable: boolean
76
- state = atom<'pending' | 'licensed' | 'licensed-with-watermark' | 'unlicensed'>(
77
- 'license state',
78
- 'pending'
79
- )
84
+ state = atom<LicenseState>('license state', 'pending')
80
85
  public verbose = true
81
86
 
82
87
  constructor(
@@ -89,21 +94,20 @@ export class LicenseManager {
89
94
  this.publicKey = testPublicKey || this.publicKey
90
95
  this.isCryptoAvailable = !!crypto.subtle
91
96
 
92
- this.getLicenseFromKey(licenseKey).then((result) => {
93
- const isUnlicensed = isEditorUnlicensed(result)
97
+ this.getLicenseFromKey(licenseKey)
98
+ .then((result) => {
99
+ const licenseState = getLicenseState(result)
94
100
 
95
- if (!this.isDevelopment && isUnlicensed) {
96
- fetch(WATERMARK_TRACK_SRC)
97
- }
101
+ if (!this.isDevelopment && licenseState === 'unlicensed') {
102
+ fetch(WATERMARK_TRACK_SRC)
103
+ }
98
104
 
99
- if (isUnlicensed) {
105
+ this.state.set(licenseState)
106
+ })
107
+ .catch((error) => {
108
+ console.error('License validation failed:', error)
100
109
  this.state.set('unlicensed')
101
- } else if ((result as ValidLicenseKeyResult).isLicensedWithWatermark) {
102
- this.state.set('licensed-with-watermark')
103
- } else {
104
- this.state.set('licensed')
105
- }
106
- })
110
+ })
107
111
  }
108
112
 
109
113
  private getIsDevelopment(testEnvironment?: TestEnvironment) {
@@ -367,15 +371,19 @@ export class LicenseManager {
367
371
  static className = 'tl-watermark_SEE-LICENSE'
368
372
  }
369
373
 
370
- export function isEditorUnlicensed(result: LicenseFromKeyResult) {
371
- if (!result.isLicenseParseable) return true
372
- if (!result.isDomainValid && !result.isDevelopment) return true
374
+ export function getLicenseState(result: LicenseFromKeyResult): LicenseState {
375
+ if (!result.isLicenseParseable) return 'unlicensed'
376
+ if (!result.isDomainValid && !result.isDevelopment) return 'unlicensed'
373
377
  if (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {
374
- if (result.isInternalLicense) {
375
- throw new Error('License: Internal license expired.')
376
- }
377
- return true
378
+ // Check if it's an expired internal license with valid domain
379
+ const internalExpired = result.isInternalLicense && result.isDomainValid
380
+ return internalExpired ? 'internal-expired' : 'unlicensed'
381
+ }
382
+
383
+ // License is valid, determine if it has watermark
384
+ if (result.isLicensedWithWatermark) {
385
+ return 'licensed-with-watermark'
378
386
  }
379
387
 
380
- return false
388
+ return 'licensed'
381
389
  }
@@ -1,3 +1,4 @@
1
+ import { useValue } from '@tldraw/state-react'
1
2
  import { createContext, ReactNode, useContext, useState } from 'react'
2
3
  import { LicenseManager } from './LicenseManager'
3
4
 
@@ -16,5 +17,12 @@ export function LicenseProvider({
16
17
  children: ReactNode
17
18
  }) {
18
19
  const [licenseManager] = useState(() => new LicenseManager(licenseKey))
20
+ const licenseState = useValue(licenseManager.state)
21
+
22
+ // If internal license has expired, don't render the editor at all
23
+ if (licenseState === 'internal-expired') {
24
+ return <div data-testid="tl-license-expired" style={{ display: 'none' }} />
25
+ }
26
+
19
27
  return <LicenseContext.Provider value={licenseManager}>{children}</LicenseContext.Provider>
20
28
  }
@@ -1,7 +1,7 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
- import { LicenseManager } from './LicenseManager'
2
+ import { LicenseManager, LicenseState } from './LicenseManager'
3
3
 
4
4
  /** @internal */
5
- export function useLicenseManagerState(licenseManager: LicenseManager) {
5
+ export function useLicenseManagerState(licenseManager: LicenseManager): LicenseState {
6
6
  return useValue('watermarkState', () => licenseManager.state.get(), [licenseManager])
7
7
  }
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '3.16.0-canary.bf9fb05eb8ef'
4
+ export const version = '3.16.0-canary.c7d3f7d5729d'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-26T14:23:01.958Z',
8
- patch: '2025-08-26T14:23:01.958Z',
7
+ minor: '2025-08-28T15:31:21.677Z',
8
+ patch: '2025-08-28T15:31:21.677Z',
9
9
  }