@gramatr/client 0.6.5 → 0.6.6
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/bin/gmtr-login.ts +23 -10
- package/package.json +1 -1
package/bin/gmtr-login.ts
CHANGED
|
@@ -148,10 +148,11 @@ function isHeadless(forceFlag: boolean = false): boolean {
|
|
|
148
148
|
if (forceFlag) return true;
|
|
149
149
|
if (process.env.GRAMATR_LOGIN_HEADLESS === '1') return true;
|
|
150
150
|
|
|
151
|
-
// SSH session
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
// SSH session — always go headless. Even on macOS (which has a local
|
|
152
|
+
// display), `open` would launch Safari on the Mac's *physical* screen,
|
|
153
|
+
// not on the remote terminal where the user actually is. Device flow
|
|
154
|
+
// lets them paste the code on whichever machine they're sitting at.
|
|
155
|
+
if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
|
|
155
156
|
// Docker / CI / no TTY
|
|
156
157
|
if (process.env.CI || process.env.DOCKER) return true;
|
|
157
158
|
// Linux without display
|
|
@@ -493,23 +494,35 @@ async function loginBrowser(opts: { forceHeadless?: boolean } = {}): Promise<voi
|
|
|
493
494
|
const code = url.searchParams.get('code');
|
|
494
495
|
const returnedState = url.searchParams.get('state');
|
|
495
496
|
const error = url.searchParams.get('error');
|
|
497
|
+
// v0.6.6: `server.close()` alone does not terminate keep-alive
|
|
498
|
+
// sockets — the browser holds the connection open and the Node
|
|
499
|
+
// event loop never exits, so the CLI prints "Authenticated" and
|
|
500
|
+
// then hangs until Ctrl+C. Send `Connection: close` on the
|
|
501
|
+
// response and call `closeAllConnections()` to forcibly drop
|
|
502
|
+
// lingering sockets after we respond.
|
|
503
|
+
const shutdown = () => {
|
|
504
|
+
callbackServer.close();
|
|
505
|
+
if (typeof (callbackServer as any).closeAllConnections === 'function') {
|
|
506
|
+
(callbackServer as any).closeAllConnections();
|
|
507
|
+
}
|
|
508
|
+
};
|
|
496
509
|
if (error) {
|
|
497
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
510
|
+
res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' });
|
|
498
511
|
res.end(errorPage('Authentication Failed', error));
|
|
499
|
-
|
|
512
|
+
shutdown();
|
|
500
513
|
reject(new Error(`OAuth error: ${error}`));
|
|
501
514
|
return;
|
|
502
515
|
}
|
|
503
516
|
if (!code || returnedState !== state) {
|
|
504
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
517
|
+
res.writeHead(400, { 'Content-Type': 'text/html', Connection: 'close' });
|
|
505
518
|
res.end(errorPage('Invalid Callback', 'Missing code or state mismatch. Please try again.'));
|
|
506
|
-
|
|
519
|
+
shutdown();
|
|
507
520
|
reject(new Error('Invalid callback'));
|
|
508
521
|
return;
|
|
509
522
|
}
|
|
510
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
523
|
+
res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' });
|
|
511
524
|
res.end(successPage());
|
|
512
|
-
|
|
525
|
+
shutdown();
|
|
513
526
|
resolve(code);
|
|
514
527
|
});
|
|
515
528
|
codeTimeoutHandle = setTimeout(() => {
|