@licenseseat/js 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -78
- package/dist/index.js +16 -4
- package/dist/types/LicenseSeat.d.ts +4 -4
- package/dist/types/utils.d.ts +4 -2
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/LicenseSeat.js +6 -6
- package/src/utils.js +28 -4
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# LicenseSeat JavaScript SDK
|
|
2
|
-
|
|
3
|
-
Official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com) – the simple, secure licensing platform for apps, games, and plugins.
|
|
1
|
+
# LicenseSeat - JavaScript SDK
|
|
4
2
|
|
|
5
3
|
[](https://github.com/licenseseat/licenseseat-js/actions/workflows/ci.yml)
|
|
6
4
|
[](https://www.npmjs.com/package/@licenseseat/js)
|
|
7
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com) – the simple, secure licensing platform for apps, games, and plugins.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -148,18 +148,18 @@ const sdk = new LicenseSeat({
|
|
|
148
148
|
|
|
149
149
|
### Configuration Options
|
|
150
150
|
|
|
151
|
-
| Option
|
|
152
|
-
|
|
153
|
-
| `apiKey`
|
|
154
|
-
| `apiBaseUrl`
|
|
155
|
-
| `storagePrefix`
|
|
156
|
-
| `autoValidateInterval`
|
|
157
|
-
| `autoInitialize`
|
|
158
|
-
| `offlineFallbackEnabled` | `boolean` | `false`
|
|
159
|
-
| `maxOfflineDays`
|
|
160
|
-
| `maxRetries`
|
|
161
|
-
| `retryDelay`
|
|
162
|
-
| `debug`
|
|
151
|
+
| Option | Type | Default | Description |
|
|
152
|
+
| ------------------------ | --------- | ------------------------------- | --------------------------------------------------------- |
|
|
153
|
+
| `apiKey` | `string` | `null` | API key for authentication (required for most operations) |
|
|
154
|
+
| `apiBaseUrl` | `string` | `'https://licenseseat.com/api'` | API base URL |
|
|
155
|
+
| `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
|
|
156
|
+
| `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
|
|
157
|
+
| `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
|
|
158
|
+
| `offlineFallbackEnabled` | `boolean` | `false` | Enable offline validation on network errors |
|
|
159
|
+
| `maxOfflineDays` | `number` | `0` | Maximum days license works offline (0 = disabled) |
|
|
160
|
+
| `maxRetries` | `number` | `3` | Max retry attempts for failed API calls |
|
|
161
|
+
| `retryDelay` | `number` | `1000` | Initial retry delay in ms (exponential backoff) |
|
|
162
|
+
| `debug` | `boolean` | `false` | Enable debug logging to console |
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
|
@@ -339,42 +339,42 @@ sdk.off('activation:success', handler);
|
|
|
339
339
|
|
|
340
340
|
### Available Events
|
|
341
341
|
|
|
342
|
-
| Event
|
|
343
|
-
|
|
344
|
-
| **Lifecycle**
|
|
345
|
-
| `license:loaded`
|
|
346
|
-
| `sdk:reset`
|
|
347
|
-
| `sdk:destroyed`
|
|
348
|
-
| `sdk:error`
|
|
349
|
-
| **Activation**
|
|
350
|
-
| `activation:start`
|
|
351
|
-
| `activation:success`
|
|
352
|
-
| `activation:error`
|
|
353
|
-
| **Deactivation**
|
|
354
|
-
| `deactivation:start`
|
|
355
|
-
| `deactivation:success`
|
|
356
|
-
| `deactivation:error`
|
|
357
|
-
| **Validation**
|
|
358
|
-
| `validation:start`
|
|
359
|
-
| `validation:success`
|
|
360
|
-
| `validation:failed`
|
|
361
|
-
| `validation:error`
|
|
362
|
-
| `validation:offline-success`
|
|
363
|
-
| `validation:offline-failed`
|
|
364
|
-
| `validation:auth-failed`
|
|
365
|
-
| **Auto-Validation**
|
|
366
|
-
| `autovalidation:cycle`
|
|
367
|
-
| `autovalidation:stopped`
|
|
368
|
-
| **Network**
|
|
369
|
-
| `network:online`
|
|
370
|
-
| `network:offline`
|
|
371
|
-
| **Offline License**
|
|
372
|
-
| `offlineLicense:fetching`
|
|
373
|
-
| `offlineLicense:fetched`
|
|
374
|
-
| `offlineLicense:fetchError`
|
|
375
|
-
| `offlineLicense:ready`
|
|
376
|
-
| `offlineLicense:verified`
|
|
377
|
-
| `offlineLicense:verificationFailed` | Offline signature invalid
|
|
342
|
+
| Event | Description | Data |
|
|
343
|
+
| ----------------------------------- | ----------------------------------- | ------------------------------- |
|
|
344
|
+
| **Lifecycle** | | |
|
|
345
|
+
| `license:loaded` | Cached license loaded on init | `CachedLicense` |
|
|
346
|
+
| `sdk:reset` | SDK was reset | – |
|
|
347
|
+
| `sdk:destroyed` | SDK was destroyed | – |
|
|
348
|
+
| `sdk:error` | General SDK error | `{ message, error? }` |
|
|
349
|
+
| **Activation** | | |
|
|
350
|
+
| `activation:start` | Activation started | `{ licenseKey, deviceId }` |
|
|
351
|
+
| `activation:success` | Activation succeeded | `CachedLicense` |
|
|
352
|
+
| `activation:error` | Activation failed | `{ licenseKey, error }` |
|
|
353
|
+
| **Deactivation** | | |
|
|
354
|
+
| `deactivation:start` | Deactivation started | `CachedLicense` |
|
|
355
|
+
| `deactivation:success` | Deactivation succeeded | `Object` |
|
|
356
|
+
| `deactivation:error` | Deactivation failed | `{ error, license }` |
|
|
357
|
+
| **Validation** | | |
|
|
358
|
+
| `validation:start` | Validation started | `{ licenseKey }` |
|
|
359
|
+
| `validation:success` | Online validation succeeded | `ValidationResult` |
|
|
360
|
+
| `validation:failed` | Validation failed (invalid license) | `ValidationResult` |
|
|
361
|
+
| `validation:error` | Validation error (network, etc.) | `{ licenseKey, error }` |
|
|
362
|
+
| `validation:offline-success` | Offline validation succeeded | `ValidationResult` |
|
|
363
|
+
| `validation:offline-failed` | Offline validation failed | `ValidationResult` |
|
|
364
|
+
| `validation:auth-failed` | Auth failed during validation | `{ licenseKey, error, cached }` |
|
|
365
|
+
| **Auto-Validation** | | |
|
|
366
|
+
| `autovalidation:cycle` | Auto-validation scheduled | `{ nextRunAt: Date }` |
|
|
367
|
+
| `autovalidation:stopped` | Auto-validation stopped | – |
|
|
368
|
+
| **Network** | | |
|
|
369
|
+
| `network:online` | Network connectivity restored | – |
|
|
370
|
+
| `network:offline` | Network connectivity lost | `{ error }` |
|
|
371
|
+
| **Offline License** | | |
|
|
372
|
+
| `offlineLicense:fetching` | Fetching offline license | `{ licenseKey }` |
|
|
373
|
+
| `offlineLicense:fetched` | Offline license fetched | `{ licenseKey, data }` |
|
|
374
|
+
| `offlineLicense:fetchError` | Offline license fetch failed | `{ licenseKey, error }` |
|
|
375
|
+
| `offlineLicense:ready` | Offline assets synced | `{ kid, exp_at }` |
|
|
376
|
+
| `offlineLicense:verified` | Offline signature verified | `{ payload }` |
|
|
377
|
+
| `offlineLicense:verificationFailed` | Offline signature invalid | `{ payload }` |
|
|
378
378
|
|
|
379
379
|
---
|
|
380
380
|
|
|
@@ -454,12 +454,12 @@ try {
|
|
|
454
454
|
|
|
455
455
|
### Error Types
|
|
456
456
|
|
|
457
|
-
| Error
|
|
458
|
-
|
|
459
|
-
| `APIError`
|
|
460
|
-
| `LicenseError`
|
|
461
|
-
| `ConfigurationError` | SDK misconfiguration
|
|
462
|
-
| `CryptoError`
|
|
457
|
+
| Error | Description |
|
|
458
|
+
| -------------------- | ---------------------------------------------------- |
|
|
459
|
+
| `APIError` | HTTP request failures (includes `status` and `data`) |
|
|
460
|
+
| `LicenseError` | License operation failures (includes `code`) |
|
|
461
|
+
| `ConfigurationError` | SDK misconfiguration |
|
|
462
|
+
| `CryptoError` | Cryptographic operation failures |
|
|
463
463
|
|
|
464
464
|
---
|
|
465
465
|
|
|
@@ -561,17 +561,17 @@ npm install
|
|
|
561
561
|
|
|
562
562
|
### Scripts
|
|
563
563
|
|
|
564
|
-
| Command
|
|
565
|
-
|
|
566
|
-
| `npm run build`
|
|
567
|
-
| `npm run build:js`
|
|
568
|
-
| `npm run build:types`
|
|
569
|
-
| `npm run build:iife`
|
|
570
|
-
| `npm run dev`
|
|
571
|
-
| `npm test`
|
|
572
|
-
| `npm run test:watch`
|
|
573
|
-
| `npm run test:coverage` | Run tests with coverage report
|
|
574
|
-
| `npm run typecheck`
|
|
564
|
+
| Command | Description |
|
|
565
|
+
| ----------------------- | ----------------------------------------- |
|
|
566
|
+
| `npm run build` | Build JS bundle + TypeScript declarations |
|
|
567
|
+
| `npm run build:js` | Build JavaScript bundle only |
|
|
568
|
+
| `npm run build:types` | Generate TypeScript declarations |
|
|
569
|
+
| `npm run build:iife` | Build global/IIFE bundle |
|
|
570
|
+
| `npm run dev` | Watch mode for development |
|
|
571
|
+
| `npm test` | Run tests |
|
|
572
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
573
|
+
| `npm run test:coverage` | Run tests with coverage report |
|
|
574
|
+
| `npm run typecheck` | Type-check without emitting |
|
|
575
575
|
|
|
576
576
|
### Project Structure
|
|
577
577
|
|
|
@@ -671,12 +671,12 @@ This ensures:
|
|
|
671
671
|
|
|
672
672
|
Once published to npm, the package is automatically available on CDNs:
|
|
673
673
|
|
|
674
|
-
| CDN
|
|
675
|
-
|
|
676
|
-
| **esm.sh**
|
|
677
|
-
| **unpkg**
|
|
674
|
+
| CDN | URL |
|
|
675
|
+
| ------------ | ------------------------------------------------------------ |
|
|
676
|
+
| **esm.sh** | `https://esm.sh/@licenseseat/js` |
|
|
677
|
+
| **unpkg** | `https://unpkg.com/@licenseseat/js/dist/index.js` |
|
|
678
678
|
| **jsDelivr** | `https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js` |
|
|
679
|
-
| **Skypack**
|
|
679
|
+
| **Skypack** | `https://cdn.skypack.dev/@licenseseat/js` |
|
|
680
680
|
|
|
681
681
|
**Version pinning** (recommended for production):
|
|
682
682
|
```html
|
|
@@ -731,10 +731,10 @@ This project follows [Semantic Versioning](https://semver.org/):
|
|
|
731
731
|
|
|
732
732
|
### Breaking Changes in v0.2.0
|
|
733
733
|
|
|
734
|
-
| Change
|
|
735
|
-
|
|
736
|
-
| `apiBaseUrl` default
|
|
737
|
-
| `offlineFallbackEnabled` default | `true` | `false`
|
|
734
|
+
| Change | Before | After | Migration |
|
|
735
|
+
| -------------------------------- | ------ | ----------------------------- | --------------------------------------------------------------- |
|
|
736
|
+
| `apiBaseUrl` default | `/api` | `https://licenseseat.com/api` | Set `apiBaseUrl` explicitly if using a relative URL |
|
|
737
|
+
| `offlineFallbackEnabled` default | `true` | `false` | Set `offlineFallbackEnabled: true` if you need offline fallback |
|
|
738
738
|
|
|
739
739
|
### New Features in v0.2.0
|
|
740
740
|
|
package/dist/index.js
CHANGED
|
@@ -260,7 +260,14 @@ function base64UrlDecode(base64UrlString) {
|
|
|
260
260
|
while (base64.length % 4) {
|
|
261
261
|
base64 += "=";
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
let raw;
|
|
264
|
+
if (typeof atob === "function") {
|
|
265
|
+
raw = atob(base64);
|
|
266
|
+
} else if (typeof Buffer !== "undefined") {
|
|
267
|
+
raw = Buffer.from(base64, "base64").toString("binary");
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error("No base64 decoder available (neither atob nor Buffer found)");
|
|
270
|
+
}
|
|
264
271
|
const outputArray = new Uint8Array(raw.length);
|
|
265
272
|
for (let i = 0; i < raw.length; ++i) {
|
|
266
273
|
outputArray[i] = raw.charCodeAt(i);
|
|
@@ -289,6 +296,11 @@ function getCanvasFingerprint() {
|
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
function generateDeviceId() {
|
|
299
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
300
|
+
const os = typeof process !== "undefined" ? process.platform : "unknown";
|
|
301
|
+
const arch = typeof process !== "undefined" ? process.arch : "unknown";
|
|
302
|
+
return `node-${hashCode(os + "|" + arch)}`;
|
|
303
|
+
}
|
|
292
304
|
const nav = window.navigator;
|
|
293
305
|
const screen = window.screen;
|
|
294
306
|
const data = [
|
|
@@ -300,7 +312,7 @@ function generateDeviceId() {
|
|
|
300
312
|
nav.hardwareConcurrency,
|
|
301
313
|
getCanvasFingerprint()
|
|
302
314
|
].join("|");
|
|
303
|
-
return `web-${hashCode(data)}
|
|
315
|
+
return `web-${hashCode(data)}`;
|
|
304
316
|
}
|
|
305
317
|
function sleep(ms) {
|
|
306
318
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -748,12 +760,12 @@ var LicenseSeatSDK = class {
|
|
|
748
760
|
* Test server authentication
|
|
749
761
|
* Useful for verifying API key/session is valid.
|
|
750
762
|
* @returns {Promise<Object>} Result from the server
|
|
751
|
-
* @throws {
|
|
763
|
+
* @throws {ConfigurationError} When API key is not configured
|
|
752
764
|
* @throws {APIError} When authentication fails
|
|
753
765
|
*/
|
|
754
766
|
async testAuth() {
|
|
755
767
|
if (!this.config.apiKey) {
|
|
756
|
-
const err = new
|
|
768
|
+
const err = new ConfigurationError("API key is required for auth test");
|
|
757
769
|
this.emit("auth_test:error", { error: err });
|
|
758
770
|
throw err;
|
|
759
771
|
}
|
|
@@ -57,7 +57,7 @@ export class LicenseSeatSDK {
|
|
|
57
57
|
private eventListeners;
|
|
58
58
|
/**
|
|
59
59
|
* Auto-validation timer ID
|
|
60
|
-
* @type {
|
|
60
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
61
61
|
* @private
|
|
62
62
|
*/
|
|
63
63
|
private validationTimer;
|
|
@@ -81,13 +81,13 @@ export class LicenseSeatSDK {
|
|
|
81
81
|
private currentAutoLicenseKey;
|
|
82
82
|
/**
|
|
83
83
|
* Connectivity polling timer ID
|
|
84
|
-
* @type {
|
|
84
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
85
85
|
* @private
|
|
86
86
|
*/
|
|
87
87
|
private connectivityTimer;
|
|
88
88
|
/**
|
|
89
89
|
* Offline license refresh timer ID
|
|
90
|
-
* @type {
|
|
90
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
91
91
|
* @private
|
|
92
92
|
*/
|
|
93
93
|
private offlineRefreshTimer;
|
|
@@ -185,7 +185,7 @@ export class LicenseSeatSDK {
|
|
|
185
185
|
* Test server authentication
|
|
186
186
|
* Useful for verifying API key/session is valid.
|
|
187
187
|
* @returns {Promise<Object>} Result from the server
|
|
188
|
-
* @throws {
|
|
188
|
+
* @throws {ConfigurationError} When API key is not configured
|
|
189
189
|
* @throws {APIError} When authentication fails
|
|
190
190
|
*/
|
|
191
191
|
testAuth(): Promise<any>;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export function constantTimeEqual(a?: string, b?: string): boolean;
|
|
|
20
20
|
export function canonicalJsonStringify(obj: any): string;
|
|
21
21
|
/**
|
|
22
22
|
* Decode a Base64URL string to a Uint8Array
|
|
23
|
+
* Works in both browser and Node.js environments.
|
|
23
24
|
* @param {string} base64UrlString - The Base64URL encoded string
|
|
24
25
|
* @returns {Uint8Array} Decoded bytes
|
|
25
26
|
*/
|
|
@@ -36,8 +37,9 @@ export function hashCode(str: string): string;
|
|
|
36
37
|
*/
|
|
37
38
|
export function getCanvasFingerprint(): string;
|
|
38
39
|
/**
|
|
39
|
-
* Generate a
|
|
40
|
-
*
|
|
40
|
+
* Generate a stable device identifier based on browser characteristics.
|
|
41
|
+
* The ID is deterministic - same device will produce the same ID across calls.
|
|
42
|
+
* @returns {string} Stable device identifier
|
|
41
43
|
*/
|
|
42
44
|
export function generateDeviceId(): string;
|
|
43
45
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wDAFa,OAAO,YAAY,EAAE,WAAW,EAAE,CAS9C;AAED;;;;;GAKG;AACH,sCAJW,MAAM,MACN,MAAM,GACJ,OAAO,CASnB;AAED;;;;;GAKG;AACH,kDAFa,MAAM,CAuBlB;AAED
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wDAFa,OAAO,YAAY,EAAE,WAAW,EAAE,CAS9C;AAED;;;;;GAKG;AACH,sCAJW,MAAM,MACN,MAAM,GACJ,OAAO,CASnB;AAED;;;;;GAKG;AACH,kDAFa,MAAM,CAuBlB;AAED;;;;;GAKG;AACH,iDAHW,MAAM,GACJ,UAAU,CA0BtB;AAED;;;;GAIG;AACH,8BAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;GAGG;AACH,wCAFa,MAAM,CAalB;AAED;;;;GAIG;AACH,oCAFa,MAAM,CAyBlB;AAED;;;;GAIG;AACH,0BAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;AAED;;;GAGG;AACH,gCAFa,MAAM,CAMlB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@licenseseat/js",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Official JavaScript SDK for LicenseSeat – simple, secure software licensing.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"node": ">=18.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@types/node": "^25.0.9",
|
|
65
66
|
"@vitest/coverage-v8": "^1.6.0",
|
|
66
67
|
"esbuild": "^0.20.2",
|
|
67
68
|
"jsdom": "^24.1.3",
|
package/src/LicenseSeat.js
CHANGED
|
@@ -21,7 +21,7 @@ import * as ed from "@noble/ed25519";
|
|
|
21
21
|
import { sha512 } from "@noble/hashes/sha512";
|
|
22
22
|
|
|
23
23
|
import { LicenseCache } from "./cache.js";
|
|
24
|
-
import { APIError, LicenseError, CryptoError } from "./errors.js";
|
|
24
|
+
import { APIError, ConfigurationError, LicenseError, CryptoError } from "./errors.js";
|
|
25
25
|
import {
|
|
26
26
|
parseActiveEntitlements,
|
|
27
27
|
constantTimeEqual,
|
|
@@ -98,7 +98,7 @@ export class LicenseSeatSDK {
|
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* Auto-validation timer ID
|
|
101
|
-
* @type {
|
|
101
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
102
102
|
* @private
|
|
103
103
|
*/
|
|
104
104
|
this.validationTimer = null;
|
|
@@ -126,14 +126,14 @@ export class LicenseSeatSDK {
|
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
128
|
* Connectivity polling timer ID
|
|
129
|
-
* @type {
|
|
129
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
130
130
|
* @private
|
|
131
131
|
*/
|
|
132
132
|
this.connectivityTimer = null;
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Offline license refresh timer ID
|
|
136
|
-
* @type {
|
|
136
|
+
* @type {ReturnType<typeof setInterval>|null}
|
|
137
137
|
* @private
|
|
138
138
|
*/
|
|
139
139
|
this.offlineRefreshTimer = null;
|
|
@@ -637,12 +637,12 @@ export class LicenseSeatSDK {
|
|
|
637
637
|
* Test server authentication
|
|
638
638
|
* Useful for verifying API key/session is valid.
|
|
639
639
|
* @returns {Promise<Object>} Result from the server
|
|
640
|
-
* @throws {
|
|
640
|
+
* @throws {ConfigurationError} When API key is not configured
|
|
641
641
|
* @throws {APIError} When authentication fails
|
|
642
642
|
*/
|
|
643
643
|
async testAuth() {
|
|
644
644
|
if (!this.config.apiKey) {
|
|
645
|
-
const err = new
|
|
645
|
+
const err = new ConfigurationError("API key is required for auth test");
|
|
646
646
|
this.emit("auth_test:error", { error: err });
|
|
647
647
|
throw err;
|
|
648
648
|
}
|
package/src/utils.js
CHANGED
|
@@ -66,6 +66,7 @@ export function canonicalJsonStringify(obj) {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Decode a Base64URL string to a Uint8Array
|
|
69
|
+
* Works in both browser and Node.js environments.
|
|
69
70
|
* @param {string} base64UrlString - The Base64URL encoded string
|
|
70
71
|
* @returns {Uint8Array} Decoded bytes
|
|
71
72
|
*/
|
|
@@ -74,7 +75,20 @@ export function base64UrlDecode(base64UrlString) {
|
|
|
74
75
|
while (base64.length % 4) {
|
|
75
76
|
base64 += "=";
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
|
|
79
|
+
// Cross-platform base64 decoding
|
|
80
|
+
/** @type {string} */
|
|
81
|
+
let raw;
|
|
82
|
+
if (typeof atob === "function") {
|
|
83
|
+
// Browser environment (or Node.js 16+ with global atob)
|
|
84
|
+
raw = atob(base64);
|
|
85
|
+
} else if (typeof Buffer !== "undefined") {
|
|
86
|
+
// Node.js environment
|
|
87
|
+
raw = Buffer.from(base64, "base64").toString("binary");
|
|
88
|
+
} else {
|
|
89
|
+
throw new Error("No base64 decoder available (neither atob nor Buffer found)");
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
const outputArray = new Uint8Array(raw.length);
|
|
79
93
|
for (let i = 0; i < raw.length; ++i) {
|
|
80
94
|
outputArray[i] = raw.charCodeAt(i);
|
|
@@ -115,10 +129,19 @@ export function getCanvasFingerprint() {
|
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
/**
|
|
118
|
-
* Generate a
|
|
119
|
-
*
|
|
132
|
+
* Generate a stable device identifier based on browser characteristics.
|
|
133
|
+
* The ID is deterministic - same device will produce the same ID across calls.
|
|
134
|
+
* @returns {string} Stable device identifier
|
|
120
135
|
*/
|
|
121
136
|
export function generateDeviceId() {
|
|
137
|
+
// Check if we're in a browser environment
|
|
138
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
139
|
+
// Node.js or non-browser environment - use a fallback
|
|
140
|
+
const os = typeof process !== "undefined" ? process.platform : "unknown";
|
|
141
|
+
const arch = typeof process !== "undefined" ? process.arch : "unknown";
|
|
142
|
+
return `node-${hashCode(os + "|" + arch)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
122
145
|
const nav = window.navigator;
|
|
123
146
|
const screen = window.screen;
|
|
124
147
|
const data = [
|
|
@@ -131,7 +154,8 @@ export function generateDeviceId() {
|
|
|
131
154
|
getCanvasFingerprint(),
|
|
132
155
|
].join("|");
|
|
133
156
|
|
|
134
|
-
|
|
157
|
+
// Stable ID without timestamp - same device produces same ID
|
|
158
|
+
return `web-${hashCode(data)}`;
|
|
135
159
|
}
|
|
136
160
|
|
|
137
161
|
/**
|