@licenseseat/js 0.2.2 → 0.3.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
@@ -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,18 +789,19 @@ 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 || []
757
796
  };
758
797
  }
759
798
  /**
760
- * Test server authentication
761
- * Useful for verifying API key/session is valid.
762
- * @returns {Promise<Object>} Result from the server
763
- * @throws {ConfigurationError} When API key is not configured
764
- * @throws {APIError} When authentication fails
799
+ * Test API connectivity
800
+ * Makes a request to the health endpoint to verify connectivity.
801
+ * Note: To fully verify API key validity, attempt an actual operation like activate() or validateLicense().
802
+ * @returns {Promise<{authenticated: boolean, healthy: boolean, api_version: string}>}
803
+ * @throws {ConfigurationError} If API key is not configured
804
+ * @throws {APIError} If the health check fails
765
805
  */
766
806
  async testAuth() {
767
807
  if (!this.config.apiKey) {
@@ -771,9 +811,15 @@ var LicenseSeatSDK = class {
771
811
  }
772
812
  try {
773
813
  this.emit("auth_test:start");
774
- const response = await this.apiCall("/auth_test", { method: "GET" });
775
- this.emit("auth_test:success", response);
776
- return response;
814
+ const response = await this.apiCall("/health", { method: "GET" });
815
+ const result = {
816
+ authenticated: true,
817
+ // API key was included in request
818
+ healthy: response.status === "healthy",
819
+ api_version: response.api_version
820
+ };
821
+ this.emit("auth_test:success", result);
822
+ return result;
777
823
  } catch (error) {
778
824
  this.emit("auth_test:error", { error });
779
825
  throw error;
@@ -910,9 +956,9 @@ var LicenseSeatSDK = class {
910
956
  startConnectivityPolling() {
911
957
  if (this.connectivityTimer)
912
958
  return;
913
- const heartbeat = async () => {
959
+ const healthCheck = async () => {
914
960
  try {
915
- await fetch(`${this.config.apiBaseUrl}/heartbeat`, {
961
+ await fetch(`${this.config.apiBaseUrl}/health`, {
916
962
  method: "GET",
917
963
  credentials: "omit"
918
964
  });
@@ -929,7 +975,7 @@ var LicenseSeatSDK = class {
929
975
  }
930
976
  };
931
977
  this.connectivityTimer = setInterval(
932
- heartbeat,
978
+ healthCheck,
933
979
  this.config.networkRecheckInterval
934
980
  );
935
981
  }
@@ -948,7 +994,7 @@ var LicenseSeatSDK = class {
948
994
  // Offline License Management
949
995
  // ============================================================
950
996
  /**
951
- * Fetch and cache offline license and public key
997
+ * Fetch and cache offline token and signing key
952
998
  * Uses a lock to prevent concurrent calls from causing race conditions
953
999
  * @returns {Promise<void>}
954
1000
  * @private
@@ -960,19 +1006,19 @@ var LicenseSeatSDK = class {
960
1006
  }
961
1007
  this.syncingOfflineAssets = true;
962
1008
  try {
963
- const offline = await this.getOfflineLicense();
964
- this.cache.setOfflineLicense(offline);
965
- const kid = offline.kid || offline.payload?.kid;
1009
+ const offline = await this.getOfflineToken();
1010
+ this.cache.setOfflineToken(offline);
1011
+ const kid = offline.signature?.key_id || offline.token?.kid;
966
1012
  if (kid) {
967
1013
  const existingKey = this.cache.getPublicKey(kid);
968
1014
  if (!existingKey) {
969
- const pub = await this.getPublicKey(kid);
970
- this.cache.setPublicKey(kid, pub);
1015
+ const signingKey = await this.getSigningKey(kid);
1016
+ this.cache.setPublicKey(kid, signingKey.public_key);
971
1017
  }
972
1018
  }
973
- this.emit("offlineLicense:ready", {
974
- kid: offline.kid || offline.payload?.kid,
975
- exp_at: offline.payload?.exp_at
1019
+ this.emit("offlineToken:ready", {
1020
+ kid,
1021
+ exp: offline.token?.exp
976
1022
  });
977
1023
  const res = await this.quickVerifyCachedOfflineLocal();
978
1024
  if (res) {
@@ -1002,39 +1048,40 @@ var LicenseSeatSDK = class {
1002
1048
  );
1003
1049
  }
1004
1050
  /**
1005
- * Verify cached offline license
1051
+ * Verify cached offline token
1006
1052
  * @returns {Promise<import('./types.js').ValidationResult>}
1007
1053
  * @private
1008
1054
  */
1009
1055
  async verifyCachedOffline() {
1010
- const signed = this.cache.getOfflineLicense();
1056
+ const signed = this.cache.getOfflineToken();
1011
1057
  if (!signed) {
1012
- return { valid: false, offline: true, reason_code: "no_offline_license" };
1058
+ return { valid: false, offline: true, code: "no_offline_token" };
1013
1059
  }
1014
- const kid = signed.kid || signed.payload?.kid;
1060
+ const kid = signed.signature?.key_id || signed.token?.kid;
1015
1061
  let pub = kid ? this.cache.getPublicKey(kid) : null;
1016
1062
  if (!pub) {
1017
1063
  try {
1018
- pub = await this.getPublicKey(kid);
1064
+ const signingKey = await this.getSigningKey(kid);
1065
+ pub = signingKey.public_key;
1019
1066
  this.cache.setPublicKey(kid, pub);
1020
1067
  } catch (e) {
1021
- return { valid: false, offline: true, reason_code: "no_public_key" };
1068
+ return { valid: false, offline: true, code: "no_public_key" };
1022
1069
  }
1023
1070
  }
1024
1071
  try {
1025
- const ok = await this.verifyOfflineLicense(signed, pub);
1072
+ const ok = await this.verifyOfflineToken(signed, pub);
1026
1073
  if (!ok) {
1027
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1074
+ return { valid: false, offline: true, code: "signature_invalid" };
1028
1075
  }
1029
- const payload = signed.payload || {};
1076
+ const token = signed.token;
1030
1077
  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" };
1078
+ if (!cached || !constantTimeEqual(token.license_key || "", cached.license_key || "")) {
1079
+ return { valid: false, offline: true, code: "license_mismatch" };
1033
1080
  }
1034
1081
  const now = Date.now();
1035
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1082
+ const expAt = token.exp ? token.exp * 1e3 : null;
1036
1083
  if (expAt && expAt < now) {
1037
- return { valid: false, offline: true, reason_code: "expired" };
1084
+ return { valid: false, offline: true, code: "expired" };
1038
1085
  }
1039
1086
  if (!expAt && this.config.maxOfflineDays > 0) {
1040
1087
  const pivot = cached.last_validated || cached.activated_at;
@@ -1044,24 +1091,24 @@ var LicenseSeatSDK = class {
1044
1091
  return {
1045
1092
  valid: false,
1046
1093
  offline: true,
1047
- reason_code: "grace_period_expired"
1094
+ code: "grace_period_expired"
1048
1095
  };
1049
1096
  }
1050
1097
  }
1051
1098
  }
1052
1099
  const lastSeen = this.cache.getLastSeenTimestamp();
1053
1100
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1054
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1101
+ return { valid: false, offline: true, code: "clock_tamper" };
1055
1102
  }
1056
1103
  this.cache.setLastSeenTimestamp(now);
1057
- const active = parseActiveEntitlements(payload);
1104
+ const active = parseActiveEntitlements(token);
1058
1105
  return {
1059
1106
  valid: true,
1060
1107
  offline: true,
1061
1108
  ...active.length ? { active_entitlements: active } : {}
1062
1109
  };
1063
1110
  } catch (e) {
1064
- return { valid: false, offline: true, reason_code: "verification_error" };
1111
+ return { valid: false, offline: true, code: "verification_error" };
1065
1112
  }
1066
1113
  }
1067
1114
  /**
@@ -1071,40 +1118,40 @@ var LicenseSeatSDK = class {
1071
1118
  * @private
1072
1119
  */
1073
1120
  async quickVerifyCachedOfflineLocal() {
1074
- const signed = this.cache.getOfflineLicense();
1121
+ const signed = this.cache.getOfflineToken();
1075
1122
  if (!signed)
1076
1123
  return null;
1077
- const kid = signed.kid || signed.payload?.kid;
1124
+ const kid = signed.signature?.key_id || signed.token?.kid;
1078
1125
  const pub = kid ? this.cache.getPublicKey(kid) : null;
1079
1126
  if (!pub)
1080
1127
  return null;
1081
1128
  try {
1082
- const ok = await this.verifyOfflineLicense(signed, pub);
1129
+ const ok = await this.verifyOfflineToken(signed, pub);
1083
1130
  if (!ok) {
1084
- return { valid: false, offline: true, reason_code: "signature_invalid" };
1131
+ return { valid: false, offline: true, code: "signature_invalid" };
1085
1132
  }
1086
- const payload = signed.payload || {};
1133
+ const token = signed.token;
1087
1134
  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" };
1135
+ if (!cached || !constantTimeEqual(token.license_key || "", cached.license_key || "")) {
1136
+ return { valid: false, offline: true, code: "license_mismatch" };
1090
1137
  }
1091
1138
  const now = Date.now();
1092
- const expAt = payload.exp_at ? Date.parse(payload.exp_at) : null;
1139
+ const expAt = token.exp ? token.exp * 1e3 : null;
1093
1140
  if (expAt && expAt < now) {
1094
- return { valid: false, offline: true, reason_code: "expired" };
1141
+ return { valid: false, offline: true, code: "expired" };
1095
1142
  }
1096
1143
  const lastSeen = this.cache.getLastSeenTimestamp();
1097
1144
  if (lastSeen && now + this.config.maxClockSkewMs < lastSeen) {
1098
- return { valid: false, offline: true, reason_code: "clock_tamper" };
1145
+ return { valid: false, offline: true, code: "clock_tamper" };
1099
1146
  }
1100
- const active = parseActiveEntitlements(payload);
1147
+ const active = parseActiveEntitlements(token);
1101
1148
  return {
1102
1149
  valid: true,
1103
1150
  offline: true,
1104
1151
  ...active.length ? { active_entitlements: active } : {}
1105
1152
  };
1106
1153
  } catch (_) {
1107
- return { valid: false, offline: true, reason_code: "verification_error" };
1154
+ return { valid: false, offline: true, code: "verification_error" };
1108
1155
  }
1109
1156
  }
1110
1157
  // ============================================================
@@ -1146,11 +1193,14 @@ var LicenseSeatSDK = class {
1146
1193
  });
1147
1194
  const data = await response.json();
1148
1195
  if (!response.ok) {
1149
- throw new APIError(
1150
- data.error || "Request failed",
1151
- response.status,
1152
- data
1153
- );
1196
+ const errorObj = data.error;
1197
+ let errorMessage = "Request failed";
1198
+ if (typeof errorObj === "object" && errorObj !== null) {
1199
+ errorMessage = errorObj.message || "Request failed";
1200
+ } else if (typeof errorObj === "string") {
1201
+ errorMessage = errorObj;
1202
+ }
1203
+ throw new APIError(errorMessage, response.status, data);
1154
1204
  }
1155
1205
  if (!this.online) {
1156
1206
  this.online = true;