@lanonasis/oauth-client 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,23 +31,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ApiKeyStorage: () => ApiKeyStorage,
34
+ ApiKeyStorageWeb: () => ApiKeyStorageWeb,
34
35
  BaseOAuthFlow: () => BaseOAuthFlow,
35
36
  DesktopOAuthFlow: () => DesktopOAuthFlow,
36
37
  MCPClient: () => MCPClient,
37
38
  TerminalOAuthFlow: () => TerminalOAuthFlow,
38
- TokenStorage: () => TokenStorage
39
+ TokenStorage: () => TokenStorage,
40
+ TokenStorageWeb: () => TokenStorageWeb
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
41
43
 
44
+ // src/flows/terminal-flow.ts
45
+ var import_cross_fetch2 = __toESM(require("cross-fetch"), 1);
46
+
42
47
  // src/flows/base-flow.ts
48
+ var import_cross_fetch = __toESM(require("cross-fetch"), 1);
43
49
  var BaseOAuthFlow = class {
44
50
  constructor(config) {
45
51
  this.clientId = config.clientId;
46
52
  this.authBaseUrl = config.authBaseUrl || "https://auth.lanonasis.com";
47
- this.scope = config.scope || "mcp:read mcp:write api_keys:manage";
53
+ this.scope = config.scope || "memories:read memories:write memories:delete profile";
48
54
  }
49
55
  async makeTokenRequest(body) {
50
- const response = await fetch(`${this.authBaseUrl}/oauth/token`, {
56
+ const response = await (0, import_cross_fetch.default)(`${this.authBaseUrl}/oauth/token`, {
51
57
  method: "POST",
52
58
  headers: { "Content-Type": "application/json" },
53
59
  body: JSON.stringify(body)
@@ -60,11 +66,10 @@ var BaseOAuthFlow = class {
60
66
  }
61
67
  generateState() {
62
68
  const array = new Uint8Array(32);
63
- if (typeof window !== "undefined" && window.crypto) {
64
- window.crypto.getRandomValues(array);
69
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
70
+ crypto.getRandomValues(array);
65
71
  } else {
66
- const crypto = require("crypto");
67
- crypto.randomFillSync(array);
72
+ throw new Error("Secure random generation is not available");
68
73
  }
69
74
  return this.base64URLEncode(array);
70
75
  }
@@ -74,7 +79,8 @@ var BaseOAuthFlow = class {
74
79
  bytes.forEach((byte) => {
75
80
  binary += String.fromCharCode(byte);
76
81
  });
77
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
82
+ const base64 = typeof btoa !== "undefined" ? btoa(binary) : Buffer.from(bytes).toString("base64");
83
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
78
84
  }
79
85
  async refreshToken(refreshToken) {
80
86
  return this.makeTokenRequest({
@@ -84,7 +90,7 @@ var BaseOAuthFlow = class {
84
90
  });
85
91
  }
86
92
  async revokeToken(token, tokenType = "access_token") {
87
- const response = await fetch(`${this.authBaseUrl}/oauth/revoke`, {
93
+ const response = await (0, import_cross_fetch.default)(`${this.authBaseUrl}/oauth/revoke`, {
88
94
  method: "POST",
89
95
  headers: { "Content-Type": "application/json" },
90
96
  body: JSON.stringify({
@@ -100,7 +106,6 @@ var BaseOAuthFlow = class {
100
106
  };
101
107
 
102
108
  // src/flows/terminal-flow.ts
103
- var import_open = __toESM(require("open"), 1);
104
109
  var TerminalOAuthFlow = class extends BaseOAuthFlow {
105
110
  constructor(config) {
106
111
  super({
@@ -123,7 +128,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
123
128
  }
124
129
  }
125
130
  async requestDeviceCode() {
126
- const response = await fetch(`${this.authBaseUrl}/oauth/device`, {
131
+ const response = await (0, import_cross_fetch2.default)(`${this.authBaseUrl}/oauth/device`, {
127
132
  method: "POST",
128
133
  headers: { "Content-Type": "application/json" },
129
134
  body: JSON.stringify({
@@ -148,12 +153,13 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
148
153
  }
149
154
  async openBrowser(url) {
150
155
  try {
156
+ const { default: open } = await import("open");
151
157
  await Promise.race([
152
158
  this.waitForEnter(),
153
159
  new Promise((resolve) => setTimeout(resolve, 2e3))
154
160
  ]);
155
161
  console.log("Opening browser...");
156
- await (0, import_open.default)(url);
162
+ await open(url);
157
163
  } catch (error) {
158
164
  console.log("Please open the URL manually in your browser.");
159
165
  }
@@ -196,7 +202,7 @@ var TerminalOAuthFlow = class extends BaseOAuthFlow {
196
202
  throw new Error("Authorization timeout - please try again");
197
203
  }
198
204
  async checkDeviceCode(deviceCode) {
199
- const response = await fetch(`${this.authBaseUrl}/oauth/token`, {
205
+ const response = await (0, import_cross_fetch2.default)(`${this.authBaseUrl}/oauth/token`, {
200
206
  method: "POST",
201
207
  headers: { "Content-Type": "application/json" },
202
208
  body: JSON.stringify({
@@ -237,25 +243,22 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
237
243
  }
238
244
  generateCodeVerifier() {
239
245
  const array = new Uint8Array(32);
240
- if (typeof window !== "undefined" && window.crypto) {
241
- window.crypto.getRandomValues(array);
246
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
247
+ crypto.getRandomValues(array);
242
248
  } else {
243
- const crypto = require("crypto");
244
- crypto.randomFillSync(array);
249
+ throw new Error("Secure random generation is not available in this environment");
245
250
  }
246
251
  return this.base64URLEncode(array);
247
252
  }
248
253
  async generateCodeChallenge(verifier) {
249
- if (typeof window !== "undefined" && window.crypto?.subtle) {
250
- const encoder = new TextEncoder();
251
- const data = encoder.encode(verifier);
252
- const hash = await window.crypto.subtle.digest("SHA-256", data);
253
- return this.base64URLEncode(hash);
254
- } else {
255
- const crypto = require("crypto");
256
- const hash = crypto.createHash("sha256").update(verifier).digest();
257
- return this.base64URLEncode(hash);
254
+ const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
255
+ if (!subtle) {
256
+ throw new Error("Web Crypto is required to generate PKCE code challenge");
258
257
  }
258
+ const encoder = new TextEncoder();
259
+ const data = encoder.encode(verifier);
260
+ const hash = await subtle.digest("SHA-256", data);
261
+ return this.base64URLEncode(hash);
259
262
  }
260
263
  buildAuthorizationUrl(codeChallenge, state) {
261
264
  const params = new URLSearchParams({
@@ -457,13 +460,13 @@ var TokenStorage = class {
457
460
  const fs = require("fs").promises;
458
461
  const path = require("path");
459
462
  const os = require("os");
460
- const crypto = require("crypto");
463
+ const crypto2 = require("crypto");
461
464
  const configDir = path.join(os.homedir(), ".lanonasis");
462
465
  const tokenFile = path.join(configDir, "mcp-tokens.enc");
463
466
  await fs.mkdir(configDir, { recursive: true });
464
467
  const key = this.getFileEncryptionKey();
465
- const iv = crypto.randomBytes(16);
466
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
468
+ const iv = crypto2.randomBytes(16);
469
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
467
470
  let encrypted = cipher.update(tokenString, "utf8", "hex");
468
471
  encrypted += cipher.final("hex");
469
472
  const authTag = cipher.getAuthTag().toString("hex");
@@ -475,7 +478,7 @@ var TokenStorage = class {
475
478
  const fs = require("fs").promises;
476
479
  const path = require("path");
477
480
  const os = require("os");
478
- const crypto = require("crypto");
481
+ const crypto2 = require("crypto");
479
482
  const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
480
483
  try {
481
484
  const data = await fs.readFile(tokenFile, "utf8");
@@ -485,7 +488,7 @@ var TokenStorage = class {
485
488
  const [ivHex, authTagHex, encrypted] = parts;
486
489
  const iv = Buffer.from(ivHex, "hex");
487
490
  const authTag = Buffer.from(authTagHex, "hex");
488
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
491
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
489
492
  decipher.setAuthTag(authTag);
490
493
  let decrypted = decipher.update(encrypted, "hex", "utf8");
491
494
  decrypted += decipher.final("utf8");
@@ -494,7 +497,7 @@ var TokenStorage = class {
494
497
  if (parts.length === 2) {
495
498
  const [ivHex, encrypted] = parts;
496
499
  const iv = Buffer.from(ivHex, "hex");
497
- const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
500
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
498
501
  let decrypted = decipher.update(encrypted, "hex", "utf8");
499
502
  decrypted += decipher.final("utf8");
500
503
  return decrypted;
@@ -516,17 +519,17 @@ var TokenStorage = class {
516
519
  }
517
520
  }
518
521
  getFileEncryptionKey() {
519
- const crypto = require("crypto");
522
+ const crypto2 = require("crypto");
520
523
  const os = require("os");
521
524
  const machineId = os.hostname() + os.userInfo().username;
522
525
  const salt = "lanonasis-mcp-oauth-2024";
523
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
526
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
524
527
  }
525
528
  async encrypt(text) {
526
529
  if (typeof window === "undefined" || !window.crypto?.subtle) {
527
530
  const encoder2 = new TextEncoder();
528
531
  const data2 = encoder2.encode(text);
529
- return btoa(String.fromCharCode(...data2));
532
+ return this.base64Encode(data2);
530
533
  }
531
534
  const encoder = new TextEncoder();
532
535
  const data = encoder.encode(text);
@@ -559,23 +562,15 @@ var TokenStorage = class {
559
562
  const combined = new Uint8Array(iv.length + encrypted.byteLength);
560
563
  combined.set(iv, 0);
561
564
  combined.set(new Uint8Array(encrypted), iv.length);
562
- return btoa(String.fromCharCode(...combined));
565
+ return this.base64Encode(combined);
563
566
  }
564
567
  async decrypt(encrypted) {
565
568
  if (typeof window === "undefined" || !window.crypto?.subtle) {
566
- const binary2 = atob(encrypted);
567
- const bytes2 = new Uint8Array(binary2.length);
568
- for (let i = 0; i < binary2.length; i++) {
569
- bytes2[i] = binary2.charCodeAt(i);
570
- }
569
+ const bytes2 = this.base64Decode(encrypted);
571
570
  const decoder2 = new TextDecoder();
572
571
  return decoder2.decode(bytes2);
573
572
  }
574
- const binary = atob(encrypted);
575
- const bytes = new Uint8Array(binary.length);
576
- for (let i = 0; i < binary.length; i++) {
577
- bytes[i] = binary.charCodeAt(i);
578
- }
573
+ const bytes = this.base64Decode(encrypted);
579
574
  const iv = bytes.slice(0, 12);
580
575
  const data = bytes.slice(12);
581
576
  const encoder = new TextEncoder();
@@ -616,6 +611,33 @@ var TokenStorage = class {
616
611
  isMobile() {
617
612
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
618
613
  }
614
+ base64Encode(bytes) {
615
+ if (typeof btoa !== "undefined") {
616
+ let binary = "";
617
+ bytes.forEach((b) => {
618
+ binary += String.fromCharCode(b);
619
+ });
620
+ return btoa(binary);
621
+ }
622
+ if (typeof Buffer !== "undefined") {
623
+ return Buffer.from(bytes).toString("base64");
624
+ }
625
+ throw new Error("No base64 encoder available");
626
+ }
627
+ base64Decode(value) {
628
+ if (typeof atob !== "undefined") {
629
+ const binary = atob(value);
630
+ const bytes = new Uint8Array(binary.length);
631
+ for (let i = 0; i < binary.length; i++) {
632
+ bytes[i] = binary.charCodeAt(i);
633
+ }
634
+ return bytes;
635
+ }
636
+ if (typeof Buffer !== "undefined") {
637
+ return new Uint8Array(Buffer.from(value, "base64"));
638
+ }
639
+ throw new Error("No base64 decoder available");
640
+ }
619
641
  async getWebEncryptionKey() {
620
642
  const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
621
643
  if (existing) {
@@ -627,7 +649,8 @@ var TokenStorage = class {
627
649
  window.crypto.getRandomValues(buf);
628
650
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
629
651
  } else {
630
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
652
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
653
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
631
654
  }
632
655
  if (typeof localStorage !== "undefined") {
633
656
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -839,13 +862,13 @@ var ApiKeyStorage = class {
839
862
  const fs = require("fs").promises;
840
863
  const path = require("path");
841
864
  const os = require("os");
842
- const crypto = require("crypto");
865
+ const crypto2 = require("crypto");
843
866
  const configDir = path.join(os.homedir(), ".lanonasis");
844
867
  const keyFile = path.join(configDir, "api-key.enc");
845
868
  await fs.mkdir(configDir, { recursive: true, mode: 448 });
846
869
  const key = this.getFileEncryptionKey();
847
- const iv = crypto.randomBytes(16);
848
- const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
870
+ const iv = crypto2.randomBytes(16);
871
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
849
872
  let encrypted = cipher.update(keyString, "utf8", "hex");
850
873
  encrypted += cipher.final("hex");
851
874
  const authTag = cipher.getAuthTag();
@@ -857,7 +880,7 @@ var ApiKeyStorage = class {
857
880
  const fs = require("fs").promises;
858
881
  const path = require("path");
859
882
  const os = require("os");
860
- const crypto = require("crypto");
883
+ const crypto2 = require("crypto");
861
884
  const keyFile = path.join(os.homedir(), ".lanonasis", "api-key.enc");
862
885
  try {
863
886
  const data = await fs.readFile(keyFile, "utf8");
@@ -868,7 +891,7 @@ var ApiKeyStorage = class {
868
891
  const key = this.getFileEncryptionKey();
869
892
  const iv = Buffer.from(ivHex, "hex");
870
893
  const authTag = Buffer.from(authTagHex, "hex");
871
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
894
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
872
895
  decipher.setAuthTag(authTag);
873
896
  let decrypted = decipher.update(encrypted, "hex", "utf8");
874
897
  decrypted += decipher.final("utf8");
@@ -912,18 +935,18 @@ var ApiKeyStorage = class {
912
935
  }
913
936
  }
914
937
  getFileEncryptionKey() {
915
- const crypto = require("crypto");
938
+ const crypto2 = require("crypto");
916
939
  const os = require("os");
917
940
  const machineId = os.hostname() + os.userInfo().username;
918
941
  const salt = "lanonasis-mcp-api-key-2024";
919
- return crypto.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
942
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
920
943
  }
921
944
  // ==================== Web Encryption ====================
922
945
  async encrypt(text) {
923
946
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
924
947
  const encoder = new TextEncoder();
925
948
  const data = encoder.encode(text);
926
- return btoa(String.fromCharCode(...data));
949
+ return this.base64Encode(data);
927
950
  }
928
951
  try {
929
952
  const encoder = new TextEncoder();
@@ -962,16 +985,12 @@ var ApiKeyStorage = class {
962
985
  console.error("Web encryption failed:", error);
963
986
  const encoder = new TextEncoder();
964
987
  const data = encoder.encode(text);
965
- return btoa(String.fromCharCode(...data));
988
+ return this.base64Encode(data);
966
989
  }
967
990
  }
968
991
  async decrypt(encrypted) {
969
992
  if (typeof window === "undefined" || !window.crypto || !window.crypto.subtle) {
970
- const binary = atob(encrypted);
971
- const bytes = new Uint8Array(binary.length);
972
- for (let i = 0; i < binary.length; i++) {
973
- bytes[i] = binary.charCodeAt(i);
974
- }
993
+ const bytes = this.base64Decode(encrypted);
975
994
  const decoder = new TextDecoder();
976
995
  return decoder.decode(bytes);
977
996
  }
@@ -1013,11 +1032,7 @@ var ApiKeyStorage = class {
1013
1032
  return decoder.decode(decrypted);
1014
1033
  } catch (error) {
1015
1034
  console.error("Web decryption failed:", error);
1016
- const binary = atob(encrypted);
1017
- const bytes = new Uint8Array(binary.length);
1018
- for (let i = 0; i < binary.length; i++) {
1019
- bytes[i] = binary.charCodeAt(i);
1020
- }
1035
+ const bytes = this.base64Decode(encrypted);
1021
1036
  const decoder = new TextDecoder();
1022
1037
  return decoder.decode(bytes);
1023
1038
  }
@@ -1033,7 +1048,8 @@ var ApiKeyStorage = class {
1033
1048
  window.crypto.getRandomValues(buf);
1034
1049
  raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1035
1050
  } else {
1036
- raw = `${navigator.userAgent}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1051
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
1052
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
1037
1053
  }
1038
1054
  if (typeof localStorage !== "undefined") {
1039
1055
  localStorage.setItem(this.webEncryptionKeyStorage, raw);
@@ -1050,6 +1066,33 @@ var ApiKeyStorage = class {
1050
1066
  isMobile() {
1051
1067
  return typeof window !== "undefined" && window.SecureStorage !== void 0;
1052
1068
  }
1069
+ base64Encode(bytes) {
1070
+ if (typeof btoa !== "undefined") {
1071
+ let binary = "";
1072
+ bytes.forEach((b) => {
1073
+ binary += String.fromCharCode(b);
1074
+ });
1075
+ return btoa(binary);
1076
+ }
1077
+ if (typeof Buffer !== "undefined") {
1078
+ return Buffer.from(bytes).toString("base64");
1079
+ }
1080
+ throw new Error("No base64 encoder available");
1081
+ }
1082
+ base64Decode(value) {
1083
+ if (typeof atob !== "undefined") {
1084
+ const binary = atob(value);
1085
+ const bytes = new Uint8Array(binary.length);
1086
+ for (let i = 0; i < binary.length; i++) {
1087
+ bytes[i] = binary.charCodeAt(i);
1088
+ }
1089
+ return bytes;
1090
+ }
1091
+ if (typeof Buffer !== "undefined") {
1092
+ return new Uint8Array(Buffer.from(value, "base64"));
1093
+ }
1094
+ throw new Error("No base64 decoder available");
1095
+ }
1053
1096
  /**
1054
1097
  * Normalize API keys to a SHA-256 hex digest.
1055
1098
  * Accepts pre-hashed input and lowercases it to prevent double hashing.
@@ -1074,7 +1117,279 @@ var ApiKeyStorage = class {
1074
1117
  }
1075
1118
  };
1076
1119
 
1120
+ // src/storage/token-storage-web.ts
1121
+ var TokenStorageWeb = class {
1122
+ constructor() {
1123
+ this.storageKey = "lanonasis_mcp_tokens";
1124
+ this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
1125
+ }
1126
+ async store(tokens) {
1127
+ const tokensWithTimestamp = {
1128
+ ...tokens,
1129
+ issued_at: Date.now()
1130
+ };
1131
+ const tokenString = JSON.stringify(tokensWithTimestamp);
1132
+ const encrypted = await this.encrypt(tokenString);
1133
+ localStorage.setItem(this.storageKey, encrypted);
1134
+ }
1135
+ async retrieve() {
1136
+ const encrypted = localStorage.getItem(this.storageKey);
1137
+ if (!encrypted) return null;
1138
+ try {
1139
+ const tokenString = await this.decrypt(encrypted);
1140
+ return JSON.parse(tokenString);
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1145
+ async clear() {
1146
+ localStorage.removeItem(this.storageKey);
1147
+ }
1148
+ isTokenExpired(tokens) {
1149
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) return false;
1150
+ if (!tokens.expires_in) return false;
1151
+ if (!tokens.issued_at) return true;
1152
+ const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
1153
+ return expiresAt - Date.now() < 3e5;
1154
+ }
1155
+ async encrypt(text) {
1156
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1157
+ const encoder2 = new TextEncoder();
1158
+ return this.base64Encode(encoder2.encode(text));
1159
+ }
1160
+ const encoder = new TextEncoder();
1161
+ const data = encoder.encode(text);
1162
+ const passphrase = await this.getWebEncryptionKey();
1163
+ const keyMaterial = await window.crypto.subtle.importKey(
1164
+ "raw",
1165
+ encoder.encode(passphrase),
1166
+ "PBKDF2",
1167
+ false,
1168
+ ["deriveBits", "deriveKey"]
1169
+ );
1170
+ const key = await window.crypto.subtle.deriveKey(
1171
+ {
1172
+ name: "PBKDF2",
1173
+ salt: encoder.encode("lanonasis-token-salt"),
1174
+ iterations: 1e5,
1175
+ hash: "SHA-256"
1176
+ },
1177
+ keyMaterial,
1178
+ { name: "AES-GCM", length: 256 },
1179
+ true,
1180
+ ["encrypt", "decrypt"]
1181
+ );
1182
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
1183
+ const encrypted = await window.crypto.subtle.encrypt(
1184
+ { name: "AES-GCM", iv },
1185
+ key,
1186
+ data
1187
+ );
1188
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
1189
+ combined.set(iv, 0);
1190
+ combined.set(new Uint8Array(encrypted), iv.length);
1191
+ return this.base64Encode(combined);
1192
+ }
1193
+ async decrypt(encrypted) {
1194
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1195
+ const decoder2 = new TextDecoder();
1196
+ return decoder2.decode(this.base64Decode(encrypted));
1197
+ }
1198
+ const bytes = this.base64Decode(encrypted);
1199
+ const iv = bytes.slice(0, 12);
1200
+ const data = bytes.slice(12);
1201
+ const encoder = new TextEncoder();
1202
+ const decoder = new TextDecoder();
1203
+ const passphrase = await this.getWebEncryptionKey();
1204
+ const keyMaterial = await window.crypto.subtle.importKey(
1205
+ "raw",
1206
+ encoder.encode(passphrase),
1207
+ "PBKDF2",
1208
+ false,
1209
+ ["deriveBits", "deriveKey"]
1210
+ );
1211
+ const key = await window.crypto.subtle.deriveKey(
1212
+ {
1213
+ name: "PBKDF2",
1214
+ salt: encoder.encode("lanonasis-token-salt"),
1215
+ iterations: 1e5,
1216
+ hash: "SHA-256"
1217
+ },
1218
+ keyMaterial,
1219
+ { name: "AES-GCM", length: 256 },
1220
+ true,
1221
+ ["encrypt", "decrypt"]
1222
+ );
1223
+ const decrypted = await window.crypto.subtle.decrypt(
1224
+ { name: "AES-GCM", iv },
1225
+ key,
1226
+ data
1227
+ );
1228
+ return decoder.decode(decrypted);
1229
+ }
1230
+ async getWebEncryptionKey() {
1231
+ const existing = localStorage.getItem(this.webEncryptionKeyStorage);
1232
+ if (existing) return existing;
1233
+ const buf = new Uint8Array(32);
1234
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
1235
+ window.crypto.getRandomValues(buf);
1236
+ }
1237
+ const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1238
+ localStorage.setItem(this.webEncryptionKeyStorage, raw);
1239
+ return raw;
1240
+ }
1241
+ base64Encode(bytes) {
1242
+ let binary = "";
1243
+ bytes.forEach((b) => {
1244
+ binary += String.fromCharCode(b);
1245
+ });
1246
+ return btoa(binary);
1247
+ }
1248
+ base64Decode(value) {
1249
+ const binary = atob(value);
1250
+ const bytes = new Uint8Array(binary.length);
1251
+ for (let i = 0; i < binary.length; i++) {
1252
+ bytes[i] = binary.charCodeAt(i);
1253
+ }
1254
+ return bytes;
1255
+ }
1256
+ };
1257
+
1258
+ // src/storage/api-key-storage-web.ts
1259
+ var ApiKeyStorageWeb = class {
1260
+ constructor() {
1261
+ this.storageKey = "lanonasis_api_key";
1262
+ this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
1263
+ }
1264
+ async store(data) {
1265
+ const payload = JSON.stringify({
1266
+ ...data,
1267
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString()
1268
+ });
1269
+ const encrypted = await this.encrypt(payload);
1270
+ localStorage.setItem(this.storageKey, encrypted);
1271
+ }
1272
+ async retrieve() {
1273
+ const encrypted = localStorage.getItem(this.storageKey);
1274
+ if (!encrypted) return null;
1275
+ try {
1276
+ const decrypted = await this.decrypt(encrypted);
1277
+ return JSON.parse(decrypted);
1278
+ } catch {
1279
+ return null;
1280
+ }
1281
+ }
1282
+ async clear() {
1283
+ localStorage.removeItem(this.storageKey);
1284
+ }
1285
+ async encrypt(text) {
1286
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1287
+ const encoder2 = new TextEncoder();
1288
+ return this.base64Encode(encoder2.encode(text));
1289
+ }
1290
+ const encoder = new TextEncoder();
1291
+ const data = encoder.encode(text);
1292
+ const passphrase = await this.getWebEncryptionKey();
1293
+ const keyMaterial = await window.crypto.subtle.importKey(
1294
+ "raw",
1295
+ encoder.encode(passphrase),
1296
+ "PBKDF2",
1297
+ false,
1298
+ ["deriveBits", "deriveKey"]
1299
+ );
1300
+ const key = await window.crypto.subtle.deriveKey(
1301
+ {
1302
+ name: "PBKDF2",
1303
+ salt: encoder.encode("lanonasis-api-key-salt"),
1304
+ iterations: 1e5,
1305
+ hash: "SHA-256"
1306
+ },
1307
+ keyMaterial,
1308
+ { name: "AES-GCM", length: 256 },
1309
+ true,
1310
+ ["encrypt", "decrypt"]
1311
+ );
1312
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
1313
+ const encrypted = await window.crypto.subtle.encrypt(
1314
+ { name: "AES-GCM", iv },
1315
+ key,
1316
+ data
1317
+ );
1318
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
1319
+ combined.set(iv, 0);
1320
+ combined.set(new Uint8Array(encrypted), iv.length);
1321
+ return this.base64Encode(combined);
1322
+ }
1323
+ async decrypt(encrypted) {
1324
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1325
+ const decoder2 = new TextDecoder();
1326
+ return decoder2.decode(this.base64Decode(encrypted));
1327
+ }
1328
+ const bytes = this.base64Decode(encrypted);
1329
+ const iv = bytes.slice(0, 12);
1330
+ const data = bytes.slice(12);
1331
+ const encoder = new TextEncoder();
1332
+ const decoder = new TextDecoder();
1333
+ const passphrase = await this.getWebEncryptionKey();
1334
+ const keyMaterial = await window.crypto.subtle.importKey(
1335
+ "raw",
1336
+ encoder.encode(passphrase),
1337
+ "PBKDF2",
1338
+ false,
1339
+ ["deriveBits", "deriveKey"]
1340
+ );
1341
+ const key = await window.crypto.subtle.deriveKey(
1342
+ {
1343
+ name: "PBKDF2",
1344
+ salt: encoder.encode("lanonasis-api-key-salt"),
1345
+ iterations: 1e5,
1346
+ hash: "SHA-256"
1347
+ },
1348
+ keyMaterial,
1349
+ { name: "AES-GCM", length: 256 },
1350
+ true,
1351
+ ["encrypt", "decrypt"]
1352
+ );
1353
+ const decrypted = await window.crypto.subtle.decrypt(
1354
+ { name: "AES-GCM", iv },
1355
+ key,
1356
+ data
1357
+ );
1358
+ return decoder.decode(decrypted);
1359
+ }
1360
+ async getWebEncryptionKey() {
1361
+ const existing = localStorage.getItem(this.webEncryptionKeyStorage);
1362
+ if (existing) return existing;
1363
+ const buf = new Uint8Array(32);
1364
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
1365
+ window.crypto.getRandomValues(buf);
1366
+ }
1367
+ const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1368
+ localStorage.setItem(this.webEncryptionKeyStorage, raw);
1369
+ return raw;
1370
+ }
1371
+ base64Encode(bytes) {
1372
+ let binary = "";
1373
+ bytes.forEach((b) => {
1374
+ binary += String.fromCharCode(b);
1375
+ });
1376
+ return btoa(binary);
1377
+ }
1378
+ base64Decode(value) {
1379
+ const binary = atob(value);
1380
+ const bytes = new Uint8Array(binary.length);
1381
+ for (let i = 0; i < binary.length; i++) {
1382
+ bytes[i] = binary.charCodeAt(i);
1383
+ }
1384
+ return bytes;
1385
+ }
1386
+ };
1387
+
1388
+ // src/client/mcp-client.ts
1389
+ var import_cross_fetch4 = __toESM(require("cross-fetch"), 1);
1390
+
1077
1391
  // src/flows/apikey-flow.ts
1392
+ var import_cross_fetch3 = __toESM(require("cross-fetch"), 1);
1078
1393
  var APIKeyFlow = class extends BaseOAuthFlow {
1079
1394
  constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
1080
1395
  super({
@@ -1117,7 +1432,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
1117
1432
  */
1118
1433
  async validateAPIKey() {
1119
1434
  try {
1120
- const response = await fetch(`${this.config.authBaseUrl}/api/v1/health`, {
1435
+ const response = await (0, import_cross_fetch3.default)(`${this.config.authBaseUrl}/api/v1/health`, {
1121
1436
  headers: {
1122
1437
  "x-api-key": this.apiKey
1123
1438
  }
@@ -1143,7 +1458,8 @@ var MCPClient = class {
1143
1458
  autoRefresh: true,
1144
1459
  ...config
1145
1460
  };
1146
- this.tokenStorage = new TokenStorage();
1461
+ const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
1462
+ this.tokenStorage = config.tokenStorage || defaultStorage;
1147
1463
  this.authMode = config.apiKey ? "apikey" : "oauth";
1148
1464
  if (this.authMode === "apikey") {
1149
1465
  this.authFlow = new APIKeyFlow(
@@ -1358,7 +1674,7 @@ var MCPClient = class {
1358
1674
  } else {
1359
1675
  headers["Authorization"] = `Bearer ${this.accessToken}`;
1360
1676
  }
1361
- const response = await fetch(`${this.config.mcpEndpoint}/api`, {
1677
+ const response = await (0, import_cross_fetch4.default)(`${this.config.mcpEndpoint}/api`, {
1362
1678
  method: "POST",
1363
1679
  headers,
1364
1680
  body: JSON.stringify({
@@ -1457,12 +1773,3 @@ var MCPClient = class {
1457
1773
  return this.request("memory/delete", { id });
1458
1774
  }
1459
1775
  };
1460
- // Annotate the CommonJS export names for ESM import in node:
1461
- 0 && (module.exports = {
1462
- ApiKeyStorage,
1463
- BaseOAuthFlow,
1464
- DesktopOAuthFlow,
1465
- MCPClient,
1466
- TerminalOAuthFlow,
1467
- TokenStorage
1468
- });