1import openBrowserAsync from 'better-opn'; 2import http from 'http'; 3import { Socket } from 'node:net'; 4import querystring from 'querystring'; 5 6import * as Log from '../../log'; 7 8export async function getSessionUsingBrowserAuthFlowAsync(options: { 9 expoWebsiteUrl: string; 10 serverPort: number; 11}): Promise<string> { 12 const { expoWebsiteUrl, serverPort } = options; 13 14 const scheme = 'http'; 15 const hostname = 'localhost'; 16 const path = '/auth/callback'; 17 const redirectUri = `${scheme}://${hostname}:${serverPort}${path}`; 18 19 const buildExpoSsoLoginUrl = (): string => { 20 const data = { 21 app_redirect_uri: redirectUri, 22 }; 23 const params = querystring.stringify(data); 24 return `${expoWebsiteUrl}/sso-login?${params}`; 25 }; 26 27 // Start server and begin auth flow 28 const executeAuthFlow = (): Promise<string> => { 29 return new Promise<string>(async (resolve, reject) => { 30 const connections = new Set<Socket>(); 31 32 const server = http.createServer( 33 (request: http.IncomingMessage, response: http.ServerResponse) => { 34 try { 35 if (!(request.method === 'GET' && request.url?.includes('/auth/callback'))) { 36 throw new Error('Unexpected SSO login response.'); 37 } 38 const url = new URL(request.url, `http:${request.headers.host}`); 39 const sessionSecret = url.searchParams.get('session_secret'); 40 41 if (!sessionSecret) { 42 throw new Error('Request missing session_secret search parameter.'); 43 } 44 resolve(sessionSecret); 45 response.writeHead(200, { 'Content-Type': 'text/plain' }); 46 response.write(`Website login has completed. You can now close this tab.`); 47 response.end(); 48 } catch (error) { 49 reject(error); 50 } finally { 51 server.close(); 52 // Ensure that the server shuts down 53 for (const connection of connections) { 54 connection.destroy(); 55 } 56 } 57 } 58 ); 59 60 server.listen(serverPort, hostname, () => { 61 Log.log('Waiting for browser login...'); 62 }); 63 64 server.on('connection', (connection) => { 65 connections.add(connection); 66 67 connection.on('close', () => { 68 connections.delete(connection); 69 }); 70 }); 71 72 const authorizeUrl = buildExpoSsoLoginUrl(); 73 openBrowserAsync(authorizeUrl); 74 }); 75 }; 76 77 return await executeAuthFlow(); 78} 79