@licenseseat/js 0.1.0 → 0.2.1

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/dist/index.js CHANGED
@@ -1,1019 +1,345 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
1
+ // src/LicenseSeat.js
2
+ import * as ed from "@noble/ed25519";
3
+ import { sha512 } from "@noble/hashes/sha512";
6
4
 
7
- // node_modules/@noble/ed25519/index.js
8
- var ed25519_exports = {};
9
- __export(ed25519_exports, {
10
- CURVE: () => ed25519_CURVE,
11
- ExtendedPoint: () => Point,
12
- Point: () => Point,
13
- etc: () => etc,
14
- getPublicKey: () => getPublicKey,
15
- getPublicKeyAsync: () => getPublicKeyAsync,
16
- sign: () => sign,
17
- signAsync: () => signAsync,
18
- utils: () => utils,
19
- verify: () => verify,
20
- verifyAsync: () => verifyAsync
21
- });
22
- var ed25519_CURVE = {
23
- p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
24
- n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
25
- h: 8n,
26
- a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
27
- d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
28
- Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
29
- Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
30
- };
31
- var { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
32
- var h = 8n;
33
- var L = 32;
34
- var L2 = 64;
35
- var err = (m = "") => {
36
- throw new Error(m);
37
- };
38
- var isBig = (n) => typeof n === "bigint";
39
- var isStr = (s) => typeof s === "string";
40
- var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
41
- var abytes = (a, l) => !isBytes(a) || typeof l === "number" && l > 0 && a.length !== l ? err("Uint8Array expected") : a;
42
- var u8n = (len) => new Uint8Array(len);
43
- var u8fr = (buf) => Uint8Array.from(buf);
44
- var padh = (n, pad) => n.toString(16).padStart(pad, "0");
45
- var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
46
- var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
47
- var _ch = (ch) => {
48
- if (ch >= C._0 && ch <= C._9)
49
- return ch - C._0;
50
- if (ch >= C.A && ch <= C.F)
51
- return ch - (C.A - 10);
52
- if (ch >= C.a && ch <= C.f)
53
- return ch - (C.a - 10);
54
- return;
55
- };
56
- var hexToBytes = (hex) => {
57
- const e = "hex invalid";
58
- if (!isStr(hex))
59
- return err(e);
60
- const hl = hex.length;
61
- const al = hl / 2;
62
- if (hl % 2)
63
- return err(e);
64
- const array = u8n(al);
65
- for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
66
- const n1 = _ch(hex.charCodeAt(hi));
67
- const n2 = _ch(hex.charCodeAt(hi + 1));
68
- if (n1 === void 0 || n2 === void 0)
69
- return err(e);
70
- array[ai] = n1 * 16 + n2;
71
- }
72
- return array;
73
- };
74
- var toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
75
- var cr = () => globalThis?.crypto;
76
- var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined");
77
- var concatBytes = (...arrs) => {
78
- const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
79
- let pad = 0;
80
- arrs.forEach((a) => {
81
- r.set(a, pad);
82
- pad += a.length;
83
- });
84
- return r;
85
- };
86
- var randomBytes = (len = L) => {
87
- const c = cr();
88
- return c.getRandomValues(u8n(len));
89
- };
90
- var big = BigInt;
91
- var arange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
92
- var M = (a, b = P) => {
93
- const r = a % b;
94
- return r >= 0n ? r : b + r;
95
- };
96
- var modN = (a) => M(a, N);
97
- var invert = (num, md) => {
98
- if (num === 0n || md <= 0n)
99
- err("no inverse n=" + num + " mod=" + md);
100
- let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
101
- while (a !== 0n) {
102
- const q = b / a, r = b % a;
103
- const m = x - u * q, n = y - v * q;
104
- b = a, a = r, x = u, y = v, u = m, v = n;
105
- }
106
- return b === 1n ? M(x, md) : err("no inverse");
107
- };
108
- var callHash = (name) => {
109
- const fn = etc[name];
110
- if (typeof fn !== "function")
111
- err("hashes." + name + " not set");
112
- return fn;
113
- };
114
- var apoint = (p) => p instanceof Point ? p : err("Point expected");
115
- var B256 = 2n ** 256n;
116
- var Point = class _Point {
117
- static BASE;
118
- static ZERO;
119
- ex;
120
- ey;
121
- ez;
122
- et;
123
- constructor(ex, ey, ez, et) {
124
- const max = B256;
125
- this.ex = arange(ex, 0n, max);
126
- this.ey = arange(ey, 0n, max);
127
- this.ez = arange(ez, 1n, max);
128
- this.et = arange(et, 0n, max);
129
- Object.freeze(this);
130
- }
131
- static fromAffine(p) {
132
- return new _Point(p.x, p.y, 1n, M(p.x * p.y));
133
- }
134
- /** RFC8032 5.1.3: Uint8Array to Point. */
135
- static fromBytes(hex, zip215 = false) {
136
- const d = _d;
137
- const normed = u8fr(abytes(hex, L));
138
- const lastByte = hex[31];
139
- normed[31] = lastByte & ~128;
140
- const y = bytesToNumLE(normed);
141
- const max = zip215 ? B256 : P;
142
- arange(y, 0n, max);
143
- const y2 = M(y * y);
144
- const u = M(y2 - 1n);
145
- const v = M(d * y2 + 1n);
146
- let { isValid, value: x } = uvRatio(u, v);
147
- if (!isValid)
148
- err("bad point: y not sqrt");
149
- const isXOdd = (x & 1n) === 1n;
150
- const isLastByteOdd = (lastByte & 128) !== 0;
151
- if (!zip215 && x === 0n && isLastByteOdd)
152
- err("bad point: x==0, isLastByteOdd");
153
- if (isLastByteOdd !== isXOdd)
154
- x = M(-x);
155
- return new _Point(x, y, 1n, M(x * y));
156
- }
157
- /** Checks if the point is valid and on-curve. */
158
- assertValidity() {
159
- const a = _a;
160
- const d = _d;
161
- const p = this;
162
- if (p.is0())
163
- throw new Error("bad point: ZERO");
164
- const { ex: X, ey: Y, ez: Z, et: T } = p;
165
- const X2 = M(X * X);
166
- const Y2 = M(Y * Y);
167
- const Z2 = M(Z * Z);
168
- const Z4 = M(Z2 * Z2);
169
- const aX2 = M(X2 * a);
170
- const left = M(Z2 * M(aX2 + Y2));
171
- const right = M(Z4 + M(d * M(X2 * Y2)));
172
- if (left !== right)
173
- throw new Error("bad point: equation left != right (1)");
174
- const XY = M(X * Y);
175
- const ZT = M(Z * T);
176
- if (XY !== ZT)
177
- throw new Error("bad point: equation left != right (2)");
178
- return this;
179
- }
180
- /** Equality check: compare points P&Q. */
181
- equals(other) {
182
- const { ex: X1, ey: Y1, ez: Z1 } = this;
183
- const { ex: X2, ey: Y2, ez: Z2 } = apoint(other);
184
- const X1Z2 = M(X1 * Z2);
185
- const X2Z1 = M(X2 * Z1);
186
- const Y1Z2 = M(Y1 * Z2);
187
- const Y2Z1 = M(Y2 * Z1);
188
- return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
189
- }
190
- is0() {
191
- return this.equals(I);
192
- }
193
- /** Flip point over y coordinate. */
194
- negate() {
195
- return new _Point(M(-this.ex), this.ey, this.ez, M(-this.et));
196
- }
197
- /** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
198
- double() {
199
- const { ex: X1, ey: Y1, ez: Z1 } = this;
200
- const a = _a;
201
- const A = M(X1 * X1);
202
- const B = M(Y1 * Y1);
203
- const C2 = M(2n * M(Z1 * Z1));
204
- const D = M(a * A);
205
- const x1y1 = X1 + Y1;
206
- const E = M(M(x1y1 * x1y1) - A - B);
207
- const G2 = D + B;
208
- const F = G2 - C2;
209
- const H = D - B;
210
- const X3 = M(E * F);
211
- const Y3 = M(G2 * H);
212
- const T3 = M(E * H);
213
- const Z3 = M(F * G2);
214
- return new _Point(X3, Y3, Z3, T3);
5
+ // src/cache.js
6
+ var LicenseCache = class {
7
+ /**
8
+ * Create a LicenseCache instance
9
+ * @param {string} [prefix="licenseseat_"] - Prefix for all localStorage keys
10
+ */
11
+ constructor(prefix = "licenseseat_") {
12
+ this.prefix = prefix;
13
+ this.publicKeyCacheKey = this.prefix + "public_keys";
215
14
  }
216
- /** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
217
- add(other) {
218
- const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
219
- const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other);
220
- const a = _a;
221
- const d = _d;
222
- const A = M(X1 * X2);
223
- const B = M(Y1 * Y2);
224
- const C2 = M(T1 * d * T2);
225
- const D = M(Z1 * Z2);
226
- const E = M((X1 + Y1) * (X2 + Y2) - A - B);
227
- const F = M(D - C2);
228
- const G2 = M(D + C2);
229
- const H = M(B - a * A);
230
- const X3 = M(E * F);
231
- const Y3 = M(G2 * H);
232
- const T3 = M(E * H);
233
- const Z3 = M(F * G2);
234
- return new _Point(X3, Y3, Z3, T3);
15
+ /**
16
+ * Get the cached license data
17
+ * @returns {import('./types.js').CachedLicense|null} Cached license or null if not found
18
+ */
19
+ getLicense() {
20
+ try {
21
+ const data = localStorage.getItem(this.prefix + "license");
22
+ return data ? JSON.parse(data) : null;
23
+ } catch (e) {
24
+ console.error("Failed to read license cache:", e);
25
+ return null;
26
+ }
235
27
  }
236
28
  /**
237
- * Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
238
- * Uses {@link wNAF} for base point.
239
- * Uses fake point to mitigate side-channel leakage.
240
- * @param n scalar by which point is multiplied
241
- * @param safe safe mode guards against timing attacks; unsafe mode is faster
29
+ * Store license data in cache
30
+ * @param {import('./types.js').CachedLicense} data - License data to cache
31
+ * @returns {void}
242
32
  */
243
- multiply(n, safe = true) {
244
- if (!safe && (n === 0n || this.is0()))
245
- return I;
246
- arange(n, 1n, N);
247
- if (n === 1n)
248
- return this;
249
- if (this.equals(G))
250
- return wNAF(n).p;
251
- let p = I;
252
- let f = G;
253
- for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
254
- if (n & 1n)
255
- p = p.add(d);
256
- else if (safe)
257
- f = f.add(d);
33
+ setLicense(data) {
34
+ try {
35
+ localStorage.setItem(this.prefix + "license", JSON.stringify(data));
36
+ } catch (e) {
37
+ console.error("Failed to cache license:", e);
258
38
  }
259
- return p;
260
39
  }
261
- /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
262
- toAffine() {
263
- const { ex: x, ey: y, ez: z } = this;
264
- if (this.equals(I))
265
- return { x: 0n, y: 1n };
266
- const iz = invert(z, P);
267
- if (M(z * iz) !== 1n)
268
- err("invalid inverse");
269
- return { x: M(x * iz), y: M(y * iz) };
40
+ /**
41
+ * Update the validation data for the cached license
42
+ * @param {import('./types.js').ValidationResult} validationData - Validation result to store
43
+ * @returns {void}
44
+ */
45
+ updateValidation(validationData) {
46
+ const license = this.getLicense();
47
+ if (license) {
48
+ license.validation = validationData;
49
+ license.last_validated = (/* @__PURE__ */ new Date()).toISOString();
50
+ this.setLicense(license);
51
+ }
270
52
  }
271
- toBytes() {
272
- const { x, y } = this.assertValidity().toAffine();
273
- const b = numTo32bLE(y);
274
- b[31] |= x & 1n ? 128 : 0;
275
- return b;
53
+ /**
54
+ * Get the device identifier from the cached license
55
+ * @returns {string|null} Device identifier or null if not found
56
+ */
57
+ getDeviceId() {
58
+ const license = this.getLicense();
59
+ return license ? license.device_identifier : null;
276
60
  }
277
- toHex() {
278
- return bytesToHex(this.toBytes());
61
+ /**
62
+ * Clear the cached license data
63
+ * @returns {void}
64
+ */
65
+ clearLicense() {
66
+ localStorage.removeItem(this.prefix + "license");
279
67
  }
280
- // encode to hex string
281
- clearCofactor() {
282
- return this.multiply(big(h), false);
68
+ /**
69
+ * Get the cached offline license
70
+ * @returns {import('./types.js').SignedOfflineLicense|null} Offline license or null if not found
71
+ */
72
+ getOfflineLicense() {
73
+ try {
74
+ const data = localStorage.getItem(this.prefix + "offline_license");
75
+ return data ? JSON.parse(data) : null;
76
+ } catch (e) {
77
+ console.error("Failed to read offline license cache:", e);
78
+ return null;
79
+ }
283
80
  }
284
- isSmallOrder() {
285
- return this.clearCofactor().is0();
81
+ /**
82
+ * Store offline license data in cache
83
+ * @param {import('./types.js').SignedOfflineLicense} data - Signed offline license to cache
84
+ * @returns {void}
85
+ */
86
+ setOfflineLicense(data) {
87
+ try {
88
+ localStorage.setItem(
89
+ this.prefix + "offline_license",
90
+ JSON.stringify(data)
91
+ );
92
+ } catch (e) {
93
+ console.error("Failed to cache offline license:", e);
94
+ }
286
95
  }
287
- isTorsionFree() {
288
- let p = this.multiply(N / 2n, false).double();
289
- if (N % 2n)
290
- p = p.add(this);
291
- return p.is0();
96
+ /**
97
+ * Clear the cached offline license
98
+ * @returns {void}
99
+ */
100
+ clearOfflineLicense() {
101
+ localStorage.removeItem(this.prefix + "offline_license");
292
102
  }
293
- static fromHex(hex, zip215) {
294
- return _Point.fromBytes(toU8(hex), zip215);
103
+ /**
104
+ * Get a cached public key by key ID
105
+ * @param {string} keyId - The key ID to look up
106
+ * @returns {string|null} Base64-encoded public key or null if not found
107
+ */
108
+ getPublicKey(keyId) {
109
+ try {
110
+ const cache = JSON.parse(
111
+ localStorage.getItem(this.publicKeyCacheKey) || "{}"
112
+ );
113
+ return cache[keyId] || null;
114
+ } catch (e) {
115
+ console.error("Failed to read public key cache:", e);
116
+ return null;
117
+ }
295
118
  }
296
- get x() {
297
- return this.toAffine().x;
119
+ /**
120
+ * Store a public key in cache
121
+ * @param {string} keyId - The key ID
122
+ * @param {string} publicKeyB64 - Base64-encoded public key
123
+ * @returns {void}
124
+ */
125
+ setPublicKey(keyId, publicKeyB64) {
126
+ try {
127
+ const cache = JSON.parse(
128
+ localStorage.getItem(this.publicKeyCacheKey) || "{}"
129
+ );
130
+ cache[keyId] = publicKeyB64;
131
+ localStorage.setItem(this.publicKeyCacheKey, JSON.stringify(cache));
132
+ } catch (e) {
133
+ console.error("Failed to cache public key:", e);
134
+ }
298
135
  }
299
- get y() {
300
- return this.toAffine().y;
136
+ /**
137
+ * Clear all LicenseSeat SDK data for this prefix
138
+ * @returns {void}
139
+ */
140
+ clear() {
141
+ Object.keys(localStorage).forEach((key) => {
142
+ if (key.startsWith(this.prefix)) {
143
+ localStorage.removeItem(key);
144
+ }
145
+ });
146
+ this.clearOfflineLicense();
147
+ localStorage.removeItem(this.prefix + "last_seen_ts");
301
148
  }
302
- toRawBytes() {
303
- return this.toBytes();
149
+ /**
150
+ * Get the last seen timestamp (for clock tamper detection)
151
+ * @returns {number|null} Unix timestamp in milliseconds or null if not set
152
+ */
153
+ getLastSeenTimestamp() {
154
+ const v = localStorage.getItem(this.prefix + "last_seen_ts");
155
+ return v ? parseInt(v, 10) : null;
304
156
  }
305
- };
306
- var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
307
- var I = new Point(0n, 1n, 1n, 0n);
308
- Point.BASE = G;
309
- Point.ZERO = I;
310
- var numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
311
- var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
312
- var pow2 = (x, power) => {
313
- let r = x;
314
- while (power-- > 0n) {
315
- r *= r;
316
- r %= P;
157
+ /**
158
+ * Store the last seen timestamp
159
+ * @param {number} ts - Unix timestamp in milliseconds
160
+ * @returns {void}
161
+ */
162
+ setLastSeenTimestamp(ts) {
163
+ try {
164
+ localStorage.setItem(this.prefix + "last_seen_ts", String(ts));
165
+ } catch (e) {
166
+ }
317
167
  }
318
- return r;
319
- };
320
- var pow_2_252_3 = (x) => {
321
- const x2 = x * x % P;
322
- const b2 = x2 * x % P;
323
- const b4 = pow2(b2, 2n) * b2 % P;
324
- const b5 = pow2(b4, 1n) * x % P;
325
- const b10 = pow2(b5, 5n) * b5 % P;
326
- const b20 = pow2(b10, 10n) * b10 % P;
327
- const b40 = pow2(b20, 20n) * b20 % P;
328
- const b80 = pow2(b40, 40n) * b40 % P;
329
- const b160 = pow2(b80, 80n) * b80 % P;
330
- const b240 = pow2(b160, 80n) * b80 % P;
331
- const b250 = pow2(b240, 10n) * b10 % P;
332
- const pow_p_5_8 = pow2(b250, 2n) * x % P;
333
- return { pow_p_5_8, b2 };
334
- };
335
- var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
336
- var uvRatio = (u, v) => {
337
- const v3 = M(v * v * v);
338
- const v7 = M(v3 * v3 * v);
339
- const pow = pow_2_252_3(u * v7).pow_p_5_8;
340
- let x = M(u * v3 * pow);
341
- const vx2 = M(v * x * x);
342
- const root1 = x;
343
- const root2 = M(x * RM1);
344
- const useRoot1 = vx2 === u;
345
- const useRoot2 = vx2 === M(-u);
346
- const noRoot = vx2 === M(-u * RM1);
347
- if (useRoot1)
348
- x = root1;
349
- if (useRoot2 || noRoot)
350
- x = root2;
351
- if ((M(x) & 1n) === 1n)
352
- x = M(-x);
353
- return { isValid: useRoot1 || useRoot2, value: x };
354
- };
355
- var modL_LE = (hash) => modN(bytesToNumLE(hash));
356
- var sha512a = (...m) => etc.sha512Async(...m);
357
- var sha512s = (...m) => callHash("sha512Sync")(...m);
358
- var hash2extK = (hashed) => {
359
- const head = hashed.slice(0, L);
360
- head[0] &= 248;
361
- head[31] &= 127;
362
- head[31] |= 64;
363
- const prefix = hashed.slice(L, L2);
364
- const scalar = modL_LE(head);
365
- const point = G.multiply(scalar);
366
- const pointBytes = point.toBytes();
367
- return { head, prefix, scalar, point, pointBytes };
368
- };
369
- var getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
370
- var getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
371
- var getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then((p) => p.pointBytes);
372
- var getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
373
- var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
374
- var hashFinishS = (res) => res.finish(sha512s(res.hashable));
375
- var _sign = (e, rBytes, msg) => {
376
- const { pointBytes: P2, scalar: s } = e;
377
- const r = modL_LE(rBytes);
378
- const R = G.multiply(r).toBytes();
379
- const hashable = concatBytes(R, P2, msg);
380
- const finish = (hashed) => {
381
- const S = modN(r + modL_LE(hashed) * s);
382
- return abytes(concatBytes(R, numTo32bLE(S)), L2);
383
- };
384
- return { hashable, finish };
385
- };
386
- var signAsync = async (msg, privKey) => {
387
- const m = toU8(msg);
388
- const e = await getExtendedPublicKeyAsync(privKey);
389
- const rBytes = await sha512a(e.prefix, m);
390
- return hashFinishA(_sign(e, rBytes, m));
391
- };
392
- var sign = (msg, privKey) => {
393
- const m = toU8(msg);
394
- const e = getExtendedPublicKey(privKey);
395
- const rBytes = sha512s(e.prefix, m);
396
- return hashFinishS(_sign(e, rBytes, m));
397
168
  };
398
- var veriOpts = { zip215: true };
399
- var _verify = (sig, msg, pub, opts = veriOpts) => {
400
- sig = toU8(sig, L2);
401
- msg = toU8(msg);
402
- pub = toU8(pub, L);
403
- const { zip215 } = opts;
404
- let A;
405
- let R;
406
- let s;
407
- let SB;
408
- let hashable = Uint8Array.of();
409
- try {
410
- A = Point.fromHex(pub, zip215);
411
- R = Point.fromHex(sig.slice(0, L), zip215);
412
- s = bytesToNumLE(sig.slice(L, L2));
413
- SB = G.multiply(s, false);
414
- hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
415
- } catch (error) {
169
+
170
+ // src/errors.js
171
+ var APIError = class extends Error {
172
+ /**
173
+ * Create an APIError
174
+ * @param {string} message - Error message
175
+ * @param {number} status - HTTP status code (0 for network failures)
176
+ * @param {import('./types.js').APIErrorData} [data] - Additional error data from the API response
177
+ */
178
+ constructor(message, status, data) {
179
+ super(message);
180
+ this.name = "APIError";
181
+ this.status = status;
182
+ this.data = data;
416
183
  }
417
- const finish = (hashed) => {
418
- if (SB == null)
419
- return false;
420
- if (!zip215 && A.isSmallOrder())
421
- return false;
422
- const k = modL_LE(hashed);
423
- const RkA = R.add(A.multiply(k, false));
424
- return RkA.add(SB.negate()).clearCofactor().is0();
425
- };
426
- return { hashable, finish };
427
184
  };
428
- var verifyAsync = async (s, m, p, opts = veriOpts) => hashFinishA(_verify(s, m, p, opts));
429
- var verify = (s, m, p, opts = veriOpts) => hashFinishS(_verify(s, m, p, opts));
430
- var etc = {
431
- sha512Async: async (...messages) => {
432
- const s = subtle();
433
- const m = concatBytes(...messages);
434
- return u8n(await s.digest("SHA-512", m.buffer));
435
- },
436
- sha512Sync: void 0,
437
- bytesToHex,
438
- hexToBytes,
439
- concatBytes,
440
- mod: M,
441
- invert,
442
- randomBytes
443
- };
444
- var utils = {
445
- getExtendedPublicKeyAsync,
446
- getExtendedPublicKey,
447
- randomPrivateKey: () => randomBytes(L),
448
- precompute: (w = 8, p = G) => {
449
- p.multiply(3n);
450
- w;
451
- return p;
185
+ var ConfigurationError = class extends Error {
186
+ /**
187
+ * Create a ConfigurationError
188
+ * @param {string} message - Error message
189
+ */
190
+ constructor(message) {
191
+ super(message);
192
+ this.name = "ConfigurationError";
452
193
  }
453
- // no-op
454
194
  };
455
- var W = 8;
456
- var scalarBits = 256;
457
- var pwindows = Math.ceil(scalarBits / W) + 1;
458
- var pwindowSize = 2 ** (W - 1);
459
- var precompute = () => {
460
- const points = [];
461
- let p = G;
462
- let b = p;
463
- for (let w = 0; w < pwindows; w++) {
464
- b = p;
465
- points.push(b);
466
- for (let i = 1; i < pwindowSize; i++) {
467
- b = b.add(p);
468
- points.push(b);
469
- }
470
- p = b.double();
195
+ var LicenseError = class extends Error {
196
+ /**
197
+ * Create a LicenseError
198
+ * @param {string} message - Error message
199
+ * @param {string} [code] - Machine-readable error code
200
+ */
201
+ constructor(message, code) {
202
+ super(message);
203
+ this.name = "LicenseError";
204
+ this.code = code;
471
205
  }
472
- return points;
473
206
  };
474
- var Gpows = void 0;
475
- var ctneg = (cnd, p) => {
476
- const n = p.negate();
477
- return cnd ? n : p;
478
- };
479
- var wNAF = (n) => {
480
- const comp = Gpows || (Gpows = precompute());
481
- let p = I;
482
- let f = G;
483
- const pow_2_w = 2 ** W;
484
- const maxNum = pow_2_w;
485
- const mask = big(pow_2_w - 1);
486
- const shiftBy = big(W);
487
- for (let w = 0; w < pwindows; w++) {
488
- let wbits = Number(n & mask);
489
- n >>= shiftBy;
490
- if (wbits > pwindowSize) {
491
- wbits -= maxNum;
492
- n += 1n;
493
- }
494
- const off = w * pwindowSize;
495
- const offF = off;
496
- const offP = off + Math.abs(wbits) - 1;
497
- const isEven = w % 2 !== 0;
498
- const isNeg = wbits < 0;
499
- if (wbits === 0) {
500
- f = f.add(ctneg(isEven, comp[offF]));
501
- } else {
502
- p = p.add(ctneg(isNeg, comp[offP]));
503
- }
207
+ var CryptoError = class extends Error {
208
+ /**
209
+ * Create a CryptoError
210
+ * @param {string} message - Error message
211
+ */
212
+ constructor(message) {
213
+ super(message);
214
+ this.name = "CryptoError";
504
215
  }
505
- return { p, f };
506
216
  };
507
217
 
508
- // node_modules/@noble/hashes/esm/utils.js
509
- function isBytes2(a) {
510
- return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
511
- }
512
- function abytes2(b, ...lengths) {
513
- if (!isBytes2(b))
514
- throw new Error("Uint8Array expected");
515
- if (lengths.length > 0 && !lengths.includes(b.length))
516
- throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
218
+ // src/utils.js
219
+ import CJSON from "canonical-json";
220
+ function parseActiveEntitlements(payload = {}) {
221
+ const raw = payload.active_ents || payload.active_entitlements || [];
222
+ return raw.map((e) => ({
223
+ key: e.key,
224
+ expires_at: e.expires_at ?? null,
225
+ metadata: e.metadata ?? null
226
+ }));
517
227
  }
518
- function aexists(instance, checkFinished = true) {
519
- if (instance.destroyed)
520
- throw new Error("Hash instance has been destroyed");
521
- if (checkFinished && instance.finished)
522
- throw new Error("Hash#digest() has already been called");
523
- }
524
- function aoutput(out, instance) {
525
- abytes2(out);
526
- const min = instance.outputLen;
527
- if (out.length < min) {
528
- throw new Error("digestInto() expects output buffer of length at least " + min);
529
- }
530
- }
531
- function clean(...arrays) {
532
- for (let i = 0; i < arrays.length; i++) {
533
- arrays[i].fill(0);
228
+ function constantTimeEqual(a = "", b = "") {
229
+ if (a.length !== b.length)
230
+ return false;
231
+ let res = 0;
232
+ for (let i = 0; i < a.length; i++) {
233
+ res |= a.charCodeAt(i) ^ b.charCodeAt(i);
534
234
  }
235
+ return res === 0;
535
236
  }
536
- function createView(arr) {
537
- return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
538
- }
539
- function utf8ToBytes(str2) {
540
- if (typeof str2 !== "string")
541
- throw new Error("string expected");
542
- return new Uint8Array(new TextEncoder().encode(str2));
543
- }
544
- function toBytes(data) {
545
- if (typeof data === "string")
546
- data = utf8ToBytes(data);
547
- abytes2(data);
548
- return data;
549
- }
550
- var Hash = class {
551
- };
552
- function createHasher(hashCons) {
553
- const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
554
- const tmp = hashCons();
555
- hashC.outputLen = tmp.outputLen;
556
- hashC.blockLen = tmp.blockLen;
557
- hashC.create = () => hashCons();
558
- return hashC;
559
- }
560
-
561
- // node_modules/@noble/hashes/esm/_md.js
562
- function setBigUint64(view, byteOffset, value, isLE) {
563
- if (typeof view.setBigUint64 === "function")
564
- return view.setBigUint64(byteOffset, value, isLE);
565
- const _32n2 = BigInt(32);
566
- const _u32_max = BigInt(4294967295);
567
- const wh = Number(value >> _32n2 & _u32_max);
568
- const wl = Number(value & _u32_max);
569
- const h2 = isLE ? 4 : 0;
570
- const l = isLE ? 0 : 4;
571
- view.setUint32(byteOffset + h2, wh, isLE);
572
- view.setUint32(byteOffset + l, wl, isLE);
573
- }
574
- var HashMD = class extends Hash {
575
- constructor(blockLen, outputLen, padOffset, isLE) {
576
- super();
577
- this.finished = false;
578
- this.length = 0;
579
- this.pos = 0;
580
- this.destroyed = false;
581
- this.blockLen = blockLen;
582
- this.outputLen = outputLen;
583
- this.padOffset = padOffset;
584
- this.isLE = isLE;
585
- this.buffer = new Uint8Array(blockLen);
586
- this.view = createView(this.buffer);
587
- }
588
- update(data) {
589
- aexists(this);
590
- data = toBytes(data);
591
- abytes2(data);
592
- const { view, buffer, blockLen } = this;
593
- const len = data.length;
594
- for (let pos = 0; pos < len; ) {
595
- const take = Math.min(blockLen - this.pos, len - pos);
596
- if (take === blockLen) {
597
- const dataView = createView(data);
598
- for (; blockLen <= len - pos; pos += blockLen)
599
- this.process(dataView, pos);
600
- continue;
601
- }
602
- buffer.set(data.subarray(pos, pos + take), this.pos);
603
- this.pos += take;
604
- pos += take;
605
- if (this.pos === blockLen) {
606
- this.process(view, 0);
607
- this.pos = 0;
608
- }
609
- }
610
- this.length += data.length;
611
- this.roundClean();
612
- return this;
613
- }
614
- digestInto(out) {
615
- aexists(this);
616
- aoutput(out, this);
617
- this.finished = true;
618
- const { buffer, view, blockLen, isLE } = this;
619
- let { pos } = this;
620
- buffer[pos++] = 128;
621
- clean(this.buffer.subarray(pos));
622
- if (this.padOffset > blockLen - pos) {
623
- this.process(view, 0);
624
- pos = 0;
237
+ function canonicalJsonStringify(obj) {
238
+ const stringify = typeof CJSON === "function" ? CJSON : CJSON && typeof CJSON === "object" ? (
239
+ /** @type {any} */
240
+ CJSON.stringify
241
+ ) : void 0;
242
+ if (!stringify || typeof stringify !== "function") {
243
+ console.warn(
244
+ "[LicenseSeat SDK] canonical-json library not loaded correctly. Falling back to basic JSON.stringify. Signature verification might be unreliable if server uses different canonicalization."
245
+ );
246
+ try {
247
+ const sortedObj = {};
248
+ Object.keys(obj).sort().forEach((key) => {
249
+ sortedObj[key] = obj[key];
250
+ });
251
+ return JSON.stringify(sortedObj);
252
+ } catch (e) {
253
+ return JSON.stringify(obj);
625
254
  }
626
- for (let i = pos; i < blockLen; i++)
627
- buffer[i] = 0;
628
- setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
629
- this.process(view, 0);
630
- const oview = createView(out);
631
- const len = this.outputLen;
632
- if (len % 4)
633
- throw new Error("_sha2: outputLen should be aligned to 32bit");
634
- const outLen = len / 4;
635
- const state = this.get();
636
- if (outLen > state.length)
637
- throw new Error("_sha2: outputLen bigger than state");
638
- for (let i = 0; i < outLen; i++)
639
- oview.setUint32(4 * i, state[i], isLE);
640
- }
641
- digest() {
642
- const { buffer, outputLen } = this;
643
- this.digestInto(buffer);
644
- const res = buffer.slice(0, outputLen);
645
- this.destroy();
646
- return res;
647
- }
648
- _cloneInto(to) {
649
- to || (to = new this.constructor());
650
- to.set(...this.get());
651
- const { blockLen, buffer, length, finished, destroyed, pos } = this;
652
- to.destroyed = destroyed;
653
- to.finished = finished;
654
- to.length = length;
655
- to.pos = pos;
656
- if (length % blockLen)
657
- to.buffer.set(buffer);
658
- return to;
659
- }
660
- clone() {
661
- return this._cloneInto();
662
255
  }
663
- };
664
- var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
665
- 1779033703,
666
- 4089235720,
667
- 3144134277,
668
- 2227873595,
669
- 1013904242,
670
- 4271175723,
671
- 2773480762,
672
- 1595750129,
673
- 1359893119,
674
- 2917565137,
675
- 2600822924,
676
- 725511199,
677
- 528734635,
678
- 4215389547,
679
- 1541459225,
680
- 327033209
681
- ]);
682
-
683
- // node_modules/@noble/hashes/esm/_u64.js
684
- var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
685
- var _32n = /* @__PURE__ */ BigInt(32);
686
- function fromBig(n, le = false) {
687
- if (le)
688
- return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
689
- return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
256
+ return stringify(obj);
690
257
  }
691
- function split(lst, le = false) {
692
- const len = lst.length;
693
- let Ah = new Uint32Array(len);
694
- let Al = new Uint32Array(len);
695
- for (let i = 0; i < len; i++) {
696
- const { h: h2, l } = fromBig(lst[i], le);
697
- [Ah[i], Al[i]] = [h2, l];
258
+ function base64UrlDecode(base64UrlString) {
259
+ let base64 = base64UrlString.replace(/-/g, "+").replace(/_/g, "/");
260
+ while (base64.length % 4) {
261
+ base64 += "=";
698
262
  }
699
- return [Ah, Al];
263
+ const raw = window.atob(base64);
264
+ const outputArray = new Uint8Array(raw.length);
265
+ for (let i = 0; i < raw.length; ++i) {
266
+ outputArray[i] = raw.charCodeAt(i);
267
+ }
268
+ return outputArray;
700
269
  }
701
- var shrSH = (h2, _l, s) => h2 >>> s;
702
- var shrSL = (h2, l, s) => h2 << 32 - s | l >>> s;
703
- var rotrSH = (h2, l, s) => h2 >>> s | l << 32 - s;
704
- var rotrSL = (h2, l, s) => h2 << 32 - s | l >>> s;
705
- var rotrBH = (h2, l, s) => h2 << 64 - s | l >>> s - 32;
706
- var rotrBL = (h2, l, s) => h2 >>> s - 32 | l << 64 - s;
707
- function add(Ah, Al, Bh, Bl) {
708
- const l = (Al >>> 0) + (Bl >>> 0);
709
- return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
270
+ function hashCode(str) {
271
+ let hash = 0;
272
+ for (let i = 0; i < str.length; i++) {
273
+ const char = str.charCodeAt(i);
274
+ hash = (hash << 5) - hash + char;
275
+ hash = hash & hash;
276
+ }
277
+ return Math.abs(hash).toString(36);
710
278
  }
711
- var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
712
- var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
713
- var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
714
- var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
715
- var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
716
- var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
717
-
718
- // node_modules/@noble/hashes/esm/sha2.js
719
- var K512 = /* @__PURE__ */ (() => split([
720
- "0x428a2f98d728ae22",
721
- "0x7137449123ef65cd",
722
- "0xb5c0fbcfec4d3b2f",
723
- "0xe9b5dba58189dbbc",
724
- "0x3956c25bf348b538",
725
- "0x59f111f1b605d019",
726
- "0x923f82a4af194f9b",
727
- "0xab1c5ed5da6d8118",
728
- "0xd807aa98a3030242",
729
- "0x12835b0145706fbe",
730
- "0x243185be4ee4b28c",
731
- "0x550c7dc3d5ffb4e2",
732
- "0x72be5d74f27b896f",
733
- "0x80deb1fe3b1696b1",
734
- "0x9bdc06a725c71235",
735
- "0xc19bf174cf692694",
736
- "0xe49b69c19ef14ad2",
737
- "0xefbe4786384f25e3",
738
- "0x0fc19dc68b8cd5b5",
739
- "0x240ca1cc77ac9c65",
740
- "0x2de92c6f592b0275",
741
- "0x4a7484aa6ea6e483",
742
- "0x5cb0a9dcbd41fbd4",
743
- "0x76f988da831153b5",
744
- "0x983e5152ee66dfab",
745
- "0xa831c66d2db43210",
746
- "0xb00327c898fb213f",
747
- "0xbf597fc7beef0ee4",
748
- "0xc6e00bf33da88fc2",
749
- "0xd5a79147930aa725",
750
- "0x06ca6351e003826f",
751
- "0x142929670a0e6e70",
752
- "0x27b70a8546d22ffc",
753
- "0x2e1b21385c26c926",
754
- "0x4d2c6dfc5ac42aed",
755
- "0x53380d139d95b3df",
756
- "0x650a73548baf63de",
757
- "0x766a0abb3c77b2a8",
758
- "0x81c2c92e47edaee6",
759
- "0x92722c851482353b",
760
- "0xa2bfe8a14cf10364",
761
- "0xa81a664bbc423001",
762
- "0xc24b8b70d0f89791",
763
- "0xc76c51a30654be30",
764
- "0xd192e819d6ef5218",
765
- "0xd69906245565a910",
766
- "0xf40e35855771202a",
767
- "0x106aa07032bbd1b8",
768
- "0x19a4c116b8d2d0c8",
769
- "0x1e376c085141ab53",
770
- "0x2748774cdf8eeb99",
771
- "0x34b0bcb5e19b48a8",
772
- "0x391c0cb3c5c95a63",
773
- "0x4ed8aa4ae3418acb",
774
- "0x5b9cca4f7763e373",
775
- "0x682e6ff3d6b2b8a3",
776
- "0x748f82ee5defb2fc",
777
- "0x78a5636f43172f60",
778
- "0x84c87814a1f0ab72",
779
- "0x8cc702081a6439ec",
780
- "0x90befffa23631e28",
781
- "0xa4506cebde82bde9",
782
- "0xbef9a3f7b2c67915",
783
- "0xc67178f2e372532b",
784
- "0xca273eceea26619c",
785
- "0xd186b8c721c0c207",
786
- "0xeada7dd6cde0eb1e",
787
- "0xf57d4f7fee6ed178",
788
- "0x06f067aa72176fba",
789
- "0x0a637dc5a2c898a6",
790
- "0x113f9804bef90dae",
791
- "0x1b710b35131c471b",
792
- "0x28db77f523047d84",
793
- "0x32caab7b40c72493",
794
- "0x3c9ebe0a15c9bebc",
795
- "0x431d67c49c100d4c",
796
- "0x4cc5d4becb3e42b6",
797
- "0x597f299cfc657e2a",
798
- "0x5fcb6fab3ad6faec",
799
- "0x6c44198c4a475817"
800
- ].map((n) => BigInt(n))))();
801
- var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
802
- var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
803
- var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
804
- var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
805
- var SHA512 = class extends HashMD {
806
- constructor(outputLen = 64) {
807
- super(128, outputLen, 16, false);
808
- this.Ah = SHA512_IV[0] | 0;
809
- this.Al = SHA512_IV[1] | 0;
810
- this.Bh = SHA512_IV[2] | 0;
811
- this.Bl = SHA512_IV[3] | 0;
812
- this.Ch = SHA512_IV[4] | 0;
813
- this.Cl = SHA512_IV[5] | 0;
814
- this.Dh = SHA512_IV[6] | 0;
815
- this.Dl = SHA512_IV[7] | 0;
816
- this.Eh = SHA512_IV[8] | 0;
817
- this.El = SHA512_IV[9] | 0;
818
- this.Fh = SHA512_IV[10] | 0;
819
- this.Fl = SHA512_IV[11] | 0;
820
- this.Gh = SHA512_IV[12] | 0;
821
- this.Gl = SHA512_IV[13] | 0;
822
- this.Hh = SHA512_IV[14] | 0;
823
- this.Hl = SHA512_IV[15] | 0;
824
- }
825
- // prettier-ignore
826
- get() {
827
- const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
828
- return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
829
- }
830
- // prettier-ignore
831
- set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
832
- this.Ah = Ah | 0;
833
- this.Al = Al | 0;
834
- this.Bh = Bh | 0;
835
- this.Bl = Bl | 0;
836
- this.Ch = Ch | 0;
837
- this.Cl = Cl | 0;
838
- this.Dh = Dh | 0;
839
- this.Dl = Dl | 0;
840
- this.Eh = Eh | 0;
841
- this.El = El | 0;
842
- this.Fh = Fh | 0;
843
- this.Fl = Fl | 0;
844
- this.Gh = Gh | 0;
845
- this.Gl = Gl | 0;
846
- this.Hh = Hh | 0;
847
- this.Hl = Hl | 0;
848
- }
849
- process(view, offset) {
850
- for (let i = 0; i < 16; i++, offset += 4) {
851
- SHA512_W_H[i] = view.getUint32(offset);
852
- SHA512_W_L[i] = view.getUint32(offset += 4);
853
- }
854
- for (let i = 16; i < 80; i++) {
855
- const W15h = SHA512_W_H[i - 15] | 0;
856
- const W15l = SHA512_W_L[i - 15] | 0;
857
- const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
858
- const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
859
- const W2h = SHA512_W_H[i - 2] | 0;
860
- const W2l = SHA512_W_L[i - 2] | 0;
861
- const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
862
- const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
863
- const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
864
- const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
865
- SHA512_W_H[i] = SUMh | 0;
866
- SHA512_W_L[i] = SUMl | 0;
867
- }
868
- let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
869
- for (let i = 0; i < 80; i++) {
870
- const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
871
- const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
872
- const CHIh = Eh & Fh ^ ~Eh & Gh;
873
- const CHIl = El & Fl ^ ~El & Gl;
874
- const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
875
- const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
876
- const T1l = T1ll | 0;
877
- const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
878
- const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
879
- const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
880
- const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
881
- Hh = Gh | 0;
882
- Hl = Gl | 0;
883
- Gh = Fh | 0;
884
- Gl = Fl | 0;
885
- Fh = Eh | 0;
886
- Fl = El | 0;
887
- ({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
888
- Dh = Ch | 0;
889
- Dl = Cl | 0;
890
- Ch = Bh | 0;
891
- Cl = Bl | 0;
892
- Bh = Ah | 0;
893
- Bl = Al | 0;
894
- const All = add3L(T1l, sigma0l, MAJl);
895
- Ah = add3H(All, T1h, sigma0h, MAJh);
896
- Al = All | 0;
897
- }
898
- ({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
899
- ({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
900
- ({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
901
- ({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
902
- ({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
903
- ({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
904
- ({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
905
- ({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
906
- this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
907
- }
908
- roundClean() {
909
- clean(SHA512_W_H, SHA512_W_L);
910
- }
911
- destroy() {
912
- clean(this.buffer);
913
- this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
279
+ function getCanvasFingerprint() {
280
+ try {
281
+ const canvas = document.createElement("canvas");
282
+ const ctx = canvas.getContext("2d");
283
+ ctx.textBaseline = "top";
284
+ ctx.font = "14px Arial";
285
+ ctx.fillText("LicenseSeat SDK", 2, 2);
286
+ return canvas.toDataURL().slice(-50);
287
+ } catch (e) {
288
+ return "no-canvas";
914
289
  }
915
- };
916
- var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
917
-
918
- // node_modules/@noble/hashes/esm/sha512.js
919
- var sha5122 = sha512;
920
-
921
- // node_modules/canonical-json/stringify.js
922
- var gap = "";
923
- var indent = "";
924
- var rep;
925
- var cmp;
926
- var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
927
- var meta = { "\b": "\\b", " ": "\\t", "\n": "\\n", "\f": "\\f", "\r": "\\r", '"': '\\"', "\\": "\\\\" };
928
- function quote(str2) {
929
- escapable.lastIndex = 0;
930
- return escapable.test(str2) ? '"' + str2.replace(escapable, (a) => meta[a] || "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4)) + '"' : '"' + str2 + '"';
931
290
  }
932
- function str(key, holder) {
933
- let value = holder[key];
934
- if (value && typeof value === "object" && typeof value.toJSON === "function") {
935
- value = value.toJSON(key);
936
- }
937
- if (typeof rep === "function") {
938
- value = rep.call(holder, key, value);
939
- }
940
- switch (typeof value) {
941
- case "string":
942
- return quote(value);
943
- case "number":
944
- return isFinite(value) ? String(value) : "null";
945
- case "boolean":
946
- case "undefined":
947
- case "object":
948
- if (value === null)
949
- return "null";
950
- const mind = gap;
951
- gap += indent;
952
- const partial = [];
953
- if (Array.isArray(value)) {
954
- for (let i = 0; i < value.length; i++)
955
- partial[i] = str(i, value) || "null";
956
- } else {
957
- const keys = Object.keys(value).sort(cmp);
958
- for (const k of keys) {
959
- if (Object.prototype.hasOwnProperty.call(value, k)) {
960
- const v2 = str(k, value);
961
- if (v2)
962
- partial.push(quote(k) + (gap ? ": " : ":") + v2);
963
- }
964
- }
965
- }
966
- let v;
967
- if (Array.isArray(value)) {
968
- v = partial.length === 0 ? "[]" : gap ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" : "[" + partial.join(",") + "]";
969
- } else {
970
- v = partial.length === 0 ? "{}" : gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" : "{" + partial.join(",") + "}";
971
- }
972
- gap = mind;
973
- return v;
974
- default:
975
- return String(value);
976
- }
291
+ function generateDeviceId() {
292
+ const nav = window.navigator;
293
+ const screen = window.screen;
294
+ const data = [
295
+ nav.userAgent,
296
+ nav.language,
297
+ screen.colorDepth,
298
+ screen.width + "x" + screen.height,
299
+ (/* @__PURE__ */ new Date()).getTimezoneOffset(),
300
+ nav.hardwareConcurrency,
301
+ getCanvasFingerprint()
302
+ ].join("|");
303
+ return `web-${hashCode(data)}-${Date.now().toString(36)}`;
977
304
  }
978
- function stringify(value, replacer, space, keyCompare) {
979
- gap = "";
980
- indent = "";
981
- rep = replacer;
982
- cmp = typeof keyCompare === "function" ? keyCompare : void 0;
983
- if (typeof space === "number") {
984
- indent = " ".repeat(space);
985
- } else if (typeof space === "string") {
986
- indent = space;
987
- }
988
- return str("", { "": value });
305
+ function sleep(ms) {
306
+ return new Promise((resolve) => setTimeout(resolve, ms));
307
+ }
308
+ function getCsrfToken() {
309
+ const token = document.querySelector('meta[name="csrf-token"]');
310
+ return token ? token.content : "";
989
311
  }
990
312
 
991
- // node_modules/canonical-json/index.js
992
- var canonical_json_default = stringify;
993
-
994
- // src/index.js
995
- var LicenseSeatSDK = class _LicenseSeatSDK {
313
+ // src/LicenseSeat.js
314
+ var DEFAULT_CONFIG = {
315
+ apiBaseUrl: "https://licenseseat.com/api",
316
+ storagePrefix: "licenseseat_",
317
+ autoValidateInterval: 36e5,
318
+ // 1 hour
319
+ networkRecheckInterval: 3e4,
320
+ // 30 seconds
321
+ maxRetries: 3,
322
+ retryDelay: 1e3,
323
+ apiKey: null,
324
+ debug: false,
325
+ offlineLicenseRefreshInterval: 1e3 * 60 * 60 * 72,
326
+ // 72 hours
327
+ offlineFallbackEnabled: false,
328
+ // default false (strict mode, matches Swift SDK)
329
+ maxOfflineDays: 0,
330
+ // 0 = disabled
331
+ maxClockSkewMs: 5 * 60 * 1e3,
332
+ // 5 minutes
333
+ autoInitialize: true
334
+ };
335
+ var LicenseSeatSDK = class {
336
+ /**
337
+ * Create a new LicenseSeat SDK instance
338
+ * @param {import('./types.js').LicenseSeatConfig} [config={}] - Configuration options
339
+ */
996
340
  constructor(config = {}) {
997
341
  this.config = {
998
- apiBaseUrl: config.apiBaseUrl || "/api",
999
- storagePrefix: config.storagePrefix || "licenseseat_",
1000
- autoValidateInterval: config.autoValidateInterval || 36e5,
1001
- // 1 hour default
1002
- networkRecheckInterval: config.networkRecheckInterval || 3e4,
1003
- // 30s while offline
1004
- maxRetries: config.maxRetries || 3,
1005
- retryDelay: config.retryDelay || 1e3,
1006
- apiKey: config.apiKey || null,
1007
- // Store apiKey
1008
- debug: config.debug || false,
1009
- offlineLicenseRefreshInterval: config.offlineLicenseRefreshInterval || 1e3 * 60 * 60 * 72,
1010
- // 72h
1011
- offlineFallbackEnabled: config.offlineFallbackEnabled !== false,
1012
- // default true
1013
- maxOfflineDays: config.maxOfflineDays || 0,
1014
- // 0 = disabled
1015
- maxClockSkewMs: config.maxClockSkewMs || 5 * 60 * 1e3,
1016
- // 5 minutes
342
+ ...DEFAULT_CONFIG,
1017
343
  ...config
1018
344
  };
1019
345
  this.eventListeners = {};
@@ -1024,15 +350,25 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1024
350
  this.connectivityTimer = null;
1025
351
  this.offlineRefreshTimer = null;
1026
352
  this.lastOfflineValidation = null;
1027
- if (ed25519_exports && etc && sha5122) {
1028
- etc.sha512Sync = (...m) => sha5122(etc.concatBytes(...m));
353
+ this.syncingOfflineAssets = false;
354
+ this.destroyed = false;
355
+ if (ed && ed.etc && sha512) {
356
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
1029
357
  } else {
1030
358
  console.error(
1031
359
  "[LicenseSeat SDK] Noble-ed25519 or Noble-hashes not loaded correctly. Sync crypto methods may fail."
1032
360
  );
1033
361
  }
1034
- this.initialize();
362
+ if (this.config.autoInitialize) {
363
+ this.initialize();
364
+ }
1035
365
  }
366
+ /**
367
+ * Initialize the SDK
368
+ * Loads cached license and starts auto-validation if configured.
369
+ * Called automatically unless autoInitialize is set to false.
370
+ * @returns {void}
371
+ */
1036
372
  initialize() {
1037
373
  this.log("LicenseSeat SDK initialized", this.config);
1038
374
  const cachedLicense = this.cache.getLicense();
@@ -1054,17 +390,15 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1054
390
  }
1055
391
  if (this.config.apiKey) {
1056
392
  this.startAutoValidation(cachedLicense.license_key);
1057
- }
1058
- if (this.config.apiKey) {
1059
- this.validateLicense(cachedLicense.license_key).catch((err2) => {
1060
- this.log("Background validation failed:", err2);
1061
- if (err2 instanceof APIError && (err2.status === 401 || err2.status === 501)) {
393
+ this.validateLicense(cachedLicense.license_key).catch((err) => {
394
+ this.log("Background validation failed:", err);
395
+ if (err instanceof APIError && (err.status === 401 || err.status === 501)) {
1062
396
  this.log(
1063
397
  "Authentication issue during validation, using cached license data"
1064
398
  );
1065
399
  this.emit("validation:auth-failed", {
1066
400
  licenseKey: cachedLicense.license_key,
1067
- error: err2,
401
+ error: err,
1068
402
  cached: true
1069
403
  });
1070
404
  }
@@ -1075,14 +409,12 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1075
409
  /**
1076
410
  * Activate a license
1077
411
  * @param {string} licenseKey - The license key to activate
1078
- * @param {Object} options - Activation options
1079
- * @param {string} [options.deviceIdentifier] - Optional custom device ID.
1080
- * @param {string} [options.softwareReleaseDate] - Optional ISO8601 date string for version-aware activation.
1081
- * @param {Object} [options.metadata] - Optional metadata.
1082
- * @returns {Promise<Object>} Activation result
412
+ * @param {import('./types.js').ActivationOptions} [options={}] - Activation options
413
+ * @returns {Promise<import('./types.js').CachedLicense>} Activation result with cached license data
414
+ * @throws {APIError} When the API request fails
1083
415
  */
1084
416
  async activate(licenseKey, options = {}) {
1085
- const deviceId = options.deviceIdentifier || this.generateDeviceId();
417
+ const deviceId = options.deviceIdentifier || generateDeviceId();
1086
418
  const payload = {
1087
419
  license_key: licenseKey,
1088
420
  device_identifier: deviceId,
@@ -1118,12 +450,14 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1118
450
  }
1119
451
  /**
1120
452
  * Deactivate the current license
1121
- * @returns {Promise<Object>} Deactivation result
453
+ * @returns {Promise<Object>} Deactivation result from the API
454
+ * @throws {LicenseError} When no active license is found
455
+ * @throws {APIError} When the API request fails
1122
456
  */
1123
457
  async deactivate() {
1124
458
  const cachedLicense = this.cache.getLicense();
1125
459
  if (!cachedLicense) {
1126
- throw new Error("No active license found");
460
+ throw new LicenseError("No active license found", "no_license");
1127
461
  }
1128
462
  try {
1129
463
  this.emit("deactivation:start", cachedLicense);
@@ -1147,13 +481,14 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1147
481
  /**
1148
482
  * Validate a license
1149
483
  * @param {string} licenseKey - License key to validate
1150
- * @param {Object} options - Validation options
1151
- * @returns {Promise<Object>} Validation result
484
+ * @param {import('./types.js').ValidationOptions} [options={}] - Validation options
485
+ * @returns {Promise<import('./types.js').ValidationResult>} Validation result
486
+ * @throws {APIError} When the API request fails and offline fallback is not available
1152
487
  */
1153
488
  async validateLicense(licenseKey, options = {}) {
1154
489
  try {
1155
490
  this.emit("validation:start", { licenseKey });
1156
- const response = await this.apiCall("/licenses/validate", {
491
+ const rawResponse = await this.apiCall("/licenses/validate", {
1157
492
  method: "POST",
1158
493
  body: {
1159
494
  license_key: licenseKey,
@@ -1161,7 +496,14 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1161
496
  product_slug: options.productSlug
1162
497
  }
1163
498
  });
499
+ const response = {
500
+ valid: rawResponse.valid,
501
+ ...rawResponse.license || {}
502
+ };
1164
503
  const cachedLicense = this.cache.getLicense();
504
+ if ((!response.active_entitlements || response.active_entitlements.length === 0) && cachedLicense?.validation?.active_entitlements?.length) {
505
+ response.active_entitlements = cachedLicense.validation.active_entitlements;
506
+ }
1165
507
  if (cachedLicense && cachedLicense.license_key === licenseKey) {
1166
508
  this.cache.updateValidation(response);
1167
509
  }
@@ -1173,36 +515,6 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1173
515
  this.stopAutoValidation();
1174
516
  this.currentAutoLicenseKey = null;
1175
517
  }
1176
- const isNetworkFailure = response instanceof TypeError && response.message.includes("fetch") || response instanceof APIError && [0, 408].includes(response.status);
1177
- if (this.config.offlineFallbackEnabled && isNetworkFailure) {
1178
- const offlineResult = await this.verifyCachedOffline();
1179
- const duplicateSuccess = offlineResult.valid && this.lastOfflineValidation?.valid === true;
1180
- const cachedLicense2 = this.cache.getLicense();
1181
- if (cachedLicense2 && cachedLicense2.license_key === licenseKey) {
1182
- this.cache.updateValidation(offlineResult);
1183
- }
1184
- if (offlineResult.valid) {
1185
- if (!duplicateSuccess) {
1186
- this.emit("validation:offline-success", offlineResult);
1187
- }
1188
- this.lastOfflineValidation = offlineResult;
1189
- return offlineResult;
1190
- } else {
1191
- this.emit("validation:offline-failed", offlineResult);
1192
- this.stopAutoValidation();
1193
- this.currentAutoLicenseKey = null;
1194
- }
1195
- }
1196
- if (response instanceof APIError && response.data) {
1197
- const cachedLicense2 = this.cache.getLicense();
1198
- if (cachedLicense2 && cachedLicense2.license_key === licenseKey) {
1199
- const invalidValidation = {
1200
- valid: false,
1201
- ...response.data
1202
- };
1203
- this.cache.updateValidation(invalidValidation);
1204
- }
1205
- }
1206
518
  this.cache.setLastSeenTimestamp(Date.now());
1207
519
  return response;
1208
520
  } catch (error) {
@@ -1226,11 +538,7 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1226
538
  if (error instanceof APIError && error.data) {
1227
539
  const cachedLicense = this.cache.getLicense();
1228
540
  if (cachedLicense && cachedLicense.license_key === licenseKey) {
1229
- const invalidValidation = {
1230
- valid: false,
1231
- ...error.data
1232
- };
1233
- this.cache.updateValidation(invalidValidation);
541
+ this.cache.updateValidation({ valid: false, ...error.data });
1234
542
  }
1235
543
  if (![0, 408, 429].includes(error.status)) {
1236
544
  this.stopAutoValidation();
@@ -1241,9 +549,9 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1241
549
  }
1242
550
  }
1243
551
  /**
1244
- * Check if a specific entitlement is active
1245
- * @param {string} entitlementKey - The entitlement to check
1246
- * @returns {Object} Entitlement status
552
+ * Check if a specific entitlement is active (detailed version)
553
+ * @param {string} entitlementKey - The entitlement key to check
554
+ * @returns {import('./types.js').EntitlementCheckResult} Entitlement status with details
1247
555
  */
1248
556
  checkEntitlement(entitlementKey) {
1249
557
  const license = this.cache.getLicense();
@@ -1269,23 +577,32 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1269
577
  return { active: true, entitlement };
1270
578
  }
1271
579
  /**
1272
- * Get offline license data for backup.
1273
- * This fetches the *signed* offline license data from the server.
1274
- * @returns {Promise<Object>} Signed offline license data (JSON structure with payload and signature).
580
+ * Check if a specific entitlement is active (simple boolean version)
581
+ * This is a convenience method that returns a simple boolean.
582
+ * Use checkEntitlement() for detailed status information.
583
+ * @param {string} entitlementKey - The entitlement key to check
584
+ * @returns {boolean} True if the entitlement is active, false otherwise
585
+ */
586
+ hasEntitlement(entitlementKey) {
587
+ return this.checkEntitlement(entitlementKey).active;
588
+ }
589
+ /**
590
+ * Get offline license data from the server
591
+ * @returns {Promise<import('./types.js').SignedOfflineLicense>} Signed offline license data
592
+ * @throws {LicenseError} When no active license is found
593
+ * @throws {APIError} When the API request fails
1275
594
  */
1276
595
  async getOfflineLicense() {
1277
596
  const license = this.cache.getLicense();
1278
597
  if (!license || !license.license_key) {
1279
598
  const errorMsg = "No active license key found in cache to fetch offline license.";
1280
599
  this.emit("sdk:error", { message: errorMsg });
1281
- throw new Error(errorMsg);
600
+ throw new LicenseError(errorMsg, "no_license");
1282
601
  }
1283
602
  try {
1284
603
  this.emit("offlineLicense:fetching", { licenseKey: license.license_key });
1285
604
  const path = `/licenses/${license.license_key}/offline_license`;
1286
- const response = await this.apiCall(path, {
1287
- method: "POST"
1288
- });
605
+ const response = await this.apiCall(path, { method: "POST" });
1289
606
  this.emit("offlineLicense:fetched", {
1290
607
  licenseKey: license.license_key,
1291
608
  data: response
@@ -1304,10 +621,10 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1304
621
  }
1305
622
  }
1306
623
  /**
1307
- * Fetches a public key for a given key ID (kid) from the server.
1308
- * Assumes an endpoint like /api/public_key/:keyId which returns { key_id: "...", public_key_b64: "..." }
1309
- * @param {string} keyId - The Key ID (kid) for which to fetch the public key.
1310
- * @returns {Promise<string>} Base64 encoded public key.
624
+ * Fetch a public key from the server by key ID
625
+ * @param {string} keyId - The Key ID (kid) for which to fetch the public key
626
+ * @returns {Promise<string>} Base64-encoded public key
627
+ * @throws {Error} When keyId is not provided or the key is not found
1311
628
  */
1312
629
  async getPublicKey(keyId) {
1313
630
  if (!keyId) {
@@ -1332,14 +649,12 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1332
649
  }
1333
650
  }
1334
651
  /**
1335
- * Verifies a signed offline license object client-side.
1336
- * IMPORTANT: This method requires a JavaScript Ed25519 library (e.g., tweetnacl, noble-ed25519).
1337
- * The actual crypto verification logic needs to be implemented using such a library.
1338
- *
1339
- * @param {Object} signedLicenseData - The signed license data object, typically { payload: Object, signature_b64u: string, kid: string }.
1340
- * @param {string} publicKeyB64 - The Base64 encoded public Ed25519 key to verify the signature.
1341
- * @returns {Promise<boolean>} True if verification is successful, false otherwise.
1342
- * @throws {Error} if crypto library is not available or inputs are invalid.
652
+ * Verify a signed offline license client-side using Ed25519
653
+ * @param {import('./types.js').SignedOfflineLicense} signedLicenseData - The signed license data
654
+ * @param {string} publicKeyB64 - Base64-encoded public Ed25519 key
655
+ * @returns {Promise<boolean>} True if verification is successful
656
+ * @throws {CryptoError} When crypto library is not available
657
+ * @throws {Error} When inputs are invalid
1343
658
  */
1344
659
  async verifyOfflineLicense(signedLicenseData, publicKeyB64) {
1345
660
  this.log("Attempting to verify offline license client-side.");
@@ -1349,32 +664,19 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1349
664
  if (!publicKeyB64) {
1350
665
  throw new Error("Public key (Base64 encoded) is required.");
1351
666
  }
1352
- if (!ed25519_exports || !verify || !etc.sha512Sync) {
1353
- console.error(
1354
- "Noble-ed25519 not properly initialized or sha512Sync not set. Please check imports and setup."
1355
- );
1356
- this.emit("sdk:error", {
1357
- message: "Client-side verification crypto library (noble-ed25519) not available or configured."
1358
- });
1359
- throw new Error(
667
+ if (!ed || !ed.verify || !ed.etc.sha512Sync) {
668
+ const err = new CryptoError(
1360
669
  "noble-ed25519 crypto library not available/configured for offline verification."
1361
670
  );
671
+ this.emit("sdk:error", { message: err.message });
672
+ throw err;
1362
673
  }
1363
674
  try {
1364
- const payloadString = this.canonicalJsonStringify(
1365
- signedLicenseData.payload
1366
- );
675
+ const payloadString = canonicalJsonStringify(signedLicenseData.payload);
1367
676
  const messageBytes = new TextEncoder().encode(payloadString);
1368
- const publicKeyBytes = this.base64UrlDecode(publicKeyB64);
1369
- const signatureBytes = this.base64UrlDecode(
1370
- signedLicenseData.signature_b64u
1371
- );
1372
- const isValid = verify(
1373
- signatureBytes,
1374
- // signature first for noble-ed25519
1375
- messageBytes,
1376
- publicKeyBytes
1377
- );
677
+ const publicKeyBytes = base64UrlDecode(publicKeyB64);
678
+ const signatureBytes = base64UrlDecode(signedLicenseData.signature_b64u);
679
+ const isValid = ed.verify(signatureBytes, messageBytes, publicKeyBytes);
1378
680
  if (isValid) {
1379
681
  this.log(
1380
682
  "Offline license signature VERIFIED successfully client-side."
@@ -1399,47 +701,163 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1399
701
  }
1400
702
  }
1401
703
  /**
1402
- * Generates a canonical JSON string from an object (keys sorted).
1403
- * This is crucial for consistent signature verification.
1404
- * @param {Object} obj - The object to stringify.
1405
- * @returns {string} Canonical JSON string.
704
+ * Get current license status
705
+ * @returns {import('./types.js').LicenseStatus} Current license status
1406
706
  */
1407
- canonicalJsonStringify(obj) {
1408
- if (!canonical_json_default || typeof canonical_json_default.stringify !== "function") {
1409
- console.warn(
1410
- "[LicenseSeat SDK] canonical-json library not loaded correctly. Falling back to basic JSON.stringify. Signature verification might be unreliable if server uses different canonicalization."
1411
- );
1412
- try {
1413
- const sortedObj = {};
1414
- Object.keys(obj).sort().forEach((key) => {
1415
- sortedObj[key] = obj[key];
1416
- });
1417
- return JSON.stringify(sortedObj);
1418
- } catch (e) {
1419
- return JSON.stringify(obj);
707
+ getStatus() {
708
+ const license = this.cache.getLicense();
709
+ if (!license) {
710
+ return { status: "inactive", message: "No license activated" };
711
+ }
712
+ const validation = license.validation;
713
+ if (!validation) {
714
+ return { status: "pending", message: "License pending validation" };
715
+ }
716
+ if (!validation.valid) {
717
+ if (validation.offline) {
718
+ return {
719
+ status: "offline-invalid",
720
+ message: validation.reason_code || "License invalid (offline)"
721
+ };
1420
722
  }
723
+ return {
724
+ status: "invalid",
725
+ message: validation.reason || "License invalid"
726
+ };
727
+ }
728
+ if (validation.offline) {
729
+ return {
730
+ status: "offline-valid",
731
+ license: license.license_key,
732
+ device: license.device_identifier,
733
+ activated_at: license.activated_at,
734
+ last_validated: license.last_validated,
735
+ entitlements: validation.active_entitlements || []
736
+ };
737
+ }
738
+ return {
739
+ status: "active",
740
+ license: license.license_key,
741
+ device: license.device_identifier,
742
+ activated_at: license.activated_at,
743
+ last_validated: license.last_validated,
744
+ entitlements: validation.active_entitlements || []
745
+ };
746
+ }
747
+ /**
748
+ * Test server authentication
749
+ * Useful for verifying API key/session is valid.
750
+ * @returns {Promise<Object>} Result from the server
751
+ * @throws {Error} When API key is not configured
752
+ * @throws {APIError} When authentication fails
753
+ */
754
+ async testAuth() {
755
+ if (!this.config.apiKey) {
756
+ const err = new Error("API key is required for auth test");
757
+ this.emit("auth_test:error", { error: err });
758
+ throw err;
759
+ }
760
+ try {
761
+ this.emit("auth_test:start");
762
+ const response = await this.apiCall("/auth_test", { method: "GET" });
763
+ this.emit("auth_test:success", response);
764
+ return response;
765
+ } catch (error) {
766
+ this.emit("auth_test:error", { error });
767
+ throw error;
768
+ }
769
+ }
770
+ /**
771
+ * Clear all data and reset SDK state
772
+ * @returns {void}
773
+ */
774
+ reset() {
775
+ this.stopAutoValidation();
776
+ this.stopConnectivityPolling();
777
+ if (this.offlineRefreshTimer) {
778
+ clearInterval(this.offlineRefreshTimer);
779
+ this.offlineRefreshTimer = null;
780
+ }
781
+ this.cache.clear();
782
+ this.lastOfflineValidation = null;
783
+ this.currentAutoLicenseKey = null;
784
+ this.emit("sdk:reset");
785
+ }
786
+ /**
787
+ * Destroy the SDK instance and release all resources
788
+ * Call this when you no longer need the SDK to prevent memory leaks.
789
+ * After calling destroy(), the SDK instance should not be used.
790
+ * @returns {void}
791
+ */
792
+ destroy() {
793
+ this.destroyed = true;
794
+ this.stopAutoValidation();
795
+ this.stopConnectivityPolling();
796
+ if (this.offlineRefreshTimer) {
797
+ clearInterval(this.offlineRefreshTimer);
798
+ this.offlineRefreshTimer = null;
799
+ }
800
+ this.eventListeners = {};
801
+ this.cache.clear();
802
+ this.lastOfflineValidation = null;
803
+ this.currentAutoLicenseKey = null;
804
+ this.emit("sdk:destroyed");
805
+ }
806
+ // ============================================================
807
+ // Event Handling
808
+ // ============================================================
809
+ /**
810
+ * Subscribe to an event
811
+ * @param {string} event - Event name
812
+ * @param {import('./types.js').EventCallback} callback - Event handler
813
+ * @returns {import('./types.js').EventUnsubscribe} Unsubscribe function
814
+ */
815
+ on(event, callback) {
816
+ if (!this.eventListeners[event]) {
817
+ this.eventListeners[event] = [];
1421
818
  }
1422
- return canonical_json_default.stringify(obj);
819
+ this.eventListeners[event].push(callback);
820
+ return () => this.off(event, callback);
1423
821
  }
1424
822
  /**
1425
- * Decodes a Base64URL string to a Uint8Array.
1426
- * @param {string} base64UrlString - The Base64URL encoded string.
1427
- * @returns {Uint8Array}
823
+ * Unsubscribe from an event
824
+ * @param {string} event - Event name
825
+ * @param {import('./types.js').EventCallback} callback - Event handler to remove
826
+ * @returns {void}
1428
827
  */
1429
- base64UrlDecode(base64UrlString) {
1430
- let base64 = base64UrlString.replace(/-/g, "+").replace(/_/g, "/");
1431
- while (base64.length % 4) {
1432
- base64 += "=";
828
+ off(event, callback) {
829
+ if (this.eventListeners[event]) {
830
+ this.eventListeners[event] = this.eventListeners[event].filter(
831
+ (cb) => cb !== callback
832
+ );
1433
833
  }
1434
- const raw = window.atob(base64);
1435
- const outputArray = new Uint8Array(raw.length);
1436
- for (let i = 0; i < raw.length; ++i) {
1437
- outputArray[i] = raw.charCodeAt(i);
834
+ }
835
+ /**
836
+ * Emit an event
837
+ * @param {string} event - Event name
838
+ * @param {*} data - Event data
839
+ * @returns {void}
840
+ * @private
841
+ */
842
+ emit(event, data) {
843
+ this.log(`Event: ${event}`, data);
844
+ if (this.eventListeners[event]) {
845
+ this.eventListeners[event].forEach((callback) => {
846
+ try {
847
+ callback(data);
848
+ } catch (error) {
849
+ console.error(`Error in event listener for ${event}:`, error);
850
+ }
851
+ });
1438
852
  }
1439
- return outputArray;
1440
853
  }
854
+ // ============================================================
855
+ // Auto-Validation & Connectivity
856
+ // ============================================================
1441
857
  /**
1442
858
  * Start automatic license validation
859
+ * @param {string} licenseKey - License key to validate
860
+ * @returns {void}
1443
861
  * @private
1444
862
  */
1445
863
  startAutoValidation(licenseKey) {
@@ -1447,24 +865,22 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1447
865
  this.currentAutoLicenseKey = licenseKey;
1448
866
  const validationInterval = this.config.autoValidateInterval;
1449
867
  const performAndReschedule = () => {
1450
- this.validateLicense(licenseKey).catch((err2) => {
1451
- this.log("Auto-validation failed:", err2);
1452
- this.emit("validation:auto-failed", { licenseKey, error: err2 });
868
+ this.validateLicense(licenseKey).catch((err) => {
869
+ this.log("Auto-validation failed:", err);
870
+ this.emit("validation:auto-failed", { licenseKey, error: err });
1453
871
  });
1454
872
  this.emit("autovalidation:cycle", {
1455
873
  nextRunAt: new Date(Date.now() + validationInterval)
1456
874
  });
1457
875
  };
1458
- this.validationTimer = setInterval(
1459
- performAndReschedule,
1460
- validationInterval
1461
- );
876
+ this.validationTimer = setInterval(performAndReschedule, validationInterval);
1462
877
  this.emit("autovalidation:cycle", {
1463
878
  nextRunAt: new Date(Date.now() + validationInterval)
1464
879
  });
1465
880
  }
1466
881
  /**
1467
882
  * Stop automatic validation
883
+ * @returns {void}
1468
884
  * @private
1469
885
  */
1470
886
  stopAutoValidation() {
@@ -1475,54 +891,222 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1475
891
  }
1476
892
  }
1477
893
  /**
1478
- * Generate a unique device identifier
894
+ * Start connectivity polling (when offline)
895
+ * @returns {void}
896
+ * @private
897
+ */
898
+ startConnectivityPolling() {
899
+ if (this.connectivityTimer)
900
+ return;
901
+ const heartbeat = async () => {
902
+ try {
903
+ await fetch(`${this.config.apiBaseUrl}/heartbeat`, {
904
+ method: "GET",
905
+ credentials: "omit"
906
+ });
907
+ if (!this.online) {
908
+ this.online = true;
909
+ this.emit("network:online");
910
+ if (this.currentAutoLicenseKey && !this.validationTimer) {
911
+ this.startAutoValidation(this.currentAutoLicenseKey);
912
+ }
913
+ this.syncOfflineAssets();
914
+ }
915
+ this.stopConnectivityPolling();
916
+ } catch (err) {
917
+ }
918
+ };
919
+ this.connectivityTimer = setInterval(
920
+ heartbeat,
921
+ this.config.networkRecheckInterval
922
+ );
923
+ }
924
+ /**
925
+ * Stop connectivity polling
926
+ * @returns {void}
927
+ * @private
928
+ */
929
+ stopConnectivityPolling() {
930
+ if (this.connectivityTimer) {
931
+ clearInterval(this.connectivityTimer);
932
+ this.connectivityTimer = null;
933
+ }
934
+ }
935
+ // ============================================================
936
+ // Offline License Management
937
+ // ============================================================
938
+ /**
939
+ * Fetch and cache offline license and public key
940
+ * Uses a lock to prevent concurrent calls from causing race conditions
941
+ * @returns {Promise<void>}
942
+ * @private
943
+ */
944
+ async syncOfflineAssets() {
945
+ if (this.syncingOfflineAssets || this.destroyed) {
946
+ this.log("Skipping syncOfflineAssets: already syncing or destroyed");
947
+ return;
948
+ }
949
+ this.syncingOfflineAssets = true;
950
+ try {
951
+ const offline = await this.getOfflineLicense();
952
+ this.cache.setOfflineLicense(offline);
953
+ const kid = offline.kid || offline.payload?.kid;
954
+ if (kid) {
955
+ const existingKey = this.cache.getPublicKey(kid);
956
+ if (!existingKey) {
957
+ const pub = await this.getPublicKey(kid);
958
+ this.cache.setPublicKey(kid, pub);
959
+ }
960
+ }
961
+ this.emit("offlineLicense:ready", {
962
+ kid: offline.kid || offline.payload?.kid,
963
+ exp_at: offline.payload?.exp_at
964
+ });
965
+ const res = await this.quickVerifyCachedOfflineLocal();
966
+ if (res) {
967
+ this.cache.updateValidation(res);
968
+ this.emit(
969
+ res.valid ? "validation:offline-success" : "validation:offline-failed",
970
+ res
971
+ );
972
+ }
973
+ } catch (err) {
974
+ this.log("Failed to sync offline assets:", err);
975
+ } finally {
976
+ this.syncingOfflineAssets = false;
977
+ }
978
+ }
979
+ /**
980
+ * Schedule periodic offline license refresh
981
+ * @returns {void}
1479
982
  * @private
1480
983
  */
1481
- generateDeviceId() {
1482
- const nav = window.navigator;
1483
- const screen = window.screen;
1484
- const data = [
1485
- nav.userAgent,
1486
- nav.language,
1487
- screen.colorDepth,
1488
- screen.width + "x" + screen.height,
1489
- (/* @__PURE__ */ new Date()).getTimezoneOffset(),
1490
- nav.hardwareConcurrency,
1491
- this.getCanvasFingerprint()
1492
- ].join("|");
1493
- return `web-${this.hashCode(data)}-${Date.now().toString(36)}`;
984
+ scheduleOfflineRefresh() {
985
+ if (this.offlineRefreshTimer)
986
+ clearInterval(this.offlineRefreshTimer);
987
+ this.offlineRefreshTimer = setInterval(
988
+ () => this.syncOfflineAssets(),
989
+ this.config.offlineLicenseRefreshInterval
990
+ );
1494
991
  }
1495
992
  /**
1496
- * Get canvas fingerprint for device ID
993
+ * Verify cached offline license
994
+ * @returns {Promise<import('./types.js').ValidationResult>}
1497
995
  * @private
1498
996
  */
1499
- getCanvasFingerprint() {
997
+ async verifyCachedOffline() {
998
+ const signed = this.cache.getOfflineLicense();
999
+ if (!signed) {
1000
+ return { valid: false, offline: true, reason_code: "no_offline_license" };
1001
+ }
1002
+ const kid = signed.kid || signed.payload?.kid;
1003
+ let pub = kid ? this.cache.getPublicKey(kid) : null;
1004
+ if (!pub) {
1005
+ try {
1006
+ pub = await this.getPublicKey(kid);
1007
+ this.cache.setPublicKey(kid, pub);
1008
+ } catch (e) {
1009
+ return { valid: false, offline: true, reason_code: "no_public_key" };
1010
+ }
1011
+ }
1500
1012
  try {
1501
- const canvas = document.createElement("canvas");
1502
- const ctx = canvas.getContext("2d");
1503
- ctx.textBaseline = "top";
1504
- ctx.font = "14px Arial";
1505
- ctx.fillText("LicenseSeat SDK", 2, 2);
1506
- return canvas.toDataURL().slice(-50);
1013
+ const ok = await this.verifyOfflineLicense(signed, pub);
1014
+ if (!ok) {
1015
+ return { valid: false, offline: true, reason_code: "signature_invalid" };
1016
+ }
1017
+ const payload = signed.payload || {};
1018
+ const cached = this.cache.getLicense();
1019
+ if (!cached || !constantTimeEqual(payload.lic_k || "", cached.license_key || "")) {
1020
+ return { valid: false, offline: true, reason_code: "license_mismatch" };
1021
+ }
1022
+ const now = Date.now();
1023
+ const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1024
+ if (expAt && expAt < now) {
1025
+ return { valid: false, offline: true, reason_code: "expired" };
1026
+ }
1027
+ if (!expAt && this.config.maxOfflineDays > 0) {
1028
+ const pivot = cached.last_validated || cached.activated_at;
1029
+ if (pivot) {
1030
+ const ageMs = now - new Date(pivot).getTime();
1031
+ if (ageMs > this.config.maxOfflineDays * 24 * 60 * 60 * 1e3) {
1032
+ return {
1033
+ valid: false,
1034
+ offline: true,
1035
+ reason_code: "grace_period_expired"
1036
+ };
1037
+ }
1038
+ }
1039
+ }
1040
+ const lastSeen = this.cache.getLastSeenTimestamp();
1041
+ if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1042
+ return { valid: false, offline: true, reason_code: "clock_tamper" };
1043
+ }
1044
+ this.cache.setLastSeenTimestamp(now);
1045
+ const active = parseActiveEntitlements(payload);
1046
+ return {
1047
+ valid: true,
1048
+ offline: true,
1049
+ ...active.length ? { active_entitlements: active } : {}
1050
+ };
1507
1051
  } catch (e) {
1508
- return "no-canvas";
1052
+ return { valid: false, offline: true, reason_code: "verification_error" };
1509
1053
  }
1510
1054
  }
1511
1055
  /**
1512
- * Simple hash function
1056
+ * Quick offline verification using only local data (no network)
1057
+ * Performs signature verification plus basic validity checks (expiry, license key match)
1058
+ * @returns {Promise<import('./types.js').ValidationResult|null>}
1513
1059
  * @private
1514
1060
  */
1515
- hashCode(str2) {
1516
- let hash = 0;
1517
- for (let i = 0; i < str2.length; i++) {
1518
- const char = str2.charCodeAt(i);
1519
- hash = (hash << 5) - hash + char;
1520
- hash = hash & hash;
1061
+ async quickVerifyCachedOfflineLocal() {
1062
+ const signed = this.cache.getOfflineLicense();
1063
+ if (!signed)
1064
+ return null;
1065
+ const kid = signed.kid || signed.payload?.kid;
1066
+ const pub = kid ? this.cache.getPublicKey(kid) : null;
1067
+ if (!pub)
1068
+ return null;
1069
+ try {
1070
+ const ok = await this.verifyOfflineLicense(signed, pub);
1071
+ if (!ok) {
1072
+ return { valid: false, offline: true, reason_code: "signature_invalid" };
1073
+ }
1074
+ const payload = signed.payload || {};
1075
+ const cached = this.cache.getLicense();
1076
+ if (!cached || !constantTimeEqual(payload.lic_k || "", cached.license_key || "")) {
1077
+ return { valid: false, offline: true, reason_code: "license_mismatch" };
1078
+ }
1079
+ const now = Date.now();
1080
+ const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1081
+ if (expAt && expAt < now) {
1082
+ return { valid: false, offline: true, reason_code: "expired" };
1083
+ }
1084
+ const lastSeen = this.cache.getLastSeenTimestamp();
1085
+ if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1086
+ return { valid: false, offline: true, reason_code: "clock_tamper" };
1087
+ }
1088
+ const active = parseActiveEntitlements(payload);
1089
+ return {
1090
+ valid: true,
1091
+ offline: true,
1092
+ ...active.length ? { active_entitlements: active } : {}
1093
+ };
1094
+ } catch (_) {
1095
+ return { valid: false, offline: true, reason_code: "verification_error" };
1521
1096
  }
1522
- return Math.abs(hash).toString(36);
1523
1097
  }
1098
+ // ============================================================
1099
+ // API Communication
1100
+ // ============================================================
1524
1101
  /**
1525
- * Make API call with retry logic
1102
+ * Make an API call with retry logic
1103
+ * @param {string} endpoint - API endpoint (will be appended to apiBaseUrl)
1104
+ * @param {Object} [options={}] - Fetch options
1105
+ * @param {string} [options.method="GET"] - HTTP method
1106
+ * @param {Object} [options.body] - Request body (will be JSON-stringified)
1107
+ * @param {Object} [options.headers] - Additional headers
1108
+ * @returns {Promise<Object>} API response data
1109
+ * @throws {APIError} When the request fails after all retries
1526
1110
  * @private
1527
1111
  */
1528
1112
  async apiCall(endpoint, options = {}) {
@@ -1545,10 +1129,8 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1545
1129
  const response = await fetch(url, {
1546
1130
  method: options.method || "GET",
1547
1131
  headers,
1548
- // Use prepared headers
1549
1132
  body: options.body ? JSON.stringify(options.body) : void 0,
1550
1133
  credentials: "omit"
1551
- // Do NOT send cookies (session-agnostic)
1552
1134
  });
1553
1135
  const data = await response.json();
1554
1136
  if (!response.ok) {
@@ -1583,7 +1165,7 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1583
1165
  `Retry attempt ${attempt + 1} after ${delay}ms for error:`,
1584
1166
  error.message
1585
1167
  );
1586
- await this.sleep(delay);
1168
+ await sleep(delay);
1587
1169
  } else {
1588
1170
  throw error;
1589
1171
  }
@@ -1593,6 +1175,8 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1593
1175
  }
1594
1176
  /**
1595
1177
  * Determine if an error should be retried
1178
+ * @param {Error} error - The error to check
1179
+ * @returns {boolean} True if the error should trigger a retry
1596
1180
  * @private
1597
1181
  */
1598
1182
  shouldRetryError(error) {
@@ -1611,404 +1195,69 @@ var LicenseSeatSDK = class _LicenseSeatSDK {
1611
1195
  }
1612
1196
  return false;
1613
1197
  }
1198
+ // ============================================================
1199
+ // Utilities
1200
+ // ============================================================
1614
1201
  /**
1615
- * Event handling
1202
+ * Get CSRF token from meta tag
1203
+ * @returns {string} CSRF token or empty string
1616
1204
  */
1617
- on(event, callback) {
1618
- if (!this.eventListeners[event]) {
1619
- this.eventListeners[event] = [];
1620
- }
1621
- this.eventListeners[event].push(callback);
1622
- return () => this.off(event, callback);
1623
- }
1624
- off(event, callback) {
1625
- if (this.eventListeners[event]) {
1626
- this.eventListeners[event] = this.eventListeners[event].filter(
1627
- (cb) => cb !== callback
1628
- );
1629
- }
1630
- }
1631
- emit(event, data) {
1632
- this.log(`Event: ${event}`, data);
1633
- if (this.eventListeners[event]) {
1634
- this.eventListeners[event].forEach((callback) => {
1635
- try {
1636
- callback(data);
1637
- } catch (error) {
1638
- console.error(`Error in event listener for ${event}:`, error);
1639
- }
1640
- });
1641
- }
1205
+ getCsrfToken() {
1206
+ return getCsrfToken();
1642
1207
  }
1643
1208
  /**
1644
- * Utilities
1209
+ * Log a message (if debug mode is enabled)
1210
+ * @param {...*} args - Arguments to log
1211
+ * @returns {void}
1212
+ * @private
1645
1213
  */
1646
- getCsrfToken() {
1647
- const token = document.querySelector('meta[name="csrf-token"]');
1648
- return token ? token.content : "";
1649
- }
1650
- sleep(ms) {
1651
- return new Promise((resolve) => setTimeout(resolve, ms));
1652
- }
1653
1214
  log(...args) {
1654
1215
  if (this.config.debug) {
1655
1216
  console.log("[LicenseSeat SDK]", ...args);
1656
1217
  }
1657
1218
  }
1658
- /**
1659
- * Test server authentication by calling a simple endpoint that requires auth.
1660
- * Useful for verifying API key/session is valid.
1661
- * @returns {Promise<Object>} Result from the server
1662
- */
1663
- async testAuth() {
1664
- if (!this.config.apiKey) {
1665
- const err2 = new Error("API key is required for auth test");
1666
- this.emit("auth_test:error", { error: err2 });
1667
- throw err2;
1668
- }
1669
- try {
1670
- this.emit("auth_test:start");
1671
- const response = await this.apiCall("/auth_test", { method: "GET" });
1672
- this.emit("auth_test:success", response);
1673
- return response;
1674
- } catch (error) {
1675
- this.emit("auth_test:error", { error });
1676
- throw error;
1677
- }
1678
- }
1679
- /**
1680
- * Get current license status
1681
- */
1682
- getStatus() {
1683
- const license = this.cache.getLicense();
1684
- if (!license) {
1685
- return { status: "inactive", message: "No license activated" };
1686
- }
1687
- const validation = license.validation;
1688
- if (!validation) {
1689
- return { status: "pending", message: "License pending validation" };
1690
- }
1691
- if (!validation.valid) {
1692
- if (validation.offline) {
1693
- return {
1694
- status: "offline-invalid",
1695
- message: validation.reason_code || "License invalid (offline)"
1696
- };
1697
- }
1698
- return {
1699
- status: "invalid",
1700
- message: validation.reason || "License invalid"
1701
- };
1702
- }
1703
- if (validation.offline) {
1704
- return {
1705
- status: "offline-valid",
1706
- license: license.license_key,
1707
- device: license.device_identifier,
1708
- activated_at: license.activated_at,
1709
- last_validated: license.last_validated,
1710
- entitlements: validation.active_entitlements || []
1711
- };
1712
- }
1713
- return {
1714
- status: "active",
1715
- license: license.license_key,
1716
- device: license.device_identifier,
1717
- activated_at: license.activated_at,
1718
- last_validated: license.last_validated,
1719
- entitlements: validation.active_entitlements || []
1720
- };
1721
- }
1722
- /**
1723
- * Clear all data and reset
1724
- */
1725
- reset() {
1726
- this.stopAutoValidation();
1727
- this.cache.clear();
1728
- this.lastOfflineValidation = null;
1729
- this.emit("sdk:reset");
1730
- }
1731
- startConnectivityPolling() {
1732
- if (this.connectivityTimer)
1733
- return;
1734
- const heartbeat = async () => {
1735
- try {
1736
- await fetch(`${this.config.apiBaseUrl}/heartbeat`, {
1737
- method: "GET",
1738
- credentials: "omit"
1739
- });
1740
- if (!this.online) {
1741
- this.online = true;
1742
- this.emit("network:online");
1743
- if (this.currentAutoLicenseKey && !this.validationTimer) {
1744
- this.startAutoValidation(this.currentAutoLicenseKey);
1745
- }
1746
- this.syncOfflineAssets();
1747
- }
1748
- this.stopConnectivityPolling();
1749
- } catch (err2) {
1750
- }
1751
- };
1752
- this.connectivityTimer = setInterval(
1753
- heartbeat,
1754
- this.config.networkRecheckInterval
1755
- );
1756
- }
1757
- stopConnectivityPolling() {
1758
- if (this.connectivityTimer) {
1759
- clearInterval(this.connectivityTimer);
1760
- this.connectivityTimer = null;
1761
- }
1762
- }
1763
- /**
1764
- * Fetch & cache offline license + public key so we can verify while offline.
1765
- * Runs in background; errors are logged but not thrown.
1766
- * @private
1767
- */
1768
- async syncOfflineAssets() {
1769
- try {
1770
- const offline = await this.getOfflineLicense();
1771
- this.cache.setOfflineLicense(offline);
1772
- const kid = offline.kid || offline.payload?.kid;
1773
- if (kid) {
1774
- const existingKey = this.cache.getPublicKey(kid);
1775
- if (!existingKey) {
1776
- const pub = await this.getPublicKey(kid);
1777
- this.cache.setPublicKey(kid, pub);
1778
- }
1779
- }
1780
- this.emit("offlineLicense:ready", {
1781
- kid: offline.kid || offline.payload?.kid,
1782
- exp_at: offline.payload?.exp_at
1783
- });
1784
- } catch (err2) {
1785
- this.log("Failed to sync offline assets:", err2);
1786
- }
1787
- }
1788
- /**
1789
- * Verify cached offline license & return synthetic validation object.
1790
- * @private
1791
- */
1792
- async verifyCachedOffline() {
1793
- const signed = this.cache.getOfflineLicense();
1794
- if (!signed) {
1795
- return { valid: false, offline: true, reason_code: "no_offline_license" };
1796
- }
1797
- const kid = signed.kid || signed.payload?.kid;
1798
- let pub = kid ? this.cache.getPublicKey(kid) : null;
1799
- if (!pub) {
1800
- try {
1801
- pub = await this.getPublicKey(kid);
1802
- this.cache.setPublicKey(kid, pub);
1803
- } catch (e) {
1804
- return { valid: false, offline: true, reason_code: "no_public_key" };
1805
- }
1806
- }
1807
- try {
1808
- const ok = await this.verifyOfflineLicense(signed, pub);
1809
- if (!ok) {
1810
- return {
1811
- valid: false,
1812
- offline: true,
1813
- reason_code: "signature_invalid"
1814
- };
1815
- }
1816
- const payload = signed.payload || {};
1817
- const cached = this.cache.getLicense();
1818
- if (!cached || !_LicenseSeatSDK.constantTimeEqual(
1819
- payload.lic_k || "",
1820
- cached.license_key || ""
1821
- )) {
1822
- return { valid: false, offline: true, reason_code: "license_mismatch" };
1823
- }
1824
- const now = Date.now();
1825
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1826
- if (expAt && expAt < now) {
1827
- return { valid: false, offline: true, reason_code: "expired" };
1828
- }
1829
- if (!expAt && this.config.maxOfflineDays > 0) {
1830
- const pivot = cached.last_validated || cached.activated_at;
1831
- if (pivot) {
1832
- const ageMs = now - new Date(pivot).getTime();
1833
- if (ageMs > this.config.maxOfflineDays * 24 * 60 * 60 * 1e3) {
1834
- return {
1835
- valid: false,
1836
- offline: true,
1837
- reason_code: "grace_period_expired"
1838
- };
1839
- }
1840
- }
1841
- }
1842
- const lastSeen = this.cache.getLastSeenTimestamp();
1843
- if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1844
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1845
- }
1846
- this.cache.setLastSeenTimestamp(now);
1847
- return { valid: true, offline: true };
1848
- } catch (e) {
1849
- return { valid: false, offline: true, reason_code: "verification_error" };
1850
- }
1851
- }
1852
- scheduleOfflineRefresh() {
1853
- if (this.offlineRefreshTimer)
1854
- clearInterval(this.offlineRefreshTimer);
1855
- this.offlineRefreshTimer = setInterval(
1856
- () => this.syncOfflineAssets(),
1857
- this.config.offlineLicenseRefreshInterval
1858
- );
1859
- }
1860
- // --- Utility: constant-time string comparison to mitigate timing attacks ---
1861
- static constantTimeEqual(a = "", b = "") {
1862
- if (a.length !== b.length)
1863
- return false;
1864
- let res = 0;
1865
- for (let i = 0; i < a.length; i++) {
1866
- res |= a.charCodeAt(i) ^ b.charCodeAt(i);
1867
- }
1868
- return res === 0;
1869
- }
1870
- /**
1871
- * Attempt to verify cached offline license using only local data (no network).
1872
- * Returns validation-like object or null if not possible.
1873
- * @private
1874
- */
1875
- async quickVerifyCachedOfflineLocal() {
1876
- const signed = this.cache.getOfflineLicense();
1877
- if (!signed)
1878
- return null;
1879
- const kid = signed.kid || signed.payload?.kid;
1880
- const pub = kid ? this.cache.getPublicKey(kid) : null;
1881
- if (!pub)
1882
- return null;
1883
- try {
1884
- const ok = await this.verifyOfflineLicense(signed, pub);
1885
- return ok ? { valid: true, offline: true } : { valid: false, offline: true, reason_code: "signature_invalid" };
1886
- } catch (_) {
1887
- return { valid: false, offline: true, reason_code: "verification_error" };
1888
- }
1889
- }
1890
1219
  };
1891
- var LicenseCache = class {
1892
- constructor(prefix = "licenseseat_") {
1893
- this.prefix = prefix;
1894
- this.publicKeyCacheKey = this.prefix + "public_keys";
1220
+ var sharedInstance = null;
1221
+ function getSharedInstance(config) {
1222
+ if (!sharedInstance) {
1223
+ sharedInstance = new LicenseSeatSDK(config);
1895
1224
  }
1896
- getLicense() {
1897
- try {
1898
- const data = localStorage.getItem(this.prefix + "license");
1899
- return data ? JSON.parse(data) : null;
1900
- } catch (e) {
1901
- console.error("Failed to read license cache:", e);
1902
- return null;
1903
- }
1904
- }
1905
- setLicense(data) {
1906
- try {
1907
- localStorage.setItem(this.prefix + "license", JSON.stringify(data));
1908
- } catch (e) {
1909
- console.error("Failed to cache license:", e);
1910
- }
1911
- }
1912
- updateValidation(validationData) {
1913
- const license = this.getLicense();
1914
- if (license) {
1915
- license.validation = validationData;
1916
- license.last_validated = (/* @__PURE__ */ new Date()).toISOString();
1917
- this.setLicense(license);
1918
- }
1919
- }
1920
- getDeviceId() {
1921
- const license = this.getLicense();
1922
- return license ? license.device_identifier : null;
1923
- }
1924
- clearLicense() {
1925
- localStorage.removeItem(this.prefix + "license");
1926
- }
1927
- // --- Offline license helpers ---
1928
- getOfflineLicense() {
1929
- try {
1930
- const data = localStorage.getItem(this.prefix + "offline_license");
1931
- return data ? JSON.parse(data) : null;
1932
- } catch (e) {
1933
- console.error("Failed to read offline license cache:", e);
1934
- return null;
1935
- }
1936
- }
1937
- setOfflineLicense(data) {
1938
- try {
1939
- localStorage.setItem(
1940
- this.prefix + "offline_license",
1941
- JSON.stringify(data)
1942
- );
1943
- } catch (e) {
1944
- console.error("Failed to cache offline license:", e);
1945
- }
1946
- }
1947
- clearOfflineLicense() {
1948
- localStorage.removeItem(this.prefix + "offline_license");
1949
- }
1950
- // Methods for caching public keys
1951
- getPublicKey(keyId) {
1952
- try {
1953
- const cache = JSON.parse(
1954
- localStorage.getItem(this.publicKeyCacheKey) || "{}"
1955
- );
1956
- return cache[keyId] || null;
1957
- } catch (e) {
1958
- console.error("Failed to read public key cache:", e);
1959
- return null;
1960
- }
1961
- }
1962
- setPublicKey(keyId, publicKeyB64) {
1963
- try {
1964
- const cache = JSON.parse(
1965
- localStorage.getItem(this.publicKeyCacheKey) || "{}"
1966
- );
1967
- cache[keyId] = publicKeyB64;
1968
- localStorage.setItem(this.publicKeyCacheKey, JSON.stringify(cache));
1969
- } catch (e) {
1970
- console.error("Failed to cache public key:", e);
1971
- }
1972
- }
1973
- // Clears *all* LicenseSeat SDK data for this prefix.
1974
- clear() {
1975
- Object.keys(localStorage).forEach((key) => {
1976
- if (key.startsWith(this.prefix)) {
1977
- localStorage.removeItem(key);
1978
- }
1979
- });
1980
- this.clearOfflineLicense();
1981
- localStorage.removeItem(this.prefix + "last_seen_ts");
1982
- }
1983
- // --- Time baseline helpers ---
1984
- getLastSeenTimestamp() {
1985
- const v = localStorage.getItem(this.prefix + "last_seen_ts");
1986
- return v ? parseInt(v, 10) : null;
1987
- }
1988
- setLastSeenTimestamp(ts) {
1989
- try {
1990
- localStorage.setItem(this.prefix + "last_seen_ts", String(ts));
1991
- } catch (e) {
1992
- }
1225
+ return sharedInstance;
1226
+ }
1227
+ function configure(config, force = false) {
1228
+ if (sharedInstance && !force) {
1229
+ console.warn(
1230
+ "[LicenseSeat SDK] Already configured. Call configure with force=true to reconfigure."
1231
+ );
1232
+ return sharedInstance;
1993
1233
  }
1994
- };
1995
- var APIError = class extends Error {
1996
- constructor(message, status, data) {
1997
- super(message);
1998
- this.name = "APIError";
1999
- this.status = status;
2000
- this.data = data;
1234
+ sharedInstance = new LicenseSeatSDK(config);
1235
+ return sharedInstance;
1236
+ }
1237
+ function resetSharedInstance() {
1238
+ if (sharedInstance) {
1239
+ sharedInstance.reset();
1240
+ sharedInstance = null;
2001
1241
  }
2002
- };
1242
+ }
1243
+
1244
+ // src/index.js
2003
1245
  var src_default = LicenseSeatSDK;
2004
1246
  export {
2005
- src_default as default
1247
+ APIError,
1248
+ ConfigurationError,
1249
+ CryptoError,
1250
+ LicenseCache,
1251
+ LicenseError,
1252
+ LicenseSeatSDK,
1253
+ base64UrlDecode,
1254
+ canonicalJsonStringify,
1255
+ configure,
1256
+ constantTimeEqual,
1257
+ src_default as default,
1258
+ generateDeviceId,
1259
+ getCsrfToken,
1260
+ getSharedInstance,
1261
+ parseActiveEntitlements,
1262
+ resetSharedInstance
2006
1263
  };
2007
- /*! Bundled license information:
2008
-
2009
- @noble/ed25519/index.js:
2010
- (*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
2011
-
2012
- @noble/hashes/esm/utils.js:
2013
- (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
2014
- */