nbound 1.0.6 → 1.1.1
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/bin/nbound.js +328 -91
- package/dist/index.js +329 -92
- package/dist/index.js.map +7 -9
- package/package.json +8 -3
package/dist/bin/nbound.js
CHANGED
|
@@ -5223,7 +5223,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
5223
5223
|
var http = __require("http");
|
|
5224
5224
|
var net = __require("net");
|
|
5225
5225
|
var tls = __require("tls");
|
|
5226
|
-
var { randomBytes:
|
|
5226
|
+
var { randomBytes: randomBytes3, createHash } = __require("crypto");
|
|
5227
5227
|
var { Duplex, Readable } = __require("stream");
|
|
5228
5228
|
var { URL: URL2 } = __require("url");
|
|
5229
5229
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -5632,7 +5632,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
5632
5632
|
}
|
|
5633
5633
|
}
|
|
5634
5634
|
const defaultPort = isSecure ? 443 : 80;
|
|
5635
|
-
const key =
|
|
5635
|
+
const key = randomBytes3(16).toString("base64");
|
|
5636
5636
|
const request2 = isSecure ? https.request : http.request;
|
|
5637
5637
|
const protocolSet = new Set;
|
|
5638
5638
|
let perMessageDeflate;
|
|
@@ -6441,7 +6441,8 @@ var {
|
|
|
6441
6441
|
} = import__.default;
|
|
6442
6442
|
|
|
6443
6443
|
// src/commands/login.ts
|
|
6444
|
-
import {
|
|
6444
|
+
import { createServer } from "node:http";
|
|
6445
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6445
6446
|
|
|
6446
6447
|
// ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
|
|
6447
6448
|
import process7 from "node:process";
|
|
@@ -6940,36 +6941,6 @@ defineLazyProperty(apps, "browser", () => "browser");
|
|
|
6940
6941
|
defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
|
|
6941
6942
|
var open_default = open;
|
|
6942
6943
|
|
|
6943
|
-
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
6944
|
-
import { webcrypto as crypto } from "node:crypto";
|
|
6945
|
-
|
|
6946
|
-
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
|
|
6947
|
-
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
6948
|
-
|
|
6949
|
-
// ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
|
|
6950
|
-
var POOL_SIZE_MULTIPLIER = 128;
|
|
6951
|
-
var pool;
|
|
6952
|
-
var poolOffset;
|
|
6953
|
-
function fillPool(bytes) {
|
|
6954
|
-
if (!pool || pool.length < bytes) {
|
|
6955
|
-
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
6956
|
-
crypto.getRandomValues(pool);
|
|
6957
|
-
poolOffset = 0;
|
|
6958
|
-
} else if (poolOffset + bytes > pool.length) {
|
|
6959
|
-
crypto.getRandomValues(pool);
|
|
6960
|
-
poolOffset = 0;
|
|
6961
|
-
}
|
|
6962
|
-
poolOffset += bytes;
|
|
6963
|
-
}
|
|
6964
|
-
function nanoid(size = 21) {
|
|
6965
|
-
fillPool(size |= 0);
|
|
6966
|
-
let id = "";
|
|
6967
|
-
for (let i = poolOffset - size;i < poolOffset; i++) {
|
|
6968
|
-
id += urlAlphabet[pool[i] & 63];
|
|
6969
|
-
}
|
|
6970
|
-
return id;
|
|
6971
|
-
}
|
|
6972
|
-
|
|
6973
6944
|
// src/lib/config.ts
|
|
6974
6945
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, renameSync, chmodSync } from "node:fs";
|
|
6975
6946
|
import { homedir } from "node:os";
|
|
@@ -7166,20 +7137,6 @@ async function createEndpoint(token, input) {
|
|
|
7166
7137
|
});
|
|
7167
7138
|
return result;
|
|
7168
7139
|
}
|
|
7169
|
-
async function exchangeCliCode(sessionId, code) {
|
|
7170
|
-
const apiUrl = getApiUrl();
|
|
7171
|
-
const response = await fetch(`${apiUrl}/api/auth/cli-exchange`, {
|
|
7172
|
-
method: "POST",
|
|
7173
|
-
headers: { "Content-Type": "application/json" },
|
|
7174
|
-
body: JSON.stringify({ sessionId, code }),
|
|
7175
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
7176
|
-
});
|
|
7177
|
-
const json = await response.json();
|
|
7178
|
-
if (!response.ok || !json.success) {
|
|
7179
|
-
throw new ApiError(response.status, json.error?.code ?? "UNKNOWN_ERROR", json.error?.message ?? "Failed to exchange code");
|
|
7180
|
-
}
|
|
7181
|
-
return json.data;
|
|
7182
|
-
}
|
|
7183
7140
|
async function revokeCliToken(token) {
|
|
7184
7141
|
const apiUrl = getApiUrl();
|
|
7185
7142
|
try {
|
|
@@ -8555,18 +8512,232 @@ function createSpinner(text) {
|
|
|
8555
8512
|
}
|
|
8556
8513
|
|
|
8557
8514
|
// src/commands/login.ts
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8515
|
+
var DEFAULT_PORT = 9847;
|
|
8516
|
+
var PORT_RANGE_START = 9800;
|
|
8517
|
+
var PORT_RANGE_END = 9900;
|
|
8518
|
+
var AUTH_TIMEOUT_MS = 2 * 60 * 1000;
|
|
8519
|
+
function tryPort(port) {
|
|
8563
8520
|
return new Promise((resolve) => {
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8521
|
+
const server = createServer();
|
|
8522
|
+
server.once("error", (err) => {
|
|
8523
|
+
if (err.code === "EADDRINUSE") {
|
|
8524
|
+
resolve(null);
|
|
8525
|
+
} else {
|
|
8526
|
+
resolve(null);
|
|
8527
|
+
}
|
|
8528
|
+
});
|
|
8529
|
+
server.listen(port, "127.0.0.1", () => {
|
|
8530
|
+
resolve(server);
|
|
8567
8531
|
});
|
|
8568
8532
|
});
|
|
8569
8533
|
}
|
|
8534
|
+
async function findAvailablePort() {
|
|
8535
|
+
const defaultServer = await tryPort(DEFAULT_PORT);
|
|
8536
|
+
if (defaultServer) {
|
|
8537
|
+
return { server: defaultServer, port: DEFAULT_PORT };
|
|
8538
|
+
}
|
|
8539
|
+
for (let i = 0;i < 10; i++) {
|
|
8540
|
+
const port = PORT_RANGE_START + Math.floor(Math.random() * (PORT_RANGE_END - PORT_RANGE_START));
|
|
8541
|
+
const server = await tryPort(port);
|
|
8542
|
+
if (server) {
|
|
8543
|
+
return { server, port };
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
8546
|
+
return null;
|
|
8547
|
+
}
|
|
8548
|
+
function generateState() {
|
|
8549
|
+
return randomBytes2(16).toString("hex");
|
|
8550
|
+
}
|
|
8551
|
+
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
8552
|
+
<html>
|
|
8553
|
+
<head>
|
|
8554
|
+
<title>Authenticated - nbound</title>
|
|
8555
|
+
<style>
|
|
8556
|
+
* { box-sizing: border-box; }
|
|
8557
|
+
body {
|
|
8558
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
8559
|
+
display: flex;
|
|
8560
|
+
justify-content: center;
|
|
8561
|
+
align-items: center;
|
|
8562
|
+
min-height: 100vh;
|
|
8563
|
+
margin: 0;
|
|
8564
|
+
background: #0a0a0a;
|
|
8565
|
+
color: #fafafa;
|
|
8566
|
+
}
|
|
8567
|
+
.container {
|
|
8568
|
+
text-align: center;
|
|
8569
|
+
padding: 2rem;
|
|
8570
|
+
animation: fadeIn 0.3s ease-out;
|
|
8571
|
+
}
|
|
8572
|
+
@keyframes fadeIn {
|
|
8573
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
8574
|
+
to { opacity: 1; transform: translateY(0); }
|
|
8575
|
+
}
|
|
8576
|
+
@keyframes checkmark {
|
|
8577
|
+
0% { transform: scale(0); }
|
|
8578
|
+
50% { transform: scale(1.2); }
|
|
8579
|
+
100% { transform: scale(1); }
|
|
8580
|
+
}
|
|
8581
|
+
.icon-wrapper {
|
|
8582
|
+
width: 80px;
|
|
8583
|
+
height: 80px;
|
|
8584
|
+
margin: 0 auto 1.5rem;
|
|
8585
|
+
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
8586
|
+
border-radius: 50%;
|
|
8587
|
+
display: flex;
|
|
8588
|
+
align-items: center;
|
|
8589
|
+
justify-content: center;
|
|
8590
|
+
animation: checkmark 0.4s ease-out;
|
|
8591
|
+
}
|
|
8592
|
+
.icon {
|
|
8593
|
+
font-size: 2.5rem;
|
|
8594
|
+
color: white;
|
|
8595
|
+
line-height: 1;
|
|
8596
|
+
}
|
|
8597
|
+
.logo {
|
|
8598
|
+
font-size: 0.875rem;
|
|
8599
|
+
font-weight: 600;
|
|
8600
|
+
letter-spacing: 0.05em;
|
|
8601
|
+
color: #71717a;
|
|
8602
|
+
margin-bottom: 1.5rem;
|
|
8603
|
+
text-transform: uppercase;
|
|
8604
|
+
}
|
|
8605
|
+
h1 {
|
|
8606
|
+
font-size: 1.75rem;
|
|
8607
|
+
font-weight: 600;
|
|
8608
|
+
margin: 0 0 0.5rem;
|
|
8609
|
+
}
|
|
8610
|
+
p {
|
|
8611
|
+
color: #a1a1aa;
|
|
8612
|
+
margin: 0;
|
|
8613
|
+
font-size: 1rem;
|
|
8614
|
+
}
|
|
8615
|
+
.hint {
|
|
8616
|
+
margin-top: 2rem;
|
|
8617
|
+
padding: 0.75rem 1.5rem;
|
|
8618
|
+
background: #18181b;
|
|
8619
|
+
border-radius: 8px;
|
|
8620
|
+
border: 1px solid #27272a;
|
|
8621
|
+
color: #71717a;
|
|
8622
|
+
font-size: 0.875rem;
|
|
8623
|
+
}
|
|
8624
|
+
.hint kbd {
|
|
8625
|
+
background: #27272a;
|
|
8626
|
+
padding: 0.125rem 0.375rem;
|
|
8627
|
+
border-radius: 4px;
|
|
8628
|
+
font-family: ui-monospace, monospace;
|
|
8629
|
+
font-size: 0.8rem;
|
|
8630
|
+
color: #a1a1aa;
|
|
8631
|
+
}
|
|
8632
|
+
</style>
|
|
8633
|
+
</head>
|
|
8634
|
+
<body>
|
|
8635
|
+
<div class="container">
|
|
8636
|
+
<div class="logo">nbound</div>
|
|
8637
|
+
<div class="icon-wrapper">
|
|
8638
|
+
<div class="icon">✓</div>
|
|
8639
|
+
</div>
|
|
8640
|
+
<h1>You're in!</h1>
|
|
8641
|
+
<p>Authentication successful. Return to your terminal.</p>
|
|
8642
|
+
<div class="hint">Press <kbd>Cmd</kbd> + <kbd>Tab</kbd> to switch back</div>
|
|
8643
|
+
</div>
|
|
8644
|
+
<script>setTimeout(() => window.close(), 1500)</script>
|
|
8645
|
+
</body>
|
|
8646
|
+
</html>`;
|
|
8647
|
+
function errorHtml(message) {
|
|
8648
|
+
return `<!DOCTYPE html>
|
|
8649
|
+
<html>
|
|
8650
|
+
<head>
|
|
8651
|
+
<title>Authentication Failed - nbound</title>
|
|
8652
|
+
<style>
|
|
8653
|
+
* { box-sizing: border-box; }
|
|
8654
|
+
body {
|
|
8655
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
8656
|
+
display: flex;
|
|
8657
|
+
justify-content: center;
|
|
8658
|
+
align-items: center;
|
|
8659
|
+
min-height: 100vh;
|
|
8660
|
+
margin: 0;
|
|
8661
|
+
background: #0a0a0a;
|
|
8662
|
+
color: #fafafa;
|
|
8663
|
+
}
|
|
8664
|
+
.container {
|
|
8665
|
+
text-align: center;
|
|
8666
|
+
padding: 2rem;
|
|
8667
|
+
animation: fadeIn 0.3s ease-out;
|
|
8668
|
+
}
|
|
8669
|
+
@keyframes fadeIn {
|
|
8670
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
8671
|
+
to { opacity: 1; transform: translateY(0); }
|
|
8672
|
+
}
|
|
8673
|
+
@keyframes shake {
|
|
8674
|
+
0%, 100% { transform: translateX(0); }
|
|
8675
|
+
20%, 60% { transform: translateX(-5px); }
|
|
8676
|
+
40%, 80% { transform: translateX(5px); }
|
|
8677
|
+
}
|
|
8678
|
+
.icon-wrapper {
|
|
8679
|
+
width: 80px;
|
|
8680
|
+
height: 80px;
|
|
8681
|
+
margin: 0 auto 1.5rem;
|
|
8682
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
8683
|
+
border-radius: 50%;
|
|
8684
|
+
display: flex;
|
|
8685
|
+
align-items: center;
|
|
8686
|
+
justify-content: center;
|
|
8687
|
+
animation: shake 0.4s ease-out;
|
|
8688
|
+
}
|
|
8689
|
+
.icon {
|
|
8690
|
+
font-size: 2.5rem;
|
|
8691
|
+
color: white;
|
|
8692
|
+
line-height: 1;
|
|
8693
|
+
}
|
|
8694
|
+
.logo {
|
|
8695
|
+
font-size: 0.875rem;
|
|
8696
|
+
font-weight: 600;
|
|
8697
|
+
letter-spacing: 0.05em;
|
|
8698
|
+
color: #71717a;
|
|
8699
|
+
margin-bottom: 1.5rem;
|
|
8700
|
+
text-transform: uppercase;
|
|
8701
|
+
}
|
|
8702
|
+
h1 {
|
|
8703
|
+
font-size: 1.75rem;
|
|
8704
|
+
font-weight: 600;
|
|
8705
|
+
margin: 0 0 0.5rem;
|
|
8706
|
+
}
|
|
8707
|
+
p {
|
|
8708
|
+
color: #a1a1aa;
|
|
8709
|
+
margin: 0;
|
|
8710
|
+
font-size: 1rem;
|
|
8711
|
+
max-width: 300px;
|
|
8712
|
+
}
|
|
8713
|
+
.hint {
|
|
8714
|
+
margin-top: 2rem;
|
|
8715
|
+
padding: 0.75rem 1.5rem;
|
|
8716
|
+
background: #18181b;
|
|
8717
|
+
border-radius: 8px;
|
|
8718
|
+
border: 1px solid #27272a;
|
|
8719
|
+
color: #71717a;
|
|
8720
|
+
font-size: 0.875rem;
|
|
8721
|
+
}
|
|
8722
|
+
.hint code {
|
|
8723
|
+
color: #a1a1aa;
|
|
8724
|
+
font-family: ui-monospace, monospace;
|
|
8725
|
+
}
|
|
8726
|
+
</style>
|
|
8727
|
+
</head>
|
|
8728
|
+
<body>
|
|
8729
|
+
<div class="container">
|
|
8730
|
+
<div class="logo">nbound</div>
|
|
8731
|
+
<div class="icon-wrapper">
|
|
8732
|
+
<div class="icon">✗</div>
|
|
8733
|
+
</div>
|
|
8734
|
+
<h1>Authentication Failed</h1>
|
|
8735
|
+
<p>${message}</p>
|
|
8736
|
+
<div class="hint">Try again: <code>nbound login</code></div>
|
|
8737
|
+
</div>
|
|
8738
|
+
</body>
|
|
8739
|
+
</html>`;
|
|
8740
|
+
}
|
|
8570
8741
|
async function loginCommand(options) {
|
|
8571
8742
|
const existingConfig = loadConfig();
|
|
8572
8743
|
if (existingConfig && !options.token) {
|
|
@@ -8576,15 +8747,15 @@ async function loginCommand(options) {
|
|
|
8576
8747
|
const expiry = checkTokenExpiry(existingConfig);
|
|
8577
8748
|
if (expiry === "expiring_soon") {
|
|
8578
8749
|
const daysLeft = getDaysUntilExpiry(existingConfig);
|
|
8579
|
-
log.warning(`Your token expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"}. Run: nbound
|
|
8750
|
+
log.warning(`Your token expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"}. Run: nbound logout && nbound login`);
|
|
8580
8751
|
}
|
|
8581
8752
|
log.dim("Run: nbound logout to switch accounts");
|
|
8582
8753
|
return;
|
|
8583
8754
|
} catch {}
|
|
8584
8755
|
}
|
|
8585
8756
|
if (options.token) {
|
|
8586
|
-
const
|
|
8587
|
-
|
|
8757
|
+
const spinner = createSpinner("Validating token...");
|
|
8758
|
+
spinner.start();
|
|
8588
8759
|
try {
|
|
8589
8760
|
const user = await getUser(options.token);
|
|
8590
8761
|
const apiUrl2 = getApiUrl();
|
|
@@ -8594,21 +8765,70 @@ async function loginCommand(options) {
|
|
|
8594
8765
|
apiUrl: apiUrl2,
|
|
8595
8766
|
wsUrl: apiUrl2.replace("https://", "wss://").replace("http://", "ws://") + "/ws"
|
|
8596
8767
|
});
|
|
8597
|
-
|
|
8768
|
+
spinner.succeed(`Logged in as ${user.email}`);
|
|
8598
8769
|
} catch (error) {
|
|
8599
|
-
|
|
8770
|
+
spinner.fail("Invalid token");
|
|
8600
8771
|
if (error instanceof ApiError) {
|
|
8601
8772
|
log.error(error.message);
|
|
8602
8773
|
}
|
|
8603
|
-
process.exit(
|
|
8774
|
+
process.exit(1);
|
|
8604
8775
|
}
|
|
8605
8776
|
return;
|
|
8606
8777
|
}
|
|
8607
|
-
const sessionId = nanoid(21);
|
|
8608
|
-
const apiUrl = getApiUrl();
|
|
8609
|
-
const appUrl = apiUrl.replace("://api.", "://app.");
|
|
8610
|
-
const authUrl = `${appUrl}/cli/auth?session=${sessionId}`;
|
|
8611
8778
|
log.blank();
|
|
8779
|
+
const portResult = await findAvailablePort();
|
|
8780
|
+
if (!portResult) {
|
|
8781
|
+
log.error("Could not find an available port for authentication callback.");
|
|
8782
|
+
log.dim("Try closing other applications or run: nbound login --token <token>");
|
|
8783
|
+
process.exit(1);
|
|
8784
|
+
}
|
|
8785
|
+
const { server, port } = portResult;
|
|
8786
|
+
const state = generateState();
|
|
8787
|
+
const apiUrl = getApiUrl();
|
|
8788
|
+
const redirectUrl = `http://localhost:${port}/callback`;
|
|
8789
|
+
const authUrl = `${apiUrl}/api/cli/auth?redirect=${encodeURIComponent(redirectUrl)}&state=${state}`;
|
|
8790
|
+
const timeoutHandle = setTimeout(() => {
|
|
8791
|
+
server.close();
|
|
8792
|
+
log.blank();
|
|
8793
|
+
log.error("Authentication timed out after 2 minutes.");
|
|
8794
|
+
log.dim("Please try again: nbound login");
|
|
8795
|
+
process.exit(1);
|
|
8796
|
+
}, AUTH_TIMEOUT_MS);
|
|
8797
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
8798
|
+
server.on("request", (req, res) => {
|
|
8799
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
8800
|
+
if (url.pathname !== "/callback") {
|
|
8801
|
+
res.writeHead(404);
|
|
8802
|
+
res.end("Not found");
|
|
8803
|
+
return;
|
|
8804
|
+
}
|
|
8805
|
+
const receivedState = url.searchParams.get("state");
|
|
8806
|
+
const token = url.searchParams.get("token");
|
|
8807
|
+
const error = url.searchParams.get("error");
|
|
8808
|
+
const expiresAt = url.searchParams.get("expiresAt");
|
|
8809
|
+
if (error) {
|
|
8810
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
8811
|
+
res.end(errorHtml(error));
|
|
8812
|
+
reject(new Error(error));
|
|
8813
|
+
return;
|
|
8814
|
+
}
|
|
8815
|
+
if (receivedState !== state) {
|
|
8816
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
8817
|
+
res.end(errorHtml("Security validation failed. Please try again."));
|
|
8818
|
+
reject(new Error("State mismatch - possible CSRF attack"));
|
|
8819
|
+
return;
|
|
8820
|
+
}
|
|
8821
|
+
if (!token) {
|
|
8822
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
8823
|
+
res.end(errorHtml("No authentication token received."));
|
|
8824
|
+
reject(new Error("No token received"));
|
|
8825
|
+
return;
|
|
8826
|
+
}
|
|
8827
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
8828
|
+
res.end(SUCCESS_HTML);
|
|
8829
|
+
resolve({ token, expiresAt: expiresAt || undefined });
|
|
8830
|
+
});
|
|
8831
|
+
});
|
|
8612
8832
|
log.info("Opening browser to authenticate...");
|
|
8613
8833
|
log.dim(authUrl);
|
|
8614
8834
|
log.blank();
|
|
@@ -8619,31 +8839,37 @@ async function loginCommand(options) {
|
|
|
8619
8839
|
log.info("Please open this URL in your browser:");
|
|
8620
8840
|
log.info(authUrl);
|
|
8621
8841
|
}
|
|
8622
|
-
log.
|
|
8623
|
-
const code = await prompt("Enter the code from the browser: ");
|
|
8624
|
-
if (!code) {
|
|
8625
|
-
log.error("No code provided");
|
|
8626
|
-
process.exit(4);
|
|
8627
|
-
}
|
|
8628
|
-
const spinner = createSpinner("Verifying code...");
|
|
8629
|
-
spinner.start();
|
|
8842
|
+
log.dim("Waiting for authentication...");
|
|
8630
8843
|
try {
|
|
8631
|
-
const { token,
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8844
|
+
const { token, expiresAt } = await authPromise;
|
|
8845
|
+
clearTimeout(timeoutHandle);
|
|
8846
|
+
server.close();
|
|
8847
|
+
const spinner = createSpinner("Verifying token...");
|
|
8848
|
+
spinner.start();
|
|
8849
|
+
try {
|
|
8850
|
+
const user = await getUser(token);
|
|
8851
|
+
saveConfig({
|
|
8852
|
+
token,
|
|
8853
|
+
user: { id: user.id, email: user.email },
|
|
8854
|
+
apiUrl,
|
|
8855
|
+
wsUrl: apiUrl.replace("https://", "wss://").replace("http://", "ws://") + "/ws",
|
|
8856
|
+
expiresAt
|
|
8857
|
+
});
|
|
8858
|
+
spinner.succeed(`Logged in as ${user.email}`);
|
|
8859
|
+
log.blank();
|
|
8860
|
+
} catch (error) {
|
|
8861
|
+
spinner.fail("Failed to verify token");
|
|
8862
|
+
if (error instanceof ApiError) {
|
|
8863
|
+
log.error(error.message);
|
|
8864
|
+
}
|
|
8865
|
+
process.exit(1);
|
|
8645
8866
|
}
|
|
8646
|
-
|
|
8867
|
+
} catch (error) {
|
|
8868
|
+
clearTimeout(timeoutHandle);
|
|
8869
|
+
server.close();
|
|
8870
|
+
log.blank();
|
|
8871
|
+
log.error(error instanceof Error ? error.message : "Authentication failed");
|
|
8872
|
+
process.exit(1);
|
|
8647
8873
|
}
|
|
8648
8874
|
}
|
|
8649
8875
|
|
|
@@ -12796,7 +13022,7 @@ function getBackoffDelay(attempt, baseMs = 1000, maxMs = 30000, jitter = true) {
|
|
|
12796
13022
|
// src/lib/ws-client.ts
|
|
12797
13023
|
var PING_INTERVAL_MS = 30000;
|
|
12798
13024
|
var PONG_TIMEOUT_MS = 1e4;
|
|
12799
|
-
var
|
|
13025
|
+
var AUTH_TIMEOUT_MS2 = 1e4;
|
|
12800
13026
|
|
|
12801
13027
|
class WebSocketClient {
|
|
12802
13028
|
ws = null;
|
|
@@ -12851,10 +13077,15 @@ class WebSocketClient {
|
|
|
12851
13077
|
this.options.onError(new Error(`Connection closed during auth: ${reasonStr}`));
|
|
12852
13078
|
} else if (this.shouldReconnect && prevState === "connecting") {
|
|
12853
13079
|
this.scheduleReconnect();
|
|
13080
|
+
} else if (this.shouldReconnect && prevState === "authenticated") {
|
|
13081
|
+
this.scheduleReconnect();
|
|
12854
13082
|
}
|
|
12855
13083
|
});
|
|
12856
13084
|
this.ws.on("error", (error) => {
|
|
12857
13085
|
if (this.state !== "subscribed" && this.shouldReconnect) {
|
|
13086
|
+
if (this.state === "authenticated" || this.state === "authenticating") {
|
|
13087
|
+
this.options.onDisconnected(`Connection error: ${error.message}`);
|
|
13088
|
+
}
|
|
12858
13089
|
this.scheduleReconnect();
|
|
12859
13090
|
} else {
|
|
12860
13091
|
this.options.onError(error);
|
|
@@ -12909,6 +13140,12 @@ class WebSocketClient {
|
|
|
12909
13140
|
this.disconnect();
|
|
12910
13141
|
break;
|
|
12911
13142
|
}
|
|
13143
|
+
case "session_replaced": {
|
|
13144
|
+
this.shouldReconnect = false;
|
|
13145
|
+
this.options.onError(new Error("Another CLI session connected to this endpoint."));
|
|
13146
|
+
this.disconnect();
|
|
13147
|
+
break;
|
|
13148
|
+
}
|
|
12912
13149
|
case "plan_changed": {
|
|
12913
13150
|
const payload = message.payload;
|
|
12914
13151
|
this.plan = payload.plan;
|
|
@@ -13014,7 +13251,7 @@ class WebSocketClient {
|
|
|
13014
13251
|
this.options.onError(new Error("Authentication timed out"));
|
|
13015
13252
|
this.disconnect();
|
|
13016
13253
|
}
|
|
13017
|
-
},
|
|
13254
|
+
}, AUTH_TIMEOUT_MS2);
|
|
13018
13255
|
}
|
|
13019
13256
|
clearAuthTimeout() {
|
|
13020
13257
|
if (this.authTimeout) {
|
|
@@ -13580,7 +13817,7 @@ async function createCommand2(nameArg, options) {
|
|
|
13580
13817
|
}
|
|
13581
13818
|
|
|
13582
13819
|
// src/index.ts
|
|
13583
|
-
program.name("nbound").description("Forward webhooks to localhost for development").version("1.0
|
|
13820
|
+
program.name("nbound").description("Forward webhooks to localhost for development").version("1.1.0");
|
|
13584
13821
|
program.command("login").description("Authenticate with nbound").option("--token <token>", "Use an existing API token").action(loginCommand);
|
|
13585
13822
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
13586
13823
|
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|