UNPKG

@swell/cli

Version:

Swell's command line interface/utility

111 lines (110 loc) 4.29 kB
import { ux } from '@oclif/core'; import * as http from 'node:http'; import open from 'open'; import { getLoginHost } from './constants.js'; import style from './style.js'; /** * Get the session id for the passed in store. If the session is not found, * open the browser to login and return the session id. * * @param storeId - The store to get the session id for * @returns A promise that resolves to the session id */ function getSessionIdOrLoginInBrowser(storeId) { return new Promise((resolve, _reject) => { const onSessionId = (sessionId, storeId) => resolve([sessionId, storeId]); createServer(onSessionId, storeId); }); } function createServer(onSessionId, storeId) { // the spinner is used to show progress while waiting for the browser const spinner = ux.action; serverForLogin({ onPost(req) { // we received the session id from the browser so we can move on const sessionId = req.headers['x-session']; const storeId = req.headers['x-store']; if (sessionId) { spinner.stop('Browser authentication successful'); onSessionId(sessionId, storeId); } }, async onStart(loginUrl) { spinner.start([ "Open this link if your browser doesn't launch it automatically:", `${style.link(loginUrl)}`, ].join(' ')); await open(loginUrl); }, storeId, }); } /** * Start a local HTTP server to handle the POST and GET requests from the * browser login flow. * * The POST request will contain the session id in the `x-session` header. * * The GET request will be used to close the connection after the login flow * is complete. * * @param onSessionId - A callback to run when the session id is received * from the browser * @returns void */ function serverForLogin({ onPost, onStart, storeId, }) { // flag to track if the POST and GET requests have been handled // we only want to handle them once let postHandled = false; let getHandled = false; // Create the HTTP server const server = http.createServer(async (req, res) => { // Set CORS headers res.setHeader('Access-Control-Allow-Origin', getLoginHost(storeId)); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type, x-session, x-store'); if (req.method === 'POST' && !postHandled) { onPost(req); res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('POST request handled'); postHandled = true; } else if (req.method === 'GET' && !getHandled) { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); // Close the HTTP connection to prevent `server.close()` from hanging res.setHeader('connection', 'close'); // The browser will show this message in the window res.end('Authentication successful, you can close this window and return to the terminal.'); getHandled = true; } else if (req.method === 'OPTIONS') { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('ok'); } else { // Return 404 for subsequent requests or other request types res.statusCode = 404; res.end('Not Found'); } }); server.listen(0, '127.0.0.1', async () => { const serverAddr = server.address(); // the port will be dynamically assigned, one of the available ports const localServerUrl = Buffer.from(`http://${serverAddr.address}:${serverAddr.port}/`).toString('base64'); const loginUrl = `${getLoginHost(storeId)}/admin?cli=${localServerUrl}`; onStart(loginUrl); }); const closeInterval = setInterval(() => { if (postHandled) { server.close(() => { clearInterval(closeInterval); }); server.emit('close'); server.removeAllListeners('close'); } }, 100); } export { getSessionIdOrLoginInBrowser };