@lanonasis/oauth-client 1.2.1 → 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.
@@ -0,0 +1,1286 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/flows/base-flow.ts
9
+ import fetch from "cross-fetch";
10
+ var BaseOAuthFlow = class {
11
+ constructor(config) {
12
+ this.clientId = config.clientId;
13
+ this.authBaseUrl = config.authBaseUrl || "https://auth.lanonasis.com";
14
+ this.scope = config.scope || "memories:read memories:write memories:delete profile";
15
+ }
16
+ async makeTokenRequest(body) {
17
+ const response = await fetch(`${this.authBaseUrl}/oauth/token`, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify(body)
21
+ });
22
+ const data = await response.json();
23
+ if (!response.ok) {
24
+ throw new Error(data.error_description || "Token request failed");
25
+ }
26
+ return data;
27
+ }
28
+ generateState() {
29
+ const array = new Uint8Array(32);
30
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
31
+ crypto.getRandomValues(array);
32
+ } else {
33
+ throw new Error("Secure random generation is not available");
34
+ }
35
+ return this.base64URLEncode(array);
36
+ }
37
+ base64URLEncode(buffer) {
38
+ const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
39
+ let binary = "";
40
+ bytes.forEach((byte) => {
41
+ binary += String.fromCharCode(byte);
42
+ });
43
+ const base64 = typeof btoa !== "undefined" ? btoa(binary) : Buffer.from(bytes).toString("base64");
44
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
45
+ }
46
+ async refreshToken(refreshToken) {
47
+ return this.makeTokenRequest({
48
+ grant_type: "refresh_token",
49
+ refresh_token: refreshToken,
50
+ client_id: this.clientId
51
+ });
52
+ }
53
+ async revokeToken(token, tokenType = "access_token") {
54
+ const response = await fetch(`${this.authBaseUrl}/oauth/revoke`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({
58
+ token,
59
+ token_type_hint: tokenType,
60
+ client_id: this.clientId
61
+ })
62
+ });
63
+ if (!response.ok) {
64
+ throw new Error("Failed to revoke token");
65
+ }
66
+ }
67
+ };
68
+
69
+ // src/flows/desktop-flow.ts
70
+ var DesktopOAuthFlow = class extends BaseOAuthFlow {
71
+ constructor(config) {
72
+ super({
73
+ ...config,
74
+ clientId: config.clientId || "lanonasis-mcp-desktop"
75
+ });
76
+ this.authWindow = null;
77
+ this.redirectUri = config.redirectUri || "lanonasis://oauth/callback";
78
+ }
79
+ async authenticate() {
80
+ const pkce = await this.generatePKCEChallenge();
81
+ const state = this.generateState();
82
+ const authUrl = this.buildAuthorizationUrl(pkce.codeChallenge, state);
83
+ const authCode = await this.openAuthWindow(authUrl, state);
84
+ return await this.exchangeCodeForToken(authCode, pkce.codeVerifier);
85
+ }
86
+ async generatePKCEChallenge() {
87
+ const codeVerifier = this.generateCodeVerifier();
88
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier);
89
+ return { codeVerifier, codeChallenge };
90
+ }
91
+ generateCodeVerifier() {
92
+ const array = new Uint8Array(32);
93
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
94
+ crypto.getRandomValues(array);
95
+ } else {
96
+ throw new Error("Secure random generation is not available in this environment");
97
+ }
98
+ return this.base64URLEncode(array);
99
+ }
100
+ async generateCodeChallenge(verifier) {
101
+ const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
102
+ if (!subtle) {
103
+ throw new Error("Web Crypto is required to generate PKCE code challenge");
104
+ }
105
+ const encoder = new TextEncoder();
106
+ const data = encoder.encode(verifier);
107
+ const hash = await subtle.digest("SHA-256", data);
108
+ return this.base64URLEncode(hash);
109
+ }
110
+ buildAuthorizationUrl(codeChallenge, state) {
111
+ const params = new URLSearchParams({
112
+ client_id: this.clientId,
113
+ response_type: "code",
114
+ redirect_uri: this.redirectUri,
115
+ scope: this.scope,
116
+ code_challenge: codeChallenge,
117
+ code_challenge_method: "S256",
118
+ state
119
+ });
120
+ return `${this.authBaseUrl}/oauth/authorize?${params}`;
121
+ }
122
+ async openAuthWindow(authUrl, expectedState) {
123
+ return new Promise((resolve, reject) => {
124
+ if (typeof window !== "undefined") {
125
+ this.openBrowserWindow(authUrl, expectedState, resolve, reject);
126
+ } else {
127
+ this.openElectronWindow(authUrl, expectedState, resolve, reject);
128
+ }
129
+ });
130
+ }
131
+ openBrowserWindow(authUrl, expectedState, resolve, reject) {
132
+ const width = 500;
133
+ const height = 700;
134
+ const left = (window.screen.width - width) / 2;
135
+ const top = (window.screen.height - height) / 2;
136
+ this.authWindow = window.open(
137
+ authUrl,
138
+ "Lan Onasis Login",
139
+ `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
140
+ );
141
+ if (!this.authWindow) {
142
+ reject(new Error("Failed to open authentication window"));
143
+ return;
144
+ }
145
+ const checkInterval = setInterval(() => {
146
+ try {
147
+ if (!this.authWindow || this.authWindow.closed) {
148
+ clearInterval(checkInterval);
149
+ reject(new Error("Authentication window was closed"));
150
+ return;
151
+ }
152
+ const currentUrl = this.authWindow.location.href;
153
+ if (currentUrl.startsWith(this.redirectUri)) {
154
+ clearInterval(checkInterval);
155
+ this.authWindow.close();
156
+ const url = new URL(currentUrl);
157
+ const code = url.searchParams.get("code");
158
+ const state = url.searchParams.get("state");
159
+ const error = url.searchParams.get("error");
160
+ if (error) {
161
+ reject(new Error(url.searchParams.get("error_description") || error));
162
+ } else if (state !== expectedState) {
163
+ reject(new Error("State mismatch - possible CSRF attack"));
164
+ } else if (code) {
165
+ resolve(code);
166
+ } else {
167
+ reject(new Error("No authorization code received"));
168
+ }
169
+ }
170
+ } catch (e) {
171
+ }
172
+ }, 500);
173
+ }
174
+ openElectronWindow(authUrl, expectedState, resolve, reject) {
175
+ const { BrowserWindow } = __require("electron");
176
+ const authWindow = new BrowserWindow({
177
+ width: 500,
178
+ height: 700,
179
+ webPreferences: {
180
+ nodeIntegration: false,
181
+ contextIsolation: true
182
+ }
183
+ });
184
+ authWindow.loadURL(authUrl);
185
+ authWindow.webContents.on("will-redirect", (event, url) => {
186
+ if (url.startsWith(this.redirectUri)) {
187
+ event.preventDefault();
188
+ authWindow.close();
189
+ const callbackUrl = new URL(url);
190
+ const code = callbackUrl.searchParams.get("code");
191
+ const state = callbackUrl.searchParams.get("state");
192
+ const error = callbackUrl.searchParams.get("error");
193
+ if (error) {
194
+ reject(new Error(callbackUrl.searchParams.get("error_description") || error));
195
+ } else if (state !== expectedState) {
196
+ reject(new Error("State mismatch"));
197
+ } else if (code) {
198
+ resolve(code);
199
+ }
200
+ }
201
+ });
202
+ authWindow.on("closed", () => {
203
+ reject(new Error("Authentication window was closed"));
204
+ });
205
+ }
206
+ async exchangeCodeForToken(code, codeVerifier) {
207
+ return this.makeTokenRequest({
208
+ grant_type: "authorization_code",
209
+ code,
210
+ client_id: this.clientId,
211
+ redirect_uri: this.redirectUri,
212
+ code_verifier: codeVerifier
213
+ });
214
+ }
215
+ };
216
+
217
+ // src/client/mcp-client.ts
218
+ import fetch4 from "cross-fetch";
219
+
220
+ // src/storage/token-storage.ts
221
+ var TokenStorage = class {
222
+ constructor() {
223
+ this.storageKey = "lanonasis_mcp_tokens";
224
+ this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
225
+ if (this.isNode()) {
226
+ try {
227
+ this.keytar = __require("keytar");
228
+ } catch (e) {
229
+ console.warn("Keytar not available - falling back to file storage");
230
+ }
231
+ }
232
+ }
233
+ async store(tokens) {
234
+ const tokensWithTimestamp = {
235
+ ...tokens,
236
+ issued_at: Date.now()
237
+ };
238
+ const tokenString = JSON.stringify(tokensWithTimestamp);
239
+ if (this.isNode()) {
240
+ if (this.keytar) {
241
+ await this.keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
242
+ } else {
243
+ await this.storeToFile(tokenString);
244
+ }
245
+ } else if (this.isElectron()) {
246
+ await window.electronAPI.secureStore.set(this.storageKey, tokensWithTimestamp);
247
+ } else if (this.isMobile()) {
248
+ await window.SecureStorage.set(this.storageKey, tokenString);
249
+ } else {
250
+ const encrypted = await this.encrypt(tokenString);
251
+ localStorage.setItem(this.storageKey, encrypted);
252
+ }
253
+ }
254
+ async retrieve() {
255
+ let tokenString = null;
256
+ try {
257
+ if (this.isNode()) {
258
+ if (this.keytar) {
259
+ tokenString = await this.keytar.getPassword("lanonasis-mcp", "tokens");
260
+ }
261
+ if (!tokenString) {
262
+ tokenString = await this.retrieveFromFile();
263
+ }
264
+ } else if (this.isElectron()) {
265
+ const tokens = await window.electronAPI.secureStore.get(this.storageKey);
266
+ return tokens || null;
267
+ } else if (this.isMobile()) {
268
+ tokenString = await window.SecureStorage.get(this.storageKey);
269
+ } else {
270
+ const encrypted = localStorage.getItem(this.storageKey);
271
+ if (encrypted) {
272
+ tokenString = await this.decrypt(encrypted);
273
+ }
274
+ }
275
+ return tokenString ? JSON.parse(tokenString) : null;
276
+ } catch (error) {
277
+ console.error("Error retrieving tokens:", error);
278
+ return null;
279
+ }
280
+ }
281
+ async clear() {
282
+ if (this.isNode()) {
283
+ if (this.keytar) {
284
+ await this.keytar.deletePassword("lanonasis-mcp", "tokens");
285
+ }
286
+ await this.deleteFile();
287
+ } else if (this.isElectron()) {
288
+ await window.electronAPI.secureStore.delete(this.storageKey);
289
+ } else if (this.isMobile()) {
290
+ await window.SecureStorage.remove(this.storageKey);
291
+ } else {
292
+ localStorage.removeItem(this.storageKey);
293
+ }
294
+ }
295
+ isTokenExpired(tokens) {
296
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
297
+ return false;
298
+ }
299
+ if (!tokens.expires_in) return false;
300
+ if (!tokens.issued_at) {
301
+ console.warn("Token missing issued_at timestamp, treating as expired");
302
+ return true;
303
+ }
304
+ const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
305
+ const now = Date.now();
306
+ return expiresAt - now < 3e5;
307
+ }
308
+ async storeToFile(tokenString) {
309
+ if (!this.isNode()) return;
310
+ const fs = __require("fs").promises;
311
+ const path = __require("path");
312
+ const os = __require("os");
313
+ const crypto2 = __require("crypto");
314
+ const configDir = path.join(os.homedir(), ".lanonasis");
315
+ const tokenFile = path.join(configDir, "mcp-tokens.enc");
316
+ await fs.mkdir(configDir, { recursive: true });
317
+ const key = this.getFileEncryptionKey();
318
+ const iv = crypto2.randomBytes(16);
319
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
320
+ let encrypted = cipher.update(tokenString, "utf8", "hex");
321
+ encrypted += cipher.final("hex");
322
+ const authTag = cipher.getAuthTag().toString("hex");
323
+ const data = [iv.toString("hex"), authTag, encrypted].join(":");
324
+ await fs.writeFile(tokenFile, data, { mode: 384 });
325
+ }
326
+ async retrieveFromFile() {
327
+ if (!this.isNode()) return null;
328
+ const fs = __require("fs").promises;
329
+ const path = __require("path");
330
+ const os = __require("os");
331
+ const crypto2 = __require("crypto");
332
+ const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
333
+ try {
334
+ const data = await fs.readFile(tokenFile, "utf8");
335
+ const parts = data.split(":");
336
+ const key = this.getFileEncryptionKey();
337
+ if (parts.length === 3) {
338
+ const [ivHex, authTagHex, encrypted] = parts;
339
+ const iv = Buffer.from(ivHex, "hex");
340
+ const authTag = Buffer.from(authTagHex, "hex");
341
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
342
+ decipher.setAuthTag(authTag);
343
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
344
+ decrypted += decipher.final("utf8");
345
+ return decrypted;
346
+ }
347
+ if (parts.length === 2) {
348
+ const [ivHex, encrypted] = parts;
349
+ const iv = Buffer.from(ivHex, "hex");
350
+ const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
351
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
352
+ decrypted += decipher.final("utf8");
353
+ return decrypted;
354
+ }
355
+ throw new Error("Invalid encrypted token format");
356
+ } catch (error) {
357
+ return null;
358
+ }
359
+ }
360
+ async deleteFile() {
361
+ if (!this.isNode()) return;
362
+ const fs = __require("fs").promises;
363
+ const path = __require("path");
364
+ const os = __require("os");
365
+ const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
366
+ try {
367
+ await fs.unlink(tokenFile);
368
+ } catch (error) {
369
+ }
370
+ }
371
+ getFileEncryptionKey() {
372
+ const crypto2 = __require("crypto");
373
+ const os = __require("os");
374
+ const machineId = os.hostname() + os.userInfo().username;
375
+ const salt = "lanonasis-mcp-oauth-2024";
376
+ return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
377
+ }
378
+ async encrypt(text) {
379
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
380
+ const encoder2 = new TextEncoder();
381
+ const data2 = encoder2.encode(text);
382
+ return this.base64Encode(data2);
383
+ }
384
+ const encoder = new TextEncoder();
385
+ const data = encoder.encode(text);
386
+ const passphrase = await this.getWebEncryptionKey();
387
+ const keyMaterial = await window.crypto.subtle.importKey(
388
+ "raw",
389
+ encoder.encode(passphrase),
390
+ "PBKDF2",
391
+ false,
392
+ ["deriveBits", "deriveKey"]
393
+ );
394
+ const key = await window.crypto.subtle.deriveKey(
395
+ {
396
+ name: "PBKDF2",
397
+ salt: encoder.encode("lanonasis-token-salt"),
398
+ iterations: 1e5,
399
+ hash: "SHA-256"
400
+ },
401
+ keyMaterial,
402
+ { name: "AES-GCM", length: 256 },
403
+ true,
404
+ ["encrypt", "decrypt"]
405
+ );
406
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
407
+ const encrypted = await window.crypto.subtle.encrypt(
408
+ { name: "AES-GCM", iv },
409
+ key,
410
+ data
411
+ );
412
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
413
+ combined.set(iv, 0);
414
+ combined.set(new Uint8Array(encrypted), iv.length);
415
+ return this.base64Encode(combined);
416
+ }
417
+ async decrypt(encrypted) {
418
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
419
+ const bytes2 = this.base64Decode(encrypted);
420
+ const decoder2 = new TextDecoder();
421
+ return decoder2.decode(bytes2);
422
+ }
423
+ const bytes = this.base64Decode(encrypted);
424
+ const iv = bytes.slice(0, 12);
425
+ const data = bytes.slice(12);
426
+ const encoder = new TextEncoder();
427
+ const decoder = new TextDecoder();
428
+ const passphrase = await this.getWebEncryptionKey();
429
+ const keyMaterial = await window.crypto.subtle.importKey(
430
+ "raw",
431
+ encoder.encode(passphrase),
432
+ "PBKDF2",
433
+ false,
434
+ ["deriveBits", "deriveKey"]
435
+ );
436
+ const key = await window.crypto.subtle.deriveKey(
437
+ {
438
+ name: "PBKDF2",
439
+ salt: encoder.encode("lanonasis-token-salt"),
440
+ iterations: 1e5,
441
+ hash: "SHA-256"
442
+ },
443
+ keyMaterial,
444
+ { name: "AES-GCM", length: 256 },
445
+ true,
446
+ ["encrypt", "decrypt"]
447
+ );
448
+ const decrypted = await window.crypto.subtle.decrypt(
449
+ { name: "AES-GCM", iv },
450
+ key,
451
+ data
452
+ );
453
+ return decoder.decode(decrypted);
454
+ }
455
+ isNode() {
456
+ return !!(typeof process !== "undefined" && process.versions && process.versions.node && !this.isElectron());
457
+ }
458
+ isElectron() {
459
+ return typeof window !== "undefined" && window.electronAPI !== void 0;
460
+ }
461
+ isMobile() {
462
+ return typeof window !== "undefined" && window.SecureStorage !== void 0;
463
+ }
464
+ base64Encode(bytes) {
465
+ if (typeof btoa !== "undefined") {
466
+ let binary = "";
467
+ bytes.forEach((b) => {
468
+ binary += String.fromCharCode(b);
469
+ });
470
+ return btoa(binary);
471
+ }
472
+ if (typeof Buffer !== "undefined") {
473
+ return Buffer.from(bytes).toString("base64");
474
+ }
475
+ throw new Error("No base64 encoder available");
476
+ }
477
+ base64Decode(value) {
478
+ if (typeof atob !== "undefined") {
479
+ const binary = atob(value);
480
+ const bytes = new Uint8Array(binary.length);
481
+ for (let i = 0; i < binary.length; i++) {
482
+ bytes[i] = binary.charCodeAt(i);
483
+ }
484
+ return bytes;
485
+ }
486
+ if (typeof Buffer !== "undefined") {
487
+ return new Uint8Array(Buffer.from(value, "base64"));
488
+ }
489
+ throw new Error("No base64 decoder available");
490
+ }
491
+ async getWebEncryptionKey() {
492
+ const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
493
+ if (existing) {
494
+ return existing;
495
+ }
496
+ let raw = "";
497
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
498
+ const buf = new Uint8Array(32);
499
+ window.crypto.getRandomValues(buf);
500
+ raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
501
+ } else {
502
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
503
+ raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
504
+ }
505
+ if (typeof localStorage !== "undefined") {
506
+ localStorage.setItem(this.webEncryptionKeyStorage, raw);
507
+ }
508
+ return raw;
509
+ }
510
+ };
511
+
512
+ // src/storage/token-storage-web.ts
513
+ var TokenStorageWeb = class {
514
+ constructor() {
515
+ this.storageKey = "lanonasis_mcp_tokens";
516
+ this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
517
+ }
518
+ async store(tokens) {
519
+ const tokensWithTimestamp = {
520
+ ...tokens,
521
+ issued_at: Date.now()
522
+ };
523
+ const tokenString = JSON.stringify(tokensWithTimestamp);
524
+ const encrypted = await this.encrypt(tokenString);
525
+ localStorage.setItem(this.storageKey, encrypted);
526
+ }
527
+ async retrieve() {
528
+ const encrypted = localStorage.getItem(this.storageKey);
529
+ if (!encrypted) return null;
530
+ try {
531
+ const tokenString = await this.decrypt(encrypted);
532
+ return JSON.parse(tokenString);
533
+ } catch {
534
+ return null;
535
+ }
536
+ }
537
+ async clear() {
538
+ localStorage.removeItem(this.storageKey);
539
+ }
540
+ isTokenExpired(tokens) {
541
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) return false;
542
+ if (!tokens.expires_in) return false;
543
+ if (!tokens.issued_at) return true;
544
+ const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
545
+ return expiresAt - Date.now() < 3e5;
546
+ }
547
+ async encrypt(text) {
548
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
549
+ const encoder2 = new TextEncoder();
550
+ return this.base64Encode(encoder2.encode(text));
551
+ }
552
+ const encoder = new TextEncoder();
553
+ const data = encoder.encode(text);
554
+ const passphrase = await this.getWebEncryptionKey();
555
+ const keyMaterial = await window.crypto.subtle.importKey(
556
+ "raw",
557
+ encoder.encode(passphrase),
558
+ "PBKDF2",
559
+ false,
560
+ ["deriveBits", "deriveKey"]
561
+ );
562
+ const key = await window.crypto.subtle.deriveKey(
563
+ {
564
+ name: "PBKDF2",
565
+ salt: encoder.encode("lanonasis-token-salt"),
566
+ iterations: 1e5,
567
+ hash: "SHA-256"
568
+ },
569
+ keyMaterial,
570
+ { name: "AES-GCM", length: 256 },
571
+ true,
572
+ ["encrypt", "decrypt"]
573
+ );
574
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
575
+ const encrypted = await window.crypto.subtle.encrypt(
576
+ { name: "AES-GCM", iv },
577
+ key,
578
+ data
579
+ );
580
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
581
+ combined.set(iv, 0);
582
+ combined.set(new Uint8Array(encrypted), iv.length);
583
+ return this.base64Encode(combined);
584
+ }
585
+ async decrypt(encrypted) {
586
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
587
+ const decoder2 = new TextDecoder();
588
+ return decoder2.decode(this.base64Decode(encrypted));
589
+ }
590
+ const bytes = this.base64Decode(encrypted);
591
+ const iv = bytes.slice(0, 12);
592
+ const data = bytes.slice(12);
593
+ const encoder = new TextEncoder();
594
+ const decoder = new TextDecoder();
595
+ const passphrase = await this.getWebEncryptionKey();
596
+ const keyMaterial = await window.crypto.subtle.importKey(
597
+ "raw",
598
+ encoder.encode(passphrase),
599
+ "PBKDF2",
600
+ false,
601
+ ["deriveBits", "deriveKey"]
602
+ );
603
+ const key = await window.crypto.subtle.deriveKey(
604
+ {
605
+ name: "PBKDF2",
606
+ salt: encoder.encode("lanonasis-token-salt"),
607
+ iterations: 1e5,
608
+ hash: "SHA-256"
609
+ },
610
+ keyMaterial,
611
+ { name: "AES-GCM", length: 256 },
612
+ true,
613
+ ["encrypt", "decrypt"]
614
+ );
615
+ const decrypted = await window.crypto.subtle.decrypt(
616
+ { name: "AES-GCM", iv },
617
+ key,
618
+ data
619
+ );
620
+ return decoder.decode(decrypted);
621
+ }
622
+ async getWebEncryptionKey() {
623
+ const existing = localStorage.getItem(this.webEncryptionKeyStorage);
624
+ if (existing) return existing;
625
+ const buf = new Uint8Array(32);
626
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
627
+ window.crypto.getRandomValues(buf);
628
+ }
629
+ const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
630
+ localStorage.setItem(this.webEncryptionKeyStorage, raw);
631
+ return raw;
632
+ }
633
+ base64Encode(bytes) {
634
+ let binary = "";
635
+ bytes.forEach((b) => {
636
+ binary += String.fromCharCode(b);
637
+ });
638
+ return btoa(binary);
639
+ }
640
+ base64Decode(value) {
641
+ const binary = atob(value);
642
+ const bytes = new Uint8Array(binary.length);
643
+ for (let i = 0; i < binary.length; i++) {
644
+ bytes[i] = binary.charCodeAt(i);
645
+ }
646
+ return bytes;
647
+ }
648
+ };
649
+
650
+ // src/flows/terminal-flow.ts
651
+ import fetch2 from "cross-fetch";
652
+ var TerminalOAuthFlow = class extends BaseOAuthFlow {
653
+ constructor(config) {
654
+ super({
655
+ ...config,
656
+ clientId: config.clientId || "lanonasis-mcp-cli"
657
+ });
658
+ this.pollInterval = 5;
659
+ }
660
+ async authenticate() {
661
+ try {
662
+ const deviceResponse = await this.requestDeviceCode();
663
+ this.displayInstructions(deviceResponse);
664
+ if (deviceResponse.verification_uri_complete) {
665
+ await this.openBrowser(deviceResponse.verification_uri_complete);
666
+ }
667
+ return await this.pollForToken(deviceResponse);
668
+ } catch (error) {
669
+ console.error("Authentication failed:", error);
670
+ throw error;
671
+ }
672
+ }
673
+ async requestDeviceCode() {
674
+ const response = await fetch2(`${this.authBaseUrl}/oauth/device`, {
675
+ method: "POST",
676
+ headers: { "Content-Type": "application/json" },
677
+ body: JSON.stringify({
678
+ client_id: this.clientId,
679
+ scope: this.scope
680
+ })
681
+ });
682
+ if (!response.ok) {
683
+ const error = await response.json();
684
+ throw new Error(error.error_description || "Failed to request device code");
685
+ }
686
+ const data = await response.json();
687
+ this.pollInterval = data.interval || 5;
688
+ return data;
689
+ }
690
+ displayInstructions(response) {
691
+ console.log("\n\u{1F510} Lan Onasis Authentication Required\n");
692
+ console.log(`Please visit: ${response.verification_uri}`);
693
+ console.log(`Enter code: ${response.user_code}
694
+ `);
695
+ console.log("Or press Enter to open your browser automatically...");
696
+ }
697
+ async openBrowser(url) {
698
+ try {
699
+ const { default: open } = await import("open");
700
+ await Promise.race([
701
+ this.waitForEnter(),
702
+ new Promise((resolve) => setTimeout(resolve, 2e3))
703
+ ]);
704
+ console.log("Opening browser...");
705
+ await open(url);
706
+ } catch (error) {
707
+ console.log("Please open the URL manually in your browser.");
708
+ }
709
+ }
710
+ waitForEnter() {
711
+ return new Promise((resolve) => {
712
+ if (process.stdin.isTTY) {
713
+ process.stdin.setRawMode(true);
714
+ process.stdin.resume();
715
+ process.stdin.once("data", () => {
716
+ process.stdin.setRawMode(false);
717
+ process.stdin.pause();
718
+ resolve();
719
+ });
720
+ } else {
721
+ resolve();
722
+ }
723
+ });
724
+ }
725
+ async pollForToken(deviceResponse) {
726
+ const startTime = Date.now();
727
+ const expiresAt = startTime + deviceResponse.expires_in * 1e3;
728
+ console.log("Waiting for authorization...");
729
+ while (Date.now() < expiresAt) {
730
+ await new Promise((resolve) => setTimeout(resolve, this.pollInterval * 1e3));
731
+ try {
732
+ const token = await this.checkDeviceCode(deviceResponse.device_code);
733
+ console.log("\u2705 Authorization successful!\n");
734
+ return token;
735
+ } catch (error) {
736
+ if (error.message === "authorization_pending") {
737
+ process.stdout.write(".");
738
+ } else if (error.message === "slow_down") {
739
+ this.pollInterval += 5;
740
+ } else {
741
+ throw error;
742
+ }
743
+ }
744
+ }
745
+ throw new Error("Authorization timeout - please try again");
746
+ }
747
+ async checkDeviceCode(deviceCode) {
748
+ const response = await fetch2(`${this.authBaseUrl}/oauth/token`, {
749
+ method: "POST",
750
+ headers: { "Content-Type": "application/json" },
751
+ body: JSON.stringify({
752
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
753
+ device_code: deviceCode,
754
+ client_id: this.clientId
755
+ })
756
+ });
757
+ const data = await response.json();
758
+ if (!response.ok) {
759
+ throw new Error(data.error || "Token request failed");
760
+ }
761
+ return data;
762
+ }
763
+ };
764
+
765
+ // src/flows/apikey-flow.ts
766
+ import fetch3 from "cross-fetch";
767
+ var APIKeyFlow = class extends BaseOAuthFlow {
768
+ constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
769
+ super({
770
+ clientId: "api-key-client",
771
+ authBaseUrl
772
+ });
773
+ this.apiKey = apiKey;
774
+ }
775
+ /**
776
+ * "Authenticate" by returning the API key as a virtual token
777
+ * The API key will be used directly in request headers
778
+ */
779
+ async authenticate() {
780
+ if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
781
+ throw new Error(
782
+ 'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
783
+ );
784
+ }
785
+ if (this.apiKey.startsWith("vx_")) {
786
+ console.warn(
787
+ '\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
788
+ );
789
+ }
790
+ return {
791
+ access_token: this.apiKey,
792
+ token_type: "api-key",
793
+ expires_in: 0,
794
+ // API keys don't expire
795
+ issued_at: Date.now()
796
+ };
797
+ }
798
+ /**
799
+ * API keys don't need refresh
800
+ */
801
+ async refreshToken(refreshToken) {
802
+ throw new Error("API keys do not support token refresh");
803
+ }
804
+ /**
805
+ * Optional: Validate API key by making a test request
806
+ */
807
+ async validateAPIKey() {
808
+ try {
809
+ const response = await fetch3(`${this.config.authBaseUrl}/api/v1/health`, {
810
+ headers: {
811
+ "x-api-key": this.apiKey
812
+ }
813
+ });
814
+ return response.ok;
815
+ } catch (error) {
816
+ console.error("API key validation failed:", error);
817
+ return false;
818
+ }
819
+ }
820
+ };
821
+
822
+ // src/client/mcp-client.ts
823
+ var MCPClient = class {
824
+ constructor(config = {}) {
825
+ // ← NEW: Track auth mode
826
+ this.ws = null;
827
+ this.eventSource = null;
828
+ this.accessToken = null;
829
+ this.refreshTimer = null;
830
+ this.config = {
831
+ mcpEndpoint: "wss://mcp.lanonasis.com",
832
+ autoRefresh: true,
833
+ ...config
834
+ };
835
+ const defaultStorage = typeof window !== "undefined" ? new TokenStorageWeb() : new TokenStorage();
836
+ this.tokenStorage = config.tokenStorage || defaultStorage;
837
+ this.authMode = config.apiKey ? "apikey" : "oauth";
838
+ if (this.authMode === "apikey") {
839
+ this.authFlow = new APIKeyFlow(
840
+ config.apiKey,
841
+ config.authBaseUrl || "https://mcp.lanonasis.com"
842
+ );
843
+ } else {
844
+ if (this.isTerminal()) {
845
+ this.authFlow = new TerminalOAuthFlow(config);
846
+ } else {
847
+ this.authFlow = new DesktopOAuthFlow(config);
848
+ }
849
+ }
850
+ }
851
+ async connect() {
852
+ try {
853
+ let tokens = await this.tokenStorage.retrieve();
854
+ if (this.authMode === "apikey") {
855
+ if (!tokens) {
856
+ tokens = await this.authenticate();
857
+ }
858
+ this.accessToken = tokens.access_token;
859
+ } else {
860
+ if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
861
+ if (tokens?.refresh_token) {
862
+ try {
863
+ tokens = await this.authFlow.refreshToken(tokens.refresh_token);
864
+ await this.tokenStorage.store(tokens);
865
+ } catch (error) {
866
+ tokens = await this.authenticate();
867
+ }
868
+ } else {
869
+ tokens = await this.authenticate();
870
+ }
871
+ }
872
+ this.accessToken = tokens.access_token;
873
+ if (this.config.autoRefresh && tokens.expires_in) {
874
+ this.scheduleTokenRefresh(tokens);
875
+ }
876
+ }
877
+ await this.establishConnection();
878
+ } catch (error) {
879
+ console.error("Failed to connect:", error);
880
+ throw error;
881
+ }
882
+ }
883
+ async authenticate() {
884
+ console.log("Authenticating with Lan Onasis...");
885
+ const tokens = await this.authFlow.authenticate();
886
+ await this.tokenStorage.store(tokens);
887
+ return tokens;
888
+ }
889
+ async ensureAccessToken() {
890
+ if (this.accessToken) return;
891
+ const tokens = await this.tokenStorage.retrieve();
892
+ if (!tokens) {
893
+ throw new Error("Not authenticated");
894
+ }
895
+ if (this.authMode === "apikey") {
896
+ this.accessToken = tokens.access_token;
897
+ return;
898
+ }
899
+ if (this.tokenStorage.isTokenExpired(tokens)) {
900
+ if (tokens.refresh_token) {
901
+ try {
902
+ const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
903
+ await this.tokenStorage.store(newTokens);
904
+ this.accessToken = newTokens.access_token;
905
+ return;
906
+ } catch (error) {
907
+ console.error("Token refresh failed:", error);
908
+ throw new Error("Token expired and refresh failed");
909
+ }
910
+ } else {
911
+ throw new Error("Token expired and no refresh token available");
912
+ }
913
+ }
914
+ this.accessToken = tokens.access_token;
915
+ }
916
+ scheduleTokenRefresh(tokens) {
917
+ if (this.refreshTimer) {
918
+ clearTimeout(this.refreshTimer);
919
+ }
920
+ const refreshIn = (tokens.expires_in - 300) * 1e3;
921
+ this.refreshTimer = setTimeout(async () => {
922
+ try {
923
+ if (tokens.refresh_token) {
924
+ const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
925
+ await this.tokenStorage.store(newTokens);
926
+ this.accessToken = newTokens.access_token;
927
+ this.scheduleTokenRefresh(newTokens);
928
+ await this.reconnect();
929
+ }
930
+ } catch (error) {
931
+ console.error("Token refresh failed:", error);
932
+ }
933
+ }, refreshIn);
934
+ }
935
+ async establishConnection() {
936
+ const endpoint = this.config.mcpEndpoint;
937
+ if (endpoint.startsWith("wss://")) {
938
+ await this.connectWebSocket(endpoint);
939
+ } else if (endpoint.startsWith("https://")) {
940
+ await this.connectSSE(endpoint);
941
+ } else {
942
+ throw new Error("Invalid MCP endpoint - must be wss:// or https://");
943
+ }
944
+ }
945
+ async connectWebSocket(endpoint) {
946
+ const wsUrl = new URL(endpoint);
947
+ wsUrl.pathname = "/ws";
948
+ if (this.accessToken) {
949
+ wsUrl.searchParams.set("access_token", this.accessToken);
950
+ }
951
+ if (typeof WebSocket !== "undefined") {
952
+ this.ws = new WebSocket(wsUrl.toString());
953
+ } else {
954
+ const { default: WS } = await import("ws");
955
+ if (this.authMode === "apikey") {
956
+ this.ws = new WS(wsUrl.toString(), {
957
+ headers: {
958
+ "x-api-key": this.accessToken
959
+ }
960
+ });
961
+ } else {
962
+ this.ws = new WS(wsUrl.toString(), {
963
+ headers: {
964
+ "Authorization": `Bearer ${this.accessToken}`
965
+ }
966
+ });
967
+ }
968
+ }
969
+ return new Promise((resolve, reject) => {
970
+ if (!this.ws) {
971
+ reject(new Error("WebSocket not initialized"));
972
+ return;
973
+ }
974
+ this.ws.onopen = () => {
975
+ console.log("MCP WebSocket connected");
976
+ resolve();
977
+ };
978
+ this.ws.onerror = (error) => {
979
+ console.error("WebSocket error:", error);
980
+ reject(error);
981
+ };
982
+ this.ws.onclose = (event) => {
983
+ console.log("WebSocket closed:", event.code, event.reason);
984
+ if (event.code !== 1008 && event.code !== 4001) {
985
+ setTimeout(() => this.reconnect(), 5e3);
986
+ }
987
+ };
988
+ this.ws.onmessage = (event) => {
989
+ this.handleMessage(event.data);
990
+ };
991
+ });
992
+ }
993
+ async connectSSE(endpoint) {
994
+ const sseUrl = new URL(endpoint);
995
+ sseUrl.pathname = "/sse";
996
+ if (typeof EventSource !== "undefined") {
997
+ this.eventSource = new EventSource(sseUrl.toString());
998
+ } else {
999
+ const EventSourceModule = await import("eventsource");
1000
+ const ES = EventSourceModule.default || EventSourceModule;
1001
+ if (this.authMode === "apikey") {
1002
+ this.eventSource = new ES(sseUrl.toString(), {
1003
+ headers: {
1004
+ "x-api-key": this.accessToken
1005
+ }
1006
+ });
1007
+ } else {
1008
+ this.eventSource = new ES(sseUrl.toString(), {
1009
+ headers: {
1010
+ "Authorization": `Bearer ${this.accessToken}`
1011
+ }
1012
+ });
1013
+ }
1014
+ }
1015
+ this.eventSource.onopen = () => {
1016
+ console.log("MCP SSE connected");
1017
+ };
1018
+ this.eventSource.onerror = (error) => {
1019
+ console.error("SSE error:", error);
1020
+ setTimeout(() => this.reconnect(), 5e3);
1021
+ };
1022
+ this.eventSource.onmessage = (event) => {
1023
+ this.handleMessage(event.data);
1024
+ };
1025
+ }
1026
+ handleMessage(data) {
1027
+ try {
1028
+ const message = JSON.parse(data);
1029
+ console.log("MCP message:", message);
1030
+ } catch (error) {
1031
+ console.error("Failed to parse message:", error);
1032
+ }
1033
+ }
1034
+ async reconnect() {
1035
+ this.disconnect();
1036
+ await this.establishConnection();
1037
+ }
1038
+ async request(method, params) {
1039
+ await this.ensureAccessToken();
1040
+ if (!this.accessToken) {
1041
+ throw new Error("Not authenticated");
1042
+ }
1043
+ const headers = {
1044
+ "Content-Type": "application/json"
1045
+ };
1046
+ if (this.authMode === "apikey") {
1047
+ headers["x-api-key"] = this.accessToken;
1048
+ } else {
1049
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1050
+ }
1051
+ const response = await fetch4(`${this.config.mcpEndpoint}/api`, {
1052
+ method: "POST",
1053
+ headers,
1054
+ body: JSON.stringify({
1055
+ jsonrpc: "2.0",
1056
+ id: this.generateId(),
1057
+ method,
1058
+ params
1059
+ })
1060
+ });
1061
+ if (response.status === 401) {
1062
+ if (this.authMode === "apikey") {
1063
+ throw new Error("Invalid API key - please check your credentials");
1064
+ }
1065
+ const tokens = await this.tokenStorage.retrieve();
1066
+ if (tokens?.refresh_token) {
1067
+ const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
1068
+ await this.tokenStorage.store(newTokens);
1069
+ this.accessToken = newTokens.access_token;
1070
+ return this.request(method, params);
1071
+ } else {
1072
+ await this.connect();
1073
+ return this.request(method, params);
1074
+ }
1075
+ }
1076
+ const result = await response.json();
1077
+ if (result.error) {
1078
+ throw new Error(result.error.message || "Request failed");
1079
+ }
1080
+ return result.result;
1081
+ }
1082
+ disconnect() {
1083
+ if (this.ws) {
1084
+ this.ws.close();
1085
+ this.ws = null;
1086
+ }
1087
+ if (this.eventSource) {
1088
+ this.eventSource.close();
1089
+ this.eventSource = null;
1090
+ }
1091
+ if (this.refreshTimer) {
1092
+ clearTimeout(this.refreshTimer);
1093
+ this.refreshTimer = null;
1094
+ }
1095
+ }
1096
+ async logout() {
1097
+ const tokens = await this.tokenStorage.retrieve();
1098
+ if (tokens) {
1099
+ try {
1100
+ if (tokens.access_token) {
1101
+ await this.authFlow.revokeToken(tokens.access_token, "access_token");
1102
+ }
1103
+ if (tokens.refresh_token) {
1104
+ await this.authFlow.revokeToken(tokens.refresh_token, "refresh_token");
1105
+ }
1106
+ } catch (error) {
1107
+ console.error("Failed to revoke tokens:", error);
1108
+ }
1109
+ }
1110
+ await this.tokenStorage.clear();
1111
+ this.disconnect();
1112
+ this.accessToken = null;
1113
+ }
1114
+ isTerminal() {
1115
+ return !!(typeof process !== "undefined" && process.versions && process.versions.node && !this.isElectron());
1116
+ }
1117
+ isElectron() {
1118
+ return typeof window !== "undefined" && window.electronAPI !== void 0;
1119
+ }
1120
+ generateId() {
1121
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1122
+ }
1123
+ // MCP-specific methods
1124
+ async createMemory(title, content, options) {
1125
+ return this.request("memory/create", {
1126
+ title,
1127
+ content,
1128
+ ...options
1129
+ });
1130
+ }
1131
+ async searchMemories(query, options) {
1132
+ return this.request("memory/search", {
1133
+ query,
1134
+ ...options
1135
+ });
1136
+ }
1137
+ async getMemory(id) {
1138
+ return this.request("memory/get", { id });
1139
+ }
1140
+ async updateMemory(id, updates) {
1141
+ return this.request("memory/update", {
1142
+ id,
1143
+ ...updates
1144
+ });
1145
+ }
1146
+ async deleteMemory(id) {
1147
+ return this.request("memory/delete", { id });
1148
+ }
1149
+ };
1150
+
1151
+ // src/storage/api-key-storage-web.ts
1152
+ var ApiKeyStorageWeb = class {
1153
+ constructor() {
1154
+ this.storageKey = "lanonasis_api_key";
1155
+ this.webEncryptionKeyStorage = "lanonasis_web_enc_key";
1156
+ }
1157
+ async store(data) {
1158
+ const payload = JSON.stringify({
1159
+ ...data,
1160
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString()
1161
+ });
1162
+ const encrypted = await this.encrypt(payload);
1163
+ localStorage.setItem(this.storageKey, encrypted);
1164
+ }
1165
+ async retrieve() {
1166
+ const encrypted = localStorage.getItem(this.storageKey);
1167
+ if (!encrypted) return null;
1168
+ try {
1169
+ const decrypted = await this.decrypt(encrypted);
1170
+ return JSON.parse(decrypted);
1171
+ } catch {
1172
+ return null;
1173
+ }
1174
+ }
1175
+ async clear() {
1176
+ localStorage.removeItem(this.storageKey);
1177
+ }
1178
+ async encrypt(text) {
1179
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1180
+ const encoder2 = new TextEncoder();
1181
+ return this.base64Encode(encoder2.encode(text));
1182
+ }
1183
+ const encoder = new TextEncoder();
1184
+ const data = encoder.encode(text);
1185
+ const passphrase = await this.getWebEncryptionKey();
1186
+ const keyMaterial = await window.crypto.subtle.importKey(
1187
+ "raw",
1188
+ encoder.encode(passphrase),
1189
+ "PBKDF2",
1190
+ false,
1191
+ ["deriveBits", "deriveKey"]
1192
+ );
1193
+ const key = await window.crypto.subtle.deriveKey(
1194
+ {
1195
+ name: "PBKDF2",
1196
+ salt: encoder.encode("lanonasis-api-key-salt"),
1197
+ iterations: 1e5,
1198
+ hash: "SHA-256"
1199
+ },
1200
+ keyMaterial,
1201
+ { name: "AES-GCM", length: 256 },
1202
+ true,
1203
+ ["encrypt", "decrypt"]
1204
+ );
1205
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
1206
+ const encrypted = await window.crypto.subtle.encrypt(
1207
+ { name: "AES-GCM", iv },
1208
+ key,
1209
+ data
1210
+ );
1211
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
1212
+ combined.set(iv, 0);
1213
+ combined.set(new Uint8Array(encrypted), iv.length);
1214
+ return this.base64Encode(combined);
1215
+ }
1216
+ async decrypt(encrypted) {
1217
+ if (typeof window === "undefined" || !window.crypto?.subtle) {
1218
+ const decoder2 = new TextDecoder();
1219
+ return decoder2.decode(this.base64Decode(encrypted));
1220
+ }
1221
+ const bytes = this.base64Decode(encrypted);
1222
+ const iv = bytes.slice(0, 12);
1223
+ const data = bytes.slice(12);
1224
+ const encoder = new TextEncoder();
1225
+ const decoder = new TextDecoder();
1226
+ const passphrase = await this.getWebEncryptionKey();
1227
+ const keyMaterial = await window.crypto.subtle.importKey(
1228
+ "raw",
1229
+ encoder.encode(passphrase),
1230
+ "PBKDF2",
1231
+ false,
1232
+ ["deriveBits", "deriveKey"]
1233
+ );
1234
+ const key = await window.crypto.subtle.deriveKey(
1235
+ {
1236
+ name: "PBKDF2",
1237
+ salt: encoder.encode("lanonasis-api-key-salt"),
1238
+ iterations: 1e5,
1239
+ hash: "SHA-256"
1240
+ },
1241
+ keyMaterial,
1242
+ { name: "AES-GCM", length: 256 },
1243
+ true,
1244
+ ["encrypt", "decrypt"]
1245
+ );
1246
+ const decrypted = await window.crypto.subtle.decrypt(
1247
+ { name: "AES-GCM", iv },
1248
+ key,
1249
+ data
1250
+ );
1251
+ return decoder.decode(decrypted);
1252
+ }
1253
+ async getWebEncryptionKey() {
1254
+ const existing = localStorage.getItem(this.webEncryptionKeyStorage);
1255
+ if (existing) return existing;
1256
+ const buf = new Uint8Array(32);
1257
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
1258
+ window.crypto.getRandomValues(buf);
1259
+ }
1260
+ const raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1261
+ localStorage.setItem(this.webEncryptionKeyStorage, raw);
1262
+ return raw;
1263
+ }
1264
+ base64Encode(bytes) {
1265
+ let binary = "";
1266
+ bytes.forEach((b) => {
1267
+ binary += String.fromCharCode(b);
1268
+ });
1269
+ return btoa(binary);
1270
+ }
1271
+ base64Decode(value) {
1272
+ const binary = atob(value);
1273
+ const bytes = new Uint8Array(binary.length);
1274
+ for (let i = 0; i < binary.length; i++) {
1275
+ bytes[i] = binary.charCodeAt(i);
1276
+ }
1277
+ return bytes;
1278
+ }
1279
+ };
1280
+ export {
1281
+ ApiKeyStorageWeb,
1282
+ BaseOAuthFlow,
1283
+ DesktopOAuthFlow,
1284
+ MCPClient,
1285
+ TokenStorageWeb
1286
+ };