nbound 1.0.6 → 1.1.0

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
@@ -5221,7 +5221,7 @@ var require_websocket = __commonJS((exports, module) => {
5221
5221
  var http = __require("http");
5222
5222
  var net = __require("net");
5223
5223
  var tls = __require("tls");
5224
- var { randomBytes: randomBytes2, createHash } = __require("crypto");
5224
+ var { randomBytes: randomBytes3, createHash } = __require("crypto");
5225
5225
  var { Duplex, Readable } = __require("stream");
5226
5226
  var { URL: URL2 } = __require("url");
5227
5227
  var PerMessageDeflate = require_permessage_deflate();
@@ -5630,7 +5630,7 @@ var require_websocket = __commonJS((exports, module) => {
5630
5630
  }
5631
5631
  }
5632
5632
  const defaultPort = isSecure ? 443 : 80;
5633
- const key = randomBytes2(16).toString("base64");
5633
+ const key = randomBytes3(16).toString("base64");
5634
5634
  const request2 = isSecure ? https.request : http.request;
5635
5635
  const protocolSet = new Set;
5636
5636
  let perMessageDeflate;
@@ -6439,7 +6439,8 @@ var {
6439
6439
  } = import__.default;
6440
6440
 
6441
6441
  // src/commands/login.ts
6442
- import { createInterface } from "node:readline";
6442
+ import { createServer } from "node:http";
6443
+ import { randomBytes as randomBytes2 } from "node:crypto";
6443
6444
 
6444
6445
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
6445
6446
  import process7 from "node:process";
@@ -6938,36 +6939,6 @@ defineLazyProperty(apps, "browser", () => "browser");
6938
6939
  defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
6939
6940
  var open_default = open;
6940
6941
 
6941
- // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
6942
- import { webcrypto as crypto } from "node:crypto";
6943
-
6944
- // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
6945
- var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
6946
-
6947
- // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
6948
- var POOL_SIZE_MULTIPLIER = 128;
6949
- var pool;
6950
- var poolOffset;
6951
- function fillPool(bytes) {
6952
- if (!pool || pool.length < bytes) {
6953
- pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
6954
- crypto.getRandomValues(pool);
6955
- poolOffset = 0;
6956
- } else if (poolOffset + bytes > pool.length) {
6957
- crypto.getRandomValues(pool);
6958
- poolOffset = 0;
6959
- }
6960
- poolOffset += bytes;
6961
- }
6962
- function nanoid(size = 21) {
6963
- fillPool(size |= 0);
6964
- let id = "";
6965
- for (let i = poolOffset - size;i < poolOffset; i++) {
6966
- id += urlAlphabet[pool[i] & 63];
6967
- }
6968
- return id;
6969
- }
6970
-
6971
6942
  // src/lib/config.ts
6972
6943
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, renameSync, chmodSync } from "node:fs";
6973
6944
  import { homedir } from "node:os";
@@ -7164,20 +7135,6 @@ async function createEndpoint(token, input) {
7164
7135
  });
7165
7136
  return result;
7166
7137
  }
