@tldraw/editor 5.3.0-internal.fba91ed55f6c → 5.3.0-next.91d2a20b898c

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.
@@ -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,62 +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
511
  })
569
512
 
570
513
  describe('Expiry date timezone handling', () => {
@@ -850,8 +793,6 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
850
793
  isLicensedWithWatermark: false,
851
794
  isEvaluationLicense: false,
852
795
  isEvaluationLicenseExpired: false,
853
- isCollaborationEnabled: false,
854
- isCommentingEnabled: false,
855
796
  daysSinceExpiry: 0,
856
797
  // WatermarkManager does not check these fields, it relies on the calculated values like isAnnualLicenseExpired
857
798
  license: {
@@ -1153,65 +1094,3 @@ describe('getLicenseState', () => {
1153
1094
  })
1154
1095
  })
1155
1096
  })
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
- })
@@ -1,4 +1,4 @@
1
- import { atom, transact } from '@tldraw/state'
1
+ import { atom } from '@tldraw/state'
2
2
  import { publishDates, version } from '../../version'
3
3
  import { getDefaultCdnBaseUrl } from '../utils/assets'
4
4
  import { importPublicKey, str2ab } from '../utils/licensing'
@@ -22,12 +22,6 @@ export const FLAGS = {
22
22
  // Native means the license is for native apps which switches
23
23
  // on special-case logic.
24
24
  NATIVE_LICENSE: 1 << 5,
25
-
26
- // -- FEATURE FLAGS --
27
- // Collaboration is the umbrella flag for collaboration features; it grants all sub-features.
28
- FEAT_COLLABORATION: 1 << 6,
29
- // Commenting is the first sub-feature of collaboration.
30
- FEAT_COMMENTING: 1 << 7,
31
25
  }
32
26
  const HIGHEST_FLAG = Math.max(...Object.values(FLAGS))
33
27
 
@@ -51,23 +45,6 @@ export interface LicenseInfo {
51
45
  expiryDate: string
52
46
  }
53
47
 
54
- /**
55
- * Names of the licensable product features gated by the license. `collaboration` is an umbrella
56
- * that also grants all of its sub-features (currently just `commenting`).
57
- *
58
- * @internal
59
- */
60
- export type LicenseFeatureName = 'collaboration' | 'commenting'
61
-
62
- const NO_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {
63
- collaboration: false,
64
- commenting: false,
65
- }
66
- const ALL_FEATURES: Readonly<Record<LicenseFeatureName, boolean>> = {
67
- collaboration: true,
68
- commenting: true,
69
- }
70
-
71
48
  /** @internal */
72
49
  export type LicenseState =
73
50
  | 'pending' // License validation is in progress
@@ -107,8 +84,6 @@ export interface ValidLicenseKeyResult {
107
84
  isLicensedWithWatermark: boolean
108
85
  isEvaluationLicense: boolean
109
86
  isEvaluationLicenseExpired: boolean
110
- isCollaborationEnabled: boolean
111
- isCommentingEnabled: boolean
112
87
  daysSinceExpiry: number
113
88
  }
114
89
 
