@licenseseat/js 0.2.2 → 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,
@@ -324,7 +322,9 @@ function getCsrfToken() {
324
322
 
325
323
  // src/LicenseSeat.js
326
324
  var DEFAULT_CONFIG = {
327
- 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")
328
328
  storagePrefix: "licenseseat_",
329
329
  autoValidateInterval: 36e5,
330
330
  // 1 hour
@@ -423,27 +423,33 @@ var LicenseSeatSDK = class {
423
423
  * @param {string} licenseKey - The license key to activate
424
424
  * @param {import('./types.js').ActivationOptions} [options={}] - Activation options
425
425
  * @returns {Promise<import('./types.js').CachedLicense>} Activation result with cached license data
426
+ * @throws {ConfigurationError} When productSlug is not configured
426
427
  * @throws {APIError} When the API request fails
427
428
  */
428
429
  async activate(licenseKey, options = {}) {
429
- 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();
430
434
  const payload = {
431
- license_key: licenseKey,
432
- device_identifier: deviceId,
435
+ device_id: deviceId,
433
436
  metadata: options.metadata || {}
434
437
  };
435
- if (options.softwareReleaseDate) {
436
- payload.software_release_date = options.softwareReleaseDate;
438
+ if (options.deviceName) {
439
+ payload.device_name = options.deviceName;
437
440
  }
438
441
  try {
439
442
  this.emit("activation:start", { licenseKey, deviceId });
440
- const response = await this.apiCall("/activations/activate", {
441
- method: "POST",
442
- body: payload
443
- });
443
+ const response = await this.apiCall(
444
+ `/products/${this.config.productSlug}/licenses/${encodeURIComponent(licenseKey)}/activate`,
445
+ {
446
+ method: "POST",
447
+ body: payload
448
+ }
449
+ );
444
450
  const licenseData = {
445
451
  license_key: licenseKey,
446
- device_identifier: deviceId,
452
+ device_id: deviceId,
447
453
  activation: response,
448
454
  activated_at: (/* @__PURE__ */ new Date()).toISOString(),
449
455
  last_validated: (/* @__PURE__ */ new Date()).toISOString()
@@ -463,25 +469,31 @@ var LicenseSeatSDK = class {
463
469
  /**
464
470
  * Deactivate the current license
465
471
  * @returns {Promise<Object>} Deactivation result from the API
472
+ * @throws {ConfigurationError} When productSlug is not configured
466
473
  * @throws {LicenseError} When no active license is found
467
474
  * @throws {APIError} When the API request fails
468
475
  */
469
476
  async deactivate() {
477
+ if (!this.config.productSlug) {
478
+ throw new ConfigurationError("productSlug is required for deactivation");
479
+ }
470
480
  const cachedLicense = this.cache.getLicense();
471
481
  if (!cachedLicense) {
472
482
  throw new LicenseError("No active license found", "no_license");
473
483
  }
474
484
  try {
475
485
  this.emit("deactivation:start", cachedLicense);
476
- const response = await this.apiCall("/activations/deactivate", {
477
- method: "POST",
478
- body: {
479
- license_key: cachedLicense.license_key,
480
- 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
+ }
481
493
  }
482
- });
494
+ );
483
495
  this.cache.clearLicense();
484
- this.cache.clearOfflineLicense();
496
+ this.cache.clearOfflineToken();
485
497
  this.stopAutoValidation();
486
498
  this.emit("deactivation:success", response);
487
499
  return response;
@@ -495,22 +507,33 @@ var LicenseSeatSDK = class {
495
507
  * @param {string} licenseKey - License key to validate
496
508
  * @param {import('./types.js').ValidationOptions} [options={}] - Validation options
497
509
  * @returns {Promise<import('./types.js').ValidationResult>} Validation result
510
+ * @throws {ConfigurationError} When productSlug is not configured
498
511
  * @throws {APIError} When the API request fails and offline fallback is not available
499
512
  */
500
513
  async validateLicense(licenseKey, options = {}) {
514
+ if (!this.config.productSlug) {
515
+ throw new ConfigurationError("productSlug is required for validation");
516
+ }
501
517
  try {
502
518
  this.emit("validation:start", { licenseKey });
503
- const rawResponse = await this.apiCall("/licenses/validate", {
504
- method: "POST",
505
- body: {
506
- license_key: licenseKey,
507
- device_identifier: options.deviceIdentifier || this.cache.getDeviceId(),
508
- 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
+ }
509
526
  }
510
- });
527
+ );
511
528
  const response = {
512
529
  valid: rawResponse.valid,
513
- ...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 || []
514
537
  };
515
538
  const cachedLicense = this.cache.getLicense();
516
539
  if ((!response.active_entitlements || response.active_entitlements.length === 0) && cachedLicense?.validation?.active_entitlements?.length) {
@@ -550,7 +573,13 @@ var LicenseSeatSDK = class {
550
573
  if (error instanceof APIError && error.data) {
551
574
  const cachedLicense = this.cache.getLicense();
552
575
  if (cachedLicense && cachedLicense.license_key === licenseKey) {
553
- 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
+ });
554
583
  }
555
584
  if (![0, 408, 429].includes(error.status)) {
556
585
  this.stopAutoValidation();
@@ -599,33 +628,50 @@ var LicenseSeatSDK = class {
599
628
  return this.checkEntitlement(entitlementKey).active;
600
629
  }
601
630
  /**
602
- * Get offline license data from the server
603
- * @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
604
637
  * @throws {LicenseError} When no active license is found
605
638
  * @throws {APIError} When the API request fails
606
639
  */
607
- async getOfflineLicense() {
640
+ async getOfflineToken(options = {}) {
641
+ if (!this.config.productSlug) {
642
+ throw new ConfigurationError("productSlug is required for offline token");
643
+ }
608
644
  const license = this.cache.getLicense();
609
645
  if (!license || !license.license_key) {
610
- 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.";
611
647
  this.emit("sdk:error", { message: errorMsg });
612
648
  throw new LicenseError(errorMsg, "no_license");
613
649
  }
614
650
  try {
615
- this.emit("offlineLicense:fetching", { licenseKey: license.license_key });
616
- const path = `/licenses/${license.license_key}/offline_license`;
617
- const response = await this.apiCall(path, { method: "POST" });
618
- 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", {
619
665
  licenseKey: license.license_key,
620
666
  data: response
621
667
  });
622
668
  return response;
623
669
  } catch (error) {
624
670
  this.log(
625
- `Failed to get offline license for ${license.license_key}:`,
671
+ `Failed to get offline token for ${license.license_key}:`,
626
672
  error
627
673
  );
628
- this.emit("offlineLicense:fetchError", {
674
+ this.emit("offlineToken:fetchError", {
629
675
  licenseKey: license.license_key,
630
676
  error
631
677
  });
@@ -633,45 +679,45 @@ var LicenseSeatSDK = class {
633
679
  }
634
680
  }
635
681
  /**
636
- * Fetch a public key from the server by key ID
637
- * @param {string} keyId - The Key ID (kid) for which to fetch the public key
638
- * @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
639
685
  * @throws {Error} When keyId is not provided or the key is not found
640
686
  */
641
- async getPublicKey(keyId) {
687
+ async getSigningKey(keyId) {
642
688
  if (!keyId) {
643
- 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.");
644
690
  }
645
691
  try {
646
- this.log(`Fetching public key for kid: ${keyId}`);
647
- 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)}`, {
648
694
  method: "GET"
649
695
  });
650
- if (response && response.public_key_b64) {
651
- this.log(`Successfully fetched public key for kid: ${keyId}`);
652
- return response.public_key_b64;
696
+ if (response && response.public_key) {
697
+ this.log(`Successfully fetched signing key for kid: ${keyId}`);
698
+ return response;
653
699
  } else {
654
700
  throw new Error(
655
- `Public key not found or invalid response for kid: ${keyId}`
701
+ `Signing key not found or invalid response for kid: ${keyId}`
656
702
  );
657
703
  }
658
704
  } catch (error) {
659
- this.log(`Failed to fetch public key for kid ${keyId}:`, error);
705
+ this.log(`Failed to fetch signing key for kid ${keyId}:`, error);
660
706
  throw error;
661
707
  }
662
708
  }
663
709
  /**
664
- * Verify a signed offline license client-side using Ed25519
665
- * @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
666
712
  * @param {string} publicKeyB64 - Base64-encoded public Ed25519 key
667
713
  * @returns {Promise<boolean>} True if verification is successful
668
714
  * @throws {CryptoError} When crypto library is not available
669
715
  * @throws {Error} When inputs are invalid
670
716
  */
671
- async verifyOfflineLicense(signedLicenseData, publicKeyB64) {
672
- this.log("Attempting to verify offline license client-side.");
673
- if (!signedLicenseData || !signedLicenseData.payload || !signedLicenseData.signature_b64u) {
674
- 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 }");
675
721
  }
676
722
  if (!publicKeyB64) {
677
723
  throw new Error("Public key (Base64 encoded) is required.");
@@ -684,27 +730,20 @@ var LicenseSeatSDK = class {
684
730
  throw err;
685
731
  }
686
732
  try {
687
- const payloadString = canonicalJsonStringify(signedLicenseData.payload);
688
- const messageBytes = new TextEncoder().encode(payloadString);
733
+ const messageBytes = new TextEncoder().encode(offlineTokenData.canonical);
734
+ const signatureBytes = base64UrlDecode(offlineTokenData.signature.value);
689
735
  const publicKeyBytes = base64UrlDecode(publicKeyB64);
690
- const signatureBytes = base64UrlDecode(signedLicenseData.signature_b64u);
691
736
  const isValid = ed.verify(signatureBytes, messageBytes, publicKeyBytes);
692
737
  if (isValid) {
693
- this.log(
694
- "Offline license signature VERIFIED successfully client-side."
695
- );
696
- this.emit("offlineLicense:verified", {
697
- payload: signedLicenseData.payload
698
- });
738
+ this.log("Offline token signature VERIFIED successfully client-side.");
739
+ this.emit("offlineToken:verified", { token: offlineTokenData.token });
699
740
  } else {
700
- this.log("Offline license signature INVALID client-side.");
701
- this.emit("offlineLicense:verificationFailed", {
702
- payload: signedLicenseData.payload
703
- });
741
+ this.log("Offline token signature INVALID client-side.");
742
+ this.emit("offlineToken:verificationFailed", { token: offlineTokenData.token });
704
743
  }
705
744
  return isValid;
706
745
  } catch (error) {
707
- this.log("Client-side offline license verification error:", error);
746
+ this.log("Client-side offline token verification error:", error);
708
747
  this.emit("sdk:error", {
709
748
  message: "Client-side verification failed.",
710
749
  error
@@ -729,19 +768,19 @@ var LicenseSeatSDK = class {
729
768
  if (validation.offline) {
730
769
  return {
731
770
  status: "offline-invalid",
732
- message: validation.reason_code || "License invalid (offline)"
771
+ message: validation.code || "License invalid (offline)"
733
772
  };
734
773
  }
735
774
  return {
736
775
  status: "invalid",
737
- message: validation.reason || "License invalid"
776
+ message: validation.message || validation.code || "License invalid"
738
777
  };
739
778
  }
740
779
  if (validation.offline) {
741
780
  return {
742
781
  status: "offline-valid",
743
782
  license: license.license_key,
744
- device: license.device_identifier,
783
+ device: license.device_id,
745
784
  activated_at: license.activated_at,
746
785
  last_validated: license.last_validated,
747
786
  entitlements: validation.active_entitlements || []
@@ -750,7 +789,7 @@ var LicenseSeatSDK = class {
750
789
  return {
751
790
  status: "active",
752
791
  license: license.license_key,
753
- device: license.device_identifier,
792
+ device: license.device_id,
754
793
  activated_at: license.activated_at,
755
794
  last_validated: license.last_validated,
756
795
  entitlements: validation.active_entitlements || []
@@ -910,9 +949,9 @@ var LicenseSeatSDK = class {
910
949
  startConnectivityPolling() {
911
950
  if (this.connectivityTimer)
912
951
  return;
913
- const heartbeat = async () => {
952
+ const healthCheck = async () => {
914
953
  try {
915
- await fetch(`${this.config.apiBaseUrl}/heartbeat`, {
954
+ await fetch(`${this.config.apiBaseUrl}/health`, {
916
955
  method: "GET",
917
956
  credentials: "omit"
918
957
  });
@@ -929,7 +968,7 @@ var LicenseSeatSDK = class {
929
968
  }
930
969
  };
931
970
  this.connectivityTimer = setInterval(
932
- heartbeat,
971
+ healthCheck,
933
972
  this.config.networkRecheckInterval
934
973
  );
935
974
  }
@@ -948,7 +987,7 @@ var LicenseSeatSDK = class {
948
987
  // Offline License Management
949
988
  // ============================================================
950
989
  /**
951
- * Fetch and cache offline license and public key
990
+ * Fetch and cache offline token and signing key
952
991
  * Uses a lock to prevent concurrent calls from causing race conditions
953
992
  * @returns {Promise<void>}
954
993
  * @private
@@ -960,19 +999,19 @@ var LicenseSeatSDK = class {
960
999
  }
961
1000
  this.syncingOfflineAssets = true;
962
1001
  try {
963
- const offline = await this.getOfflineLicense();
964
- this.cache.setOfflineLicense(offline);
965
- 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;
966
1005
  if (kid) {
967
1006
  const existingKey = this.cache.getPublicKey(kid);
968
1007
  if (!existingKey) {
969
- const pub = await this.getPublicKey(kid);
970
- this.cache.setPublicKey(kid, pub);
1008
+ const signingKey = await this.getSigningKey(kid);
1009
+ this.cache.setPublicKey(kid, signingKey.public_key);
971
1010
  }
972
1011
  }
973
- this.emit("offlineLicense:ready", {
974
- kid: offline.kid || offline.payload?.kid,
975
- exp_at: offline.payload?.exp_at
1012
+ this.emit("offlineToken:ready", {
1013
+ kid,
1014
+ exp: offline.token?.exp
976
1015
  });
977
1016
  const res = await this.quickVerifyCachedOfflineLocal();
978
1017
  if (res) {
@@ -1002,39 +1041,40 @@ var LicenseSeatSDK = class {
1002
1041
  );
1003
1042
  }
1004
1043
  /**
1005
- * Verify cached offline license
1044
+ * Verify cached offline token
1006
1045
  * @returns {Promise<import('./types.js').ValidationResult>}
1007
1046
  * @private
1008
1047
  */
1009
1048
  async verifyCachedOffline() {
1010
- const signed = this.cache.getOfflineLicense();
1049
+ const signed = this.cache.getOfflineToken();
1011
1050
  if (!signed) {
1012
- return { valid: false, offline: true, reason_code: "no_offline_license" };
1051
+ return { valid: false, offline: true, code: "no_offline_token" };
1013
1052
  }
1014
- const kid = signed.kid || signed.payload?.kid;
1053
+ const kid = signed.signature?.key_id || signed.token?.kid;
1015
1054
  let pub = kid ? this.cache.getPublicKey(kid) : null;
1016
1055
  if (!pub) {
1017
1056
  try {
1018
- pub = await this.getPublicKey(kid);
1057
+ const signingKey = await this.getSigningKey(kid);
1058
+ pub = signingKey.public_key;
1019
1059
  this.cache.setPublicKey(kid, pub);
1020
1060
  } catch (e) {
1021
- return { valid: false, offline: true, reason_code: "no_public_key" };
1061
+ return { valid: false, offline: true, code: "no_public_key" };
1022
1062
  }
1023
1063
  }
1024
1064
  try {
1025
- const ok = await this.verifyOfflineLicense(signed, pub);
1065
+ const ok = await this.verifyOfflineToken(signed, pub);
1026
1066
  if (!ok) {
1027
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1067
+ return { valid: false, offline: true, code: "signature_invalid" };
1028
1068
  }
1029
- const payload = signed.payload || {};
1069
+ const token = signed.token;
1030
1070
  const cached = this.cache.getLicense();
1031
- if (!cached || !constantTimeEqual(payload.lic_k || "", cached.license_key || "")) {
1032
- 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" };
1033
1073
  }
1034
1074
  const now = Date.now();
1035
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1075
+ const expAt = token.exp ? token.exp * 1e3 : null;
1036
1076
  if (expAt && expAt < now) {
1037
- return { valid: false, offline: true, reason_code: "expired" };
1077
+ return { valid: false, offline: true, code: "expired" };
1038
1078
  }
1039
1079
  if (!expAt && this.config.maxOfflineDays > 0) {
1040
1080
  const pivot = cached.last_validated || cached.activated_at;
@@ -1044,24 +1084,24 @@ var LicenseSeatSDK = class {
1044
1084
  return {
1045
1085
  valid: false,
1046
1086
  offline: true,
1047
- reason_code: "grace_period_expired"
1087
+ code: "grace_period_expired"
1048
1088
  };
1049
1089
  }
1050
1090
  }
1051
1091
  }
1052
1092
  const lastSeen = this.cache.getLastSeenTimestamp();
1053
1093
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1054
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1094
+ return { valid: false, offline: true, code: "clock_tamper" };
1055
1095
  }
1056
1096
  this.cache.setLastSeenTimestamp(now);
1057
- const active = parseActiveEntitlements(payload);
1097
+ const active = parseActiveEntitlements(token);
1058
1098
  return {
1059
1099
  valid: true,
1060
1100
  offline: true,
1061
1101
  ...active.length ? { active_entitlements: active } : {}
1062
1102
  };
1063
1103
  } catch (e) {
1064
- return { valid: false, offline: true, reason_code: "verification_error" };
1104
+ return { valid: false, offline: true, code: "verification_error" };
1065
1105
  }
1066
1106
  }
1067
1107
  /**
@@ -1071,40 +1111,40 @@ var LicenseSeatSDK = class {
1071
1111
  * @private
1072
1112
  */
1073
1113
  async quickVerifyCachedOfflineLocal() {
1074
- const signed = this.cache.getOfflineLicense();
1114
+ const signed = this.cache.getOfflineToken();
1075
1115
  if (!signed)
1076
1116
  return null;
1077
- const kid = signed.kid || signed.payload?.kid;
1117
+ const kid = signed.signature?.key_id || signed.token?.kid;
1078
1118
  const pub = kid ? this.cache.getPublicKey(kid) : null;
1079
1119
  if (!pub)
1080
1120
  return null;
1081
1121
  try {
1082
- const ok = await this.verifyOfflineLicense(signed, pub);
1122
+ const ok = await this.verifyOfflineToken(signed, pub);
1083
1123
  if (!ok) {
1084
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1124
+ return { valid: false, offline: true, code: "signature_invalid" };
1085
1125
  }
1086
- const payload = signed.payload || {};
1126
+ const token = signed.token;
1087
1127
  const cached = this.cache.getLicense();
1088
- if (!cached || !constantTimeEqual(payload.lic_k || "", cached.license_key || "")) {
1089
- 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" };
1090
1130
  }
1091
1131
  const now = Date.now();
1092
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1132
+ const expAt = token.exp ? token.exp * 1e3 : null;
1093
1133
  if (expAt && expAt < now) {
1094
- return { valid: false, offline: true, reason_code: "expired" };
1134
+ return { valid: false, offline: true, code: "expired" };
1095
1135
  }
1096
1136
  const lastSeen = this.cache.getLastSeenTimestamp();
1097
1137
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1098
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1138
+ return { valid: false, offline: true, code: "clock_tamper" };
1099
1139
  }
1100
- const active = parseActiveEntitlements(payload);
1140
+ const active = parseActiveEntitlements(token);
1101
1141
  return {
1102
1142
  valid: true,
1103
1143
  offline: true,
1104
1144
  ...active.length ? { active_entitlements: active } : {}
1105
1145
  };
1106
1146
  } catch (_) {
1107
- return { valid: false, offline: true, reason_code: "verification_error" };
1147
+ return { valid: false, offline: true, code: "verification_error" };
1108
1148
  }
1109
1149
  }
1110
1150
  // ============================================================
@@ -1146,11 +1186,14 @@ var LicenseSeatSDK = class {
1146
1186
  });
1147
1187
  const data = await response.json();
1148
1188
  if (!response.ok) {
1149
- throw new APIError(
1150
- data.error || "Request failed",
1151
- response.status,
1152
- data
1153
- );
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);
1154
1197
  }
1155
1198
  if (!this.online) {
1156
1199
  this.online = true;