@buoy-gg/license 7.0.21 → 7.0.23

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