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