@@ -123,9 +98,6 @@ export class LicenseManager {
123
98
  public isTest: boolean
124
99
  public isCryptoAvailable: boolean
125
100
  state = atom<LicenseState>('license state', 'pending')
126
- featureFlags = atom<Record<LicenseFeatureName, boolean>>('license feature flags', {
127
- ...NO_FEATURES,
128
- })
129
101
  public verbose = true
130
102
 
131
103
  constructor(licenseKey: string | undefined, testPublicKey?: string) {
@@ -134,14 +106,6 @@ export class LicenseManager {
134
106
  this.publicKey = testPublicKey || this.publicKey
135
107
  this.isCryptoAvailable = !!crypto.subtle
136
108
 
137
- // In development every feature is enabled (see `getEnabledFeatures`), and that doesn't depend
138
- // on the async validation result. Reflect it eagerly so features aren't reported as disabled
139
- // during the validation window — or left disabled if validation rejects (the `.catch` below
140
- // never sets `featureFlags`). In production the fail-closed default stands until validation.
141
- if (this.isDevelopment) {
142
- this.featureFlags.set({ ...ALL_FEATURES })
143
- }
144
-
145
109
  this.getLicenseFromKey(licenseKey)
146
110
  .then((result) => {
147
111
  const licenseState = getLicenseState(
@@ -152,12 +116,7 @@ export class LicenseManager {
152
116
 
153
117
  this.maybeTrack(result, licenseState)
154
118
 
155
- // Update both atoms atomically so dependents never observe the license state and the
156
- // feature flags out of sync mid-update.
157
- transact(() => {
158
- this.state.set(licenseState)
159
- this.featureFlags.set(getEnabledFeatures(result, licenseState, this.isDevelopment))
160
- })
119
+ this.state.set(licenseState)
161
120
  })
162
121
  .catch((error) => {
163
122
  console.error('License validation failed:', error)
@@ -165,14 +124,6 @@ export class LicenseManager {
165
124
  })
166
125
  }
167
126
 
168
- /**
169
- * Returns whether a given licensable feature is enabled. Reactive: reading this inside a signal
170
- * recomputes when license validation resolves.
171
- */
172
- isFeatureEnabled(feature: LicenseFeatureName): boolean {
173
- return this.featureFlags.get()[feature]
174
- }
175
-
176
127
  private getIsDevelopment() {
177
128
  // If we are using https on a non-loopback domain we assume it's a production env and a development one otherwise
178
129
  return (
@@ -335,12 +286,6 @@ export class LicenseManager {
335
286
  const isPerpetualLicenseExpired =
336
287
  isPerpetualLicense && this.isPerpetualLicenseExpired(expiryDate)
337
288
 
338
- // The collaboration umbrella grants all of its sub-features, so commenting is enabled
339
- // by either the commenting flag or the collaboration flag.
340
- const isCollaborationEnabled = this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COLLABORATION)
341
- const isCommentingEnabled =
342
- isCollaborationEnabled || this.isFlagEnabled(licenseInfo.flags, FLAGS.FEAT_COMMENTING)
343
-
344
289
  // For perpetual licenses, the calendar expiry date only gates access to future
345
290
  // major/minor releases; it does not "expire" the license itself. While the user
346
291
  // is still on a covered version we report `daysSinceExpiry` as 0 so consumers
@@ -364,8 +309,6 @@ export class LicenseManager {
364
309
  isEvaluationLicense,
365
310
  isEvaluationLicenseExpired:
366
311
  isEvaluationLicense && this.isEvaluationLicenseExpired(expiryDate),
367
- isCollaborationEnabled,
368
- isCommentingEnabled,
369
312
  daysSinceExpiry,
370
313
  }
371
314
  this.outputLicenseInfoIfNeeded(result)
@@ -540,38 +483,6 @@ export class LicenseManager {
540
483
  static className = 'tl-watermark_SEE-LICENSE'
541
484
  }
542
485
 
543
- /**
544
- * Derives which licensable features are enabled from the parse result and the derived license
545
- * state. In development every feature is enabled so SDK developers can build against them; in
546
- * production a feature requires both an active, valid license and the corresponding flag.
547
- *
548
- * @internal
549
- */
550
- export function getEnabledFeatures(
551
- result: LicenseFromKeyResult,
552
- licenseState: LicenseState,
553
- isDevelopment: boolean
554
- ): Record<LicenseFeatureName, boolean> {
555
- // Development gets all features so SDK developers can build against them.
556
- if (isDevelopment) {
557
- return { ...ALL_FEATURES }
558
- }
559
-
560
- // Features require an active, valid license. Both the 30-day grace-period 'licensed' state and
561
- // the watermark state count as valid; unlicensed/expired/pending do not.
562
- if (
563
- !result.isLicenseParseable ||
564
- (licenseState !== 'licensed' && licenseState !== 'licensed-with-watermark')
565
- ) {
566
- return { ...NO_FEATURES }
567
- }
568
-
569
- return {
570
- collaboration: result.isCollaborationEnabled,
571
- commenting: result.isCommentingEnabled,
572
- }
573
- }
574
-
575
486
  export function getLicenseState(
576
487
  result: LicenseFromKeyResult,
577
488
  outputMessages: (messages: string[]) => void,
@@ -1,23 +1,7 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
- import { LicenseFeatureName, LicenseManager, LicenseState } from './LicenseManager'
2
+ import { LicenseManager, LicenseState } from './LicenseManager'
3
3
 
4
4
  /** @internal */
5
5
  export function useLicenseManagerState(licenseManager: LicenseManager): LicenseState {
6
6
  return useValue('watermarkState', () => licenseManager.state.get(), [licenseManager])
7
7
  }
8
-
9
- /**
10
- * Reactively reads whether a licensable feature is enabled for the current license. Re-renders when
11
- * license validation resolves.
12
- *
13
- * @internal
14
- */
15
- export function useLicenseFeatureFlag(
16
- licenseManager: LicenseManager,
17
- feature: LicenseFeatureName
18
- ): boolean {
19
- return useValue('licenseFeature', () => licenseManager.isFeatureEnabled(feature), [
20
- licenseManager,
21
- feature,
22
- ])
23
- }
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 = '5.3.0-internal.fba91ed55f6c'
4
+ export const version = '5.3.0-next.91d2a20b898c'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-07-17T10:57:42.512Z',
8
- patch: '2026-07-17T10:57:42.512Z',
7
+ minor: '2026-07-15T09:31:43.721Z',
8
+ patch: '2026-07-15T09:31:43.721Z',
9
9
  }