@tldraw/editor 3.16.0-canary.c2c4563957ce → 3.16.0-canary.c360426d8b7a
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 +59 -3
- package/dist-cjs/index.js +6 -2
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +2 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +9 -4
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +10 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +15 -12
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useHandleEvents.js +3 -3
- package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useSelectionEvents.js +4 -4
- package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +133 -38
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +36 -3
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +143 -75
- package/dist-cjs/lib/license/Watermark.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +24 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +5 -1
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +12 -1
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.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 +59 -3
- package/dist-esm/index.mjs +9 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +3 -3
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +12 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +9 -4
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +10 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -13
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +11 -6
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +2 -3
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useHandleEvents.mjs +9 -4
- package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useSelectionEvents.mjs +6 -5
- package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +134 -39
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +36 -4
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +144 -76
- package/dist-esm/lib/license/Watermark.mjs.map +3 -3
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +24 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +5 -1
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +12 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +8 -3
- package/package.json +7 -7
- package/src/index.ts +3 -0
- package/src/lib/TldrawEditor.tsx +3 -4
- package/src/lib/components/default-components/DefaultCanvas.tsx +8 -2
- package/src/lib/editor/Editor.test.ts +90 -0
- package/src/lib/editor/Editor.ts +16 -4
- package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
- package/src/lib/editor/shapes/ShapeUtil.ts +11 -0
- package/src/lib/hooks/useCanvasEvents.ts +17 -11
- package/src/lib/hooks/useDocumentEvents.ts +11 -6
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +2 -2
- package/src/lib/hooks/useGestureEvents.ts +2 -2
- package/src/lib/hooks/useHandleEvents.ts +9 -4
- package/src/lib/hooks/useSelectionEvents.ts +6 -5
- package/src/lib/license/LicenseManager.test.ts +719 -387
- package/src/lib/license/LicenseManager.ts +187 -49
- package/src/lib/license/LicenseProvider.tsx +69 -5
- package/src/lib/license/Watermark.tsx +151 -77
- package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +29 -2
- package/src/lib/primitives/geometry/Group2d.ts +6 -1
- package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
- package/src/lib/utils/dom.test.ts +94 -0
- package/src/lib/utils/dom.ts +38 -1
- package/src/lib/utils/getPointerInfo.ts +2 -1
- package/src/version.ts +3 -3
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { atom } from '@tldraw/state'
|
|
2
|
-
import {
|
|
3
|
-
import { publishDates } from '../../version'
|
|
2
|
+
import { publishDates, version } from '../../version'
|
|
4
3
|
import { getDefaultCdnBaseUrl } from '../utils/assets'
|
|
5
4
|
import { importPublicKey, str2ab } from '../utils/licensing'
|
|
6
5
|
|
|
7
|
-
const GRACE_PERIOD_DAYS =
|
|
6
|
+
const GRACE_PERIOD_DAYS = 30
|
|
8
7
|
|
|
9
8
|
export const FLAGS = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
// -- MUTUALLY EXCLUSIVE FLAGS --
|
|
10
|
+
// Annual means the license expires after a time period, usually 1 year.
|
|
11
|
+
ANNUAL_LICENSE: 1,
|
|
12
|
+
// Perpetual means the license never expires up to the max supported version.
|
|
13
|
+
PERPETUAL_LICENSE: 1 << 1,
|
|
14
|
+
|
|
15
|
+
// -- ADDITIVE FLAGS --
|
|
16
|
+
// Internal means the license is for internal use only.
|
|
17
|
+
INTERNAL_LICENSE: 1 << 2,
|
|
18
|
+
// Watermark means the product is watermarked.
|
|
19
|
+
WITH_WATERMARK: 1 << 3,
|
|
20
|
+
// Evaluation means the license is for evaluation purposes only.
|
|
21
|
+
EVALUATION_LICENSE: 1 << 4,
|
|
22
|
+
// Native means the license is for native apps which switches
|
|
23
|
+
// on special-case logic.
|
|
24
|
+
NATIVE_LICENSE: 1 << 5,
|
|
14
25
|
}
|
|
15
26
|
const HIGHEST_FLAG = Math.max(...Object.values(FLAGS))
|
|
16
27
|
|
|
@@ -36,11 +47,12 @@ export interface LicenseInfo {
|
|
|
36
47
|
|
|
37
48
|
/** @internal */
|
|
38
49
|
export type LicenseState =
|
|
39
|
-
| 'pending'
|
|
40
|
-
| 'licensed'
|
|
41
|
-
| 'licensed-with-watermark'
|
|
42
|
-
| 'unlicensed'
|
|
43
|
-
| '
|
|
50
|
+
| 'pending' // License validation is in progress
|
|
51
|
+
| 'licensed' // License is valid and active (no restrictions)
|
|
52
|
+
| 'licensed-with-watermark' // License is valid but shows watermark (evaluation licenses, WITH_WATERMARK licenses)
|
|
53
|
+
| 'unlicensed' // No valid license found or license is invalid (development)
|
|
54
|
+
| 'unlicensed-production' // No valid license in production deployment (missing, invalid, or wrong domain)
|
|
55
|
+
| 'expired' // License has been expired (30 days past expiration for regular licenses, immediately for evaluation licenses)
|
|
44
56
|
/** @internal */
|
|
45
57
|
export type InvalidLicenseReason =
|
|
46
58
|
| 'invalid-license-key'
|
|
@@ -68,12 +80,19 @@ export interface ValidLicenseKeyResult {
|
|
|
68
80
|
isPerpetualLicense: boolean
|
|
69
81
|
isPerpetualLicenseExpired: boolean
|
|
70
82
|
isInternalLicense: boolean
|
|
83
|
+
isNativeLicense: boolean
|
|
71
84
|
isLicensedWithWatermark: boolean
|
|
85
|
+
isEvaluationLicense: boolean
|
|
86
|
+
isEvaluationLicenseExpired: boolean
|
|
87
|
+
daysSinceExpiry: number
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
/** @internal */
|
|
75
91
|
export type TestEnvironment = 'development' | 'production'
|
|
76
92
|
|
|
93
|
+
/** @internal */
|
|
94
|
+
export type TrackType = 'unlicensed' | 'with_watermark' | 'evaluation' | null
|
|
95
|
+
|
|
77
96
|
/** @internal */
|
|
78
97
|
export class LicenseManager {
|
|
79
98
|
private publicKey =
|
|
@@ -96,11 +115,13 @@ export class LicenseManager {
|
|
|
96
115
|
|
|
97
116
|
this.getLicenseFromKey(licenseKey)
|
|
98
117
|
.then((result) => {
|
|
99
|
-
const licenseState = getLicenseState(
|
|
118
|
+
const licenseState = getLicenseState(
|
|
119
|
+
result,
|
|
120
|
+
(messages: string[]) => this.outputMessages(messages),
|
|
121
|
+
this.isDevelopment
|
|
122
|
+
)
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
fetch(WATERMARK_TRACK_SRC)
|
|
103
|
-
}
|
|
124
|
+
this.maybeTrack(result, licenseState)
|
|
104
125
|
|
|
105
126
|
this.state.set(licenseState)
|
|
106
127
|
})
|
|
@@ -121,6 +142,50 @@ export class LicenseManager {
|
|
|
121
142
|
)
|
|
122
143
|
}
|
|
123
144
|
|
|
145
|
+
private getTrackType(result: LicenseFromKeyResult, licenseState: LicenseState): TrackType {
|
|
146
|
+
// Track watermark for unlicensed production deployments
|
|
147
|
+
if (licenseState === 'unlicensed-production') {
|
|
148
|
+
return 'unlicensed'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.isDevelopment) {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!result.isLicenseParseable) {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Track evaluation licenses (for analytics, even though no watermark is shown)
|
|
160
|
+
if (result.isEvaluationLicense) {
|
|
161
|
+
return 'evaluation'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Track licenses that show watermarks
|
|
165
|
+
if (licenseState === 'licensed-with-watermark') {
|
|
166
|
+
return 'with_watermark'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private maybeTrack(result: LicenseFromKeyResult, licenseState: LicenseState): void {
|
|
173
|
+
const trackType = this.getTrackType(result, licenseState)
|
|
174
|
+
if (!trackType) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const url = new URL(WATERMARK_TRACK_SRC)
|
|
179
|
+
url.searchParams.set('version', version)
|
|
180
|
+
url.searchParams.set('license_type', trackType)
|
|
181
|
+
if ('license' in result) {
|
|
182
|
+
url.searchParams.set('license_id', result.license.id)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// eslint-disable-next-line no-restricted-globals
|
|
186
|
+
fetch(url.toString())
|
|
187
|
+
}
|
|
188
|
+
|
|
124
189
|
private async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {
|
|
125
190
|
const [data, signature] = licenseKey.split('.')
|
|
126
191
|
const [prefix, encodedData] = data.split('/')
|
|
@@ -207,6 +272,9 @@ export class LicenseManager {
|
|
|
207
272
|
const isAnnualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.ANNUAL_LICENSE)
|
|
208
273
|
const isPerpetualLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.PERPETUAL_LICENSE)
|
|
209
274
|
|
|
275
|
+
const isEvaluationLicense = this.isFlagEnabled(licenseInfo.flags, FLAGS.EVALUATION_LICENSE)
|
|
276
|
+
const daysSinceExpiry = this.getDaysSinceExpiry(expiryDate)
|
|
277
|
+
|
|
210
278
|
const result: ValidLicenseKeyResult = {
|
|
211
279
|
license: licenseInfo,
|
|
212
280
|
isLicenseParseable: true,
|
|
@@ -218,7 +286,12 @@ export class LicenseManager {
|
|
|
218
286
|
isPerpetualLicense,
|
|
219
287
|
isPerpetualLicenseExpired: isPerpetualLicense && this.isPerpetualLicenseExpired(expiryDate),
|
|
220
288
|
isInternalLicense: this.isFlagEnabled(licenseInfo.flags, FLAGS.INTERNAL_LICENSE),
|
|
289
|
+
isNativeLicense: this.isNativeLicense(licenseInfo),
|
|
221
290
|
isLicensedWithWatermark: this.isFlagEnabled(licenseInfo.flags, FLAGS.WITH_WATERMARK),
|
|
291
|
+
isEvaluationLicense,
|
|
292
|
+
isEvaluationLicenseExpired:
|
|
293
|
+
isEvaluationLicense && this.isEvaluationLicenseExpired(expiryDate),
|
|
294
|
+
daysSinceExpiry,
|
|
222
295
|
}
|
|
223
296
|
this.outputLicenseInfoIfNeeded(result)
|
|
224
297
|
|
|
@@ -234,13 +307,13 @@ export class LicenseManager {
|
|
|
234
307
|
const currentHostname = window.location.hostname.toLowerCase()
|
|
235
308
|
|
|
236
309
|
return licenseInfo.hosts.some((host) => {
|
|
237
|
-
const
|
|
310
|
+
const normalizedHostOrUrlRegex = host.toLowerCase().trim()
|
|
238
311
|
|
|
239
312
|
// Allow the domain if listed and www variations, 'example.com' allows 'example.com' and 'www.example.com'
|
|
240
313
|
if (
|
|
241
|
-
|
|
242
|
-
`www.${
|
|
243
|
-
|
|
314
|
+
normalizedHostOrUrlRegex === currentHostname ||
|
|
315
|
+
`www.${normalizedHostOrUrlRegex}` === currentHostname ||
|
|
316
|
+
normalizedHostOrUrlRegex === `www.${currentHostname}`
|
|
244
317
|
) {
|
|
245
318
|
return true
|
|
246
319
|
}
|
|
@@ -251,6 +324,12 @@ export class LicenseManager {
|
|
|
251
324
|
return true
|
|
252
325
|
}
|
|
253
326
|
|
|
327
|
+
// Native license support
|
|
328
|
+
// In this case, `normalizedHost` is actually a protocol, e.g. `app-bundle:`
|
|
329
|
+
if (this.isNativeLicense(licenseInfo)) {
|
|
330
|
+
return new RegExp(normalizedHostOrUrlRegex).test(window.location.href)
|
|
331
|
+
}
|
|
332
|
+
|
|
254
333
|
// Glob testing, we only support '*.somedomain.com' right now.
|
|
255
334
|
if (host.includes('*')) {
|
|
256
335
|
const globToRegex = new RegExp(host.replace(/\*/g, '.*?'))
|
|
@@ -261,7 +340,7 @@ export class LicenseManager {
|
|
|
261
340
|
if (window.location.protocol === 'vscode-webview:') {
|
|
262
341
|
const currentUrl = new URL(window.location.href)
|
|
263
342
|
const extensionId = currentUrl.searchParams.get('extensionId')
|
|
264
|
-
if (
|
|
343
|
+
if (normalizedHostOrUrlRegex === extensionId) {
|
|
265
344
|
return true
|
|
266
345
|
}
|
|
267
346
|
}
|
|
@@ -270,6 +349,10 @@ export class LicenseManager {
|
|
|
270
349
|
})
|
|
271
350
|
}
|
|
272
351
|
|
|
352
|
+
private isNativeLicense(licenseInfo: LicenseInfo) {
|
|
353
|
+
return this.isFlagEnabled(licenseInfo.flags, FLAGS.NATIVE_LICENSE)
|
|
354
|
+
}
|
|
355
|
+
|
|
273
356
|
private getExpirationDateWithoutGracePeriod(expiryDate: Date) {
|
|
274
357
|
return new Date(expiryDate.getFullYear(), expiryDate.getMonth(), expiryDate.getDate())
|
|
275
358
|
}
|
|
@@ -284,15 +367,7 @@ export class LicenseManager {
|
|
|
284
367
|
|
|
285
368
|
private isAnnualLicenseExpired(expiryDate: Date) {
|
|
286
369
|
const expiration = this.getExpirationDateWithGracePeriod(expiryDate)
|
|
287
|
-
|
|
288
|
-
// If it is not expired yet (including the grace period), but after the expiry date we warn the users
|
|
289
|
-
if (!isExpired && new Date() >= this.getExpirationDateWithoutGracePeriod(expiryDate)) {
|
|
290
|
-
this.outputMessages([
|
|
291
|
-
'tldraw license is about to expire, you are in a grace period.',
|
|
292
|
-
`Please reach out to ${LICENSE_EMAIL} if you would like to renew your license.`,
|
|
293
|
-
])
|
|
294
|
-
}
|
|
295
|
-
return isExpired
|
|
370
|
+
return new Date() >= expiration
|
|
296
371
|
}
|
|
297
372
|
|
|
298
373
|
private isPerpetualLicenseExpired(expiryDate: Date) {
|
|
@@ -305,6 +380,21 @@ export class LicenseManager {
|
|
|
305
380
|
return dates.major >= expiration || dates.minor >= expiration
|
|
306
381
|
}
|
|
307
382
|
|
|
383
|
+
private getDaysSinceExpiry(expiryDate: Date): number {
|
|
384
|
+
const now = new Date()
|
|
385
|
+
const expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)
|
|
386
|
+
const diffTime = now.getTime() - expiration.getTime()
|
|
387
|
+
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
|
388
|
+
return Math.max(0, diffDays)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private isEvaluationLicenseExpired(expiryDate: Date): boolean {
|
|
392
|
+
// Evaluation licenses have no grace period - they expire immediately
|
|
393
|
+
const now = new Date()
|
|
394
|
+
const expiration = this.getExpirationDateWithoutGracePeriod(expiryDate)
|
|
395
|
+
return now >= expiration
|
|
396
|
+
}
|
|
397
|
+
|
|
308
398
|
private isFlagEnabled(flags: number, flag: number) {
|
|
309
399
|
return (flags & flag) === flag
|
|
310
400
|
}
|
|
@@ -322,19 +412,6 @@ export class LicenseManager {
|
|
|
322
412
|
}
|
|
323
413
|
|
|
324
414
|
private outputLicenseInfoIfNeeded(result: ValidLicenseKeyResult) {
|
|
325
|
-
if (result.isAnnualLicenseExpired) {
|
|
326
|
-
this.outputMessages([
|
|
327
|
-
'Your tldraw license has expired!',
|
|
328
|
-
`Please reach out to ${LICENSE_EMAIL} to renew.`,
|
|
329
|
-
])
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (!result.isDomainValid && !result.isDevelopment) {
|
|
333
|
-
this.outputMessages([
|
|
334
|
-
'This tldraw license key is not valid for this domain!',
|
|
335
|
-
`Please reach out to ${LICENSE_EMAIL} if you would like to use tldraw on other domains.`,
|
|
336
|
-
])
|
|
337
|
-
}
|
|
338
415
|
// If we added a new flag it will be twice the value of the currently highest flag.
|
|
339
416
|
// 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.
|
|
340
417
|
if (result.license.flags >= HIGHEST_FLAG * 2) {
|
|
@@ -371,13 +448,74 @@ export class LicenseManager {
|
|
|
371
448
|
static className = 'tl-watermark_SEE-LICENSE'
|
|
372
449
|
}
|
|
373
450
|
|
|
374
|
-
export function getLicenseState(
|
|
375
|
-
|
|
376
|
-
|
|
451
|
+
export function getLicenseState(
|
|
452
|
+
result: LicenseFromKeyResult,
|
|
453
|
+
outputMessages: (messages: string[]) => void,
|
|
454
|
+
isDevelopment: boolean
|
|
455
|
+
): LicenseState {
|
|
456
|
+
if (!result.isLicenseParseable) {
|
|
457
|
+
if (isDevelopment) {
|
|
458
|
+
return 'unlicensed'
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// All unlicensed scenarios should not work in production
|
|
462
|
+
if (result.reason === 'no-key-provided') {
|
|
463
|
+
outputMessages([
|
|
464
|
+
'No tldraw license key provided!',
|
|
465
|
+
'A license is required for production deployments.',
|
|
466
|
+
`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,
|
|
467
|
+
])
|
|
468
|
+
} else {
|
|
469
|
+
outputMessages([
|
|
470
|
+
'Invalid license key. tldraw requires a valid license for production use.',
|
|
471
|
+
`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,
|
|
472
|
+
])
|
|
473
|
+
}
|
|
474
|
+
return 'unlicensed-production'
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!result.isDomainValid && !result.isDevelopment) {
|
|
478
|
+
outputMessages([
|
|
479
|
+
'License key is not valid for this domain.',
|
|
480
|
+
'A license is required for production deployments.',
|
|
481
|
+
`Please reach out to ${LICENSE_EMAIL} to purchase a license.`,
|
|
482
|
+
])
|
|
483
|
+
return 'unlicensed-production'
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Handle evaluation licenses - they expire immediately with no grace period
|
|
487
|
+
if (result.isEvaluationLicense) {
|
|
488
|
+
if (result.isEvaluationLicenseExpired) {
|
|
489
|
+
outputMessages([
|
|
490
|
+
'Your tldraw evaluation license has expired!',
|
|
491
|
+
`Please reach out to ${LICENSE_EMAIL} to purchase a full license.`,
|
|
492
|
+
])
|
|
493
|
+
return 'expired'
|
|
494
|
+
} else {
|
|
495
|
+
// Valid evaluation license - tracked but no watermark shown
|
|
496
|
+
return 'licensed'
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Handle expired regular licenses (both annual and perpetual)
|
|
377
501
|
if (result.isPerpetualLicenseExpired || result.isAnnualLicenseExpired) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
502
|
+
outputMessages([
|
|
503
|
+
'Your tldraw license has been expired for more than 30 days!',
|
|
504
|
+
`Please reach out to ${LICENSE_EMAIL} to renew your license.`,
|
|
505
|
+
])
|
|
506
|
+
return 'expired'
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Check if license is past expiry date but within grace period
|
|
510
|
+
const daysSinceExpiry = result.daysSinceExpiry
|
|
511
|
+
if (daysSinceExpiry > 0 && !result.isEvaluationLicense) {
|
|
512
|
+
outputMessages([
|
|
513
|
+
'Your tldraw license has expired.',
|
|
514
|
+
`License expired ${daysSinceExpiry} days ago.`,
|
|
515
|
+
`Please reach out to ${LICENSE_EMAIL} to renew your license.`,
|
|
516
|
+
])
|
|
517
|
+
// Within 30-day grace period: still licensed (no watermark)
|
|
518
|
+
return 'licensed'
|
|
381
519
|
}
|
|
382
520
|
|
|
383
521
|
// License is valid, determine if it has watermark
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
|
-
import { createContext, ReactNode, useContext, useState } from 'react'
|
|
2
|
+
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
|
3
3
|
import { LicenseManager } from './LicenseManager'
|
|
4
4
|
|
|
5
5
|
/** @internal */
|
|
@@ -8,9 +8,16 @@ export const LicenseContext = createContext({} as LicenseManager)
|
|
|
8
8
|
/** @internal */
|
|
9
9
|
export const useLicenseContext = () => useContext(LicenseContext)
|
|
10
10
|
|
|
11
|
+
function shouldHideEditorAfterDelay(licenseState: string): boolean {
|
|
12
|
+
return licenseState === 'expired' || licenseState === 'unlicensed-production'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
export const LICENSE_TIMEOUT = 5000
|
|
17
|
+
|
|
11
18
|
/** @internal */
|
|
12
19
|
export function LicenseProvider({
|
|
13
|
-
licenseKey,
|
|
20
|
+
licenseKey = getLicenseKeyFromEnv() ?? undefined,
|
|
14
21
|
children,
|
|
15
22
|
}: {
|
|
16
23
|
licenseKey?: string
|
|
@@ -18,11 +25,68 @@ export function LicenseProvider({
|
|
|
18
25
|
}) {
|
|
19
26
|
const [licenseManager] = useState(() => new LicenseManager(licenseKey))
|
|
20
27
|
const licenseState = useValue(licenseManager.state)
|
|
28
|
+
const [showEditor, setShowEditor] = useState(true)
|
|
21
29
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// When license expires or no license in production, show for 5 seconds then hide
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (shouldHideEditorAfterDelay(licenseState) && showEditor) {
|
|
33
|
+
// eslint-disable-next-line no-restricted-globals
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
setShowEditor(false)
|
|
36
|
+
}, LICENSE_TIMEOUT)
|
|
37
|
+
|
|
38
|
+
return () => clearTimeout(timer)
|
|
39
|
+
}
|
|
40
|
+
}, [licenseState, showEditor])
|
|
41
|
+
|
|
42
|
+
// If license is expired or no license in production and 5 seconds have passed, don't render anything (blank screen)
|
|
43
|
+
if (shouldHideEditorAfterDelay(licenseState) && !showEditor) {
|
|
44
|
+
return <LicenseGate />
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
return <LicenseContext.Provider value={licenseManager}>{children}</LicenseContext.Provider>
|
|
28
48
|
}
|
|
49
|
+
|
|
50
|
+
// Renders as a hidden div that can be detected by tests
|
|
51
|
+
function LicenseGate() {
|
|
52
|
+
return <div data-testid="tl-license-expired" style={{ display: 'none' }} />
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let envLicenseKey: string | undefined | null = undefined
|
|
56
|
+
function getLicenseKeyFromEnv() {
|
|
57
|
+
if (envLicenseKey !== undefined) {
|
|
58
|
+
return envLicenseKey
|
|
59
|
+
}
|
|
60
|
+
// it's important here that we write out the full process.env.WHATEVER expression instead of
|
|
61
|
+
// doing something like process.env[someVariable]. This is because most bundlers do something
|
|
62
|
+
// like a find-replace inject environment variables, and so won't pick up on dynamic ones. It
|
|
63
|
+
// also means we can't do checks like `process.env && process.env.WHATEVER`, which is why we use
|
|
64
|
+
// the `getEnv` try/catch approach.
|
|
65
|
+
|
|
66
|
+
// framework-specific prefixes borrowed from the ones vercel uses, but trimmed down to just the
|
|
67
|
+
// react-y ones: https://vercel.com/docs/environment-variables/framework-environment-variables
|
|
68
|
+
envLicenseKey =
|
|
69
|
+
getEnv(() => process.env.TLDRAW_LICENSE_KEY) ||
|
|
70
|
+
getEnv(() => process.env.NEXT_PUBLIC_TLDRAW_LICENSE_KEY) ||
|
|
71
|
+
getEnv(() => process.env.REACT_APP_TLDRAW_LICENSE_KEY) ||
|
|
72
|
+
getEnv(() => process.env.GATSBY_TLDRAW_LICENSE_KEY) ||
|
|
73
|
+
getEnv(() => process.env.VITE_TLDRAW_LICENSE_KEY) ||
|
|
74
|
+
getEnv(() => process.env.PUBLIC_TLDRAW_LICENSE_KEY) ||
|
|
75
|
+
getEnv(() => (import.meta as any).env.TLDRAW_LICENSE_KEY) ||
|
|
76
|
+
getEnv(() => (import.meta as any).env.NEXT_PUBLIC_TLDRAW_LICENSE_KEY) ||
|
|
77
|
+
getEnv(() => (import.meta as any).env.REACT_APP_TLDRAW_LICENSE_KEY) ||
|
|
78
|
+
getEnv(() => (import.meta as any).env.GATSBY_TLDRAW_LICENSE_KEY) ||
|
|
79
|
+
getEnv(() => (import.meta as any).env.VITE_TLDRAW_LICENSE_KEY) ||
|
|
80
|
+
getEnv(() => (import.meta as any).env.PUBLIC_TLDRAW_LICENSE_KEY) ||
|
|
81
|
+
null
|
|
82
|
+
|
|
83
|
+
return envLicenseKey
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getEnv(cb: () => string | undefined) {
|
|
87
|
+
try {
|
|
88
|
+
return cb()
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
}
|