@buoy-gg/license 7.0.22 → 7.0.24

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 (68) hide show
  1. package/LICENSE +46 -0
  2. package/lib/commonjs/DeviceRegistrationModal.js +1 -189
  3. package/lib/commonjs/LicenseManager.js +14 -610
  4. package/lib/commonjs/api.js +1 -172
  5. package/lib/commonjs/cacheSignature.js +15 -0
  6. package/lib/commonjs/config.js +16 -28
  7. package/lib/commonjs/device-info.js +1 -93
  8. package/lib/commonjs/device-info.web.js +1 -227
  9. package/lib/commonjs/fingerprint.js +1 -30
  10. package/lib/commonjs/fingerprint.web.js +1 -30
  11. package/lib/commonjs/hooks.js +13 -120
  12. package/lib/commonjs/index.js +17 -106
  13. package/lib/commonjs/index.web.js +17 -98
  14. package/lib/commonjs/tier.js +16 -0
  15. package/lib/commonjs/types.js +1 -1
  16. package/lib/commonjs/weekendPro.js +15 -41
  17. package/lib/module/DeviceRegistrationModal.js +1 -185
  18. package/lib/module/LicenseManager.js +14 -607
  19. package/lib/module/api.js +1 -165
  20. package/lib/module/cacheSignature.js +15 -0
  21. package/lib/module/config.js +16 -24
  22. package/lib/module/device-info.js +1 -87
  23. package/lib/module/device-info.web.js +1 -220
  24. package/lib/module/fingerprint.js +1 -10
  25. package/lib/module/fingerprint.web.js +1 -10
  26. package/lib/module/hooks.js +13 -114
  27. package/lib/module/index.js +15 -54
  28. package/lib/module/index.web.js +15 -52
  29. package/lib/module/tier.js +16 -0
  30. package/lib/module/types.js +1 -1
  31. package/lib/module/weekendPro.js +15 -37
  32. package/lib/typescript/commonjs/LicenseManager.d.ts +31 -6
  33. package/lib/typescript/commonjs/LicenseManager.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/cacheSignature.d.ts +42 -0
  35. package/lib/typescript/commonjs/cacheSignature.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/config.d.ts +62 -1
  37. package/lib/typescript/commonjs/config.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/hooks.d.ts +27 -5
  39. package/lib/typescript/commonjs/hooks.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/index.d.ts +20 -2
  41. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/index.web.d.ts +20 -2
  43. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/tier.d.ts +55 -0
  45. package/lib/typescript/commonjs/tier.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/types.d.ts +44 -0
  47. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/weekendPro.d.ts +30 -7
  49. package/lib/typescript/commonjs/weekendPro.d.ts.map +1 -1
  50. package/lib/typescript/module/LicenseManager.d.ts +31 -6
  51. package/lib/typescript/module/LicenseManager.d.ts.map +1 -1
  52. package/lib/typescript/module/cacheSignature.d.ts +42 -0
  53. package/lib/typescript/module/cacheSignature.d.ts.map +1 -0
  54. package/lib/typescript/module/config.d.ts +62 -1
  55. package/lib/typescript/module/config.d.ts.map +1 -1
  56. package/lib/typescript/module/hooks.d.ts +27 -5
  57. package/lib/typescript/module/hooks.d.ts.map +1 -1
  58. package/lib/typescript/module/index.d.ts +20 -2
  59. package/lib/typescript/module/index.d.ts.map +1 -1
  60. package/lib/typescript/module/index.web.d.ts +20 -2
  61. package/lib/typescript/module/index.web.d.ts.map +1 -1
  62. package/lib/typescript/module/tier.d.ts +55 -0
  63. package/lib/typescript/module/tier.d.ts.map +1 -0
  64. package/lib/typescript/module/types.d.ts +44 -0
  65. package/lib/typescript/module/types.d.ts.map +1 -1
  66. package/lib/typescript/module/weekendPro.d.ts +30 -7
  67. package/lib/typescript/module/weekendPro.d.ts.map +1 -1
  68. package/package.json +7 -4
