@buoy-gg/license 7.0.41 → 7.0.43

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 (86) hide show
  1. package/lib/commonjs/DeviceRegistrationModal.js +1 -1
  2. package/lib/commonjs/LicenseManager.js +16 -16
  3. package/lib/commonjs/accountAccess.js +1 -1
  4. package/lib/commonjs/api.js +1 -1
  5. package/lib/commonjs/cacheSignature.js +15 -15
  6. package/lib/commonjs/config.js +16 -16
  7. package/lib/commonjs/freeToken.js +5 -5
  8. package/lib/commonjs/hooks.js +16 -16
  9. package/lib/commonjs/index.js +2 -2
  10. package/lib/commonjs/index.web.js +1 -1
  11. package/lib/commonjs/realNow.js +1 -0
  12. package/lib/commonjs/storage.js +1 -1
  13. package/lib/commonjs/tier.js +16 -16
  14. package/lib/commonjs/weekendPro.js +18 -18
  15. package/lib/module/DeviceRegistrationModal.js +1 -1
  16. package/lib/module/LicenseManager.js +15 -15
  17. package/lib/module/accountAccess.js +1 -1
  18. package/lib/module/api.js +1 -1
  19. package/lib/module/cacheSignature.js +15 -15
  20. package/lib/module/config.js +16 -16
  21. package/lib/module/device-info.web.js +1 -1
  22. package/lib/module/freeToken.js +5 -5
  23. package/lib/module/hooks.js +16 -16
  24. package/lib/module/index.js +17 -17
  25. package/lib/module/index.web.js +17 -17
  26. package/lib/module/realNow.js +1 -0
  27. package/lib/module/storage.js +1 -1
  28. package/lib/module/storage.web.js +1 -1
  29. package/lib/module/tier.js +16 -16
  30. package/lib/module/weekendPro.js +17 -17
  31. package/lib/typescript/commonjs/realNow.d.ts +11 -0
  32. package/lib/typescript/module/realNow.d.ts +11 -0
  33. package/lib/web/chunks/LicenseManager-7D2GGBCB.mjs +1 -0
  34. package/lib/web/chunks/chunk-ZSTLYSNF.mjs +68 -0
  35. package/lib/web/index.mjs +3 -7
  36. package/package.json +2 -5
  37. package/lib/typescript/commonjs/DeviceRegistrationModal.d.ts.map +0 -1
  38. package/lib/typescript/commonjs/LicenseManager.d.ts.map +0 -1
  39. package/lib/typescript/commonjs/accountAccess.d.ts.map +0 -1
  40. package/lib/typescript/commonjs/api.d.ts.map +0 -1
  41. package/lib/typescript/commonjs/cacheSignature.d.ts.map +0 -1
  42. package/lib/typescript/commonjs/config.d.ts.map +0 -1
  43. package/lib/typescript/commonjs/device-info.d.ts.map +0 -1
  44. package/lib/typescript/commonjs/device-info.web.d.ts.map +0 -1
  45. package/lib/typescript/commonjs/fingerprint.d.ts.map +0 -1
  46. package/lib/typescript/commonjs/fingerprint.web.d.ts.map +0 -1
  47. package/lib/typescript/commonjs/foreground.d.ts.map +0 -1
  48. package/lib/typescript/commonjs/foreground.web.d.ts.map +0 -1
  49. package/lib/typescript/commonjs/freeToken.d.ts.map +0 -1
  50. package/lib/typescript/commonjs/headless.d.ts.map +0 -1
  51. package/lib/typescript/commonjs/hooks.d.ts.map +0 -1
  52. package/lib/typescript/commonjs/index.d.ts.map +0 -1
  53. package/lib/typescript/commonjs/index.web.d.ts.map +0 -1
  54. package/lib/typescript/commonjs/storage.d.ts.map +0 -1
  55. package/lib/typescript/commonjs/storage.web.d.ts.map +0 -1
  56. package/lib/typescript/commonjs/tier.d.ts.map +0 -1
  57. package/lib/typescript/commonjs/types.d.ts.map +0 -1
  58. package/lib/typescript/commonjs/weekendPro.d.ts.map +0 -1
  59. package/lib/typescript/module/DeviceRegistrationModal.d.ts.map +0 -1
  60. package/lib/typescript/module/LicenseManager.d.ts.map +0 -1
  61. package/lib/typescript/module/accountAccess.d.ts.map +0 -1
  62. package/lib/typescript/module/api.d.ts.map +0 -1
  63. package/lib/typescript/module/cacheSignature.d.ts.map +0 -1
  64. package/lib/typescript/module/config.d.ts.map +0 -1
  65. package/lib/typescript/module/device-info.d.ts.map +0 -1
  66. package/lib/typescript/module/device-info.web.d.ts.map +0 -1
  67. package/lib/typescript/module/fingerprint.d.ts.map +0 -1
  68. package/lib/typescript/module/fingerprint.web.d.ts.map +0 -1
  69. package/lib/typescript/module/foreground.d.ts.map +0 -1
  70. package/lib/typescript/module/foreground.web.d.ts.map +0 -1
  71. package/lib/typescript/module/freeToken.d.ts.map +0 -1
  72. package/lib/typescript/module/headless.d.ts.map +0 -1
  73. package/lib/typescript/module/hooks.d.ts.map +0 -1
  74. package/lib/typescript/module/index.d.ts.map +0 -1
  75. package/lib/typescript/module/index.web.d.ts.map +0 -1
  76. package/lib/typescript/module/storage.d.ts.map +0 -1
  77. package/lib/typescript/module/storage.web.d.ts.map +0 -1
  78. package/lib/typescript/module/tier.d.ts.map +0 -1
  79. package/lib/typescript/module/types.d.ts.map +0 -1
  80. package/lib/typescript/module/weekendPro.d.ts.map +0 -1
  81. package/lib/web/chunks/LicenseManager-AEFCYYMQ.mjs +0 -7
  82. package/lib/web/chunks/LicenseManager-JHLFWSUD.mjs +0 -2
  83. package/lib/web/chunks/LicenseManager-VWEJJHBH.mjs +0 -7
  84. package/lib/web/chunks/chunk-GRUXDUJG.mjs +0 -74
  85. package/lib/web/chunks/chunk-OPWF6JR6.mjs +0 -1480
  86. package/lib/web/chunks/chunk-TVOTAEQX.mjs +0 -1483
