import * as path from 'path' ; import * as fs from 'fs' ; import * as envPaths from 'env-paths' ; import { getStandalone , generateCACertificate } from 'mockttp' ; import { MockttpStandalone } from 'mockttp/dist/standalone/mockttp-standalone' ; import { Mutex } from 'async-mutex' ; import updateCommand from '@oclif/plugin-update/lib/commands/update' ; import { HttpToolkitServerApi } from './api-server' ; import { checkBrowserConfig } from './browsers' ; import { reportError } from './error-tracking' ; import { ALLOWED_ORIGINS } from './constants' ; import { delay } from './util/promise' ; import { isErrorLike } from './util/error' ; import { readFile , checkAccess , writeFile , ensureDirectoryExists } from './util/fs' ; import { registerShutdownHandler } from './shutdown' ; import { getTimeToCertExpiry , parseCert } from './certificates' ; import { startDockerInterceptionServices , stopDockerInterceptionServices } from './interceptors/docker/docker-interception-services' ; const APP_NAME = "HTTP Toolkit" ; async function generateHTTPSConfig ( configPath ) { const keyPath = path . join ( configPath , 'ca.key' ) ; const certPath = path . join ( configPath , 'ca.pem' ) ; const [ certContent ] = await Promise . all ( [ readFile ( certPath , 'utf8' ) . then ( ( certContent ) => { checkCertExpiry ( certContent ) ; return certContent ; } ) , checkAccess ( keyPath , fs . constants . R_OK ) , ] ) . catch ( async ( ) => { const newCertPair = await generateCACertificate ( { commonName : APP_NAME + ' CA' } ) ; return Promise . all ( [ writeFile ( certPath , newCertPair . cert ) . then ( ( ) => newCertPair . cert ) , writeFile ( keyPath , newCertPair . key ) ] ) ; } ) ; return { keyPath , certPath , certContent , keyLength : 2048 } ; } function checkCertExpiry ( certContents ) { const remainingLifetime = getTimeToCertExpiry ( parseCert ( certContents ) ) ; if ( remainingLifetime < 1000 * 60 * 60 * 48 ) { console . warn ( 'Certificate expires soon - it must be regenerated' ) ; throw new Error ( 'Certificate regeneration required' ) ; } } function manageBackgroundServices ( standalone , httpsConfig ) { standalone . on ( 'mock-server-started' , async ( server ) => { startDockerInterceptionServices ( server . port , httpsConfig , ruleParameters ) . catch ( ( error ) => { console . log ( "Could not start Docker components:" , error ) ; } ) ; } ) ; standalone . on ( 'mock-server-stopping' , ( server ) => { stopDockerInterceptionServices ( server . port , ruleParameters ) . catch ( ( error ) => { console . log ( "Could not stop Docker components:" , error ) ; } ) ; } ) ; } const ruleParameters = { } ; export async function runHTK ( options = { } ) { const startTime = Date . now ( ) ; registerShutdownHandler ( ) ; const configPath = options . configPath || envPaths ( 'httptoolkit' , { suffix : '' } ) . config ; await ensureDirectoryExists ( configPath ) ; await checkBrowserConfig ( configPath ) ; const configCheckTime = Date . now ( ) ; console . log ( 'Config checked in' , configCheckTime - startTime , 'ms' ) ; const httpsConfig = await generateHTTPSConfig ( configPath ) ; const certSetupTime = Date . now ( ) ; console . log ( 'Certificates setup in' , certSetupTime - configCheckTime , 'ms' ) ; const standalone = getStandalone ( { serverDefaults : { cors : false , recordTraffic : false , https : httpsConfig } , corsOptions : { strict : true , origin : ALLOWED_ORIGINS , maxAge : 86400 } , webSocketKeepAlive : 20000 , ruleParameters } ) ; manageBackgroundServices ( standalone , httpsConfig ) ; await standalone . start ( { port : 45456 , host : '127.0.0.1' } ) ; const standaloneSetupTime = Date . now ( ) ; console . log ( 'Standalone server started in' , standaloneSetupTime - certSetupTime , 'ms' ) ; const apiServer = new HttpToolkitServerApi ( { configPath , authToken : options . authToken , https : httpsConfig } , standalone ) ; const updateMutex = new Mutex ( ) ; apiServer . on ( 'update-requested' , ( ) => { updateMutex . runExclusive ( ( ) => ( < Promise < void > > updateCommand . run ( [ 'stable' ] ) ) . catch ( ( error ) => { if ( isErrorLike ( error ) && error . code === 'EEXIT' ) { return delay ( 1000 * 60 * 60 ) ; } console . log ( error ) ; reportError ( 'Failed to check for updates' ) ; } ) ) ; } ) ; await apiServer . start ( ) ; console . log ( 'Server started in' , Date . now ( ) - standaloneSetupTime , 'ms' ) ; console . log ( 'Total startup took' , Date . now ( ) - startTime , 'ms' ) ; }