@@ -1,611 +1,15 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.LicenseManager = void 0;
7
- var _config = require("./config.js");
8
- var _api = require("./api.js");
9
- var _fingerprint = require("./fingerprint");
10
- /**
11
- * LicenseManager - Singleton for managing React Buoy Pro licenses
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.LicenseManager=void 0;var _config=require("./config.js");var _api=require("./api.js");var _fingerprint=require("./fingerprint");var _cacheSignature=require("./cacheSignature.js");var _tier=require("./tier.js");/*!
2
+ * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
12
3
  *
13
- * Simple license validation + device registration for visibility.
14
- * No limits enforced - we trust our customers.
15
- * Keygen tracks validation calls for usage analytics.
16
- */
17
-
18
- // Types for file system storage
19
-
20
- // Protected storage directory - separate from devtools to survive "Clear Settings"
21
- const LICENSE_STORAGE_DIR = "buoy-license";
22
-
23
- // Cached modules
24
- let _FileSystem = null;
25
- let _licenseStorage = null;
26
- const _memoryStore = new Map();
27
-
28
- /**
29
- * Load expo-file-system module
30
- */
31
- function loadFileSystem() {
32
- if (_FileSystem) return _FileSystem;
33
-
34
- // Try legacy import first (v19+)
35
- try {
36
- const mod = require("expo-file-system/legacy");
37
- _FileSystem = mod?.default || mod;
38
- if (_FileSystem?.documentDirectory) {
39
- return _FileSystem;
40
- }
41
- _FileSystem = null;
42
- } catch {
43
- // Not available
44
- }
45
-
46
- // Fall back to main import
47
- try {
48
- const mod = require("expo-file-system");
49
- _FileSystem = mod?.default || mod;
50
- if (_FileSystem?.documentDirectory) {
51
- return _FileSystem;
52
- }
53
- _FileSystem = null;
54
- } catch {
55
- // Not available
56
- }
57
- return null;
58
- }
59
-
60
- /**
61
- * Get the protected license storage directory path
62
- */
63
- function getLicenseStorageDir() {
64
- const fs = loadFileSystem();
65
- if (!fs?.documentDirectory) return null;
66
- return `${fs.documentDirectory}${LICENSE_STORAGE_DIR}/`;
67
- }
68
-
69
- /**
70
- * Get file path for a license storage key
71
- */
72
- function getLicenseFilePath(key) {
73
- const dir = getLicenseStorageDir();
74
- if (!dir) return null;
75
- return `${dir}${encodeURIComponent(key)}.json`;
76
- }
77
-
78
- /**
79
- * Get protected license storage (file system based, separate from devtools)
80
- * This storage survives app cache clears AND devtools "Clear Settings"
81
- */
82
- async function getLicenseStorage() {
83
- if (_licenseStorage) return _licenseStorage;
84
- const fs = loadFileSystem();
85
- if (fs?.documentDirectory) {
86
- // Use file system storage in protected directory
87
- _licenseStorage = {
88
- async getItem(key) {
89
- const filePath = getLicenseFilePath(key);
90
- if (!filePath) return null;
91
- try {
92
- const fileInfo = await fs.getInfoAsync(filePath);
93
- if (!fileInfo.exists) return null;
94
- return await fs.readAsStringAsync(filePath);
95
- } catch {
96
- return null;
97
- }
98
- },
99
- async setItem(key, value) {
100
- const storageDir = getLicenseStorageDir();
101
- const filePath = getLicenseFilePath(key);
102
- if (!storageDir || !filePath) return;
103
- try {
104
- // Ensure directory exists
105
- const dirInfo = await fs.getInfoAsync(storageDir);
106
- if (!dirInfo.exists) {
107
- await fs.makeDirectoryAsync(storageDir, {
108
- intermediates: true
109
- });
110
- }
111
- await fs.writeAsStringAsync(filePath, value);
112
- } catch (error) {
113
- console.warn("[Buoy] License storage write failed:", error);
114
- }
115
- },
116
- async removeItem(key) {
117
- const filePath = getLicenseFilePath(key);
118
- if (!filePath) return;
119
- try {
120
- await fs.deleteAsync(filePath, {
121
- idempotent: true
122
- });
123
- } catch {
124
- // Ignore
125
- }
126
- }
127
- };
128
- } else {
129
- // Fallback to memory storage
130
- _licenseStorage = {
131
- async getItem(key) {
132
- return _memoryStore.get(key) ?? null;
133
- },
134
- async setItem(key, value) {
135
- _memoryStore.set(key, value);
136
- },
137
- async removeItem(key) {
138
- _memoryStore.delete(key);
139
- }
140
- };
141
- }
142
- return _licenseStorage;
143
- }
144
- /** Info passed to device registration callback */
145
-
146
- /** Callback type for device registration events */
147
-
148
- /** Pending registration info for async completion */
149
-
150
- class LicenseManagerImpl {
151
- state = {
152
- isInitialized: false,
153
- isValidating: false,
154
- isPro: false,
155
- licenseKey: null,
156
- error: null,
157
- expiresAt: null
158
- };
159
- listeners = new Set();
160
- revalidationTimer = null;
161
- cachedLicense = null;
162
-
163
- // Promise for initialization to prevent double-init race condition
164
- initPromise = null;
165
-
166
- // Device registration callback for UI integration
167
- deviceRegistrationCallback = null;
168
- pendingRegistration = null;
169
-
170
- /**
171
- * Initialize the license manager and load cached license
172
- */
173
- async initialize() {
174
- // Return existing promise if already initializing
175
- if (this.initPromise) return this.initPromise;
176
- if (this.state.isInitialized) return;
177
-
178
- // Store the promise so concurrent calls wait for the same initialization
179
- this.initPromise = this._doInitialize();
180
- return this.initPromise;
181
- }
182
-
183
- /**
184
- * Internal initialization logic
185
- */
186
- async _doInitialize() {
187
- if (this.state.isInitialized) return;
188
- try {
189
- // Load cached license from storage
190
- const cached = await this.loadCachedLicense();
191
- if (cached) {
192
- this.cachedLicense = cached;
193
- this.state.licenseKey = cached.licenseKey;
194
- this.state.isPro = cached.isPro && this.isCacheValid(cached);
195
- this.state.expiresAt = cached.expiresAt ? new Date(cached.expiresAt) : null;
196
-
197
- // If we have a cached license, validate in background
198
- if (cached.licenseKey) {
199
- this.validateInBackground(cached.licenseKey);
200
- }
201
- }
202
- this.state.isInitialized = true;
203
- this.emit({
204
- type: "initialized",
205
- isPro: this.state.isPro
206
- });
207
-
208
- // Start periodic revalidation (once per day for usage analytics)
209
- this.startRevalidationTimer();
210
- } catch (error) {
211
- console.warn("[React Buoy] License initialization failed:", error);
212
- this.state.isInitialized = true;
213
- this.emit({
214
- type: "initialized",
215
- isPro: false
216
- });
217
- }
218
- }
219
-
220
- /**
221
- * Set and validate a license key.
222
- *
223
- * Simple validation - just checks if the key is valid.
224
- * No device registration or limits - we trust our customers.
225
- * Keygen tracks validation calls for usage analytics.
226
- */
227
- async setLicenseKey(licenseKey) {
228
- if (!licenseKey || licenseKey.trim().length === 0) {
229
- this.state.error = "License key is required";
230
- return false;
231
- }
232
-
233
- // Normalize license key
234
- const normalizedKey = licenseKey.trim().toUpperCase();
235
- this.state.licenseKey = normalizedKey;
236
- this.state.isValidating = true;
237
- this.state.error = null;
238
- this.emit({
239
- type: "validating",
240
- isPro: this.state.isPro
241
- });
242
- try {
243
- // Validate the license (just check if key is valid)
244
- const response = await (0, _api.validateLicense)(normalizedKey);
245
- const result = (0, _api.parseValidationResult)(response);
246
-
247
- // Check for fatal errors (expired, suspended, not found, etc.)
248
- if ((0, _api.isFatalLicenseError)(result.code)) {
249
- this.state.isValidating = false;
250
- this.state.error = result.detail;
251
- await this.clearCachedLicense();
252
- this.emit({
253
- type: "error",
254
- isPro: false,
255
- error: result.detail
256
- });
257
- return false;
258
- }
259
-
260
- // If response.data is null, the license is invalid
261
- if (!response.data) {
262
- this.state.isValidating = false;
263
- this.state.error = result.detail || "License not found";
264
- await this.clearCachedLicense();
265
- this.emit({
266
- type: "error",
267
- isPro: false,
268
- error: this.state.error
269
- });
270
- return false;
271
- }
272
-
273
- // License is valid!
274
- this.state.isPro = true;
275
- this.state.isValidating = false;
276
- this.state.expiresAt = response.data.attributes.expiry ? new Date(response.data.attributes.expiry) : null;
277
- const licenseId = response.data.id;
278
- const now = Date.now();
279
-
280
- // Get or generate fingerprint for this device
281
- let fingerprint = this.cachedLicense?.fingerprint;
282
- let deviceName = this.cachedLicense?.deviceName;
283
- let needsRegistration = false;
284
- if (!fingerprint) {
285
- // First time on this device - generate fingerprint and register
286
- fingerprint = (0, _fingerprint.generateUUID)();
287
- deviceName = (0, _fingerprint.getDefaultDeviceName)();
288
- needsRegistration = true;
289
- }
290
-
291
- // Only register if this is a new device (no cached fingerprint)
292
- let deferCaching = false;
293
- if (needsRegistration) {
294
- if (this.deviceRegistrationCallback) {
295
- // UI callback registered - defer to UI for device naming
296
- // Don't cache fingerprint yet - wait for completeDeviceRegistration
297
- this.pendingRegistration = {
298
- licenseId,
299
- licenseKey: normalizedKey,
300
- fingerprint
301
- };
302
- this.deviceRegistrationCallback({
303
- fingerprint,
304
- defaultName: deviceName
305
- });
306
- deferCaching = true;
307
- } else {
308
- // No UI callback - register immediately with default name
309
- this.registerDeviceAsync(licenseId, normalizedKey, fingerprint, deviceName);
310
- }
311
- }
312
-
313
- // Cache the validation result
314
- const storage = await getLicenseStorage();
315
- const cached = {
316
- licenseKey: normalizedKey,
317
- licenseId,
318
- valid: true,
319
- isPro: true,
320
- expiresAt: response.data.attributes.expiry ?? null,
321
- cachedAt: now,
322
- lastValidatedAt: now,
323
- // Don't cache fingerprint if we're deferring to UI for registration
324
- fingerprint: deferCaching ? undefined : fingerprint,
325
- deviceName: deferCaching ? undefined : deviceName
326
- };
327
- await storage.setItem(_config.LICENSE_STORAGE_KEY, JSON.stringify(cached));
328
- this.cachedLicense = cached;
329
- this.emit({
330
- type: "validated",
331
- isPro: true
332
- });
333
- return true;
334
- } catch (error) {
335
- const errorMessage = error instanceof Error ? error.message : "Validation failed";
336
-
337
- // Check for network error and use cache
338
- if (this.isNetworkError(error) && this.cachedLicense && this.isCacheValid(this.cachedLicense)) {
339
- this.state.isPro = this.cachedLicense.isPro;
340
- this.state.isValidating = false;
341
- this.emit({
342
- type: "validated",
343
- isPro: this.state.isPro
344
- });
345
- return this.state.isPro;
346
- }
347
- this.state.isValidating = false;
348
- this.state.error = errorMessage;
349
- this.emit({
350
- type: "error",
351
- isPro: false,
352
- error: errorMessage
353
- });
354
- return false;
355
- }
356
- }
357
-
358
- /**
359
- * Check if an error is a network error (not an API validation error)
360
- */
361
- isNetworkError(error) {
362
- if (error instanceof TypeError) {
363
- return true;
364
- }
365
- if (error instanceof Error) {
366
- const message = error.message.toLowerCase();
367
- return message.includes("network") || message.includes("fetch") || message.includes("timeout") || message.includes("connection") || message.includes("offline") || message.includes("failed to fetch");
368
- }
369
- return false;
370
- }
371
-
372
- /**
373
- * Clear the current license
374
- */
375
- async clearLicense() {
376
- this.state.licenseKey = null;
377
- this.state.isPro = false;
378
- this.state.expiresAt = null;
379
- this.state.error = null;
380
- this.cachedLicense = null;
381
- await this.clearCachedLicense();
382
- this.emit({
383
- type: "deactivated",
384
- isPro: false
385
- });
386
- }
387
-
388
- /**
389
- * Check if the user has Pro features
390
- */
391
- isPro() {
392
- return this.state.isPro;
393
- }
394
-
395
- /**
396
- * Check if a specific entitlement is available
397
- */
398
- hasEntitlement(code) {
399
- if (code === "PRO") {
400
- return this.state.isPro;
401
- }
402
- return this.state.isPro;
403
- }
404
-
405
- /**
406
- * Get current license state
407
- */
408
- getState() {
409
- return {
410
- ...this.state
411
- };
412
- }
413
-
414
- /**
415
- * Subscribe to license events
416
- */
417
- subscribe(listener) {
418
- this.listeners.add(listener);
419
- return () => this.listeners.delete(listener);
420
- }
421
-
422
- /**
423
- * Subscribe to device registration events
424
- * When a new device needs to be registered, the callback is invoked
425
- * with the fingerprint and default name suggestion.
426
- * Call completeDeviceRegistration() to finish registration with user's chosen name.
427
- */
428
- onDeviceRegistrationNeeded(callback) {
429
- this.deviceRegistrationCallback = callback;
430
- return () => {
431
- this.deviceRegistrationCallback = null;
432
- };
433
- }
434
-
435
- /**
436
- * Complete device registration with user-provided name
437
- * Call this after onDeviceRegistrationNeeded callback to finish registration
438
- */
439
- async completeDeviceRegistration(deviceName) {
440
- if (!this.pendingRegistration) {
441
- console.warn("[Buoy] No pending device registration");
442
- return;
443
- }
444
- const {
445
- licenseId,
446
- licenseKey,
447
- fingerprint
448
- } = this.pendingRegistration;
449
- const name = deviceName.trim() || (0, _fingerprint.getDefaultDeviceName)();
450
-
451
- // Register the device and wait for completion
452
- const metadata = (0, _fingerprint.getDeviceMetadata)();
453
- try {
454
- await (0, _api.activateMachine)(licenseId, licenseKey, fingerprint, name, metadata);
455
- } catch (error) {
456
- const errorMessage = error instanceof Error ? error.message : String(error);
457
- // Handle expected "errors" that aren't really errors
458
- if (errorMessage === "FINGERPRINT_TAKEN" || errorMessage === "MACHINE_LIMIT_EXCEEDED") {
459
- // Device already registered or limit exceeded but we're not enforcing - continue
460
- } else {
461
- console.warn("[Buoy] Device registration failed:", errorMessage);
462
- throw error; // Re-throw unexpected errors
463
- }
464
- }
465
-
466
- // Update cached license with device info
467
- if (this.cachedLicense) {
468
- this.cachedLicense.fingerprint = fingerprint;
469
- this.cachedLicense.deviceName = name;
470
- const storage = await getLicenseStorage();
471
- await storage.setItem(_config.LICENSE_STORAGE_KEY, JSON.stringify(this.cachedLicense));
472
- }
473
- this.pendingRegistration = null;
474
- }
475
-
476
- // Private methods
477
-
478
- emit(event) {
479
- // Iterate over a copy to prevent issues if listeners modify the Set
480
- const listeners = [...this.listeners];
481
- listeners.forEach(listener => {
482
- try {
483
- listener(event);
484
- } catch (error) {
485
- console.warn("[React Buoy] License listener error:", error);
486
- }
487
- });
488
- }
489
- isCacheValid(cached) {
490
- const now = Date.now();
491
- const cacheAge = now - cached.cachedAt;
492
- if (cacheAge > _config.LICENSE_CACHE_DURATION) {
493
- return false;
494
- }
495
- if (cached.expiresAt) {
496
- const expiry = new Date(cached.expiresAt).getTime();
497
- if (now > expiry) {
498
- return false;
499
- }
500
- }
501
- return true;
502
- }
503
- async loadCachedLicense() {
504
- try {
505
- const storage = await getLicenseStorage();
506
- const data = await storage.getItem(_config.LICENSE_STORAGE_KEY);
507
- if (!data) return null;
508
- const cached = JSON.parse(data);
509
- return cached;
510
- } catch (error) {
511
- console.warn("[Buoy] Error loading cached license:", error);
512
- return null;
513
- }
514
- }
515
- async clearCachedLicense() {
516
- this.cachedLicense = null;
517
- try {
518
- const storage = await getLicenseStorage();
519
- await storage.removeItem(_config.LICENSE_STORAGE_KEY);
520
- } catch {
521
- // Ignore
522
- }
523
- }
524
-
525
- /**
526
- * Register device in background for visibility (non-blocking)
527
- * Failures don't affect Pro status - this is just for analytics
528
- */
529
- registerDeviceAsync(licenseId, licenseKey, fingerprint, deviceName) {
530
- const metadata = (0, _fingerprint.getDeviceMetadata)();
531
- (0, _api.activateMachine)(licenseId, licenseKey, fingerprint, deviceName, metadata).catch(error => {
532
- // Don't block on registration failures - user still gets Pro
533
- if (error.message !== "FINGERPRINT_TAKEN" && error.message !== "MACHINE_LIMIT_EXCEEDED") {
534
- console.warn("[Buoy] Device registration failed (non-blocking):", error.message);
535
- }
536
- });
537
- }
538
- validateInBackground(licenseKey) {
539
- (0, _api.validateLicense)(licenseKey).then(async response => {
540
- const result = (0, _api.parseValidationResult)(response);
541
- if (result.valid && response.data) {
542
- this.state.isPro = true;
543
- this.emit({
544
- type: "validated",
545
- isPro: true
546
- });
547
- } else if ((0, _api.isFatalLicenseError)(result.code)) {
548
- // License is truly invalid - clear everything
549
- console.warn("[React Buoy] Background validation: license is invalid -", result.code);
550
- this.state.isPro = false;
551
- await this.clearCachedLicense();
552
- this.emit({
553
- type: "deactivated",
554
- isPro: false
555
- });
556
- }
557
- }).catch(error => {
558
- // Network errors - don't clear anything, just log
559
- console.warn("[React Buoy] Background validation failed:", error);
560
- });
561
- }
562
- startRevalidationTimer() {
563
- if (this.revalidationTimer) {
564
- clearInterval(this.revalidationTimer);
565
- }
566
- this.revalidationTimer = setInterval(() => {
567
- if (this.state.licenseKey && !this.state.isValidating) {
568
- this.validateInBackground(this.state.licenseKey);
569
- }
570
- }, _config.LICENSE_REVALIDATION_INTERVAL);
571
-
572
- // Store for hot reload cleanup
573
- if (__DEV__) {
574
- global.__LICENSE_MANAGER_TIMER__ = this.revalidationTimer;
575
- }
576
- }
577
-
578
- /**
579
- * Stop the revalidation timer
580
- */
581
- stopRevalidationTimer() {
582
- if (this.revalidationTimer) {
583
- clearInterval(this.revalidationTimer);
584
- this.revalidationTimer = null;
585
- }
586
- }
587
-
588
- /**
589
- * Force re-registration by clearing cached fingerprint
590
- * (For debugging/testing - forces device to re-register on next validation)
591
- */
592
- async forceReregister() {
593
- if (this.cachedLicense) {
594
- this.cachedLicense.fingerprint = undefined;
595
- this.cachedLicense.deviceName = undefined;
596
- }
597
- await this.clearCachedLicense();
598
- }
599
- }
600
-
601
- // Singleton instance
602
- const LicenseManager = exports.LicenseManager = new LicenseManagerImpl();
603
-
604
- // Hot reload cleanup - prevent timer leaks during development
605
-
606
- if (__DEV__) {
607
- // Clear any existing timer from previous hot reload
608
- if (global.__LICENSE_MANAGER_TIMER__) {
609
- clearInterval(global.__LICENSE_MANAGER_TIMER__);
610
- }
611
- }
4
+ * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
5
+ * (https://buoy.gg/terms), not an OSS license.
6
+ *
7
+ * This file is the technological measure that controls access to paid Buoy Pro
8
+ * features. Modifying, patching, aliasing, stubbing, or otherwise altering it
9
+ * so that Pro features unlock without a valid license is a breach of the EULA
10
+ * and, in the United States, circumvention of an access control under
11
+ * 17 U.S.C. § 1201 — which applies whether or not any code is copied.
12
+ *
13
+ * Pro is $9/mo, and is free for everyone every weekend. Buy a key at
14
+ * https://buoy.gg or use it free on Saturday and Sunday.
15
+ */const LICENSE_STORAGE_DIR="buoy-license";let _FileSystem=null;let _licenseStorage=null;const _memoryStore=new Map;function loadFileSystem(){if(_FileSystem)return _FileSystem;try{const a=require("expo-file-system/legacy");_FileSystem=a?.default||a;if(_FileSystem?.documentDirectory){return _FileSystem}_FileSystem=null}catch{}try{const a=require("expo-file-system");_FileSystem=a?.default||a;if(_FileSystem?.documentDirectory){return _FileSystem}_FileSystem=null}catch{}return null}function getLicenseStorageDir(){const a=loadFileSystem();if(!a?.documentDirectory)return null;return`${a.documentDirectory}${LICENSE_STORAGE_DIR}/`}function getLicenseFilePath(a){const e=getLicenseStorageDir();if(!e)return null;return`${e}${encodeURIComponent(a)}.json`}async function getLicenseStorage(){if(_licenseStorage)return _licenseStorage;const a=loadFileSystem();if(a?.documentDirectory){_licenseStorage={async getItem(e){const t=getLicenseFilePath(e);if(!t)return null;try{const i=await a.getInfoAsync(t);if(!i.exists)return null;return await a.readAsStringAsync(t)}catch{return null}},async setItem(e,t){const i=getLicenseStorageDir();const s=getLicenseFilePath(e);if(!i||!s)return;try{const r=await a.getInfoAsync(i);if(!r.exists){await a.makeDirectoryAsync(i,{intermediates:true})}await a.writeAsStringAsync(s,t)}catch(r){console.warn("[Buoy] License storage write failed:",r)}},async removeItem(e){const t=getLicenseFilePath(e);if(!t)return;try{await a.deleteAsync(t,{idempotent:true})}catch{}}}}else{_licenseStorage={async getItem(e){return _memoryStore.get(e)??null},async setItem(e,t){_memoryStore.set(e,t)},async removeItem(e){_memoryStore.delete(e)}}}return _licenseStorage}function tierOfCache(a){if(a.tier)return a.tier;return a.isPro?"pro":"anonymous"}class LicenseManagerImpl{state={isInitialized:false,isValidating:false,isPro:false,tier:"anonymous",tierSource:"none",licenseKey:null,error:null,expiresAt:null};listeners=new Set;revalidationTimer=null;cachedLicense=null;initPromise=null;deviceRegistrationCallback=null;pendingRegistration=null;async initialize(){if(this.initPromise)return this.initPromise;if(this.state.isInitialized)return;this.initPromise=this._doInitialize();return this.initPromise}async _doInitialize(){if(this.state.isInitialized)return;try{const e=await this.loadCachedLicense();if(e){const t=(0,_cacheSignature.verifyCacheSignature)(e);this.cachedLicense=e;this.state.licenseKey=e.licenseKey;this.applyTier(t&&this.isCacheValid(e)?tierOfCache(e):"anonymous");this.state.expiresAt=e.expiresAt?new Date(e.expiresAt):null;if(e.licenseKey){this.validateInBackground(e.licenseKey)}}this.state.isInitialized=true;this.emit({type:"initialized",isPro:this.state.isPro});this.startRevalidationTimer()}catch(e){console.warn("[React Buoy] License initialization failed:",e);this.state.isInitialized=true;this.emit({type:"initialized",isPro:false})}}async setLicenseKey(e){if(!e||e.trim().length===0){this.state.error="License key is required";return false}const t=e.trim().toUpperCase();this.state.licenseKey=t;this.state.isValidating=true;this.state.error=null;this.emit({type:"validating",isPro:this.state.isPro});try{const i=await(0,_api.validateLicense)(t);const s=(0,_api.parseValidationResult)(i);if((0,_api.isFatalLicenseError)(s.code)){this.applyTier((0,_tier.tierAfterFatalError)(s.code));this.state.isValidating=false;this.state.error=s.detail;await this.clearCachedLicense();this.emit({type:"error",isPro:false,error:s.detail});return false}if(!i.data){this.state.isValidating=false;this.state.error=s.detail||"License not found";await this.clearCachedLicense();this.emit({type:"error",isPro:false,error:this.state.error});return false}const r=(0,_tier.resolveTier)(i.data);this.applyTier(r);this.state.isValidating=false;this.state.expiresAt=i.data.attributes.expiry?new Date(i.data.attributes.expiry):null;const c=i.data.id;const l=Date.now();let n=this.cachedLicense?.fingerprint;let o=this.cachedLicense?.deviceName;let h=false;if(!n){n=(0,_fingerprint.generateUUID)();o=(0,_fingerprint.getDefaultDeviceName)();h=true}let d=false;if(h){if(this.deviceRegistrationCallback){this.pendingRegistration={licenseId:c,licenseKey:t,fingerprint:n};this.deviceRegistrationCallback({fingerprint:n,defaultName:o});d=true}else{this.registerDeviceAsync(c,t,n,o)}}const u={licenseKey:t,licenseId:c,valid:true,isPro:r==="pro",tier:r,expiresAt:i.data.attributes.expiry??null,cachedAt:l,lastValidatedAt:l,fingerprint:d?void 0:n,deviceName:d?void 0:o};this.cachedLicense=await this.persistCache(u);this.emit({type:"validated",isPro:this.state.isPro});return true}catch(i){const s=i instanceof Error?i.message:"Validation failed";if(this.isNetworkError(i)&&this.cachedLicense&&this.isCacheValid(this.cachedLicense)){this.applyTier(tierOfCache(this.cachedLicense));this.state.isValidating=false;this.emit({type:"validated",isPro:this.state.isPro});return true}this.state.isValidating=false;this.state.error=s;this.emit({type:"error",isPro:false,error:s});return false}}isNetworkError(e){if(e instanceof TypeError){return true}if(e instanceof Error){const t=e.message.toLowerCase();return t.includes("network")||t.includes("fetch")||t.includes("timeout")||t.includes("connection")||t.includes("offline")||t.includes("failed to fetch")}return false}async clearLicense(){this.state.licenseKey=null;this.applyTier("anonymous");this.state.expiresAt=null;this.state.error=null;this.cachedLicense=null;await this.clearCachedLicense();this.emit({type:"deactivated",isPro:false})}applyTier(e){this.state.tier=e;this.state.tierSource=e==="anonymous"?"none":"key";this.state.isPro=e==="pro"}getTier(){return this.state.tier}isPro(){return this.state.tier==="pro"}hasEntitlement(e){if(e==="PRO"){return this.state.isPro}return this.state.isPro}getState(){return{...this.state}}subscribe(e){this.listeners.add(e);return()=>this.listeners.delete(e)}onDeviceRegistrationNeeded(e){this.deviceRegistrationCallback=e;return()=>{this.deviceRegistrationCallback=null}}async completeDeviceRegistration(e){if(!this.pendingRegistration){console.warn("[Buoy] No pending device registration");return}const{licenseId:t,licenseKey:i,fingerprint:s}=this.pendingRegistration;const r=e.trim()||(0,_fingerprint.getDefaultDeviceName)();const c=(0,_fingerprint.getDeviceMetadata)();try{await(0,_api.activateMachine)(t,i,s,r,c)}catch(l){const n=l instanceof Error?l.message:String(l);if(n==="FINGERPRINT_TAKEN"||n==="MACHINE_LIMIT_EXCEEDED"){}else{console.warn("[Buoy] Device registration failed:",n);throw l}}if(this.cachedLicense){this.cachedLicense.fingerprint=s;this.cachedLicense.deviceName=r;this.cachedLicense=await this.persistCache(this.cachedLicense)}this.pendingRegistration=null}emit(e){const t=[...this.listeners];t.forEach(i=>{try{i(e)}catch(s){console.warn("[React Buoy] License listener error:",s)}})}isCacheValid(e){const t=Date.now();const i=t-e.cachedAt;if(i>_config.LICENSE_CACHE_DURATION){return false}if(e.expiresAt){const s=new Date(e.expiresAt).getTime();if(t>s){return false}}return true}async persistCache(e){const t=(0,_cacheSignature.signCache)(e);const i=await getLicenseStorage();await i.setItem(_config.LICENSE_STORAGE_KEY,JSON.stringify(t));return t}async loadCachedLicense(){try{const e=await getLicenseStorage();const t=await e.getItem(_config.LICENSE_STORAGE_KEY);if(!t)return null;const i=JSON.parse(t);return i}catch(e){console.warn("[Buoy] Error loading cached license:",e);return null}}async clearCachedLicense(){this.cachedLicense=null;try{const e=await getLicenseStorage();await e.removeItem(_config.LICENSE_STORAGE_KEY)}catch{}}registerDeviceAsync(e,t,i,s){const r=(0,_fingerprint.getDeviceMetadata)();(0,_api.activateMachine)(e,t,i,s,r).catch(c=>{if(c.message!=="FINGERPRINT_TAKEN"&&c.message!=="MACHINE_LIMIT_EXCEEDED"){console.warn("[Buoy] Device registration failed (non-blocking):",c.message)}})}validateInBackground(e){(0,_api.validateLicense)(e).then(async t=>{const i=(0,_api.parseValidationResult)(t);if(i.valid&&t.data){const s=(0,_tier.resolveTier)(t.data);this.applyTier(s);const r=Date.now();this.cachedLicense=await this.persistCache({...this.cachedLicense??{},licenseKey:e,licenseId:t.data.id,valid:true,isPro:s==="pro",tier:s,expiresAt:t.data.attributes.expiry??null,cachedAt:r,lastValidatedAt:r});this.state.expiresAt=t.data.attributes.expiry?new Date(t.data.attributes.expiry):null;this.emit({type:"validated",isPro:this.state.isPro})}else if((0,_api.isFatalLicenseError)(i.code)){console.warn("[React Buoy] Background validation: license is invalid -",i.code);this.applyTier((0,_tier.tierAfterFatalError)(i.code));await this.clearCachedLicense();this.emit({type:"deactivated",isPro:false})}}).catch(t=>{console.warn("[React Buoy] Background validation failed:",t)})}startRevalidationTimer(){if(this.revalidationTimer){clearInterval(this.revalidationTimer)}this.revalidationTimer=setInterval(()=>{if(this.state.licenseKey&&!this.state.isValidating){this.validateInBackground(this.state.licenseKey)}},_config.LICENSE_REVALIDATION_INTERVAL);if(__DEV__){global.__LICENSE_MANAGER_TIMER__=this.revalidationTimer}}stopRevalidationTimer(){if(this.revalidationTimer){clearInterval(this.revalidationTimer);this.revalidationTimer=null}}async forceReregister(){if(this.cachedLicense){this.cachedLicense.fingerprint=void 0;this.cachedLicense.deviceName=void 0}await this.clearCachedLicense()}}const LicenseManager=exports.LicenseManager=new LicenseManagerImpl;if(__DEV__){if(global.__LICENSE_MANAGER_TIMER__){clearInterval(global.__LICENSE_MANAGER_TIMER__)}}