run402 2.41.0 → 2.41.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/lib/operator.mjs +28 -6
- package/package.json +1 -1
package/lib/operator.mjs
CHANGED
|
@@ -147,6 +147,10 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
147
147
|
rejectCode = rej;
|
|
148
148
|
});
|
|
149
149
|
let timer;
|
|
150
|
+
// Track live sockets: server.close() alone stops NEW connections but leaves
|
|
151
|
+
// the browser's keep-alive socket open, which keeps Node's event loop alive
|
|
152
|
+
// and hangs the CLI after a successful login. close() must destroy them.
|
|
153
|
+
const sockets = new Set();
|
|
150
154
|
const server = createServer((req, res) => {
|
|
151
155
|
let u;
|
|
152
156
|
try {
|
|
@@ -162,19 +166,32 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
162
166
|
const code = u.searchParams.get("code");
|
|
163
167
|
const gotState = u.searchParams.get("state");
|
|
164
168
|
const errParam = u.searchParams.get("error");
|
|
165
|
-
|
|
169
|
+
// `connection: close` so the browser does not keep the socket alive.
|
|
170
|
+
res.writeHead(200, { "content-type": "text/html", connection: "close" });
|
|
166
171
|
res.end(
|
|
167
172
|
"<!doctype html><html><body style=\"font-family:system-ui;padding:3rem\">" +
|
|
168
173
|
"<h2>run402 - you're signed in.</h2><p>You can close this window and return to your terminal.</p></body></html>",
|
|
169
174
|
);
|
|
170
|
-
|
|
175
|
+
// Do NOT tear down here — let the response flush. The caller calls close()
|
|
176
|
+
// once it has the code (or on any failure path).
|
|
171
177
|
if (errParam) rejectCode(new Error(`authorization error: ${errParam}`));
|
|
172
178
|
else if (!code) rejectCode(new Error("no authorization code on the loopback redirect"));
|
|
173
179
|
else if (gotState !== expectedState) rejectCode(new Error("state mismatch on the loopback redirect (possible CSRF) - aborted"));
|
|
174
180
|
else resolveCode(code);
|
|
175
181
|
});
|
|
176
|
-
|
|
182
|
+
server.on("connection", (s) => {
|
|
183
|
+
sockets.add(s);
|
|
184
|
+
s.once("close", () => sockets.delete(s));
|
|
185
|
+
});
|
|
186
|
+
function close() {
|
|
177
187
|
clearTimeout(timer);
|
|
188
|
+
for (const s of sockets) {
|
|
189
|
+
try {
|
|
190
|
+
s.destroy();
|
|
191
|
+
} catch {
|
|
192
|
+
// already gone
|
|
193
|
+
}
|
|
194
|
+
}
|
|
178
195
|
try {
|
|
179
196
|
server.close();
|
|
180
197
|
} catch {
|
|
@@ -182,18 +199,18 @@ function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
|
182
199
|
}
|
|
183
200
|
}
|
|
184
201
|
timer = setTimeout(() => {
|
|
185
|
-
|
|
202
|
+
close();
|
|
186
203
|
rejectCode(new Error("timed out waiting for browser approval"));
|
|
187
204
|
}, timeoutMs);
|
|
188
205
|
server.on("error", (e) => {
|
|
189
|
-
|
|
206
|
+
close();
|
|
190
207
|
rejectCode(e);
|
|
191
208
|
});
|
|
192
209
|
const ready = new Promise((res, rej) => {
|
|
193
210
|
server.once("error", rej);
|
|
194
211
|
server.listen(0, "127.0.0.1", () => res(server.address().port));
|
|
195
212
|
});
|
|
196
|
-
return { ready, codePromise, close
|
|
213
|
+
return { ready, codePromise, close };
|
|
197
214
|
}
|
|
198
215
|
|
|
199
216
|
/**
|
|
@@ -242,8 +259,13 @@ async function loopbackLogin(args, { stepUp }) {
|
|
|
242
259
|
try {
|
|
243
260
|
session = await sdk.operator.exchangeCliToken({ code, codeVerifier, redirectUri, state });
|
|
244
261
|
} catch (err) {
|
|
262
|
+
close();
|
|
245
263
|
return reportSdkError(err);
|
|
246
264
|
}
|
|
265
|
+
// The loopback server has done its job. Tear it down (destroying any
|
|
266
|
+
// keep-alive socket) so Node's event loop drains and the CLI exits instead
|
|
267
|
+
// of hanging until Ctrl+C.
|
|
268
|
+
close();
|
|
247
269
|
|
|
248
270
|
const cached = controlPlaneSessionFromTokenResponse(session);
|
|
249
271
|
saveControlPlaneSession(cached);
|
package/package.json
CHANGED