oauth-callback 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/mcp.js CHANGED
@@ -47,16 +47,15 @@ function inMemoryStore() {
47
47
  }
48
48
 
49
49
  // node_modules/open/index.js
50
- import process6 from "node:process";
51
- import { Buffer } from "node:buffer";
50
+ import process7 from "node:process";
52
51
  import path from "node:path";
53
52
  import { fileURLToPath } from "node:url";
54
- import { promisify as promisify5 } from "node:util";
55
- import childProcess from "node:child_process";
53
+ import childProcess3 from "node:child_process";
56
54
  import fs5, { constants as fsConstants2 } from "node:fs/promises";
57
55
 
58
56
  // node_modules/wsl-utils/index.js
59
- import process2 from "node:process";
57
+ import { promisify as promisify2 } from "node:util";
58
+ import childProcess2 from "node:child_process";
60
59
  import fs4, { constants as fsConstants } from "node:fs/promises";
61
60
 
62
61
  // node_modules/is-wsl/index.js
@@ -128,7 +127,54 @@ var isWsl = () => {
128
127
  };
129
128
  var is_wsl_default = process.env.__IS_WSL_TEST__ ? isWsl : isWsl();
130
129
 
130
+ // node_modules/powershell-utils/index.js
131
+ import process2 from "node:process";
132
+ import { Buffer } from "node:buffer";
133
+ import { promisify } from "node:util";
134
+ import childProcess from "node:child_process";
135
+ var execFile = promisify(childProcess.execFile);
136
+ var powerShellPath = () => `${process2.env.SYSTEMROOT || process2.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
137
+ var executePowerShell = async (command, options = {}) => {
138
+ const {
139
+ powerShellPath: psPath,
140
+ ...execFileOptions
141
+ } = options;
142
+ const encodedCommand = executePowerShell.encodeCommand(command);
143
+ return execFile(psPath ?? powerShellPath(), [
144
+ ...executePowerShell.argumentsPrefix,
145
+ encodedCommand
146
+ ], {
147
+ encoding: "utf8",
148
+ ...execFileOptions
149
+ });
150
+ };
151
+ executePowerShell.argumentsPrefix = [
152
+ "-NoProfile",
153
+ "-NonInteractive",
154
+ "-ExecutionPolicy",
155
+ "Bypass",
156
+ "-EncodedCommand"
157
+ ];
158
+ executePowerShell.encodeCommand = (command) => Buffer.from(command, "utf16le").toString("base64");
159
+ executePowerShell.escapeArgument = (value) => `'${String(value).replaceAll("'", "''")}'`;
160
+
161
+ // node_modules/wsl-utils/utilities.js
162
+ function parseMountPointFromConfig(content) {
163
+ for (const line of content.split(`
164
+ `)) {
165
+ if (/^\s*#/.test(line)) {
166
+ continue;
167
+ }
168
+ const match = /^\s*root\s*=\s*(?<mountPoint>"[^"]*"|'[^']*'|[^#]*)/.exec(line);
169
+ if (!match) {
170
+ continue;
171
+ }
172
+ return match.groups.mountPoint.trim().replaceAll(/^["']|["']$/g, "");
173
+ }
174
+ }
175
+
131
176
  // node_modules/wsl-utils/index.js
177
+ var execFile2 = promisify2(childProcess2.execFile);
132
178
  var wslDrivesMountPoint = (() => {
133
179
  const defaultMountPoint = "/mnt/";
134
180
  let mountPoint;
@@ -146,11 +192,11 @@ var wslDrivesMountPoint = (() => {
146
192
  return defaultMountPoint;
147
193
  }
148
194
  const configContent = await fs4.readFile(configFilePath, { encoding: "utf8" });
149
- const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
150
- if (!configMountPoint) {
195
+ const parsedMountPoint = parseMountPointFromConfig(configContent);
196
+ if (parsedMountPoint === undefined) {
151
197
  return defaultMountPoint;
152
198
  }
153
- mountPoint = configMountPoint.groups.mountPoint.trim();
199
+ mountPoint = parsedMountPoint;
154
200
  mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
155
201
  return mountPoint;
156
202
  };
@@ -159,11 +205,36 @@ var powerShellPathFromWsl = async () => {
159
205
  const mountPoint = await wslDrivesMountPoint();
160
206
  return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
161
207
  };
162
- var powerShellPath = async () => {
163
- if (is_wsl_default) {
164
- return powerShellPathFromWsl();
208
+ var powerShellPath2 = is_wsl_default ? powerShellPathFromWsl : powerShellPath;
209
+ var canAccessPowerShellPromise;
210
+ var canAccessPowerShell = async () => {
211
+ canAccessPowerShellPromise ??= (async () => {
212
+ try {
213
+ const psPath = await powerShellPath2();
214
+ await fs4.access(psPath, fsConstants.X_OK);
215
+ return true;
216
+ } catch {
217
+ return false;
218
+ }
219
+ })();
220
+ return canAccessPowerShellPromise;
221
+ };
222
+ var wslDefaultBrowser = async () => {
223
+ const psPath = await powerShellPath2();
224
+ const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
225
+ const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
226
+ return stdout.trim();
227
+ };
228
+ var convertWslPathToWindows = async (path) => {
229
+ if (/^[a-z]+:\/\//i.test(path)) {
230
+ return path;
231
+ }
232
+ try {
233
+ const { stdout } = await execFile2("wslpath", ["-aw", path], { encoding: "utf8" });
234
+ return stdout.trim();
235
+ } catch {
236
+ return path;
165
237
  }
166
- return `${process2.env.SYSTEMROOT || process2.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
167
238
  };
168
239
 
169
240
  // node_modules/define-lazy-prop/index.js
@@ -185,15 +256,15 @@ function defineLazyProperty(object, propertyName, valueGetter) {
185
256
  }
186
257
 
187
258
  // node_modules/default-browser/index.js
188
- import { promisify as promisify4 } from "node:util";
259
+ import { promisify as promisify6 } from "node:util";
189
260
  import process5 from "node:process";
190
- import { execFile as execFile4 } from "node:child_process";
261
+ import { execFile as execFile6 } from "node:child_process";
191
262
 
192
263
  // node_modules/default-browser-id/index.js
193
- import { promisify } from "node:util";
264
+ import { promisify as promisify3 } from "node:util";
194
265
  import process3 from "node:process";
195
- import { execFile } from "node:child_process";
196
- var execFileAsync = promisify(execFile);
266
+ import { execFile as execFile3 } from "node:child_process";
267
+ var execFileAsync = promisify3(execFile3);
197
268
  async function defaultBrowserId() {
198
269
  if (process3.platform !== "darwin") {
199
270
  throw new Error("macOS only");
@@ -205,9 +276,9 @@ async function defaultBrowserId() {
205
276
 
206
277
  // node_modules/run-applescript/index.js
207
278
  import process4 from "node:process";
208
- import { promisify as promisify2 } from "node:util";
209
- import { execFile as execFile2, execFileSync } from "node:child_process";
210
- var execFileAsync2 = promisify2(execFile2);
279
+ import { promisify as promisify4 } from "node:util";
280
+ import { execFile as execFile4, execFileSync } from "node:child_process";
281
+ var execFileAsync2 = promisify4(execFile4);
211
282
  async function runAppleScript(script, { humanReadableOutput = true } = {}) {
212
283
  if (process4.platform !== "darwin") {
213
284
  throw new Error("macOS only");
@@ -224,20 +295,28 @@ tell application "System Events" to get value of property list item "CFBundleNam
224
295
  }
225
296
 
226
297
  // node_modules/default-browser/windows.js
227
- import { promisify as promisify3 } from "node:util";
228
- import { execFile as execFile3 } from "node:child_process";
229
- var execFileAsync3 = promisify3(execFile3);
298
+ import { promisify as promisify5 } from "node:util";
299
+ import { execFile as execFile5 } from "node:child_process";
300
+ var execFileAsync3 = promisify5(execFile5);
230
301
  var windowsBrowserProgIds = {
231
- AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
232
- MSEdgeDHTML: { name: "Edge", id: "com.microsoft.edge" },
233
302
  MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
234
- "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" },
235
- FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
303
+ MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
304
+ MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
305
+ AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
236
306
  ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
307
+ ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
308
+ ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
309
+ ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
237
310
  BraveHTML: { name: "Brave", id: "com.brave.Browser" },
238
311
  BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
239
- BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" }
312
+ BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
313
+ BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
314
+ FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
315
+ OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
316
+ VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
317
+ "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
240
318
  };
319
+ var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
241
320
 
242
321
  class UnknownBrowserError extends Error {
243
322
  }
@@ -261,7 +340,7 @@ async function defaultBrowser(_execFileAsync = execFileAsync3) {
261
340
  }
262
341
 
263
342
  // node_modules/default-browser/index.js
264
- var execFileAsync4 = promisify4(execFile4);
343
+ var execFileAsync4 = promisify6(execFile6);
265
344
  var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
266
345
  async function defaultBrowser2() {
267
346
  if (process5.platform === "darwin") {
@@ -281,42 +360,29 @@ async function defaultBrowser2() {
281
360
  throw new Error("Only macOS, Linux, and Windows are supported");
282
361
  }
283
362
 
363
+ // node_modules/is-in-ssh/index.js
364
+ import process6 from "node:process";
365
+ var isInSsh = Boolean(process6.env.SSH_CONNECTION || process6.env.SSH_CLIENT || process6.env.SSH_TTY);
366
+ var is_in_ssh_default = isInSsh;
367
+
284
368
  // node_modules/open/index.js
285
- var execFile5 = promisify5(childProcess.execFile);
286
- var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
369
+ var fallbackAttemptSymbol = Symbol("fallbackAttempt");
370
+ var __dirname2 = import.meta.url ? path.dirname(fileURLToPath(import.meta.url)) : "";
287
371
  var localXdgOpenPath = path.join(__dirname2, "xdg-open");
288
- var { platform, arch } = process6;
289
- async function getWindowsDefaultBrowserFromWsl() {
290
- const powershellPath = await powerShellPath();
291
- const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
292
- const encodedCommand = Buffer.from(rawCommand, "utf16le").toString("base64");
293
- const { stdout } = await execFile5(powershellPath, [
294
- "-NoProfile",
295
- "-NonInteractive",
296
- "-ExecutionPolicy",
297
- "Bypass",
298
- "-EncodedCommand",
299
- encodedCommand
300
- ], { encoding: "utf8" });
301
- const progId = stdout.trim();
302
- const browserMap = {
303
- ChromeHTML: "com.google.chrome",
304
- BraveHTML: "com.brave.Browser",
305
- MSEdgeHTM: "com.microsoft.edge",
306
- FirefoxURL: "org.mozilla.firefox"
307
- };
308
- return browserMap[progId] ? { id: browserMap[progId] } : {};
309
- }
310
- var pTryEach = async (array, mapper) => {
311
- let latestError;
312
- for (const item of array) {
372
+ var { platform, arch } = process7;
373
+ var tryEachApp = async (apps, opener) => {
374
+ if (apps.length === 0) {
375
+ return;
376
+ }
377
+ const errors = [];
378
+ for (const app of apps) {
313
379
  try {
314
- return await mapper(item);
380
+ return await opener(app);
315
381
  } catch (error) {
316
- latestError = error;
382
+ errors.push(error);
317
383
  }
318
384
  }
319
- throw latestError;
385
+ throw new AggregateError(errors, "Failed to open in all supported apps");
320
386
  };
321
387
  var baseOpen = async (options) => {
322
388
  options = {
@@ -326,34 +392,39 @@ var baseOpen = async (options) => {
326
392
  allowNonzeroExitCode: false,
327
393
  ...options
328
394
  };
395
+ const isFallbackAttempt = options[fallbackAttemptSymbol] === true;
396
+ delete options[fallbackAttemptSymbol];
329
397
  if (Array.isArray(options.app)) {
330
- return pTryEach(options.app, (singleApp) => baseOpen({
398
+ return tryEachApp(options.app, (singleApp) => baseOpen({
331
399
  ...options,
332
- app: singleApp
400
+ app: singleApp,
401
+ [fallbackAttemptSymbol]: true
333
402
  }));
334
403
  }
335
404
  let { name: app, arguments: appArguments = [] } = options.app ?? {};
336
405
  appArguments = [...appArguments];
337
406
  if (Array.isArray(app)) {
338
- return pTryEach(app, (appName) => baseOpen({
407
+ return tryEachApp(app, (appName) => baseOpen({
339
408
  ...options,
340
409
  app: {
341
410
  name: appName,
342
411
  arguments: appArguments
343
- }
412
+ },
413
+ [fallbackAttemptSymbol]: true
344
414
  }));
345
415
  }
346
416
  if (app === "browser" || app === "browserPrivate") {
347
417
  const ids = {
348
418
  "com.google.chrome": "chrome",
349
419
  "google-chrome.desktop": "chrome",
350
- "com.brave.Browser": "brave",
420
+ "com.brave.browser": "brave",
351
421
  "org.mozilla.firefox": "firefox",
352
422
  "firefox.desktop": "firefox",
353
423
  "com.microsoft.msedge": "edge",
354
424
  "com.microsoft.edge": "edge",
355
425
  "com.microsoft.edgemac": "edge",
356
- "microsoft-edge.desktop": "edge"
426
+ "microsoft-edge.desktop": "edge",
427
+ "com.apple.safari": "safari"
357
428
  };
358
429
  const flags = {
359
430
  chrome: "--incognito",
@@ -361,10 +432,20 @@ var baseOpen = async (options) => {
361
432
  firefox: "--private-window",
362
433
  edge: "--inPrivate"
363
434
  };
364
- const browser = is_wsl_default ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser2();
435
+ let browser;
436
+ if (is_wsl_default) {
437
+ const progId = await wslDefaultBrowser();
438
+ const browserInfo = _windowsBrowserProgIdMap.get(progId);
439
+ browser = browserInfo ?? {};
440
+ } else {
441
+ browser = await defaultBrowser2();
442
+ }
365
443
  if (browser.id in ids) {
366
- const browserName = ids[browser.id];
444
+ const browserName = ids[browser.id.toLowerCase()];
367
445
  if (app === "browserPrivate") {
446
+ if (browserName === "safari") {
447
+ throw new Error("Safari doesn't support opening in private mode via command line");
448
+ }
368
449
  appArguments.push(flags[browserName]);
369
450
  }
370
451
  return baseOpen({
@@ -380,6 +461,10 @@ var baseOpen = async (options) => {
380
461
  let command;
381
462
  const cliArguments = [];
382
463
  const childProcessOptions = {};
464
+ let shouldUseWindowsInWsl = false;
465
+ if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
466
+ shouldUseWindowsInWsl = await canAccessPowerShell();
467
+ }
383
468
  if (platform === "darwin") {
384
469
  command = "open";
385
470
  if (options.wait) {
@@ -394,29 +479,35 @@ var baseOpen = async (options) => {
394
479
  if (app) {
395
480
  cliArguments.push("-a", app);
396
481
  }
397
- } else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
398
- command = await powerShellPath();
399
- cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
482
+ } else if (platform === "win32" || shouldUseWindowsInWsl) {
483
+ command = await powerShellPath2();
484
+ cliArguments.push(...executePowerShell.argumentsPrefix);
400
485
  if (!is_wsl_default) {
401
486
  childProcessOptions.windowsVerbatimArguments = true;
402
487
  }
403
- const encodedArguments = ["Start"];
488
+ if (is_wsl_default && options.target) {
489
+ options.target = await convertWslPathToWindows(options.target);
490
+ }
491
+ const encodedArguments = ["$ProgressPreference = 'SilentlyContinue';", "Start"];
404
492
  if (options.wait) {
405
493
  encodedArguments.push("-Wait");
406
494
  }
407
495
  if (app) {
408
- encodedArguments.push(`"\`"${app}\`""`);
496
+ encodedArguments.push(executePowerShell.escapeArgument(app));
409
497
  if (options.target) {
410
498
  appArguments.push(options.target);
411
499
  }
412
500
  } else if (options.target) {
413
- encodedArguments.push(`"${options.target}"`);
501
+ encodedArguments.push(executePowerShell.escapeArgument(options.target));
414
502
  }
415
503
  if (appArguments.length > 0) {
416
- appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
504
+ appArguments = appArguments.map((argument) => executePowerShell.escapeArgument(argument));
417
505
  encodedArguments.push("-ArgumentList", appArguments.join(","));
418
506
  }
419
- options.target = Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
507
+ options.target = executePowerShell.encodeCommand(encodedArguments.join(" "));
508
+ if (!options.wait) {
509
+ childProcessOptions.stdio = "ignore";
510
+ }
420
511
  } else {
421
512
  if (app) {
422
513
  command = app;
@@ -427,7 +518,7 @@ var baseOpen = async (options) => {
427
518
  await fs5.access(localXdgOpenPath, fsConstants2.X_OK);
428
519
  exeLocalXdgOpen = true;
429
520
  } catch {}
430
- const useSystemXdgOpen = process6.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
521
+ const useSystemXdgOpen = process7.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
431
522
  command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
432
523
  }
433
524
  if (appArguments.length > 0) {
@@ -444,12 +535,12 @@ var baseOpen = async (options) => {
444
535
  if (options.target) {
445
536
  cliArguments.push(options.target);
446
537
  }
447
- const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
538
+ const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
448
539
  if (options.wait) {
449
540
  return new Promise((resolve, reject) => {
450
541
  subprocess.once("error", reject);
451
542
  subprocess.once("close", (exitCode) => {
452
- if (!options.allowNonzeroExitCode && exitCode > 0) {
543
+ if (!options.allowNonzeroExitCode && exitCode !== 0) {
453
544
  reject(new Error(`Exited with code ${exitCode}`));
454
545
  return;
455
546
  }
@@ -457,8 +548,30 @@ var baseOpen = async (options) => {
457
548
  });
458
549
  });
459
550
  }
551
+ if (isFallbackAttempt) {
552
+ return new Promise((resolve, reject) => {
553
+ subprocess.once("error", reject);
554
+ subprocess.once("spawn", () => {
555
+ subprocess.once("close", (exitCode) => {
556
+ subprocess.off("error", reject);
557
+ if (exitCode !== 0) {
558
+ reject(new Error(`Exited with code ${exitCode}`));
559
+ return;
560
+ }
561
+ subprocess.unref();
562
+ resolve(subprocess);
563
+ });
564
+ });
565
+ });
566
+ }
460
567
  subprocess.unref();
461
- return subprocess;
568
+ return new Promise((resolve, reject) => {
569
+ subprocess.once("error", reject);
570
+ subprocess.once("spawn", () => {
571
+ subprocess.off("error", reject);
572
+ resolve(subprocess);
573
+ });
574
+ });
462
575
  };
463
576
  var open = (target, options) => {
464
577
  if (typeof target !== "string") {
@@ -479,7 +592,7 @@ function detectArchBinary(binary) {
479
592
  }
480
593
  return archBinary;
481
594
  }
482
- function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
595
+ function detectPlatformBinary({ [platform]: platformBinary }, { wsl } = {}) {
483
596
  if (wsl && is_wsl_default) {
484
597
  return detectArchBinary(wsl);
485
598
  }
@@ -488,11 +601,14 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
488
601
  }
489
602
  return detectArchBinary(platformBinary);
490
603
  }
491
- var apps = {};
604
+ var apps = {
605
+ browser: "browser",
606
+ browserPrivate: "browserPrivate"
607
+ };
492
608
  defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
493
609
  darwin: "google chrome",
494
610
  win32: "chrome",
495
- linux: ["google-chrome", "google-chrome-stable", "chromium"]
611
+ linux: ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]
496
612
  }, {
497
613
  wsl: {
498
614
  ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
@@ -523,8 +639,9 @@ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
523
639
  }, {
524
640
  wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
525
641
  }));
526
- defineLazyProperty(apps, "browser", () => "browser");
527
- defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
642
+ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
643
+ darwin: "Safari"
644
+ }));
528
645
  var open_default = open;
529
646
 
530
647
  // src/errors.ts
@@ -605,270 +722,132 @@ function renderError(params) {
605
722
 
606
723
  // src/server.ts
607
724
  function generateCallbackHTML(params, successHtml, errorHtml) {
608
- if (params.error) {
609
- if (errorHtml) {
610
- return errorHtml.replace(/{{error}}/g, params.error || "").replace(/{{error_description}}/g, params.error_description || "").replace(/{{error_uri}}/g, params.error_uri || "");
611
- }
612
- return renderError({
613
- error: params.error,
614
- error_description: params.error_description,
615
- error_uri: params.error_uri
616
- });
617
- }
618
- return successHtml || successTemplate;
725
+ if (!params.error)
726
+ return successHtml || successTemplate;
727
+ if (errorHtml)
728
+ return errorHtml.replace(/{{error}}/g, params.error || "").replace(/{{error_description}}/g, params.error_description || "").replace(/{{error_uri}}/g, params.error_uri || "");
729
+ return renderError({
730
+ error: params.error,
731
+ error_description: params.error_description,
732
+ error_uri: params.error_uri
733
+ });
619
734
  }
620
735
 
621
- class BunCallbackServer {
622
- server;
623
- callbackPromise;
624
- callbackPath = "/callback";
736
+ class BaseCallbackServer {
737
+ callbackListeners = new Map;
625
738
  successHtml;
626
739
  errorHtml;
627
740
  onRequest;
628
741
  abortHandler;
629
- async start(options) {
630
- const {
631
- port,
632
- hostname = "localhost",
633
- successHtml,
634
- errorHtml,
635
- signal,
636
- onRequest
637
- } = options;
742
+ signal;
743
+ setup(options) {
744
+ const { successHtml, errorHtml, signal, onRequest } = options;
638
745
  this.successHtml = successHtml;
639
746
  this.errorHtml = errorHtml;
640
747
  this.onRequest = onRequest;
641
- if (signal) {
642
- if (signal.aborted) {
643
- throw new Error("Operation aborted");
644
- }
645
- this.abortHandler = () => {
646
- this.stop();
647
- if (this.callbackPromise) {
648
- this.callbackPromise.reject(new Error("Operation aborted"));
649
- }
650
- };
651
- signal.addEventListener("abort", this.abortHandler);
652
- }
653
- this.server = Bun.serve({
654
- port,
655
- hostname,
656
- fetch: (request) => this.handleRequest(request)
657
- });
748
+ this.signal = signal;
749
+ if (!signal)
750
+ return;
751
+ if (signal.aborted)
752
+ throw new Error("Operation aborted");
753
+ this.abortHandler = () => this.stop();
754
+ signal.addEventListener("abort", this.abortHandler);
658
755
  }
659
756
  handleRequest(request) {
660
- if (this.onRequest) {
661
- this.onRequest(request);
662
- }
757
+ this.onRequest?.(request);
663
758
  const url = new URL(request.url);
664
- if (url.pathname === this.callbackPath) {
665
- const params = {};
666
- for (const [key, value] of url.searchParams) {
667
- params[key] = value;
668
- }
669
- if (this.callbackPromise) {
670
- this.callbackPromise.resolve(params);
671
- }
672
- return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
673
- status: 200,
674
- headers: { "Content-Type": "text/html" }
675
- });
676
- }
677
- return new Response("Not Found", { status: 404 });
759
+ const listener = this.callbackListeners.get(url.pathname);
760
+ if (!listener)
761
+ return new Response("Not Found", { status: 404 });
762
+ const params = {};
763
+ for (const [key, value] of url.searchParams)
764
+ params[key] = value;
765
+ listener.resolve(params);
766
+ return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
767
+ status: 200,
768
+ headers: { "Content-Type": "text/html" }
769
+ });
678
770
  }
679
771
  async waitForCallback(path2, timeout) {
680
- this.callbackPath = path2;
681
- return new Promise((resolve, reject) => {
682
- let isResolved = false;
683
- const timer = setTimeout(() => {
684
- if (!isResolved) {
685
- isResolved = true;
686
- this.callbackPromise = undefined;
687
- reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
688
- }
689
- }, timeout);
690
- const wrappedResolve = (result) => {
691
- if (!isResolved) {
692
- isResolved = true;
693
- clearTimeout(timer);
694
- this.callbackPromise = undefined;
695
- resolve(result);
696
- }
697
- };
698
- const wrappedReject = (error) => {
699
- if (!isResolved) {
700
- isResolved = true;
701
- clearTimeout(timer);
702
- this.callbackPromise = undefined;
703
- reject(error);
704
- }
705
- };
706
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
707
- });
772
+ if (this.callbackListeners.has(path2))
773
+ return Promise.reject(new Error(`A listener for the path "${path2}" is already active.`));
774
+ try {
775
+ return await Promise.race([
776
+ new Promise((resolve, reject) => {
777
+ this.callbackListeners.set(path2, { resolve, reject });
778
+ }),
779
+ new Promise((_, reject) => {
780
+ setTimeout(() => {
781
+ reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
782
+ }, timeout);
783
+ })
784
+ ]);
785
+ } finally {
786
+ this.callbackListeners.delete(path2);
787
+ }
708
788
  }
709
789
  async stop() {
710
- if (this.abortHandler) {
711
- const signal = this.server?.signal;
712
- if (signal) {
713
- signal.removeEventListener("abort", this.abortHandler);
714
- }
790
+ if (this.abortHandler && this.signal) {
791
+ this.signal.removeEventListener("abort", this.abortHandler);
715
792
  this.abortHandler = undefined;
716
793
  }
717
- if (this.callbackPromise) {
718
- this.callbackPromise.reject(new Error("Server stopped before callback received"));
719
- this.callbackPromise = undefined;
720
- }
721
- if (this.server) {
722
- this.server.stop();
723
- this.server = undefined;
724
- }
794
+ for (const listener of this.callbackListeners.values())
795
+ listener.reject(new Error("Server stopped before callback received"));
796
+ this.callbackListeners.clear();
797
+ await this.stopServer();
725
798
  }
726
799
  }
727
800
 
728
- class DenoCallbackServer {
801
+ class BunCallbackServer extends BaseCallbackServer {
729
802
  server;
730
- callbackPromise;
731
- callbackPath = "/callback";
732
- abortController;
733
- successHtml;
734
- errorHtml;
735
- onRequest;
736
- abortHandler;
737
803
  async start(options) {
738
- const {
804
+ this.setup(options);
805
+ const { port, hostname = "localhost" } = options;
806
+ this.server = Bun.serve({
739
807
  port,
740
- hostname = "localhost",
741
- successHtml,
742
- errorHtml,
743
- signal,
744
- onRequest
745
- } = options;
746
- this.successHtml = successHtml;
747
- this.errorHtml = errorHtml;
748
- this.onRequest = onRequest;
749
- this.abortController = new AbortController;
750
- if (signal) {
751
- if (signal.aborted) {
752
- throw new Error("Operation aborted");
753
- }
754
- this.abortHandler = () => {
755
- this.abortController?.abort();
756
- if (this.callbackPromise) {
757
- this.callbackPromise.reject(new Error("Operation aborted"));
758
- }
759
- };
760
- signal.addEventListener("abort", this.abortHandler);
761
- }
762
- this.server = Deno.serve({ port, hostname, signal: this.abortController.signal }, (request) => this.handleRequest(request));
763
- }
764
- handleRequest(request) {
765
- if (this.onRequest) {
766
- this.onRequest(request);
767
- }
768
- const url = new URL(request.url);
769
- if (url.pathname === this.callbackPath) {
770
- const params = {};
771
- for (const [key, value] of url.searchParams) {
772
- params[key] = value;
773
- }
774
- if (this.callbackPromise) {
775
- this.callbackPromise.resolve(params);
776
- }
777
- return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
778
- status: 200,
779
- headers: { "Content-Type": "text/html" }
780
- });
781
- }
782
- return new Response("Not Found", { status: 404 });
783
- }
784
- async waitForCallback(path2, timeout) {
785
- this.callbackPath = path2;
786
- return new Promise((resolve, reject) => {
787
- let isResolved = false;
788
- const timer = setTimeout(() => {
789
- if (!isResolved) {
790
- isResolved = true;
791
- this.callbackPromise = undefined;
792
- reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
793
- }
794
- }, timeout);
795
- const wrappedResolve = (result) => {
796
- if (!isResolved) {
797
- isResolved = true;
798
- clearTimeout(timer);
799
- this.callbackPromise = undefined;
800
- resolve(result);
801
- }
802
- };
803
- const wrappedReject = (error) => {
804
- if (!isResolved) {
805
- isResolved = true;
806
- clearTimeout(timer);
807
- this.callbackPromise = undefined;
808
- reject(error);
809
- }
810
- };
811
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
808
+ hostname,
809
+ fetch: (request) => this.handleRequest(request)
812
810
  });
813
811
  }
814
- async stop() {
815
- if (this.abortHandler) {
816
- const signal = this.server?.signal;
817
- if (signal) {
818
- signal.removeEventListener("abort", this.abortHandler);
819
- }
820
- this.abortHandler = undefined;
821
- }
822
- if (this.callbackPromise) {
823
- this.callbackPromise.reject(new Error("Server stopped before callback received"));
824
- this.callbackPromise = undefined;
825
- }
826
- if (this.abortController) {
827
- this.abortController.abort();
828
- this.abortController = undefined;
812
+ async stopServer() {
813
+ if (!this.server)
814
+ return;
815
+ while (this.server.pendingRequests > 0) {
816
+ await new Promise((resolve) => setTimeout(resolve, 10));
829
817
  }
818
+ this.server.stop();
830
819
  this.server = undefined;
831
820
  }
832
821
  }
833
822
 
834
- class NodeCallbackServer {
823
+ class DenoCallbackServer extends BaseCallbackServer {
824
+ abortController;
825
+ async start(options) {
826
+ this.setup(options);
827
+ const { port, hostname = "localhost" } = options;
828
+ this.abortController = new AbortController;
829
+ options.signal?.addEventListener("abort", () => this.abortController?.abort());
830
+ Deno.serve({ port, hostname, signal: this.abortController.signal }, (request) => this.handleRequest(request));
831
+ }
832
+ async stopServer() {
833
+ if (!this.abortController)
834
+ return;
835
+ this.abortController.abort();
836
+ this.abortController = undefined;
837
+ }
838
+ }
839
+
840
+ class NodeCallbackServer extends BaseCallbackServer {
835
841
  server;
836
- callbackPromise;
837
- callbackPath = "/callback";
838
- successHtml;
839
- errorHtml;
840
- onRequest;
841
- abortHandler;
842
842
  async start(options) {
843
- const {
844
- port,
845
- hostname = "localhost",
846
- successHtml,
847
- errorHtml,
848
- signal,
849
- onRequest
850
- } = options;
851
- this.successHtml = successHtml;
852
- this.errorHtml = errorHtml;
853
- this.onRequest = onRequest;
854
- if (signal) {
855
- if (signal.aborted) {
856
- throw new Error("Operation aborted");
857
- }
858
- this.abortHandler = () => {
859
- this.stop();
860
- if (this.callbackPromise) {
861
- this.callbackPromise.reject(new Error("Operation aborted"));
862
- }
863
- };
864
- signal.addEventListener("abort", this.abortHandler);
865
- }
843
+ this.setup(options);
844
+ const { port, hostname = "localhost" } = options;
866
845
  const { createServer } = await import("node:http");
867
846
  return new Promise((resolve, reject) => {
868
847
  this.server = createServer(async (req, res) => {
869
848
  try {
870
- const request = this.nodeToWebRequest(req, port);
871
- const response = await this.handleRequest(request);
849
+ const request = this.nodeToWebRequest(req, port, hostname);
850
+ const response = this.handleRequest(request);
872
851
  res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
873
852
  const body = await response.text();
874
853
  res.end(body);
@@ -877,102 +856,44 @@ class NodeCallbackServer {
877
856
  res.end("Internal Server Error");
878
857
  }
879
858
  });
859
+ if (options.signal)
860
+ options.signal.addEventListener("abort", () => this.server?.close());
880
861
  this.server.listen(port, hostname, () => resolve());
881
862
  this.server.on("error", reject);
882
863
  });
883
864
  }
884
- nodeToWebRequest(req, port) {
885
- const url = new URL(req.url, `http://localhost:${port}`);
865
+ async stopServer() {
866
+ if (!this.server)
867
+ return;
868
+ this.server.closeAllConnections();
869
+ return new Promise((resolve) => {
870
+ this.server?.close(() => {
871
+ this.server = undefined;
872
+ resolve();
873
+ });
874
+ });
875
+ }
876
+ nodeToWebRequest(req, port, hostname) {
877
+ const host = req.headers.host || `${hostname}:${port}`;
878
+ const url = new URL(req.url, `http://${host}`);
886
879
  const headers = new Headers;
887
880
  for (const [key, value] of Object.entries(req.headers)) {
888
- if (typeof value === "string") {
881
+ if (typeof value === "string")
889
882
  headers.set(key, value);
890
- } else if (Array.isArray(value)) {
883
+ else if (Array.isArray(value))
891
884
  headers.set(key, value.join(", "));
892
- }
893
885
  }
894
886
  return new Request(url.toString(), {
895
887
  method: req.method,
896
888
  headers
897
889
  });
898
890
  }
899
- async handleRequest(request) {
900
- if (this.onRequest) {
901
- this.onRequest(request);
902
- }
903
- const url = new URL(request.url);
904
- if (url.pathname === this.callbackPath) {
905
- const params = {};
906
- for (const [key, value] of url.searchParams) {
907
- params[key] = value;
908
- }
909
- if (this.callbackPromise) {
910
- this.callbackPromise.resolve(params);
911
- }
912
- return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
913
- status: 200,
914
- headers: { "Content-Type": "text/html" }
915
- });
916
- }
917
- return new Response("Not Found", { status: 404 });
918
- }
919
- async waitForCallback(path2, timeout) {
920
- this.callbackPath = path2;
921
- return new Promise((resolve, reject) => {
922
- let isResolved = false;
923
- const timer = setTimeout(() => {
924
- if (!isResolved) {
925
- isResolved = true;
926
- this.callbackPromise = undefined;
927
- reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
928
- }
929
- }, timeout);
930
- const wrappedResolve = (result) => {
931
- if (!isResolved) {
932
- isResolved = true;
933
- clearTimeout(timer);
934
- this.callbackPromise = undefined;
935
- resolve(result);
936
- }
937
- };
938
- const wrappedReject = (error) => {
939
- if (!isResolved) {
940
- isResolved = true;
941
- clearTimeout(timer);
942
- this.callbackPromise = undefined;
943
- reject(error);
944
- }
945
- };
946
- this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
947
- });
948
- }
949
- async stop() {
950
- if (this.abortHandler) {
951
- const signal = this.server?.signal;
952
- if (signal) {
953
- signal.removeEventListener("abort", this.abortHandler);
954
- }
955
- this.abortHandler = undefined;
956
- }
957
- if (this.callbackPromise) {
958
- this.callbackPromise.reject(new Error("Server stopped before callback received"));
959
- this.callbackPromise = undefined;
960
- }
961
- if (this.server) {
962
- return new Promise((resolve) => {
963
- this.server.close(() => resolve());
964
- this.server = undefined;
965
- });
966
- }
967
- }
968
891
  }
969
892
  function createCallbackServer() {
970
- if (typeof Bun !== "undefined") {
893
+ if (typeof Bun !== "undefined")
971
894
  return new BunCallbackServer;
972
- }
973
- if (typeof Deno !== "undefined") {
895
+ if (typeof Deno !== "undefined")
974
896
  return new DenoCallbackServer;
975
- }
976
897
  return new NodeCallbackServer;
977
898
  }
978
899
  // src/storage/file.ts