@licenseseat/js 0.2.1 → 0.3.0

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
@@ -51,12 +51,12 @@ var LicenseCache = class {
51
51
  }
52
52
  }
53
53
  /**
54
- * Get the device identifier from the cached license
55
- * @returns {string|null} Device identifier or null if not found
54
+ * Get the device ID from the cached license
55
+ * @returns {string|null} Device ID or null if not found
56
56
  */
57
57
  getDeviceId() {
58
58
  const license = this.getLicense();
59
- return license ? license.device_identifier : null;
59
+ return license ? license.device_id : null;
60
60
  }
61
61
  /**
62
62
  * Clear the cached license data
@@ -66,39 +66,39 @@ var LicenseCache = class {
66
66
  localStorage.removeItem(this.prefix + "license");
67
67
  }
68
68
  /**
69
- * Get the cached offline license
70
- * @returns {import('./types.js').SignedOfflineLicense|null} Offline license or null if not found
69
+ * Get the cached offline token
70
+ * @returns {import('./types.js').OfflineToken|null} Offline token or null if not found
71
71
  */
72
- getOfflineLicense() {
72
+ getOfflineToken() {
73
73
  try {
74
- const data = localStorage.getItem(this.prefix + "offline_license");
74
+ const data = localStorage.getItem(this.prefix + "offline_token");
75
75
  return data ? JSON.parse(data) : null;
76
76
  } catch (e) {
77
- console.error("Failed to read offline license cache:", e);
77
+ console.error("Failed to read offline token cache:", e);
78
78
  return null;
79
79
  }
80
80
  }
81
81
  /**
82
- * Store offline license data in cache
83
- * @param {import('./types.js').SignedOfflineLicense} data - Signed offline license to cache
82
+ * Store offline token data in cache
83
+ * @param {import('./types.js').OfflineToken} data - Offline token to cache
84
84
  * @returns {void}
85
85
  */
86
- setOfflineLicense(data) {
86
+ setOfflineToken(data) {
87
87
  try {
88
88
  localStorage.setItem(
89
- this.prefix + "offline_license",
89
+ this.prefix + "offline_token",
90
90
  JSON.stringify(data)
91
91
  );
92
92
  } catch (e) {
93
- console.error("Failed to cache offline license:", e);
93
+ console.error("Failed to cache offline token:", e);
94
94
  }
95
95
  }
96
96
  /**
97
- * Clear the cached offline license
97
+ * Clear the cached offline token
98
98
  * @returns {void}
99
99
  */
100
- clearOfflineLicense() {
101
- localStorage.removeItem(this.prefix + "offline_license");
100
+ clearOfflineToken() {
101
+ localStorage.removeItem(this.prefix + "offline_token");
102
102
  }
103
103
  /**
104
104
  * Get a cached public key by key ID
@@ -143,8 +143,6 @@ var LicenseCache = class {
143
143
  localStorage.removeItem(key);
144
144
  }
145
145
  });
146
- this.clearOfflineLicense();
147
- localStorage.removeItem(this.prefix + "last_seen_ts");
148
146
  }
149
147
  /**
150
148
  * Get the last seen timestamp (for clock tamper detection)
@@ -218,7 +216,7 @@ var CryptoError = class extends Error {
218
216
  // src/utils.js
219
217
  import CJSON from "canonical-json";
220
218
  function parseActiveEntitlements(payload = {}) {
221
- const raw = payload.active_ents || payload.active_entitlements || [];
219
+ const raw = payload.entitlements || payload.active_entitlements || [];
222
220
  return raw.map((e) => ({
223
221
  key: e.key,
224
222
  expires_at: e.expires_at ?? null,
@@ -260,7 +258,14 @@ function base64UrlDecode(base64UrlString) {
260
258
  while (base64.length % 4) {
261
259
  base64 += "=";
262
260
  }
263
- const raw = window.atob(base64);
261
+ let raw;
262
+ if (typeof atob === "function") {
263
+ raw = atob(base64);
264
+ } else if (typeof Buffer !== "undefined") {
265
+ raw = Buffer.from(base64, "base64").toString("binary");
266
+ } else {
267
+ throw new Error("No base64 decoder available (neither atob nor Buffer found)");
268
+ }
264
269
  const outputArray = new Uint8Array(raw.length);
265
270
  for (let i = 0; i < raw.length; ++i) {
266
271
  outputArray[i] = raw.charCodeAt(i);
@@ -289,6 +294,11 @@ function getCanvasFingerprint() {
289
294
  }
290
295
  }
291
296
  function generateDeviceId() {
297
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
298
+ const os = typeof process !== "undefined" ? process.platform : "unknown";
299
+ const arch = typeof process !== "undefined" ? process.arch : "unknown";
300
+ return `node-${hashCode(os + "|" + arch)}`;
301
+ }
292
302
  const nav = window.navigator;
293
303
  const screen = window.screen;
294
304
  const data = [
@@ -300,7 +310,7 @@ function generateDeviceId() {
300
310
  nav.hardwareConcurrency,
301
311
  getCanvasFingerprint()
302
312
  ].join("|");
303
- return `web-${hashCode(data)}-${Date.now().toString(36)}`;
313
+ return `web-${hashCode(data)}`;
304
314
  }
305
315
  function sleep(ms) {
306
316
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -312,7 +322,9 @@ function getCsrfToken() {
312
322
 
313
323
  // src/LicenseSeat.js
314
324
  var DEFAULT_CONFIG = {
315
- apiBaseUrl: "https://licenseseat.com/api",
325
+ apiBaseUrl: "https://licenseseat.com/api/v1",
326
+ productSlug: null,
327
+ // Required: Product slug for API calls (e.g., "my-app")
316
328
  storagePrefix: "licenseseat_",
317
329
  autoValidateInterval: 36e5,
318
330
  // 1 hour
@@ -411,27 +423,33 @@ var LicenseSeatSDK = class {
411
423
  * @param {string} licenseKey - The license key to activate
412
424
  * @param {import('./types.js').ActivationOptions} [options={}] - Activation options
413
425
  * @returns {Promise<import('./types.js').CachedLicense>} Activation result with cached license data
426
+ * @throws {ConfigurationError} When productSlug is not configured
414
427
  * @throws {APIError} When the API request fails
415
428
  */
416
429
  async activate(licenseKey, options = {}) {
417
- const deviceId = options.deviceIdentifier || generateDeviceId();
430
+ if (!this.config.productSlug) {
431
+ throw new ConfigurationError("productSlug is required for activation");
432
+ }
433
+ const deviceId = options.deviceId || generateDeviceId();
418
434
  const payload = {
419
- license_key: licenseKey,
420
- device_identifier: deviceId,
435
+ device_id: deviceId,
421
436
  metadata: options.metadata || {}
422
437
  };
423
- if (options.softwareReleaseDate) {
424
- payload.software_release_date = options.softwareReleaseDate;
438
+ if (options.deviceName) {
439
+ payload.device_name = options.deviceName;
425
440
  }
426
441
  try {
427
442
  this.emit("activation:start", { licenseKey, deviceId });
428
- const response = await this.apiCall("/activations/activate", {
429
- method: "POST",
430
- body: payload
431
- });
443
+ const response = await this.apiCall(
444
+ `/products/${this.config.productSlug}/licenses/${encodeURIComponent(licenseKey)}/activate`,
445
+ {
446
+ method: "POST",
447
+ body: payload
448
+ }
449
+ );
432
450
  const licenseData = {
433
451
  license_key: licenseKey,
434
- device_identifier: deviceId,
452
+ device_id: deviceId,
435
453
  activation: response,
436
454
  activated_at: (/* @__PURE__ */ new Date()).toISOString(),
437
455
  last_validated: (/* @__PURE__ */ new Date()).toISOString()
@@ -451,25 +469,31 @@ var LicenseSeatSDK = class {
451
469
  /**
452
470
  * Deactivate the current license
453
471
  * @returns {Promise<Object>} Deactivation result from the API
472
+ * @throws {ConfigurationError} When productSlug is not configured
454
473
  * @throws {LicenseError} When no active license is found
455
474
  * @throws {APIError} When the API request fails
456
475
  */
457
476
  async deactivate() {
477
+ if (!this.config.productSlug) {
478
+ throw new ConfigurationError("productSlug is required for deactivation");
479
+ }
458
480
  const cachedLicense = this.cache.getLicense();
459
481
  if (!cachedLicense) {
460
482
  throw new LicenseError("No active license found", "no_license");
461
483
  }
462
484
  try {
463
485
  this.emit("deactivation:start", cachedLicense);
464
- const response = await this.apiCall("/activations/deactivate", {
465
- method: "POST",
466
- body: {
467
- license_key: cachedLicense.license_key,
468
- device_identifier: cachedLicense.device_identifier
486
+ const response = await this.apiCall(
487
+ `/products/${this.config.productSlug}/licenses/${encodeURIComponent(cachedLicense.license_key)}/deactivate`,
488
+ {
489
+ method: "POST",
490
+ body: {
491
+ device_id: cachedLicense.device_id
492
+ }
469
493
  }
470
- });
494
+ );
471
495
  this.cache.clearLicense();
472
- this.cache.clearOfflineLicense();
496
+ this.cache.clearOfflineToken();
473
497
  this.stopAutoValidation();
474
498
  this.emit("deactivation:success", response);
475
499
  return response;
@@ -483,22 +507,33 @@ var LicenseSeatSDK = class {
483
507
  * @param {string} licenseKey - License key to validate
484
508
  * @param {import('./types.js').ValidationOptions} [options={}] - Validation options
485
509
  * @returns {Promise<import('./types.js').ValidationResult>} Validation result
510
+ * @throws {ConfigurationError} When productSlug is not configured
486
511
  * @throws {APIError} When the API request fails and offline fallback is not available
487
512
  */
488
513
  async validateLicense(licenseKey, options = {}) {
514
+ if (!this.config.productSlug) {
515
+ throw new ConfigurationError("productSlug is required for validation");
516
+ }
489
517
  try {
490
518
  this.emit("validation:start", { licenseKey });
491
- const rawResponse = await this.apiCall("/licenses/validate", {
492
- method: "POST",
493
- body: {
494
- license_key: licenseKey,
495
- device_identifier: options.deviceIdentifier || this.cache.getDeviceId(),
496
- product_slug: options.productSlug
519
+ const rawResponse = await this.apiCall(
520
+ `/products/${this.config.productSlug}/licenses/${encodeURIComponent(licenseKey)}/validate`,
521
+ {
522
+ method: "POST",
523
+ body: {
524
+ device_id: options.deviceId || this.cache.getDeviceId()
525
+ }
497
526
  }
498
- });
527
+ );
499
528
  const response = {
500
529
  valid: rawResponse.valid,
501
- ...rawResponse.license || {}
530
+ code: rawResponse.code,
531
+ message: rawResponse.message,
532
+ warnings: rawResponse.warnings,
533
+ license: rawResponse.license,
534
+ activation: rawResponse.activation,
535
+ // Extract entitlements from license for easy access
536
+ active_entitlements: rawResponse.license?.active_entitlements || []
502
537
  };
503
538
  const cachedLicense = this.cache.getLicense();
504
539
  if ((!response.active_entitlements || response.active_entitlements.length === 0) && cachedLicense?.validation?.active_entitlements?.length) {
@@ -538,7 +573,13 @@ var LicenseSeatSDK = class {
538
573
  if (error instanceof APIError && error.data) {
539
574
  const cachedLicense = this.cache.getLicense();
540
575
  if (cachedLicense && cachedLicense.license_key === licenseKey) {
541
- this.cache.updateValidation({ valid: false, ...error.data });
576
+ const errorCode = error.data.error?.code || error.data.code;
577
+ const errorMessage = error.data.error?.message || error.data.message;
578
+ this.cache.updateValidation({
579
+ valid: false,
580
+ code: errorCode,
581
+ message: errorMessage
582
+ });
542
583
  }
543
584
  if (![0, 408, 429].includes(error.status)) {
544
585
  this.stopAutoValidation();
@@ -587,33 +628,50 @@ var LicenseSeatSDK = class {
587
628
  return this.checkEntitlement(entitlementKey).active;
588
629
  }
589
630
  /**
590
- * Get offline license data from the server
591
- * @returns {Promise<import('./types.js').SignedOfflineLicense>} Signed offline license data
631
+ * Get offline token data from the server
632
+ * @param {Object} [options={}] - Options for offline token generation
633
+ * @param {string} [options.deviceId] - Device ID to bind the token to (required for hardware_locked mode)
634
+ * @param {number} [options.ttlDays] - Token lifetime in days (default: 30, max: 90)
635
+ * @returns {Promise<import('./types.js').OfflineToken>} Offline token data
636
+ * @throws {ConfigurationError} When productSlug is not configured
592
637
  * @throws {LicenseError} When no active license is found
593
638
  * @throws {APIError} When the API request fails
594
639
  */
595
- async getOfflineLicense() {
640
+ async getOfflineToken(options = {}) {
641
+ if (!this.config.productSlug) {
642
+ throw new ConfigurationError("productSlug is required for offline token");
643
+ }
596
644
  const license = this.cache.getLicense();
597
645
  if (!license || !license.license_key) {
598
- const errorMsg = "No active license key found in cache to fetch offline license.";
646
+ const errorMsg = "No active license key found in cache to fetch offline token.";
599
647
  this.emit("sdk:error", { message: errorMsg });
600
648
  throw new LicenseError(errorMsg, "no_license");
601
649
  }
602
650
  try {
603
- this.emit("offlineLicense:fetching", { licenseKey: license.license_key });
604
- const path = `/licenses/${license.license_key}/offline_license`;
605
- const response = await this.apiCall(path, { method: "POST" });
606
- this.emit("offlineLicense:fetched", {
651
+ this.emit("offlineToken:fetching", { licenseKey: license.license_key });
652
+ const body = {};
653
+ if (options.deviceId) {
654
+ body.device_id = options.deviceId;
655
+ }
656
+ if (options.ttlDays) {
657
+ body.ttl_days = options.ttlDays;
658
+ }
659
+ const path = `/products/${this.config.productSlug}/licenses/${encodeURIComponent(license.license_key)}/offline-token`;
660
+ const response = await this.apiCall(path, {
661
+ method: "POST",
662
+ body: Object.keys(body).length > 0 ? body : void 0
663
+ });
664
+ this.emit("offlineToken:fetched", {
607
665
  licenseKey: license.license_key,
608
666
  data: response
609
667
  });
610
668
  return response;
611
669
  } catch (error) {
612
670
  this.log(
613
- `Failed to get offline license for ${license.license_key}:`,
671
+ `Failed to get offline token for ${license.license_key}:`,
614
672
  error
615
673
  );
616
- this.emit("offlineLicense:fetchError", {
674
+ this.emit("offlineToken:fetchError", {
617
675
  licenseKey: license.license_key,
618
676
  error
619
677
  });
@@ -621,45 +679,45 @@ var LicenseSeatSDK = class {
621
679
  }
622
680
  }
623
681
  /**
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
682
+ * Fetch a signing key from the server by key ID
683
+ * @param {string} keyId - The Key ID (kid) for which to fetch the signing key
684
+ * @returns {Promise<import('./types.js').SigningKey>} Signing key data
627
685
  * @throws {Error} When keyId is not provided or the key is not found
628
686
  */
629
- async getPublicKey(keyId) {
687
+ async getSigningKey(keyId) {
630
688
  if (!keyId) {
631
- throw new Error("Key ID is required to fetch a public key.");
689
+ throw new Error("Key ID is required to fetch a signing key.");
632
690
  }
633
691
  try {
634
- this.log(`Fetching public key for kid: ${keyId}`);
635
- const response = await this.apiCall(`/public_keys/${keyId}`, {
692
+ this.log(`Fetching signing key for kid: ${keyId}`);
693
+ const response = await this.apiCall(`/signing-keys/${encodeURIComponent(keyId)}`, {
636
694
  method: "GET"
637
695
  });
638
- if (response && response.public_key_b64) {
639
- this.log(`Successfully fetched public key for kid: ${keyId}`);
640
- return response.public_key_b64;
696
+ if (response && response.public_key) {
697
+ this.log(`Successfully fetched signing key for kid: ${keyId}`);
698
+ return response;
641
699
  } else {
642
700
  throw new Error(
643
- `Public key not found or invalid response for kid: ${keyId}`
701
+ `Signing key not found or invalid response for kid: ${keyId}`
644
702
  );
645
703
  }
646
704
  } catch (error) {
647
- this.log(`Failed to fetch public key for kid ${keyId}:`, error);
705
+ this.log(`Failed to fetch signing key for kid ${keyId}:`, error);
648
706
  throw error;
649
707
  }
650
708
  }
651
709
  /**
652
- * Verify a signed offline license client-side using Ed25519
653
- * @param {import('./types.js').SignedOfflineLicense} signedLicenseData - The signed license data
710
+ * Verify a signed offline token client-side using Ed25519
711
+ * @param {import('./types.js').OfflineToken} offlineTokenData - The offline token data
654
712
  * @param {string} publicKeyB64 - Base64-encoded public Ed25519 key
655
713
  * @returns {Promise<boolean>} True if verification is successful
656
714
  * @throws {CryptoError} When crypto library is not available
657
715
  * @throws {Error} When inputs are invalid
658
716
  */
659
- async verifyOfflineLicense(signedLicenseData, publicKeyB64) {
660
- this.log("Attempting to verify offline license client-side.");
661
- if (!signedLicenseData || !signedLicenseData.payload || !signedLicenseData.signature_b64u) {
662
- throw new Error("Invalid signedLicenseData object provided.");
717
+ async verifyOfflineToken(offlineTokenData, publicKeyB64) {
718
+ this.log("Attempting to verify offline token client-side.");
719
+ if (!offlineTokenData || !offlineTokenData.canonical || !offlineTokenData.signature) {
720
+ throw new Error("Invalid offline token object provided. Expected format: { token, signature, canonical }");
663
721
  }
664
722
  if (!publicKeyB64) {
665
723
  throw new Error("Public key (Base64 encoded) is required.");
@@ -672,27 +730,20 @@ var LicenseSeatSDK = class {
672
730
  throw err;
673
731
  }
674
732
  try {
675
- const payloadString = canonicalJsonStringify(signedLicenseData.payload);
676
- const messageBytes = new TextEncoder().encode(payloadString);
733
+ const messageBytes = new TextEncoder().encode(offlineTokenData.canonical);
734
+ const signatureBytes = base64UrlDecode(offlineTokenData.signature.value);
677
735
  const publicKeyBytes = base64UrlDecode(publicKeyB64);
678
- const signatureBytes = base64UrlDecode(signedLicenseData.signature_b64u);
679
736
  const isValid = ed.verify(signatureBytes, messageBytes, publicKeyBytes);
680
737
  if (isValid) {
681
- this.log(
682
- "Offline license signature VERIFIED successfully client-side."
683
- );
684
- this.emit("offlineLicense:verified", {
685
- payload: signedLicenseData.payload
686
- });
738
+ this.log("Offline token signature VERIFIED successfully client-side.");
739
+ this.emit("offlineToken:verified", { token: offlineTokenData.token });
687
740
  } else {
688
- this.log("Offline license signature INVALID client-side.");
689
- this.emit("offlineLicense:verificationFailed", {
690
- payload: signedLicenseData.payload
691
- });
741
+ this.log("Offline token signature INVALID client-side.");
742
+ this.emit("offlineToken:verificationFailed", { token: offlineTokenData.token });
692
743
  }
693
744
  return isValid;
694
745
  } catch (error) {
695
- this.log("Client-side offline license verification error:", error);
746
+ this.log("Client-side offline token verification error:", error);
696
747
  this.emit("sdk:error", {
697
748
  message: "Client-side verification failed.",
698
749
  error
@@ -717,19 +768,19 @@ var LicenseSeatSDK = class {
717
768
  if (validation.offline) {
718
769
  return {
719
770
  status: "offline-invalid",
720
- message: validation.reason_code || "License invalid (offline)"
771
+ message: validation.code || "License invalid (offline)"
721
772
  };
722
773
  }
723
774
  return {
724
775
  status: "invalid",
725
- message: validation.reason || "License invalid"
776
+ message: validation.message || validation.code || "License invalid"
726
777
  };
727
778
  }
728
779
  if (validation.offline) {
729
780
  return {
730
781
  status: "offline-valid",
731
782
  license: license.license_key,
732
- device: license.device_identifier,
783
+ device: license.device_id,
733
784
  activated_at: license.activated_at,
734
785
  last_validated: license.last_validated,
735
786
  entitlements: validation.active_entitlements || []
@@ -738,7 +789,7 @@ var LicenseSeatSDK = class {
738
789
  return {
739
790
  status: "active",
740
791
  license: license.license_key,
741
- device: license.device_identifier,
792
+ device: license.device_id,
742
793
  activated_at: license.activated_at,
743
794
  last_validated: license.last_validated,
744
795
  entitlements: validation.active_entitlements || []
@@ -748,12 +799,12 @@ var LicenseSeatSDK = class {
748
799
  * Test server authentication
749
800
  * Useful for verifying API key/session is valid.
750
801
  * @returns {Promise<Object>} Result from the server
751
- * @throws {Error} When API key is not configured
802
+ * @throws {ConfigurationError} When API key is not configured
752
803
  * @throws {APIError} When authentication fails
753
804
  */
754
805
  async testAuth() {
755
806
  if (!this.config.apiKey) {
756
- const err = new Error("API key is required for auth test");
807
+ const err = new ConfigurationError("API key is required for auth test");
757
808
  this.emit("auth_test:error", { error: err });
758
809
  throw err;
759
810
  }
@@ -898,9 +949,9 @@ var LicenseSeatSDK = class {
898
949
  startConnectivityPolling() {
899
950
  if (this.connectivityTimer)
900
951
  return;
901
- const heartbeat = async () => {
952
+ const healthCheck = async () => {
902
953
  try {
903
- await fetch(`${this.config.apiBaseUrl}/heartbeat`, {
954
+ await fetch(`${this.config.apiBaseUrl}/health`, {
904
955
  method: "GET",
905
956
  credentials: "omit"
906
957
  });
@@ -917,7 +968,7 @@ var LicenseSeatSDK = class {
917
968
  }
918
969
  };
919
970
  this.connectivityTimer = setInterval(
920
- heartbeat,
971
+ healthCheck,
921
972
  this.config.networkRecheckInterval
922
973
  );
923
974
  }
@@ -936,7 +987,7 @@ var LicenseSeatSDK = class {
936
987
  // Offline License Management
937
988
  // ============================================================
938
989
  /**
939
- * Fetch and cache offline license and public key
990
+ * Fetch and cache offline token and signing key
940
991
  * Uses a lock to prevent concurrent calls from causing race conditions
941
992
  * @returns {Promise<void>}
942
993
  * @private
@@ -948,19 +999,19 @@ var LicenseSeatSDK = class {
948
999
  }
949
1000
  this.syncingOfflineAssets = true;
950
1001
  try {
951
- const offline = await this.getOfflineLicense();
952
- this.cache.setOfflineLicense(offline);
953
- const kid = offline.kid || offline.payload?.kid;
1002
+ const offline = await this.getOfflineToken();
1003
+ this.cache.setOfflineToken(offline);
1004
+ const kid = offline.signature?.key_id || offline.token?.kid;
954
1005
  if (kid) {
955
1006
  const existingKey = this.cache.getPublicKey(kid);
956
1007
  if (!existingKey) {
957
- const pub = await this.getPublicKey(kid);
958
- this.cache.setPublicKey(kid, pub);
1008
+ const signingKey = await this.getSigningKey(kid);
1009
+ this.cache.setPublicKey(kid, signingKey.public_key);
959
1010
  }
960
1011
  }
961
- this.emit("offlineLicense:ready", {
962
- kid: offline.kid || offline.payload?.kid,
963
- exp_at: offline.payload?.exp_at
1012
+ this.emit("offlineToken:ready", {
1013
+ kid,
1014
+ exp: offline.token?.exp
964
1015
  });
965
1016
  const res = await this.quickVerifyCachedOfflineLocal();
966
1017
  if (res) {
@@ -990,39 +1041,40 @@ var LicenseSeatSDK = class {
990
1041
  );
991
1042
  }
992
1043
  /**
993
- * Verify cached offline license
1044
+ * Verify cached offline token
994
1045
  * @returns {Promise<import('./types.js').ValidationResult>}
995
1046
  * @private
996
1047
  */
997
1048
  async verifyCachedOffline() {
998
- const signed = this.cache.getOfflineLicense();
1049
+ const signed = this.cache.getOfflineToken();
999
1050
  if (!signed) {
1000
- return { valid: false, offline: true, reason_code: "no_offline_license" };
1051
+ return { valid: false, offline: true, code: "no_offline_token" };
1001
1052
  }
1002
- const kid = signed.kid || signed.payload?.kid;
1053
+ const kid = signed.signature?.key_id || signed.token?.kid;
1003
1054
  let pub = kid ? this.cache.getPublicKey(kid) : null;
1004
1055
  if (!pub) {
1005
1056
  try {
1006
- pub = await this.getPublicKey(kid);
1057
+ const signingKey = await this.getSigningKey(kid);
1058
+ pub = signingKey.public_key;
1007
1059
  this.cache.setPublicKey(kid, pub);
1008
1060
  } catch (e) {
1009
- return { valid: false, offline: true, reason_code: "no_public_key" };
1061
+ return { valid: false, offline: true, code: "no_public_key" };
1010
1062
  }
1011
1063
  }
1012
1064
  try {
1013
- const ok = await this.verifyOfflineLicense(signed, pub);
1065
+ const ok = await this.verifyOfflineToken(signed, pub);
1014
1066
  if (!ok) {
1015
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1067
+ return { valid: false, offline: true, code: "signature_invalid" };
1016
1068
  }
1017
- const payload = signed.payload || {};
1069
+ const token = signed.token;
1018
1070
  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" };
1071
+ if (!cached || !constantTimeEqual(token.license_key || "", cached.license_key || "")) {
1072
+ return { valid: false, offline: true, code: "license_mismatch" };
1021
1073
  }
1022
1074
  const now = Date.now();
1023
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1075
+ const expAt = token.exp ? token.exp * 1e3 : null;
1024
1076
  if (expAt && expAt < now) {
1025
- return { valid: false, offline: true, reason_code: "expired" };
1077
+ return { valid: false, offline: true, code: "expired" };
1026
1078
  }
1027
1079
  if (!expAt && this.config.maxOfflineDays > 0) {
1028
1080
  const pivot = cached.last_validated || cached.activated_at;
@@ -1032,24 +1084,24 @@ var LicenseSeatSDK = class {
1032
1084
  return {
1033
1085
  valid: false,
1034
1086
  offline: true,
1035
- reason_code: "grace_period_expired"
1087
+ code: "grace_period_expired"
1036
1088
  };
1037
1089
  }
1038
1090
  }
1039
1091
  }
1040
1092
  const lastSeen = this.cache.getLastSeenTimestamp();
1041
1093
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1042
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1094
+ return { valid: false, offline: true, code: "clock_tamper" };
1043
1095
  }
1044
1096
  this.cache.setLastSeenTimestamp(now);
1045
- const active = parseActiveEntitlements(payload);
1097
+ const active = parseActiveEntitlements(token);
1046
1098
  return {
1047
1099
  valid: true,
1048
1100
  offline: true,
1049
1101
  ...active.length ? { active_entitlements: active } : {}
1050
1102
  };
1051
1103
  } catch (e) {
1052
- return { valid: false, offline: true, reason_code: "verification_error" };
1104
+ return { valid: false, offline: true, code: "verification_error" };
1053
1105
  }
1054
1106
  }
1055
1107
  /**
@@ -1059,40 +1111,40 @@ var LicenseSeatSDK = class {
1059
1111
  * @private
1060
1112
  */
1061
1113
  async quickVerifyCachedOfflineLocal() {
1062
- const signed = this.cache.getOfflineLicense();
1114
+ const signed = this.cache.getOfflineToken();
1063
1115
  if (!signed)
1064
1116
  return null;
1065
- const kid = signed.kid || signed.payload?.kid;
1117
+ const kid = signed.signature?.key_id || signed.token?.kid;
1066
1118
  const pub = kid ? this.cache.getPublicKey(kid) : null;
1067
1119
  if (!pub)
1068
1120
  return null;
1069
1121
  try {
1070
- const ok = await this.verifyOfflineLicense(signed, pub);
1122
+ const ok = await this.verifyOfflineToken(signed, pub);
1071
1123
  if (!ok) {
1072
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1124
+ return { valid: false, offline: true, code: "signature_invalid" };
1073
1125
  }
1074
- const payload = signed.payload || {};
1126
+ const token = signed.token;
1075
1127
  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" };
1128
+ if (!cached || !constantTimeEqual(token.license_key || "", cached.license_key || "")) {
1129
+ return { valid: false, offline: true, code: "license_mismatch" };
1078
1130
  }
1079
1131
  const now = Date.now();
1080
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1132
+ const expAt = token.exp ? token.exp * 1e3 : null;
1081
1133
  if (expAt && expAt < now) {
1082
- return { valid: false, offline: true, reason_code: "expired" };
1134
+ return { valid: false, offline: true, code: "expired" };
1083
1135
  }
1084
1136
  const lastSeen = this.cache.getLastSeenTimestamp();
1085
1137
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1086
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1138
+ return { valid: false, offline: true, code: "clock_tamper" };
1087
1139
  }
1088
- const active = parseActiveEntitlements(payload);
1140
+ const active = parseActiveEntitlements(token);
1089
1141
  return {
1090
1142
  valid: true,
1091
1143
  offline: true,
1092
1144
  ...active.length ? { active_entitlements: active } : {}
1093
1145
  };
1094
1146
  } catch (_) {
1095
- return { valid: false, offline: true, reason_code: "verification_error" };
1147
+ return { valid: false, offline: true, code: "verification_error" };
1096
1148
  }
1097
1149
  }
1098
1150
  // ============================================================
@@ -1134,11 +1186,14 @@ var LicenseSeatSDK = class {
1134
1186
  });
1135
1187
  const data = await response.json();
1136
1188
  if (!response.ok) {
1137
- throw new APIError(
1138
- data.error || "Request failed",
1139
- response.status,
1140
- data
1141
- );
1189
+ const errorObj = data.error;
1190
+ let errorMessage = "Request failed";
1191
+ if (typeof errorObj === "object" && errorObj !== null) {
1192
+ errorMessage = errorObj.message || "Request failed";
1193
+ } else if (typeof errorObj === "string") {
1194
+ errorMessage = errorObj;
1195
+ }
1196
+ throw new APIError(errorMessage, response.status, data);
1142
1197
  }
1143
1198
  if (!this.online) {
1144
1199
  this.online = true;