@tldraw/editor 3.15.4 → 3.15.5
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 +2 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +28 -28
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +5 -0
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +4 -4
- package/dist-cjs/lib/license/Watermark.js.map +1 -1
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/version.js +2 -2
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +2 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +28 -28
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +5 -0
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +4 -4
- package/dist-esm/lib/license/Watermark.mjs.map +1 -1
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/version.mjs +2 -2
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +1 -0
- package/src/lib/license/LicenseManager.test.ts +58 -51
- package/src/lib/license/LicenseManager.ts +43 -30
- package/src/lib/license/LicenseProvider.tsx +8 -0
- package/src/lib/license/Watermark.tsx +4 -4
- package/src/lib/license/useLicenseManagerState.ts +2 -2
- package/src/version.ts +2 -2
|
@@ -3,7 +3,7 @@ import { publishDates } from '../../version'
|
|
|
3
3
|
import { str2ab } from '../utils/licensing'
|
|
4
4
|
import {
|
|
5
5
|
FLAGS,
|
|
6
|
-
|
|
6
|
+
getLicenseState,
|
|
7
7
|
LicenseManager,
|
|
8
8
|
PROPERTIES,
|
|
9
9
|
ValidLicenseKeyResult,
|
|
@@ -485,115 +485,122 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
describe(
|
|
489
|
-
it('
|
|
488
|
+
describe('getLicenseState', () => {
|
|
489
|
+
it('returns "unlicensed" for unparseable license', () => {
|
|
490
490
|
const licenseResult = getDefaultLicenseResult({
|
|
491
491
|
// @ts-ignore
|
|
492
492
|
isLicenseParseable: false,
|
|
493
493
|
})
|
|
494
|
-
expect(
|
|
494
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
495
495
|
})
|
|
496
496
|
|
|
497
|
-
it('
|
|
497
|
+
it('returns "unlicensed" for invalid domain in production', () => {
|
|
498
498
|
const licenseResult = getDefaultLicenseResult({
|
|
499
499
|
isDomainValid: false,
|
|
500
|
+
isDevelopment: false,
|
|
500
501
|
})
|
|
501
|
-
expect(
|
|
502
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
502
503
|
})
|
|
503
504
|
|
|
504
|
-
it('
|
|
505
|
+
it('returns "licensed" for invalid domain in development mode', () => {
|
|
506
|
+
const licenseResult = getDefaultLicenseResult({
|
|
507
|
+
isDomainValid: false,
|
|
508
|
+
isDevelopment: true,
|
|
509
|
+
})
|
|
510
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('returns "unlicensed" for expired annual license', () => {
|
|
505
514
|
const licenseResult = getDefaultLicenseResult({
|
|
506
515
|
isAnnualLicense: true,
|
|
507
516
|
isAnnualLicenseExpired: true,
|
|
517
|
+
isInternalLicense: false,
|
|
508
518
|
})
|
|
509
|
-
expect(
|
|
519
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
510
520
|
})
|
|
511
521
|
|
|
512
|
-
it('
|
|
522
|
+
it('returns "unlicensed" for expired annual license even in dev mode', () => {
|
|
513
523
|
const licenseResult = getDefaultLicenseResult({
|
|
514
524
|
isAnnualLicense: true,
|
|
515
525
|
isAnnualLicenseExpired: true,
|
|
516
526
|
isDevelopment: true,
|
|
527
|
+
isInternalLicense: false,
|
|
517
528
|
})
|
|
518
|
-
expect(
|
|
529
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
519
530
|
})
|
|
520
531
|
|
|
521
|
-
it('
|
|
532
|
+
it('returns "unlicensed" for expired perpetual license', () => {
|
|
522
533
|
const licenseResult = getDefaultLicenseResult({
|
|
523
534
|
isPerpetualLicense: true,
|
|
524
535
|
isPerpetualLicenseExpired: true,
|
|
536
|
+
isInternalLicense: false,
|
|
525
537
|
})
|
|
526
|
-
expect(
|
|
538
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
527
539
|
})
|
|
528
540
|
|
|
529
|
-
it('
|
|
541
|
+
it('returns "internal-expired" for expired internal annual license with valid domain', () => {
|
|
542
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
530
543
|
const licenseResult = getDefaultLicenseResult({
|
|
531
544
|
isAnnualLicense: true,
|
|
532
|
-
isAnnualLicenseExpired:
|
|
533
|
-
isInternalLicense:
|
|
545
|
+
isAnnualLicenseExpired: true,
|
|
546
|
+
isInternalLicense: true,
|
|
547
|
+
isDomainValid: true,
|
|
548
|
+
expiryDate,
|
|
534
549
|
})
|
|
535
|
-
expect(
|
|
550
|
+
expect(getLicenseState(licenseResult)).toBe('internal-expired')
|
|
536
551
|
})
|
|
537
552
|
|
|
538
|
-
it('
|
|
553
|
+
it('returns "internal-expired" for expired internal perpetual license with valid domain', () => {
|
|
554
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
539
555
|
const licenseResult = getDefaultLicenseResult({
|
|
540
556
|
isPerpetualLicense: true,
|
|
541
|
-
isPerpetualLicenseExpired:
|
|
542
|
-
isInternalLicense:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
it('does not show watermark when in development mode', () => {
|
|
548
|
-
const licenseResult = getDefaultLicenseResult({
|
|
549
|
-
isDevelopment: true,
|
|
557
|
+
isPerpetualLicenseExpired: true,
|
|
558
|
+
isInternalLicense: true,
|
|
559
|
+
isDomainValid: true,
|
|
560
|
+
expiryDate,
|
|
550
561
|
})
|
|
551
|
-
expect(
|
|
562
|
+
expect(getLicenseState(licenseResult)).toBe('internal-expired')
|
|
552
563
|
})
|
|
553
564
|
|
|
554
|
-
it('
|
|
565
|
+
it('returns "unlicensed" for expired internal license with invalid domain', () => {
|
|
566
|
+
const expiryDate = new Date(2023, 1, 1)
|
|
555
567
|
const licenseResult = getDefaultLicenseResult({
|
|
556
|
-
|
|
557
|
-
|
|
568
|
+
isAnnualLicense: true,
|
|
569
|
+
isAnnualLicenseExpired: true,
|
|
570
|
+
isInternalLicense: true,
|
|
571
|
+
isDomainValid: false,
|
|
572
|
+
expiryDate,
|
|
558
573
|
})
|
|
559
|
-
expect(
|
|
574
|
+
expect(getLicenseState(licenseResult)).toBe('unlicensed')
|
|
560
575
|
})
|
|
561
576
|
|
|
562
|
-
it('
|
|
577
|
+
it('returns "licensed-with-watermark" for watermarked license', () => {
|
|
563
578
|
const licenseResult = getDefaultLicenseResult({
|
|
564
|
-
|
|
565
|
-
isDomainValid: false,
|
|
566
|
-
isDevelopment: true,
|
|
579
|
+
isLicensedWithWatermark: true,
|
|
567
580
|
})
|
|
568
|
-
expect(
|
|
581
|
+
expect(getLicenseState(licenseResult)).toBe('licensed-with-watermark')
|
|
569
582
|
})
|
|
570
583
|
|
|
571
|
-
it('
|
|
572
|
-
const expiryDate = new Date(2023, 1, 1)
|
|
584
|
+
it('returns "licensed" for valid annual license', () => {
|
|
573
585
|
const licenseResult = getDefaultLicenseResult({
|
|
574
586
|
isAnnualLicense: true,
|
|
575
|
-
isAnnualLicenseExpired:
|
|
576
|
-
isInternalLicense: true,
|
|
577
|
-
expiryDate,
|
|
587
|
+
isAnnualLicenseExpired: false,
|
|
578
588
|
})
|
|
579
|
-
expect((
|
|
589
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
580
590
|
})
|
|
581
591
|
|
|
582
|
-
it('
|
|
583
|
-
const expiryDate = new Date(2023, 1, 1)
|
|
592
|
+
it('returns "licensed" for valid perpetual license', () => {
|
|
584
593
|
const licenseResult = getDefaultLicenseResult({
|
|
585
594
|
isPerpetualLicense: true,
|
|
586
|
-
isPerpetualLicenseExpired:
|
|
587
|
-
isInternalLicense: true,
|
|
588
|
-
expiryDate,
|
|
595
|
+
isPerpetualLicenseExpired: false,
|
|
589
596
|
})
|
|
590
|
-
expect((
|
|
597
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
591
598
|
})
|
|
592
599
|
|
|
593
|
-
it('
|
|
600
|
+
it('returns "licensed" for valid license in development mode', () => {
|
|
594
601
|
const licenseResult = getDefaultLicenseResult({
|
|
595
|
-
|
|
602
|
+
isDevelopment: true,
|
|
596
603
|
})
|
|
597
|
-
expect(
|
|
604
|
+
expect(getLicenseState(licenseResult)).toBe('licensed')
|
|
598
605
|
})
|
|
599
606
|
})
|
|
@@ -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<'
|
|
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)
|
|
93
|
-
|
|
97
|
+
this.getLicenseFromKey(licenseKey)
|
|
98
|
+
.then((result) => {
|
|
99
|
+
const licenseState = getLicenseState(result)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
if (!this.isDevelopment && licenseState === 'unlicensed') {
|
|
102
|
+
fetch(WATERMARK_TRACK_SRC)
|
|
103
|
+
}
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
this.state.set(licenseState)
|
|
106
|
+
})
|
|
107
|
+
.catch((error) => {
|
|
108
|
+
console.error('License validation failed:', error)
|
|
100
109
|
this.state.set('unlicensed')
|
|
101
|
-
}
|
|
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) {
|
|
@@ -334,22 +338,27 @@ export class LicenseManager {
|
|
|
334
338
|
// If we added a new flag it will be twice the value of the currently highest flag.
|
|
335
339
|
// 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.
|
|
336
340
|
if (result.license.flags >= HIGHEST_FLAG * 2) {
|
|
337
|
-
this.outputMessages(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
this.outputMessages(
|
|
342
|
+
[
|
|
343
|
+
'Warning: This tldraw license contains some unknown flags.',
|
|
344
|
+
'This will still work, however, you may want to update tldraw packages to a newer version to get access to new functionality.',
|
|
345
|
+
],
|
|
346
|
+
'warning'
|
|
347
|
+
)
|
|
341
348
|
}
|
|
342
349
|
}
|
|
343
350
|
|
|
344
|
-
private outputMessages(messages: string[]) {
|
|
351
|
+
private outputMessages(messages: string[], type: 'warning' | 'error' = 'error') {
|
|
345
352
|
if (this.isTest) return
|
|
346
353
|
if (this.verbose) {
|
|
347
354
|
this.outputDelimiter()
|
|
348
355
|
for (const message of messages) {
|
|
356
|
+
const color = type === 'warning' ? 'orange' : 'crimson'
|
|
357
|
+
const bgColor = type === 'warning' ? 'orange' : 'crimson'
|
|
349
358
|
// eslint-disable-next-line no-console
|
|
350
359
|
console.log(
|
|
351
360
|
`%c${message}`,
|
|
352
|
-
`color:
|
|
361
|
+
`color: ${color}; background: ${bgColor}; padding: 2px; border-radius: 3px;`
|
|
353
362
|
)
|
|
354
363
|
}
|
|
355
364
|
this.outputDelimiter()
|
|
@@ -367,15 +376,19 @@ export class LicenseManager {
|
|
|
367
376
|
static className = 'tl-watermark_SEE-LICENSE'
|
|
368
377
|
}
|
|
369
378
|
|
|
370
|
-
export function
|
|
371
|
-
if (!result.isLicenseParseable) return
|
|
372
|
-
if (!result.isDomainValid && !result.isDevelopment) return
|
|
379
|
+
export function getLicenseState(result: LicenseFromKeyResult): LicenseState {
|
|
380
|
+
if (!result.isLicenseParseable) return 'unlicensed'
|
|
381
|
+
if (!result.isDomainValid && !result.isDevelopment) return 'unlicensed'
|
|
373
382
|
if (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
// Check if it's an expired internal license with valid domain
|
|
384
|
+
const internalExpired = result.isInternalLicense && result.isDomainValid
|
|
385
|
+
return internalExpired ? 'internal-expired' : 'unlicensed'
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// License is valid, determine if it has watermark
|
|
389
|
+
if (result.isLicensedWithWatermark) {
|
|
390
|
+
return 'licensed-with-watermark'
|
|
378
391
|
}
|
|
379
392
|
|
|
380
|
-
return
|
|
393
|
+
return 'licensed'
|
|
381
394
|
}
|
|
@@ -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
|
}
|
|
@@ -86,8 +86,8 @@ To remove the watermark, please purchase a license at tldraw.dev.
|
|
|
86
86
|
|
|
87
87
|
.${className} {
|
|
88
88
|
position: absolute;
|
|
89
|
-
bottom: var(--space-2);
|
|
90
|
-
right: var(--space-2);
|
|
89
|
+
bottom: max(var(--space-2), env(safe-area-inset-bottom));
|
|
90
|
+
right: max(var(--space-2), env(safe-area-inset-right));
|
|
91
91
|
width: 96px;
|
|
92
92
|
height: 32px;
|
|
93
93
|
display: flex;
|
|
@@ -116,12 +116,12 @@ To remove the watermark, please purchase a license at tldraw.dev.
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
.${className}[data-debug='true'] {
|
|
119
|
-
bottom: 46px;
|
|
119
|
+
bottom: max(46px, env(safe-area-inset-bottom));
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
.${className}[data-mobile='true'] {
|
|
123
123
|
border-radius: 4px 0px 0px 4px;
|
|
124
|
-
right: -2px;
|
|
124
|
+
right: max(-2px, calc(env(safe-area-inset-right) - 2px));
|
|
125
125
|
width: 8px;
|
|
126
126
|
height: 48px;
|
|
127
127
|
}
|
|
@@ -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.15.
|
|
4
|
+
export const version = '3.15.5'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
7
|
minor: '2025-07-30T09:07:27.887Z',
|
|
8
|
-
patch: '2025-
|
|
8
|
+
patch: '2025-09-30T13:16:39.779Z',
|
|
9
9
|
}
|