7167
- async function exchangeCliCode(sessionId, code) {
7168
- const apiUrl = getApiUrl();
7169
- const response = await fetch(`${apiUrl}/api/auth/cli-exchange`, {
7170
- method: "POST",
7171
- headers: { "Content-Type": "application/json" },
7172
- body: JSON.stringify({ sessionId, code }),
7173
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
7174
- });
7175
- const json = await response.json();
7176
- if (!response.ok || !json.success) {
7177
- throw new ApiError(response.status, json.error?.code ?? "UNKNOWN_ERROR", json.error?.message ?? "Failed to exchange code");
7178
- }
7179
- return json.data;
7180
- }
7181
7138
  async function revokeCliToken(token) {
7182
7139
  const apiUrl = getApiUrl();
7183
7140
  try {
@@ -8553,18 +8510,130 @@ function createSpinner(text) {
8553
8510
  }
8554
8511
 
8555
8512
  // src/commands/login.ts
8556
- function prompt(question) {
8557
- const rl = createInterface({
8558
- input: process.stdin,
8559
- output: process.stdout
8560
- });
8513
+ var DEFAULT_PORT = 9847;
8514
+ var PORT_RANGE_START = 9800;
8515
+ var PORT_RANGE_END = 9900;
8516
+ var AUTH_TIMEOUT_MS = 2 * 60 * 1000;
8517
+ function tryPort(port) {
8561
8518
  return new Promise((resolve) => {
8562
- rl.question(question, (answer) => {
8563
- rl.close();
8564
- resolve(answer.trim());
8519
+ const server = createServer();
8520
+ server.once("error", (err) => {
8521
+ if (err.code === "EADDRINUSE") {
8522
+ resolve(null);
8523
+ } else {
8524
+ resolve(null);
8525
+ }
8526
+ });
8527
+ server.listen(port, "127.0.0.1", () => {
8528
+ resolve(server);
8565
8529
  });
8566
8530
  });
8567
8531
  }
8532
+ async function findAvailablePort() {
8533
+ const defaultServer = await tryPort(DEFAULT_PORT);
8534
+ if (defaultServer) {
8535
+ return { server: defaultServer, port: DEFAULT_PORT };
8536
+ }
8537
+ for (let i = 0;i < 10; i++) {
8538
+ const port = PORT_RANGE_START + Math.floor(Math.random() * (PORT_RANGE_END - PORT_RANGE_START));
8539
+ const server = await tryPort(port);
8540
+ if (server) {
8541
+ return { server, port };
8542
+ }
8543
+ }
8544
+ return null;
8545
+ }
8546
+ function generateState() {
8547
+ return randomBytes2(16).toString("hex");
8548
+ }
8549
+ var SUCCESS_HTML = `<!DOCTYPE html>
8550
+ <html>
8551
+ <head>
8552
+ <title>Authenticated - nbound</title>
8553
+ <style>
8554
+ body {
8555
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8556
+ display: flex;
8557
+ justify-content: center;
8558
+ align-items: center;
8559
+ min-height: 100vh;
8560
+ margin: 0;
8561
+ background: #0a0a0a;
8562
+ color: #fafafa;
8563
+ }
8564
+ .container {
8565
+ text-align: center;
8566
+ padding: 2rem;
8567
+ }
8568
+ .icon {
8569
+ font-size: 4rem;
8570
+ margin-bottom: 1rem;
8571
+ }
8572
+ h1 {
8573
+ font-size: 1.5rem;
8574
+ font-weight: 600;
8575
+ margin: 0 0 0.5rem;
8576
+ }
8577
+ p {
8578
+ color: #a1a1aa;
8579
+ margin: 0;
8580
+ }
8581
+ </style>
8582
+ </head>
8583
+ <body>
8584
+ <div class="container">
8585
+ <div class="icon">&#10003;</div>
8586
+ <h1>Authenticated!</h1>
8587
+ <p>You can close this tab and return to your terminal.</p>
8588
+ </div>
8589
+ <script>window.close()</script>
8590
+ </body>
8591
+ </html>`;
8592
+ function errorHtml(message) {
8593
+ return `<!DOCTYPE html>
8594
+ <html>
8595
+ <head>
8596
+ <title>Authentication Failed - nbound</title>
8597
+ <style>
8598
+ body {
8599
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8600
+ display: flex;
8601
+ justify-content: center;
8602
+ align-items: center;
8603
+ min-height: 100vh;
8604
+ margin: 0;
8605
+ background: #0a0a0a;
8606
+ color: #fafafa;
8607
+ }
8608
+ .container {
8609
+ text-align: center;
8610
+ padding: 2rem;
8611
+ }
8612
+ .icon {
8613
+ font-size: 4rem;
8614
+ margin-bottom: 1rem;
8615
+ color: #ef4444;
8616
+ }
8617
+ h1 {
8618
+ font-size: 1.5rem;
8619
+ font-weight: 600;
8620
+ margin: 0 0 0.5rem;
8621
+ }
8622
+ p {
8623
+ color: #a1a1aa;
8624
+ margin: 0;
8625
+ }
8626
+ </style>
8627
+ </head>
8628
+ <body>
8629
+ <div class="container">
8630
+ <div class="icon">&#10007;</div>
8631
+ <h1>Authentication Failed</h1>
8632
+ <p>${message}</p>
8633
+ </div>
8634
+ </body>
8635
+ </html>`;
8636
+ }
8568
8637
  async function loginCommand(options) {
8569
8638
  const existingConfig = loadConfig();
8570
8639
  if (existingConfig && !options.token) {
@@ -8574,15 +8643,15 @@ async function loginCommand(options) {
8574
8643
  const expiry = checkTokenExpiry(existingConfig);
8575
8644
  if (expiry === "expiring_soon") {
8576
8645
  const daysLeft = getDaysUntilExpiry(existingConfig);
8577
- log.warning(`Your token expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"}. Run: nbound login to refresh.`);
8646
+ log.warning(`Your token expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"}. Run: nbound logout && nbound login`);
8578
8647
  }
8579
8648
  log.dim("Run: nbound logout to switch accounts");
8580
8649
  return;
8581
8650
  } catch {}
8582
8651
  }
8583
8652
  if (options.token) {
8584
- const spinner2 = createSpinner("Validating token...");
8585
- spinner2.start();
8653
+ const spinner = createSpinner("Validating token...");
8654
+ spinner.start();
8586
8655
  try {
8587
8656
  const user = await getUser(options.token);
8588
8657
  const apiUrl2 = getApiUrl();
@@ -8592,21 +8661,70 @@ async function loginCommand(options) {
8592
8661
  apiUrl: apiUrl2,
8593
8662
  wsUrl: apiUrl2.replace("https://", "wss://").replace("http://", "ws://") + "/ws"
8594
8663
  });
8595
- spinner2.succeed(`Logged in as ${user.email}`);
8664
+ spinner.succeed(`Logged in as ${user.email}`);
8596
8665
  } catch (error) {
8597
- spinner2.fail("Invalid token");
8666
+ spinner.fail("Invalid token");
8598
8667
  if (error instanceof ApiError) {
8599
8668
  log.error(error.message);
8600
8669
  }
8601
- process.exit(2);
8670
+ process.exit(1);
8602
8671
  }
8603
8672
  return;
8604
8673
  }
8605
- const sessionId = nanoid(21);
8606
- const apiUrl = getApiUrl();
8607
- const appUrl = apiUrl.replace("://api.", "://app.");
8608
- const authUrl = `${appUrl}/cli/auth?session=${sessionId}`;
8609
8674
  log.blank();
8675
+ const portResult = await findAvailablePort();
8676
+ if (!portResult) {
8677
+ log.error("Could not find an available port for authentication callback.");
8678
+ log.dim("Try closing other applications or run: nbound login --token <token>");
8679
+ process.exit(1);
8680
+ }
8681
+ const { server, port } = portResult;
8682
+ const state = generateState();
8683
+ const apiUrl = getApiUrl();
8684
+ const redirectUrl = `http://localhost:${port}/callback`;
8685
+ const authUrl = `${apiUrl}/api/cli/auth?redirect=${encodeURIComponent(redirectUrl)}&state=${state}`;
8686
+ const timeoutHandle = setTimeout(() => {
8687
+ server.close();
8688
+ log.blank();
8689
+ log.error("Authentication timed out after 2 minutes.");
8690
+ log.dim("Please try again: nbound login");
8691
+ process.exit(1);
8692
+ }, AUTH_TIMEOUT_MS);
8693
+ const authPromise = new Promise((resolve, reject) => {
8694
+ server.on("request", (req, res) => {
8695
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
8696
+ if (url.pathname !== "/callback") {
8697
+ res.writeHead(404);
8698
+ res.end("Not found");
8699
+ return;
8700
+ }
8701
+ const receivedState = url.searchParams.get("state");
8702
+ const token = url.searchParams.get("token");
8703
+ const error = url.searchParams.get("error");
8704
+ const expiresAt = url.searchParams.get("expiresAt");
8705
+ if (error) {
8706
+ res.writeHead(200, { "Content-Type": "text/html" });
8707
+ res.end(errorHtml(error));
8708
+ reject(new Error(error));
8709
+ return;
8710
+ }
8711
+ if (receivedState !== state) {
8712
+ res.writeHead(200, { "Content-Type": "text/html" });
8713
+ res.end(errorHtml("Security validation failed. Please try again."));
8714
+ reject(new Error("State mismatch - possible CSRF attack"));
8715
+ return;
8716
+ }
8717
+ if (!token) {
8718
+ res.writeHead(200, { "Content-Type": "text/html" });
8719
+ res.end(errorHtml("No authentication token received."));
8720
+ reject(new Error("No token received"));
8721
+ return;
8722
+ }
8723
+ res.writeHead(200, { "Content-Type": "text/html" });
8724
+ res.end(SUCCESS_HTML);
8725
+ resolve({ token, expiresAt: expiresAt || undefined });
8726
+ });
8727
+ });
8610
8728
  log.info("Opening browser to authenticate...");
8611
8729
  log.dim(authUrl);
8612
8730
  log.blank();
@@ -8617,31 +8735,37 @@ async function loginCommand(options) {
8617
8735
  log.info("Please open this URL in your browser:");
8618
8736
  log.info(authUrl);
8619
8737
  }
8620
- log.blank();
8621
- const code = await prompt("Enter the code from the browser: ");
8622
- if (!code) {
8623
- log.error("No code provided");
8624
- process.exit(4);
8625
- }
8626
- const spinner = createSpinner("Verifying code...");
8627
- spinner.start();
8738
+ log.dim("Waiting for authentication...");
8628
8739
  try {
8629
- const { token, user, expiresAt } = await exchangeCliCode(sessionId, code);
8630
- saveConfig({
8631
- token,
8632
- user: { id: user.id, email: user.email },
8633
- apiUrl,
8634
- wsUrl: apiUrl.replace("https://", "wss://").replace("http://", "ws://") + "/ws",
8635
- expiresAt
8636
- });
8637
- spinner.succeed(`Logged in as ${user.email}`);
8638
- log.blank();
8639
- } catch (error) {
8640
- spinner.fail("Failed to verify code");
8641
- if (error instanceof ApiError) {
8642
- log.error(error.message);
8740
+ const { token, expiresAt } = await authPromise;
8741
+ clearTimeout(timeoutHandle);
8742
+ server.close();
8743
+ const spinner = createSpinner("Verifying token...");
8744
+ spinner.start();
8745
+ try {
8746
+ const user = await getUser(token);
8747
+ saveConfig({
8748
+ token,
8749
+ user: { id: user.id, email: user.email },
8750
+ apiUrl,
8751
+ wsUrl: apiUrl.replace("https://", "wss://").replace("http://", "ws://") + "/ws",
8752
+ expiresAt
8753
+ });
8754
+ spinner.succeed(`Logged in as ${user.email}`);
8755
+ log.blank();
8756
+ } catch (error) {
8757
+ spinner.fail("Failed to verify token");
8758
+ if (error instanceof ApiError) {
8759
+ log.error(error.message);
8760
+ }
8761
+ process.exit(1);
8643
8762
  }
8644
- process.exit(2);
8763
+ } catch (error) {
8764
+ clearTimeout(timeoutHandle);
8765
+ server.close();
8766
+ log.blank();
8767
+ log.error(error instanceof Error ? error.message : "Authentication failed");
8768
+ process.exit(1);
8645
8769
  }
8646
8770
  }
8647
8771
 
@@ -12794,7 +12918,7 @@ function getBackoffDelay(attempt, baseMs = 1000, maxMs = 30000, jitter = true) {
12794
12918
  // src/lib/ws-client.ts
12795
12919
  var PING_INTERVAL_MS = 30000;
12796
12920
  var PONG_TIMEOUT_MS = 1e4;
12797
- var AUTH_TIMEOUT_MS = 1e4;
12921
+ var AUTH_TIMEOUT_MS2 = 1e4;
12798
12922
 
12799
12923
  class WebSocketClient {
12800
12924
  ws = null;
@@ -12849,10 +12973,15 @@ class WebSocketClient {
12849
12973
  this.options.onError(new Error(`Connection closed during auth: ${reasonStr}`));
12850
12974
  } else if (this.shouldReconnect && prevState === "connecting") {
12851
12975
  this.scheduleReconnect();
12976
+ } else if (this.shouldReconnect && prevState === "authenticated") {
12977
+ this.scheduleReconnect();
12852
12978
  }
12853
12979
  });
12854
12980
  this.ws.on("error", (error) => {
12855
12981
  if (this.state !== "subscribed" && this.shouldReconnect) {
12982
+ if (this.state === "authenticated" || this.state === "authenticating") {
12983
+ this.options.onDisconnected(`Connection error: ${error.message}`);
12984
+ }
12856
12985
  this.scheduleReconnect();
12857
12986
  } else {
12858
12987
  this.options.onError(error);
@@ -12907,6 +13036,12 @@ class WebSocketClient {
12907
13036
  this.disconnect();
12908
13037
  break;
12909
13038
  }
13039
+ case "session_replaced": {
13040
+ this.shouldReconnect = false;
13041
+ this.options.onError(new Error("Another CLI session connected to this endpoint."));
13042
+ this.disconnect();
13043
+ break;
13044
+ }
12910
13045
  case "plan_changed": {
12911
13046
  const payload = message.payload;
12912
13047
  this.plan = payload.plan;
@@ -13012,7 +13147,7 @@ class WebSocketClient {
13012
13147
  this.options.onError(new Error("Authentication timed out"));
13013
13148
  this.disconnect();
13014
13149
  }
13015
- }, AUTH_TIMEOUT_MS);
13150
+ }, AUTH_TIMEOUT_MS2);
13016
13151
  }
13017
13152
  clearAuthTimeout() {
13018
13153
  if (this.authTimeout) {
@@ -13578,7 +13713,7 @@ async function createCommand2(nameArg, options) {
13578
13713
  }
13579
13714
 
13580
13715
  // src/index.ts
13581
- program.name("nbound").description("Forward webhooks to localhost for development").version("1.0.6");
13716
+ program.name("nbound").description("Forward webhooks to localhost for development").version("1.1.0");
13582
13717
  program.command("login").description("Authenticate with nbound").option("--token <token>", "Use an existing API token").action(loginCommand);
13583
13718
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
13584
13719
  program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
@@ -13587,5 +13722,5 @@ program.command("endpoints").description("List your endpoints").option("--json",
13587
13722
  program.command("create [name]").description("Create a new endpoint").option("-n, --name <name>", "Endpoint name").option("-d, --description <description>", "Endpoint description").action(createCommand2);
13588
13723
  program.parse();
13589
13724
 
13590
- //# debugId=A13DD1F1898AF0FC64756E2164756E21
13725
+ //# debugId=C24205CDBF507F4264756E2164756E21
13591
13726
  //# sourceMappingURL=index.js.map