@@ -1,1480 +0,0 @@
1
- "use client";
2
-
3
- // ../../scripts/web-environment.js
4
- var __DEV__ = process.env.NODE_ENV !== "production";
5
-
6
- // src/storage.web.ts
7
- var memory = /* @__PURE__ */ new Map();
8
- var storage = {
9
- async getItem(key) {
10
- try {
11
- return localStorage.getItem(key) ?? memory.get(key) ?? null;
12
- } catch {
13
- return memory.get(key) ?? null;
14
- }
15
- },
16
- async setItem(key, value) {
17
- memory.set(key, value);
18
- try {
19
- localStorage.setItem(key, value);
20
- } catch {
21
- }
22
- },
23
- async removeItem(key) {
24
- memory.delete(key);
25
- try {
26
- localStorage.removeItem(key);
27
- } catch {
28
- }
29
- }
30
- };
31
- async function getLicenseStorage() {
32
- return storage;
33
- }
34
-
35
- // src/config.ts
36
- var KEYGEN_CONFIG = {
37
- accountId: "b0eba0e8-e904-4906-b644-38f9aab57bed",
38
- /**
39
- * The Keygen PRODUCT id, used to check that a validated license belongs to
40
- * Buoy (see `isAcceptedValidation`).
41
- *
42
- * Careful: this field previously held `1037431d-b2fe-48db-aca9-10c5e9c4d944`,
43
- * which is not a product at all — it was the ID of a product token.
44
- * Verified against the Keygen dashboard (Aug 2026): the account has exactly
45
- * one product, "Buoy", and this is its id.
46
- */
47
- productId: "bf67cb43-eeef-40ca-b308-e6e1b22c0eaa",
48
- /**
49
- * Buoy's license endpoint. It sits in front of Keygen and answers from a
50
- * cache, so the license server hears about a key about once an hour no
51
- * matter how many devices hold it; Keygen's response body comes back
52
- * unchanged. Device registration goes through it too, which is why no
53
- * product token ships in this package any more: the token used to be
54
- * embedded here, and anything embedded in an app is public.
55
- */
56
- apiUrl: "https://buoy.gg/api/license"
57
- };
58
- var FREE_TOKEN_PUBLIC_KEY = "3b546fc717a62683f3236c70167899a271bee3ffc04df6ec1eea60a394a2f1e9";
59
- var KEYGEN_POLICIES = {
60
- /** Free tier — issued automatically on first dashboard visit. */
61
- free: "394e3351-1249-4f73-807a-703408604c0e",
62
- /** Solo (sold as "Pro"; the id predates the Solo/Business split). */
63
- solo: "fa5d3c8f-5379-40f1-b63e-ab8a974b2f28",
64
- /** Business — an exact clone of the Solo policy. */
65
- business: "690c2212-8e54-426f-b4b4-002ee1c0ae2b"
66
- };
67
- var LICENSE_CACHE_DURATION = 30 * 24 * 60 * 60 * 1e3;
68
- var LICENSE_REVALIDATION_INTERVAL = 24 * 60 * 60 * 1e3;
69
- var LICENSE_STORAGE_KEY = "buoy-gg-license";
70
- var DEVICE_FINGERPRINT_STORAGE_KEY = "buoy-gg-device-fingerprint";
71
- var LICENSE_LAUNCH_VALIDATION_TTL = 60 * 60 * 1e3;
72
- var LICENSE_TRANSIENT_RETRY_DELAYS = [
73
- 5 * 1e3,
74
- 20 * 1e3,
75
- 60 * 1e3,
76
- 5 * 60 * 1e3,
77
- 15 * 60 * 1e3
78
- ];
79
- var LICENSE_RATE_LIMIT_DEFAULT_WAIT = 60 * 1e3;
80
- var LICENSE_QUOTA_COOLDOWN = 60 * 60 * 1e3;
81
- var LICENSE_VERDICT_CACHE_DURATION = 24 * 60 * 60 * 1e3;
82
- var LICENSE_FLEET_MACHINE_THRESHOLD = 50;
83
- var LICENSE_QUIET_VALIDATION_TTL = LICENSE_CACHE_DURATION;
84
- var LICENSE_HOLD_STORAGE_KEY = "buoy-gg-license-hold";
85
-
86
- // src/api.ts
87
- var { apiUrl } = KEYGEN_CONFIG;
88
- var LicenseTransportError = class extends Error {
89
- constructor(message, status, retryAfterMs = null) {
90
- super(message);
91
- /** Discriminator that survives bundling, subclassing and realm boundaries. */
92
- this.isTransient = true;
93
- this.name = "LicenseTransportError";
94
- this.status = status;
95
- this.retryAfterMs = retryAfterMs;
96
- }
97
- };
98
- function isRateLimited(error) {
99
- const status = error?.status;
100
- return status === 429 || status === 402;
101
- }
102
- function backoffAfter(error, quotaCooldown, defaultWait) {
103
- if (error.status === 402) return quotaCooldown;
104
- return error.retryAfterMs ?? defaultWait;
105
- }
106
- function readRetryAfter(response) {
107
- const get = (name) => {
108
- try {
109
- return response.headers?.get?.(name) ?? null;
110
- } catch {
111
- return null;
112
- }
113
- };
114
- const retryAfter = get("Retry-After");
115
- if (retryAfter) {
116
- const seconds = Number(retryAfter);
117
- if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
118
- const date = Date.parse(retryAfter);
119
- if (Number.isFinite(date)) return Math.max(0, date - Date.now());
120
- }
121
- const reset = Number(get("X-RateLimit-Reset"));
122
- if (Number.isFinite(reset) && reset > 0) return Math.max(0, reset * 1e3 - Date.now());
123
- return null;
124
- }
125
- function isTransientLicenseError(error) {
126
- if (error instanceof LicenseTransportError) return true;
127
- if (error instanceof TypeError) return true;
128
- return typeof error === "object" && error !== null && error.isTransient === true;
129
- }
130
- async function describeFailure(response) {
131
- try {
132
- const errorData = await response.json();
133
- return errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
134
- } catch {
135
- return `HTTP ${response.status}`;
136
- }
137
- }
138
- function getBaseHeaders() {
139
- return {
140
- "Content-Type": "application/vnd.api+json",
141
- Accept: "application/vnd.api+json"
142
- };
143
- }
144
- async function requestValidation(licenseKey, signal) {
145
- const url = `${apiUrl}/validate`;
146
- const body = {
147
- meta: {
148
- key: licenseKey
149
- }
150
- };
151
- let response;
152
- try {
153
- response = await fetch(url, {
154
- signal,
155
- method: "POST",
156
- headers: getBaseHeaders(),
157
- body: JSON.stringify(body)
158
- });
159
- } catch (error) {
160
- throw new LicenseTransportError(
161
- error instanceof Error ? error.message : "Network request failed",
162
- null
163
- );
164
- }
165
- if (!response.ok) {
166
- throw new LicenseTransportError(
167
- await describeFailure(response),
168
- response.status,
169
- response.status === 429 ? readRetryAfter(response) : null
170
- );
171
- }
172
- try {
173
- const json = await response.json();
174
- if (!json || typeof json !== "object" || typeof json.meta?.valid !== "boolean" || typeof json.meta?.code !== "string" || !json.meta.code) throw new Error("Missing license verdict");
175
- return json;
176
- } catch {
177
- throw new LicenseTransportError(
178
- "License server returned an unreadable response",
179
- response.status
180
- );
181
- }
182
- }
183
- async function validateLicense(key) {
184
- const controller = typeof AbortController === "function" ? new AbortController() : null;
185
- let timer;
186
- try {
187
- return await Promise.race([
188
- requestValidation(key, controller?.signal),
189
- new Promise((_, reject) => {
190
- timer = setTimeout(() => {
191
- reject(new LicenseTransportError("License verification timed out. Please retry.", null));
192
- controller?.abort();
193
- }, 15e3);
194
- })
195
- ]);
196
- } finally {
197
- if (timer) clearTimeout(timer);
198
- }
199
- }
200
- function isAcceptedValidation(response) {
201
- if (!response.data) return false;
202
- const product = response.data.relationships?.product?.data?.id;
203
- if (product && product !== KEYGEN_CONFIG.productId) return false;
204
- if (response.meta.valid && response.meta.code === "VALID") return true;
205
- return [
206
- "OVERDUE",
207
- "NO_MACHINE",
208
- "NO_MACHINES",
209
- "TOO_MANY_MACHINES",
210
- "TOO_MANY_CORES",
211
- "TOO_MANY_PROCESSES",
212
- "FINGERPRINT_SCOPE_MISMATCH",
213
- "HEARTBEAT_NOT_STARTED",
214
- "HEARTBEAT_DEAD"
215
- ].includes(response.meta.code);
216
- }
217
- function machineCount(response) {
218
- const count = response.data?.relationships?.machines?.meta?.count;
219
- return typeof count === "number" && Number.isFinite(count) ? count : 0;
220
- }
221
- async function activateMachine(licenseId, licenseKey, fingerprint, name, metadata) {
222
- const url = `${apiUrl}/machines`;
223
- const response = await fetch(url, {
224
- method: "POST",
225
- headers: getBaseHeaders(),
226
- body: JSON.stringify({ licenseId, licenseKey, fingerprint, name, metadata: metadata || {} })
227
- });
228
- if (!response.ok) {
229
- const errorData = await response.json();
230
- const errorCode = errorData.errors?.[0]?.code;
231
- if (errorCode === "FINGERPRINT_TAKEN") {
232
- throw new Error("FINGERPRINT_TAKEN");
233
- }
234
- if (errorCode === "MACHINE_LIMIT_EXCEEDED") {
235
- throw new Error("MACHINE_LIMIT_EXCEEDED");
236
- }
237
- const errorMessage = errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
238
- throw new Error(errorMessage);
239
- }
240
- return response.json();
241
- }
242
- function parseValidationResult(response) {
243
- const { meta, data } = response;
244
- return {
245
- valid: meta.valid,
246
- code: meta.code,
247
- detail: meta.detail,
248
- // data can be null for invalid licenses
249
- metadata: data?.attributes?.metadata
250
- };
251
- }
252
- function isFatalLicenseError(code) {
253
- return [
254
- "NOT_FOUND",
255
- "SUSPENDED",
256
- "EXPIRED",
257
- "BANNED",
258
- "PRODUCT_SCOPE_MISMATCH"
259
- ].includes(code);
260
- }
261
-
262
- // src/device-info.web.ts
263
- function generateUUID() {
264
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
265
- return crypto.randomUUID();
266
- }
267
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
268
- const r = Math.random() * 16 | 0;
269
- const v = c === "x" ? r : r & 3 | 8;
270
- return v.toString(16);
271
- });
272
- }
273
- function isDevBundle() {
274
- if (typeof __DEV__ !== "undefined") return __DEV__;
275
- try {
276
- if (typeof navigator !== "undefined" && /electron/i.test(navigator.userAgent)) return true;
277
- } catch {
278
- }
279
- return typeof process !== "undefined" && process.env.NODE_ENV !== "production";
280
- }
281
- function detectPlatform() {
282
- if (typeof navigator === "undefined") {
283
- return "unknown";
284
- }
285
- const ua = navigator.userAgent.toLowerCase();
286
- const platform = navigator.platform?.toLowerCase() || "";
287
- if (ua.includes("electron")) {
288
- if (platform.includes("mac") || ua.includes("mac")) {
289
- return "electron-macos";
290
- }
291
- if (platform.includes("win") || ua.includes("win")) {
292
- return "electron-windows";
293
- }
294
- if (platform.includes("linux") || ua.includes("linux")) {
295
- return "electron-linux";
296
- }
297
- return "electron";
298
- }
299
- if (platform.includes("mac") || ua.includes("mac")) {
300
- return "macos";
301
- }
302
- if (platform.includes("win") || ua.includes("win")) {
303
- return "windows";
304
- }
305
- if (platform.includes("linux") || ua.includes("linux")) {
306
- return "linux";
307
- }
308
- if (/iphone|ipad|ipod/.test(ua)) {
309
- return "ios-web";
310
- }
311
- if (ua.includes("android")) {
312
- return "android-web";
313
- }
314
- return "web";
315
- }
316
- function getOSVersion() {
317
- if (typeof navigator === "undefined") {
318
- return "unknown";
319
- }
320
- const ua = navigator.userAgent;
321
- const macMatch = ua.match(/Mac OS X ([\d_.]+)/);
322
- if (macMatch) {
323
- return macMatch[1].replace(/_/g, ".");
324
- }
325
- const winMatch = ua.match(/Windows NT ([\d.]+)/);
326
- if (winMatch) {
327
- const versions = {
328
- "10.0": "10/11",
329
- "6.3": "8.1",
330
- "6.2": "8",
331
- "6.1": "7"
332
- };
333
- return versions[winMatch[1]] || winMatch[1];
334
- }
335
- if (ua.includes("Linux")) {
336
- return "Linux";
337
- }
338
- return "unknown";
339
- }
340
- function getBrowserInfo() {
341
- if (typeof navigator === "undefined") {
342
- return { name: "unknown", version: "unknown" };
343
- }
344
- const ua = navigator.userAgent;
345
- const electronMatch = ua.match(/Electron\/([\d.]+)/);
346
- if (electronMatch) {
347
- return { name: "Electron", version: electronMatch[1] };
348
- }
349
- const chromeMatch = ua.match(/Chrome\/([\d.]+)/);
350
- if (chromeMatch && !ua.includes("Edg")) {
351
- return { name: "Chrome", version: chromeMatch[1] };
352
- }
353
- const edgeMatch = ua.match(/Edg\/([\d.]+)/);
354
- if (edgeMatch) {
355
- return { name: "Edge", version: edgeMatch[1] };
356
- }
357
- const firefoxMatch = ua.match(/Firefox\/([\d.]+)/);
358
- if (firefoxMatch) {
359
- return { name: "Firefox", version: firefoxMatch[1] };
360
- }
361
- const safariMatch = ua.match(/Version\/([\d.]+).*Safari/);
362
- if (safariMatch) {
363
- return { name: "Safari", version: safariMatch[1] };
364
- }
365
- return { name: "unknown", version: "unknown" };
366
- }
367
- function getDeviceMetadata() {
368
- const metadata = {
369
- platform: detectPlatform(),
370
- osVersion: getOSVersion(),
371
- isSimulator: "false"
372
- };
373
- const browserInfo = getBrowserInfo();
374
- metadata.browser = browserInfo.name;
375
- metadata.browserVersion = browserInfo.version;
376
- if (typeof window !== "undefined" && window.screen) {
377
- metadata.screenResolution = `${window.screen.width}x${window.screen.height}`;
378
- }
379
- if (typeof navigator !== "undefined" && navigator.language) {
380
- metadata.language = navigator.language;
381
- }
382
- try {
383
- metadata.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
384
- } catch {
385
- }
386
- return metadata;
387
- }
388
- function getDefaultDeviceName() {
389
- const platform = detectPlatform();
390
- const browserInfo = getBrowserInfo();
391
- if (platform.startsWith("electron")) {
392
- const os = platform.replace("electron-", "").replace("electron", "Desktop");
393
- return `${os.charAt(0).toUpperCase() + os.slice(1)} App`;
394
- }
395
- if (browserInfo.name !== "unknown") {
396
- return `${browserInfo.name} Browser`;
397
- }
398
- return "Web Browser";
399
- }
400
-
401
- // src/cacheSignature.ts
402
- var SIGNATURE_VERSION = "v2";
403
- var LEGACY_VERSIONS = ["v1"];
404
- var SIGNING_SALT = "buoy.gg/pro:9f3c1d7a4b2e:license-cache";
405
- var FIELD_SEPARATOR = "\0";
406
- function fnv1a(input, seed) {
407
- let hash = seed >>> 0;
408
- for (let i = 0; i < input.length; i++) {
409
- hash ^= input.charCodeAt(i);
410
- hash = Math.imul(hash, 16777619) >>> 0;
411
- }
412
- return hash >>> 0;
413
- }
414
- function canonicalize(cache, version) {
415
- const fields = [
416
- version,
417
- cache.licenseKey,
418
- cache.licenseId,
419
- cache.isPro ? "1" : "0",
420
- cache.expiresAt ?? "",
421
- String(cache.cachedAt)
422
- ];
423
- if (version !== "v1") {
424
- fields.push(cache.tier ?? "");
425
- }
426
- fields.push(SIGNING_SALT);
427
- return fields.join(FIELD_SEPARATOR);
428
- }
429
- function signatureFor(cache, version) {
430
- const input = canonicalize(cache, version);
431
- const a = fnv1a(input, 2166136261);
432
- const b = fnv1a(input, 2135587861);
433
- return `${version}.${a.toString(16)}${b.toString(16)}`;
434
- }
435
- function cacheSignature(cache) {
436
- return signatureFor(cache, SIGNATURE_VERSION);
437
- }
438
- function signCache(cache) {
439
- return { ...cache, sig: cacheSignature(cache) };
440
- }
441
- function verifyCacheSignature(cache) {
442
- if (!cache || typeof cache.sig !== "string" || cache.sig.length === 0) {
443
- return false;
444
- }
445
- if (cache.sig === cacheSignature(cache)) return true;
446
- return LEGACY_VERSIONS.some(
447
- (version) => cache.sig.startsWith(`${version}.`) && cache.sig === signatureFor(cache, version)
448
- );
449
- }
450
-
451
- // src/freeToken.ts
452
- import nacl from "tweetnacl";
453
- var FREE_TOKEN_VERSION = 1;
454
- var FREE_TOKEN_TIER_FREE = 1;
455
- var FREE_TOKEN_DOMAIN = "buoy-free-token-v1:";
456
- var PAYLOAD_LENGTH = 22;
457
- var SIGNATURE_LENGTH = 64;
458
- var TOKEN_PATTERN = /^BUOY1-([A-Z2-7]+)-([A-Z2-7]+)$/;
459
- function isBuoyToken(key) {
460
- return TOKEN_PATTERN.test(key.trim().toUpperCase());
461
- }
462
- var B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
463
- function base32Decode(text) {
464
- const out = [];
465
- let bits = 0;
466
- let value = 0;
467
- for (const char of text) {
468
- const index = B32.indexOf(char);
469
- if (index === -1) return null;
470
- value = value << 5 | index;
471
- bits += 5;
472
- if (bits >= 8) {
473
- out.push(value >>> bits - 8 & 255);
474
- bits -= 8;
475
- }
476
- }
477
- return new Uint8Array(out);
478
- }
479
- function hexToBytes(hex) {
480
- const clean = hex.trim().toLowerCase();
481
- const out = new Uint8Array(clean.length / 2);
482
- for (let i = 0; i < out.length; i += 1) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
483
- return out;
484
- }
485
- function bytesToHex(bytes) {
486
- let out = "";
487
- for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
488
- return out;
489
- }
490
- function utf8(text) {
491
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(text);
492
- const out = new Uint8Array(text.length);
493
- for (let i = 0; i < text.length; i += 1) out[i] = text.charCodeAt(i) & 255;
494
- return out;
495
- }
496
- function concat(a, b) {
497
- const out = new Uint8Array(a.length + b.length);
498
- out.set(a, 0);
499
- out.set(b, a.length);
500
- return out;
501
- }
502
- function signedMessage(payload) {
503
- return concat(utf8(FREE_TOKEN_DOMAIN), payload);
504
- }
505
- var activePublicKey = FREE_TOKEN_PUBLIC_KEY;
506
- function verifyFreeToken(key, publicKeyHex = activePublicKey) {
507
- const match = TOKEN_PATTERN.exec(key.trim().toUpperCase());
508
- if (!match) return null;
509
- const payload = base32Decode(match[1]);
510
- const signature = base32Decode(match[2]);
511
- if (!payload || !signature) return null;
512
- if (payload.length !== PAYLOAD_LENGTH || signature.length !== SIGNATURE_LENGTH) return null;
513
- if (payload[0] !== FREE_TOKEN_VERSION || payload[1] !== FREE_TOKEN_TIER_FREE) return null;
514
- try {
515
- if (!nacl.sign.detached.verify(signedMessage(payload), signature, hexToBytes(publicKeyHex))) return null;
516
- } catch {
517
- return null;
518
- }
519
- const seconds = (payload[2] << 24 >>> 0) + (payload[3] << 16) + (payload[4] << 8) + payload[5];
520
- return { tier: "free", issuedAt: seconds * 1e3, accountHash: bytesToHex(payload.slice(6, 22)) };
521
- }
522
-
523
- // src/tier.ts
524
- function resolveTier(license) {
525
- const policyId = license.relationships?.policy?.data?.id;
526
- if (policyId === KEYGEN_POLICIES.free) return "free";
527
- if (policyId === KEYGEN_POLICIES.solo || policyId === KEYGEN_POLICIES.business) {
528
- return "pro";
529
- }
530
- const plan = license.attributes?.metadata?.plan;
531
- if (plan === "free") return "free";
532
- return "pro";
533
- }
534
- function isRecognizedAccount(license) {
535
- if (!license) return false;
536
- const product = license.relationships?.product?.data?.id;
537
- if (product) return product === KEYGEN_CONFIG.productId;
538
- const policy = license.relationships?.policy?.data?.id;
539
- return Object.values(KEYGEN_POLICIES).some((id) => id === policy);
540
- }
541
-
542
- // src/accountAccess.ts
543
- function resolveAccountAccess(input, now = Date.now()) {
544
- const validUntil = input.verifiedAt !== null && Number.isFinite(input.verifiedAt) && input.verifiedAt <= now ? input.verifiedAt + LICENSE_CACHE_DURATION : null;
545
- if (input.initialized && input.key && input.tierSource !== "none" && validUntil !== null && now < validUntil) {
546
- return { status: "admitted", reason: null, validUntil };
547
- }
548
- if (validUntil !== null && now >= validUntil) {
549
- return { status: "locked", reason: "verification_expired", validUntil };
550
- }
551
- if (!input.initialized || input.validating) {
552
- return { status: "initializing", reason: "checking", validUntil };
553
- }
554
- return {
555
- status: "locked",
556
- reason: validUntil !== null && now >= validUntil ? "verification_expired" : "account_required",
557
- validUntil
558
- };
559
- }
560
-
561
- // src/foreground.web.ts
562
- function subscribeForeground(refresh) {
563
- if (typeof window === "undefined" || typeof window.addEventListener !== "function") return () => {
564
- };
565
- const visible = () => {
566
- if (!document.hidden) refresh();
567
- };
568
- window.addEventListener("focus", refresh);
569
- document.addEventListener("visibilitychange", visible);
570
- return () => {
571
- window.removeEventListener("focus", refresh);
572
- document.removeEventListener("visibilitychange", visible);
573
- };
574
- }
575
-
576
- // src/LicenseManager.ts
577
- async function boundedStorage(operation, fallback) {
578
- let timer;
579
- try {
580
- return await Promise.race([operation, new Promise((resolve) => {
581
- timer = setTimeout(() => resolve(fallback), 15e3);
582
- })]);
583
- } finally {
584
- if (timer) clearTimeout(timer);
585
- }
586
- }
587
- function tierOfCache(cached) {
588
- if (cached.tier === "pro" || cached.tier === "free") return cached.tier;
589
- return cached.isPro ? "pro" : "free";
590
- }
591
- var LicenseManagerImpl = class {
592
- constructor() {
593
- this.state = {
594
- isInitialized: false,
595
- isValidating: false,
596
- isPro: false,
597
- tier: "free",
598
- tierSource: "none",
599
- licenseKey: null,
600
- error: null,
601
- errorKind: null,
602
- expiresAt: null
603
- };
604
- this.listeners = /* @__PURE__ */ new Set();
605
- this.revalidationTimer = null;
606
- this.cachedLicense = null;
607
- this.fingerprintPromise = null;
608
- // Promise for initialization to prevent double-init race condition
609
- this.initPromise = null;
610
- /**
611
- * Bumped whenever the license is deliberately changed or cleared.
612
- *
613
- * `initialize()` kicks off a background revalidation of the cached key and
614
- * does NOT await it, so a `clearLicense()` that lands in between would be
615
- * silently undone: the in-flight response re-applies the old tier and
616
- * re-persists the cache blob that was just deleted. Removing a key would
617
- * then appear to do nothing — Pro comes straight back a moment later.
618
- *
619
- * Background work captures this value when it starts and drops its result if
620
- * it no longer matches.
621
- */
622
- this.generation = 0;
623
- this.persistence = Promise.resolve();
624
- this.verifiedAt = null;
625
- this.expiryTimer = null;
626
- this.removeForegroundListener = null;
627
- /**
628
- * Pending backoff retry after a transient failure (see
629
- * LICENSE_TRANSIENT_RETRY_DELAYS). At most one is ever outstanding, and it
630
- * is cancelled by any deliberate credential change or by a success.
631
- */
632
- this.retryTimer = null;
633
- this.retryAttempt = 0;
634
- // Device registration callback for UI integration
635
- this.deviceRegistrationCallback = null;
636
- this.pendingRegistration = null;
637
- }
638
- /**
639
- * Initialize the license manager and load cached license
640
- */
641
- async initialize() {
642
- if (this.initPromise) return this.initPromise;
643
- if (this.state.isInitialized) return;
644
- this.initPromise = this._doInitialize();
645
- return this.initPromise;
646
- }
647
- /**
648
- * Internal initialization logic
649
- */
650
- async _doInitialize() {
651
- if (this.state.isInitialized) return;
652
- const startedAt = this.generation;
653
- try {
654
- const cached = await boundedStorage(this.loadCachedLicense(), null);
655
- if (startedAt !== this.generation) {
656
- this.state.isInitialized = true;
657
- this.emit({ type: "initialized", isPro: this.state.isPro });
658
- this.startRevalidationTimer();
659
- return;
660
- }
661
- if (cached && (this.configuredKey === void 0 || cached.licenseKey === this.configuredKey)) {
662
- const trusted = verifyCacheSignature(cached);
663
- this.cachedLicense = cached;
664
- this.state.licenseKey = cached.licenseKey;
665
- if (trusted && this.isCacheValid(cached)) this.applyTier(tierOfCache(cached));
666
- else this.lock();
667
- this.state.expiresAt = cached.expiresAt ? new Date(cached.expiresAt) : null;
668
- if (trusted && this.isCacheValid(cached)) this.setVerifiedAt(cached.cachedAt);
669
- if (this.configuredKey === void 0 && cached.licenseKey && !(trusted && this.isCacheValid(cached) && this.isCacheFresh(cached))) {
670
- this.validateInBackground(cached.licenseKey);
671
- }
672
- }
673
- this.state.isInitialized = true;
674
- this.emit({ type: "initialized", isPro: this.state.isPro });
675
- this.startRevalidationTimer();
676
- } catch (error) {
677
- console.warn("[React Buoy] License initialization failed:", error);
678
- this.state.isInitialized = true;
679
- this.emit({ type: "initialized", isPro: false });
680
- }
681
- }
682
- /** Omitted configuration preserves activation; an explicit blank signs out. */
683
- async configure(key) {
684
- if (key === void 0) {
685
- await this.initialize();
686
- return this.hasAccountAccess();
687
- }
688
- const normalized = key.trim().toUpperCase();
689
- const startedAt = ++this.generation;
690
- this.cancelRetry();
691
- this.configuredKey = normalized || null;
692
- if (this.state.licenseKey !== this.configuredKey) this.lock();
693
- this.state.licenseKey = this.configuredKey;
694
- this.state.isValidating = !!normalized;
695
- this.emit({ type: "validating", isPro: this.isPro() });
696
- await this.initialize();
697
- if (startedAt !== this.generation) return false;
698
- if (!normalized) {
699
- await this.clearLicense();
700
- return false;
701
- }
702
- return this.setLicenseKey(normalized);
703
- }
704
- /**
705
- * Set and validate a license key.
706
- *
707
- * Simple validation - just checks if the key is valid.
708
- * No device registration or limits - we trust our customers.
709
- * Keygen tracks validation calls for usage analytics.
710
- */
711
- async setLicenseKey(licenseKey, opts) {
712
- if (!licenseKey || licenseKey.trim().length === 0) {
713
- this.state.error = "License key is required";
714
- this.state.errorKind = "verdict";
715
- return false;
716
- }
717
- const normalizedKey = licenseKey.trim().toUpperCase();
718
- const startedAt = ++this.generation;
719
- this.cancelRetry();
720
- if (!opts?.isRetry) this.retryAttempt = 0;
721
- if (this.state.licenseKey !== normalizedKey) this.lock();
722
- this.state.licenseKey = normalizedKey;
723
- this.state.isValidating = true;
724
- this.state.error = null;
725
- this.state.errorKind = null;
726
- this.emit({ type: "validating", isPro: this.isPro() });
727
- if (isBuoyToken(normalizedKey)) return this.admitToken(normalizedKey, startedAt);
728
- if (!this.cachedLicense) {
729
- const cached = await boundedStorage(this.loadCachedLicense(), null);
730
- if (startedAt !== this.generation) return false;
731
- if (cached?.licenseKey === normalizedKey) {
732
- this.cachedLicense = cached;
733
- if (verifyCacheSignature(cached) && this.isCacheValid(cached)) {
734
- this.applyTier(tierOfCache(cached));
735
- this.state.expiresAt = cached.expiresAt ? new Date(cached.expiresAt) : null;
736
- this.setVerifiedAt(cached.cachedAt);
737
- this.emit({ type: "validating", isPro: this.isPro() });
738
- }
739
- }
740
- }
741
- const fresh = this.cachedLicense;
742
- if (!opts?.force && fresh && fresh.valid && fresh.licenseKey === normalizedKey && fresh.fingerprint && verifyCacheSignature(fresh) && this.isCacheValid(fresh) && this.isCacheFresh(fresh)) {
743
- this.applyTier(tierOfCache(fresh));
744
- this.state.licenseKey = normalizedKey;
745
- this.state.isValidating = false;
746
- this.state.error = null;
747
- this.state.errorKind = null;
748
- this.state.expiresAt = fresh.expiresAt ? new Date(fresh.expiresAt) : null;
749
- this.setVerifiedAt(fresh.cachedAt);
750
- this.emit({ type: "validated", isPro: this.state.isPro });
751
- return true;
752
- }
753
- const hold = await boundedStorage(this.loadHold(), null);
754
- if (startedAt !== this.generation) return false;
755
- const remainingBackoff = (hold?.backoffUntil ?? 0) - Date.now();
756
- if (remainingBackoff > 0) {
757
- return this.standDown(normalizedKey, startedAt, "License server is rate limited; retrying later.", remainingBackoff);
758
- }
759
- if (!opts?.force && hold?.key === normalizedKey && hold.verdictAt !== void 0 && Date.now() - hold.verdictAt >= 0 && Date.now() - hold.verdictAt < LICENSE_VERDICT_CACHE_DURATION) {
760
- this.lock();
761
- this.state.isValidating = false;
762
- this.state.error = hold.detail || "License could not be verified";
763
- this.state.errorKind = "verdict";
764
- this.emit({ type: "error", isPro: false, error: this.state.error });
765
- return false;
766
- }
767
- let response;
768
- try {
769
- response = await validateLicense(normalizedKey);
770
- } catch (error) {
771
- const errorMessage = error instanceof Error ? error.message : "Validation failed";
772
- if (startedAt !== this.generation) return false;
773
- if (isTransientLicenseError(error)) {
774
- if (isRateLimited(error)) {
775
- const wait = Math.min(
776
- backoffAfter(error, LICENSE_QUOTA_COOLDOWN, LICENSE_RATE_LIMIT_DEFAULT_WAIT) * 2 ** this.retryAttempt,
777
- LICENSE_QUOTA_COOLDOWN
778
- );
779
- this.retryAttempt += 1;
780
- await this.saveHold({ ...this.hold, backoffUntil: Date.now() + wait });
781
- if (startedAt !== this.generation) return false;
782
- return this.standDown(normalizedKey, startedAt, errorMessage, wait);
783
- }
784
- this.state.isValidating = false;
785
- if (this.cachedLicense && // The cache can only stand in for the key it was written FOR. Without
786
- // this, typing a new (or mistyped) key while the server is
787
- // unreachable would "validate" it by serving the old key's cache.
788
- this.cachedLicense.licenseKey === normalizedKey && verifyCacheSignature(this.cachedLicense) && this.isCacheValid(this.cachedLicense)) {
789
- this.applyTier(tierOfCache(this.cachedLicense));
790
- this.state.error = null;
791
- this.state.errorKind = null;
792
- this.state.expiresAt = this.cachedLicense.expiresAt ? new Date(this.cachedLicense.expiresAt) : null;
793
- this.setVerifiedAt(this.cachedLicense.cachedAt);
794
- if (!this.isPro()) this.scheduleRetry(normalizedKey);
795
- this.emit({ type: "validated", isPro: this.state.isPro });
796
- return true;
797
- }
798
- this.state.error = errorMessage;
799
- this.state.errorKind = "transport";
800
- this.scheduleRetry(normalizedKey);
801
- this.emit({
802
- type: "error",
803
- isPro: this.state.isPro,
804
- error: errorMessage
805
- });
806
- return false;
807
- }
808
- this.state.isValidating = false;
809
- this.state.error = errorMessage;
810
- this.state.errorKind = "verdict";
811
- this.emit({ type: "error", isPro: false, error: errorMessage });
812
- return false;
813
- }
814
- try {
815
- if (startedAt !== this.generation) return false;
816
- const result = parseValidationResult(response);
817
- const product = response.data?.relationships?.product?.data?.id;
818
- if ((!product || product === KEYGEN_CONFIG.productId) && result.code === "EXPIRED" && (isRecognizedAccount(response.data) || this.cachedLicense?.licenseKey === normalizedKey && verifyCacheSignature(this.cachedLicense) && this.isCacheValid(this.cachedLicense))) {
819
- const now2 = Date.now();
820
- const saved2 = await this.saveCache({
821
- licenseKey: normalizedKey,
822
- licenseId: response.data?.id ?? "",
823
- valid: true,
824
- isPro: false,
825
- tier: "free",
826
- expiresAt: null,
827
- cachedAt: now2,
828
- lastValidatedAt: now2,
829
- fingerprint: this.cachedLicense?.licenseKey === normalizedKey ? this.cachedLicense.fingerprint : void 0
830
- }, startedAt);
831
- if (startedAt !== this.generation || !saved2) return false;
832
- this.cachedLicense = saved2;
833
- this.applyTier("free");
834
- this.state.isValidating = false;
835
- this.state.expiresAt = null;
836
- this.setVerifiedAt(now2);
837
- this.emit({ type: "validated", isPro: false });
838
- return true;
839
- }
840
- if (isFatalLicenseError(result.code) || !isAcceptedValidation(response)) {
841
- this.lock();
842
- this.state.isValidating = false;
843
- this.state.error = result.detail || "License could not be verified";
844
- this.state.errorKind = "verdict";
845
- this.emit({ type: "error", isPro: false, error: this.state.error });
846
- await this.clearCachedLicense();
847
- await this.saveHold({
848
- key: normalizedKey,
849
- code: result.code,
850
- detail: this.state.error,
851
- verdictAt: Date.now()
852
- });
853
- return false;
854
- }
855
- if (!response.data) {
856
- this.lock();
857
- this.state.isValidating = false;
858
- this.state.error = result.detail || "License not found";
859
- this.state.errorKind = "verdict";
860
- this.emit({ type: "error", isPro: false, error: this.state.error });
861
- await this.clearCachedLicense();
862
- return false;
863
- }
864
- const tier = resolveTier(response.data);
865
- const licenseId = response.data.id;
866
- const now = Date.now();
867
- const fleet = machineCount(response) >= LICENSE_FLEET_MACHINE_THRESHOLD;
868
- const devBundle = isDevBundle();
869
- const canRegister = devBundle && !fleet;
870
- const quiet = tier === "free" && (fleet || !devBundle);
871
- let fingerprint = this.cachedLicense?.fingerprint;
872
- let deviceName = this.cachedLicense?.deviceName;
873
- let needsRegistration = false;
874
- if (!fingerprint) {
875
- fingerprint = await boundedStorage(this.getOrCreateDeviceFingerprint(), generateUUID());
876
- if (startedAt !== this.generation) return false;
877
- deviceName = getDefaultDeviceName();
878
- needsRegistration = canRegister;
879
- }
880
- let deferCaching = false;
881
- if (needsRegistration) {
882
- if (this.deviceRegistrationCallback) {
883
- this.pendingRegistration = {
884
- licenseId,
885
- licenseKey: normalizedKey,
886
- fingerprint
887
- };
888
- this.deviceRegistrationCallback({
889
- fingerprint,
890
- defaultName: deviceName
891
- });
892
- deferCaching = true;
893
- } else {
894
- this.registerDeviceAsync(licenseId, normalizedKey, fingerprint, deviceName);
895
- }
896
- }
897
- const cached = {
898
- licenseKey: normalizedKey,
899
- licenseId,
900
- valid: true,
901
- isPro: tier === "pro",
902
- tier,
903
- expiresAt: response.data.attributes.expiry ?? null,
904
- cachedAt: now,
905
- lastValidatedAt: now,
906
- // Don't cache fingerprint if we're deferring to UI for registration
907
- fingerprint: deferCaching ? void 0 : fingerprint,
908
- deviceName: deferCaching ? void 0 : deviceName,
909
- quiet: quiet || void 0
910
- };
911
- const saved = await this.saveCache(cached, startedAt);
912
- if (startedAt !== this.generation || !saved) return false;
913
- this.cachedLicense = saved;
914
- this.applyTier(tier);
915
- this.state.isValidating = false;
916
- this.state.expiresAt = cached.expiresAt ? new Date(cached.expiresAt) : null;
917
- this.setVerifiedAt(now);
918
- if (this.hold) await this.saveHold(null);
919
- if (startedAt !== this.generation) return false;
920
- this.retryAttempt = 0;
921
- this.emit({ type: "validated", isPro: this.state.isPro });
922
- return true;
923
- } catch (error) {
924
- const errorMessage = error instanceof Error ? error.message : "Validation failed";
925
- if (startedAt !== this.generation) return false;
926
- this.state.isValidating = false;
927
- this.state.error = errorMessage;
928
- this.state.errorKind = "verdict";
929
- this.emit({ type: "error", isPro: false, error: errorMessage });
930
- return false;
931
- }
932
- }
933
- /**
934
- * Retry a transiently-failed validation on a short, finite backoff.
935
- *
936
- * Only ever scheduled when there was nothing cached to fall back on, which
937
- * is the one case that leaves a real customer stuck: the key comes in as a
938
- * prop, so without this the next attempt is the daily revalidation timer.
939
- */
940
- scheduleRetry(licenseKey) {
941
- this.cancelRetry();
942
- const delay = LICENSE_TRANSIENT_RETRY_DELAYS[this.retryAttempt];
943
- if (delay === void 0) return;
944
- this.retryAttempt += 1;
945
- const startedAt = this.generation;
946
- this.retryTimer = setTimeout(() => {
947
- this.retryTimer = null;
948
- if (startedAt !== this.generation) return;
949
- if (this.state.isValidating) return;
950
- void this.setLicenseKey(licenseKey, { force: true, isRetry: true });
951
- }, delay);
952
- if (typeof __DEV__ !== "undefined" && __DEV__) {
953
- globalThis.__LICENSE_MANAGER_RETRY_TIMER__ = this.retryTimer;
954
- }
955
- }
956
- /**
957
- * Admit (or reject) a free-tier token. Verification is a signature check
958
- * against the public key in config.ts; the cache is written so the next
959
- * launch and the account-admission clock see a validated key, and marked
960
- * quiet so nothing ever revalidates it against a server.
961
- */
962
- async admitToken(token, startedAt) {
963
- const claims = verifyFreeToken(token);
964
- if (startedAt !== this.generation) return false;
965
- if (!claims) {
966
- this.lock();
967
- this.state.isValidating = false;
968
- this.state.error = "This key is not valid. Check that it was copied completely.";
969
- this.state.errorKind = "verdict";
970
- this.emit({ type: "error", isPro: false, error: this.state.error });
971
- await this.clearCachedLicense();
972
- return false;
973
- }
974
- const now = Date.now();
975
- const fingerprint = this.cachedLicense?.fingerprint ?? await boundedStorage(this.getOrCreateDeviceFingerprint(), generateUUID());
976
- if (startedAt !== this.generation) return false;
977
- const saved = await this.saveCache({
978
- licenseKey: token,
979
- licenseId: `token:${claims.accountHash}`,
980
- valid: true,
981
- isPro: false,
982
- tier: "free",
983
- expiresAt: null,
984
- cachedAt: now,
985
- lastValidatedAt: now,
986
- fingerprint,
987
- deviceName: this.cachedLicense?.deviceName ?? getDefaultDeviceName(),
988
- quiet: true
989
- }, startedAt);
990
- if (startedAt !== this.generation || !saved) return false;
991
- this.cachedLicense = saved;
992
- this.applyTier("free");
993
- this.state.isValidating = false;
994
- this.state.expiresAt = null;
995
- this.setVerifiedAt(now);
996
- this.retryAttempt = 0;
997
- this.emit({ type: "validated", isPro: false });
998
- return true;
999
- }
1000
- /**
1001
- * "Don't ask right now": serve the trusted cache if it vouches for this
1002
- * key, otherwise record why the tier can't move, then try exactly once when
1003
- * the wait is over. Used for server-set backoffs, both fresh and remembered.
1004
- */
1005
- standDown(licenseKey, startedAt, reason, wait) {
1006
- if (startedAt !== this.generation) return false;
1007
- this.state.isValidating = false;
1008
- const cached = this.cachedLicense;
1009
- if (cached && cached.licenseKey === licenseKey && verifyCacheSignature(cached) && this.isCacheValid(cached)) {
1010
- this.applyTier(tierOfCache(cached));
1011
- this.state.error = null;
1012
- this.state.errorKind = null;
1013
- this.state.expiresAt = cached.expiresAt ? new Date(cached.expiresAt) : null;
1014
- this.setVerifiedAt(cached.cachedAt);
1015
- if (!this.isPro()) this.scheduleRetryIn(licenseKey, wait);
1016
- this.emit({ type: "validated", isPro: this.state.isPro });
1017
- return true;
1018
- }
1019
- this.state.error = reason;
1020
- this.state.errorKind = "transport";
1021
- this.scheduleRetryIn(licenseKey, wait);
1022
- this.emit({ type: "error", isPro: this.state.isPro, error: reason });
1023
- return false;
1024
- }
1025
- /** One retry at a server-chosen time. Replaces any ladder step pending. */
1026
- scheduleRetryIn(licenseKey, delay) {
1027
- this.cancelRetry();
1028
- const startedAt = this.generation;
1029
- this.retryTimer = setTimeout(() => {
1030
- this.retryTimer = null;
1031
- if (startedAt !== this.generation) return;
1032
- if (this.state.isValidating) return;
1033
- void this.setLicenseKey(licenseKey, { force: true, isRetry: true });
1034
- }, Math.max(1, Math.min(delay, 2147483647)));
1035
- if (typeof __DEV__ !== "undefined" && __DEV__) devTimers.__LICENSE_MANAGER_RETRY_TIMER__ = this.retryTimer;
1036
- }
1037
- /** Drop any pending retry — a newer decision has replaced it. */
1038
- cancelRetry() {
1039
- if (this.retryTimer) {
1040
- clearTimeout(this.retryTimer);
1041
- this.retryTimer = null;
1042
- }
1043
- }
1044
- /**
1045
- * Clear the current license
1046
- */
1047
- async clearLicense() {
1048
- this.generation += 1;
1049
- this.cancelRetry();
1050
- this.retryAttempt = 0;
1051
- this.configuredKey = null;
1052
- this.state.licenseKey = null;
1053
- this.lock();
1054
- this.state.expiresAt = null;
1055
- this.state.error = null;
1056
- this.state.errorKind = null;
1057
- this.cachedLicense = null;
1058
- this.pendingRegistration = null;
1059
- this.state.isValidating = false;
1060
- this.emit({ type: "deactivated", isPro: false });
1061
- await this.clearCachedLicense();
1062
- if (this.hold?.verdictAt !== void 0) {
1063
- await this.saveHold(this.hold.backoffUntil ? { backoffUntil: this.hold.backoffUntil } : null);
1064
- }
1065
- }
1066
- /**
1067
- * Set the tier a validated key granted and keep the derived fields in step.
1068
- *
1069
- * `isPro` is a mirror of `tier === "pro"` and must never be assigned
1070
- * directly — that is exactly how a free key would end up granting Pro.
1071
- */
1072
- applyTier(tier) {
1073
- this.state.tier = tier;
1074
- this.state.tierSource = "key";
1075
- this.state.isPro = tier === "pro";
1076
- }
1077
- /**
1078
- * Forget whatever the current key had proven. There is no tier for "nothing
1079
- * validated": the tier falls back to free and `tierSource: "none"` is what
1080
- * makes `getAccountAccess()` report the manager locked.
1081
- */
1082
- lock() {
1083
- this.state.tier = "free";
1084
- this.state.tierSource = "none";
1085
- this.state.isPro = false;
1086
- this.verifiedAt = null;
1087
- if (this.expiryTimer) clearTimeout(this.expiryTimer);
1088
- this.expiryTimer = null;
1089
- }
1090
- /**
1091
- * The current tier. This is what caps should read; whether the tools are
1092
- * usable at all is `getAccountAccess()`.
1093
- */
1094
- getTier() {
1095
- if (!this.hasAccountAccess()) return "free";
1096
- if (this.state.expiresAt && Date.now() >= this.state.expiresAt.getTime()) return "free";
1097
- return this.state.tier;
1098
- }
1099
- /**
1100
- * Check if the user has Pro features
1101
- */
1102
- isPro() {
1103
- return this.getTier() === "pro";
1104
- }
1105
- /**
1106
- * Check if a specific entitlement is available
1107
- */
1108
- hasEntitlement(code) {
1109
- return this.isPro();
1110
- }
1111
- /**
1112
- * Get current license state
1113
- */
1114
- getState() {
1115
- const access = this.getAccountAccess();
1116
- const tier = this.getTier();
1117
- return { ...this.state, tier, isPro: tier === "pro", access };
1118
- }
1119
- getAccountAccess() {
1120
- return resolveAccountAccess({
1121
- initialized: this.state.isInitialized,
1122
- validating: this.state.isValidating,
1123
- key: this.state.licenseKey,
1124
- tierSource: this.state.tierSource,
1125
- verifiedAt: this.verifiedAt
1126
- });
1127
- }
1128
- hasAccountAccess() {
1129
- return this.getAccountAccess().status === "admitted";
1130
- }
1131
- setVerifiedAt(at) {
1132
- this.verifiedAt = at;
1133
- if (this.expiryTimer) clearTimeout(this.expiryTimer);
1134
- const accountDeadline = at + LICENSE_CACHE_DURATION;
1135
- const paidDeadline = this.state.expiresAt?.getTime();
1136
- const nextDeadline = paidDeadline && paidDeadline > Date.now() ? Math.min(accountDeadline, paidDeadline) : accountDeadline;
1137
- const remaining = nextDeadline - Date.now();
1138
- this.expiryTimer = setTimeout(() => {
1139
- this.expiryTimer = null;
1140
- if (this.verifiedAt !== at) return;
1141
- this.emit({ type: "validated", isPro: this.isPro() });
1142
- if (Date.now() < accountDeadline) this.setVerifiedAt(at);
1143
- }, Math.max(1, Math.min(remaining, 2147483647)));
1144
- if (typeof __DEV__ !== "undefined" && __DEV__) devTimers.__LICENSE_MANAGER_EXPIRY_TIMER__ = this.expiryTimer;
1145
- }
1146
- /**
1147
- * Subscribe to license events
1148
- */
1149
- subscribe(listener) {
1150
- this.listeners.add(listener);
1151
- return () => this.listeners.delete(listener);
1152
- }
1153
- /**
1154
- * Subscribe to device registration events
1155
- * When a new device needs to be registered, the callback is invoked
1156
- * with the fingerprint and default name suggestion.
1157
- * Call completeDeviceRegistration() to finish registration with user's chosen name.
1158
- */
1159
- onDeviceRegistrationNeeded(callback) {
1160
- this.deviceRegistrationCallback = callback;
1161
- return () => {
1162
- this.deviceRegistrationCallback = null;
1163
- };
1164
- }
1165
- /**
1166
- * Complete device registration with user-provided name
1167
- * Call this after onDeviceRegistrationNeeded callback to finish registration
1168
- */
1169
- async completeDeviceRegistration(deviceName) {
1170
- if (!this.pendingRegistration) {
1171
- console.warn("[Buoy] No pending device registration");
1172
- return;
1173
- }
1174
- const { licenseId, licenseKey, fingerprint } = this.pendingRegistration;
1175
- const startedAt = this.generation;
1176
- const name = deviceName.trim() || getDefaultDeviceName();
1177
- const metadata = getDeviceMetadata();
1178
- try {
1179
- await activateMachine(licenseId, licenseKey, fingerprint, name, metadata);
1180
- } catch (error) {
1181
- const errorMessage = error instanceof Error ? error.message : String(error);
1182
- if (errorMessage === "FINGERPRINT_TAKEN" || errorMessage === "MACHINE_LIMIT_EXCEEDED") {
1183
- } else {
1184
- console.warn("[Buoy] Device registration failed:", errorMessage);
1185
- throw error;
1186
- }
1187
- }
1188
- if (startedAt !== this.generation) return;
1189
- if (this.cachedLicense?.licenseKey === licenseKey) {
1190
- const saved = await this.saveCache({ ...this.cachedLicense, fingerprint, deviceName: name }, startedAt);
1191
- if (startedAt !== this.generation) return;
1192
- if (saved) this.cachedLicense = saved;
1193
- }
1194
- this.pendingRegistration = null;
1195
- }
1196
- // Private methods
1197
- emit(event) {
1198
- const listeners = [...this.listeners];
1199
- listeners.forEach((listener) => {
1200
- try {
1201
- listener(event);
1202
- } catch (error) {
1203
- console.warn("[React Buoy] License listener error:", error);
1204
- }
1205
- });
1206
- }
1207
- isCacheValid(cached) {
1208
- const now = Date.now();
1209
- const cacheAge = now - cached.cachedAt;
1210
- if (!cached.valid || !Number.isFinite(cached.cachedAt) || cacheAge < 0 || cacheAge >= LICENSE_CACHE_DURATION) {
1211
- return false;
1212
- }
1213
- return true;
1214
- }
1215
- /**
1216
- * Whether the cache was validated against Keygen recently enough that a
1217
- * launch-time revalidation would add nothing (see LICENSE_LAUNCH_VALIDATION_TTL).
1218
- */
1219
- isCacheFresh(cached) {
1220
- const ttl = this.isQuiet(cached) ? LICENSE_QUIET_VALIDATION_TTL : LICENSE_LAUNCH_VALIDATION_TTL;
1221
- return Date.now() - (cached.lastValidatedAt ?? 0) < ttl;
1222
- }
1223
- /**
1224
- * Whether a cache has anything to learn before it expires. Blobs written
1225
- * before the flag existed are judged the same way they would be today.
1226
- */
1227
- isQuiet(cached) {
1228
- if (cached.quiet !== void 0) return cached.quiet;
1229
- return tierOfCache(cached) === "free" && !isDevBundle();
1230
- }
1231
- async loadHold() {
1232
- if (this.hold !== void 0) return this.hold;
1233
- try {
1234
- const storage2 = await getLicenseStorage();
1235
- const raw = await storage2.getItem(LICENSE_HOLD_STORAGE_KEY);
1236
- const parsed = raw ? JSON.parse(raw) : null;
1237
- this.hold = parsed && typeof parsed === "object" ? parsed : null;
1238
- } catch {
1239
- this.hold = null;
1240
- }
1241
- return this.hold;
1242
- }
1243
- /** Replace the hold record (null clears it). Never throws: a failed write only costs a request. */
1244
- async saveHold(record) {
1245
- this.hold = record;
1246
- try {
1247
- const storage2 = await getLicenseStorage();
1248
- if (record) await storage2.setItem(LICENSE_HOLD_STORAGE_KEY, JSON.stringify(record));
1249
- else await storage2.removeItem(LICENSE_HOLD_STORAGE_KEY);
1250
- } catch {
1251
- }
1252
- }
1253
- /**
1254
- * Write the cache with a fresh checksum. Every write goes through here so a
1255
- * blob can never land on disk unsigned — an unsigned blob is treated as
1256
- * untrusted on the next read, which would silently drop a paying customer.
1257
- */
1258
- async persistCache(cache) {
1259
- const signed = signCache(cache);
1260
- const storage2 = await getLicenseStorage();
1261
- await storage2.setItem(LICENSE_STORAGE_KEY, JSON.stringify(signed));
1262
- return signed;
1263
- }
1264
- saveCache(cache, startedAt) {
1265
- const operation = this.persistence.catch(() => {
1266
- }).then(async () => {
1267
- if (startedAt !== this.generation) return null;
1268
- const saved = await this.persistCache(cache);
1269
- return startedAt === this.generation ? saved : null;
1270
- });
1271
- this.persistence = operation;
1272
- return boundedStorage(operation, signCache(cache));
1273
- }
1274
- async loadCachedLicense() {
1275
- try {
1276
- await this.persistence.catch(() => {
1277
- });
1278
- const storage2 = await getLicenseStorage();
1279
- const data = await storage2.getItem(LICENSE_STORAGE_KEY);
1280
- if (!data) return null;
1281
- const cached = JSON.parse(data);
1282
- return cached;
1283
- } catch (error) {
1284
- console.warn("[Buoy] Error loading cached license:", error);
1285
- return null;
1286
- }
1287
- }
1288
- /**
1289
- * The install's stable device fingerprint, created once and persisted under
1290
- * its own key. Older versions kept the fingerprint only inside the license
1291
- * blob, so anything that cleared the blob gave the device a new identity —
1292
- * this adopts a pre-existing blob fingerprint on first read so upgrading
1293
- * doesn't mint a second machine record for the same install.
1294
- */
1295
- getOrCreateDeviceFingerprint() {
1296
- if (!this.fingerprintPromise) {
1297
- this.fingerprintPromise = (async () => {
1298
- const storage2 = await getLicenseStorage();
1299
- const existing = await storage2.getItem(DEVICE_FINGERPRINT_STORAGE_KEY);
1300
- if (existing) return existing;
1301
- const fingerprint = this.cachedLicense?.fingerprint || generateUUID();
1302
- await storage2.setItem(DEVICE_FINGERPRINT_STORAGE_KEY, fingerprint);
1303
- return fingerprint;
1304
- })();
1305
- }
1306
- return this.fingerprintPromise;
1307
- }
1308
- async clearCachedLicense() {
1309
- this.cachedLicense = null;
1310
- const operation = this.persistence.catch(() => {
1311
- }).then(async () => {
1312
- const storage2 = await getLicenseStorage();
1313
- await storage2.removeItem(LICENSE_STORAGE_KEY);
1314
- });
1315
- this.persistence = operation;
1316
- await boundedStorage(operation.catch(() => {
1317
- }), void 0);
1318
- }
1319
- /**
1320
- * Register device in background for visibility (non-blocking)
1321
- * Failures don't affect Pro status - this is just for analytics
1322
- */
1323
- registerDeviceAsync(licenseId, licenseKey, fingerprint, deviceName) {
1324
- const metadata = getDeviceMetadata();
1325
- activateMachine(licenseId, licenseKey, fingerprint, deviceName, metadata).catch((error) => {
1326
- if (error.message !== "FINGERPRINT_TAKEN" && error.message !== "MACHINE_LIMIT_EXCEEDED") {
1327
- console.warn("[Buoy] Device registration failed (non-blocking):", error.message);
1328
- }
1329
- });
1330
- }
1331
- validateInBackground(licenseKey) {
1332
- void this.setLicenseKey(licenseKey, { force: true, isRetry: true });
1333
- }
1334
- refreshAccess() {
1335
- this.emit({ type: "validated", isPro: this.isPro() });
1336
- }
1337
- startRevalidationTimer() {
1338
- if (!this.removeForegroundListener) {
1339
- this.removeForegroundListener = subscribeForeground(() => this.refreshAccess());
1340
- if (typeof __DEV__ !== "undefined" && __DEV__) devTimers.__LICENSE_MANAGER_FOREGROUND_OFF__ = this.removeForegroundListener;
1341
- }
1342
- if (this.revalidationTimer) {
1343
- clearInterval(this.revalidationTimer);
1344
- }
1345
- this.revalidationTimer = setInterval(() => {
1346
- if (!this.state.licenseKey || this.state.isValidating) return;
1347
- const cached = this.cachedLicense;
1348
- if (cached && cached.licenseKey === this.state.licenseKey && this.isCacheFresh(cached)) return;
1349
- this.validateInBackground(this.state.licenseKey);
1350
- }, LICENSE_REVALIDATION_INTERVAL);
1351
- if (typeof __DEV__ !== "undefined" && __DEV__) {
1352
- globalThis.__LICENSE_MANAGER_TIMER__ = this.revalidationTimer;
1353
- }
1354
- }
1355
- /**
1356
- * Stop the revalidation timer
1357
- */
1358
- stopRevalidationTimer() {
1359
- this.removeForegroundListener?.();
1360
- this.removeForegroundListener = null;
1361
- if (this.expiryTimer) clearTimeout(this.expiryTimer);
1362
- this.expiryTimer = null;
1363
- if (this.revalidationTimer) {
1364
- clearInterval(this.revalidationTimer);
1365
- this.revalidationTimer = null;
1366
- }
1367
- this.cancelRetry();
1368
- }
1369
- /**
1370
- * Force re-registration by clearing cached fingerprint
1371
- * (For debugging/testing - forces device to re-register on next validation)
1372
- */
1373
- async forceReregister() {
1374
- if (this.cachedLicense) {
1375
- this.cachedLicense.fingerprint = void 0;
1376
- this.cachedLicense.deviceName = void 0;
1377
- }
1378
- this.fingerprintPromise = null;
1379
- await this.clearCachedLicense();
1380
- try {
1381
- const storage2 = await getLicenseStorage();
1382
- await storage2.removeItem(DEVICE_FINGERPRINT_STORAGE_KEY);
1383
- } catch {
1384
- }
1385
- }
1386
- };
1387
- var LicenseManager = new LicenseManagerImpl();
1388
- var devTimers = globalThis;
1389
- if (typeof __DEV__ !== "undefined" && __DEV__) {
1390
- devTimers.__LICENSE_MANAGER_FOREGROUND_OFF__?.();
1391
- devTimers.__LICENSE_MANAGER_FOREGROUND_OFF__ = void 0;
1392
- if (devTimers.__LICENSE_MANAGER_EXPIRY_TIMER__) clearTimeout(devTimers.__LICENSE_MANAGER_EXPIRY_TIMER__);
1393
- if (devTimers.__LICENSE_MANAGER_TIMER__) {
1394
- clearInterval(devTimers.__LICENSE_MANAGER_TIMER__);
1395
- }
1396
- if (devTimers.__LICENSE_MANAGER_RETRY_TIMER__) {
1397
- clearTimeout(devTimers.__LICENSE_MANAGER_RETRY_TIMER__);
1398
- devTimers.__LICENSE_MANAGER_RETRY_TIMER__ = void 0;
1399
- }
1400
- }
1401
-
1402
- export {
1403
- LicenseTransportError,
1404
- isTransientLicenseError,
1405
- isBuoyToken,
1406
- resolveAccountAccess,
1407
- LicenseManager
1408
- };
1409
- /*!
1410
- * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
1411
- *
1412
- * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
1413
- * (https://buoy.gg/terms), not an OSS license.
1414
- *
1415
- * This file configures the technological measure that controls access to paid
1416
- * Buoy Pro features. Modifying, patching, aliasing, stubbing, or otherwise
1417
- * altering it — including repointing `apiUrl` at a stand-in license server — so
1418
- * that Pro features unlock without a valid license is a breach of the EULA and,
1419
- * in the United States, circumvention of an access control under 17 U.S.C.
1420
- * § 1201, which applies whether or not any code is copied.
1421
- *
1422
- * Pro is $9/mo, and is free every weekend for anyone holding a key —
1423
- * including a free one (`npx buoy login`). Buy a key at
1424
- * https://buoy.gg or use it free on Saturday and Sunday.
1425
- */
1426
- /*!
1427
- * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
1428
- *
1429
- * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
1430
- * (https://buoy.gg/terms), not an OSS license.
1431
- *
1432
- * This file is part of the technological measure that controls access to paid
1433
- * Buoy Pro features. Modifying, patching, aliasing, stubbing, or otherwise
1434
- * altering it so that Pro features unlock without a valid license is a breach
1435
- * of the EULA and, in the United States, circumvention of an access control
1436
- * under 17 U.S.C. § 1201 — which applies whether or not any code is copied.
1437
- *
1438
- * Pro is $9/mo, and is free every weekend for anyone holding a key —
1439
- * including a free one (`npx buoy login`). Buy a key at
1440
- * https://buoy.gg or use it free on Saturday and Sunday.
1441
- */
1442
- /*!
1443
- * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
1444
- *
1445
- * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
1446
- * (https://buoy.gg/terms), not an OSS license.
1447
- */
1448
- /*!
1449
- * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
1450
- *
1451
- * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
1452
- * (https://buoy.gg/terms), not an OSS license.
1453
- *
1454
- * This file is part of the technological measure that controls access to paid
1455
- * Buoy Pro features: it decides which tier a validated key grants. Modifying,
1456
- * patching, aliasing, stubbing, or otherwise altering it so that Pro features
1457
- * unlock without a valid license is a breach of the EULA and, in the United
1458
- * States, circumvention of an access control under 17 U.S.C. § 1201 — which
1459
- * applies whether or not any code is copied.
1460
- *
1461
- * Pro is $9/mo, and is free every weekend for anyone holding a key —
1462
- * including a free one (`npx buoy login`). Buy a key at
1463
- * https://buoy.gg or use it free on Saturday and Sunday.
1464
- */
1465
- /*!
1466
- * PROPRIETARY — Buoy Pro license enforcement. NOT open source.
1467
- *
1468
- * Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
1469
- * (https://buoy.gg/terms), not an OSS license.
1470
- *
1471
- * This file is the technological measure that controls access to paid Buoy Pro
1472
- * features. Modifying, patching, aliasing, stubbing, or otherwise altering it
1473
- * so that Pro features unlock without a valid license is a breach of the EULA
1474
- * and, in the United States, circumvention of an access control under
1475
- * 17 U.S.C. § 1201 — which applies whether or not any code is copied.
1476
- *
1477
- * Pro is $9/mo, and is free every weekend for anyone holding a key —
1478
- * including a free one (`npx buoy login`). Buy a key at
1479
- * https://buoy.gg or use it free on Saturday and Sunday.
1480
- */