import Koa from 'koa' ; import cors from '@koa/cors' ; import WebSocket from 'ws' ; import fs from 'fs' ; import path from 'path' ; import { ReboostInstance } from './index' ; import { resolveDependency } from './transformer/import-resolver' ; import { createFileHandler } from './file-handler' ; import { onServerCreated , uniqueID } from './utils' ; const webSockets = new Set < WebSocket > ( ) ; export const createRouter = ( instance ) : Koa . Middleware => { const routedPaths = { } ; routedPaths [ '/transformed' ] = createFileHandler ( instance ) ; const loadRuntimeCode = ( ) => ( fs . readFileSync ( path . resolve ( __dirname , '../browser/runtime.js' ) , 'utf8' ) ) ; const runtimeCode = loadRuntimeCode ( ) ; routedPaths [ '/runtime' ] = ( ctx ) => { ctx . type = 'text/javascript' ; ctx . body = `const address = "${ instance . proxyAddress }";\n` ; ctx . body += `const debugMode = ${ instance . config . debugMode };\n` ; ctx . body += `const mode = "${ instance . config . mode }";\n` ; ctx . body += `const hotReload = ${ instance . config . hotReload };\n\n` ; ctx . body += instance . config . debugMode ? loadRuntimeCode ( ) : runtimeCode ; } ; const eTagKey = uniqueID ( 10 ) + '-' ; routedPaths [ '/raw' ] = async ( ctx ) => { const filePath = ctx . query . q as string ; try { const stat = await fs . promises . stat ( filePath ) ; const etag = eTagKey + Math . floor ( stat . mtimeMs ) ; if ( ctx . get ( 'If-None-Match' ) === etag ) { ctx . status = 304 ; } else { ctx . body = fs . createReadStream ( filePath ) ; ctx . set ( 'Content-Length' , stat . size + '' ) ; ctx . set ( 'ETag' , etag ) ; } } catch ( e ) { } } ; routedPaths [ '/hot' ] = ( ctx ) => { ctx . type = 'text/javascript' ; ctx . body = 'import { Hot } from "/runtime";\n' ; ctx . body += `export const hot = new Hot(${ JSON . stringify ( ctx . query . q ) });` ; } ; const importerCode = fs . readFileSync ( path . resolve ( __dirname , '../browser/importer.js' ) , 'utf8' ) ; routedPaths [ '/importer' ] = ( ctx ) => { ctx . type = 'text/javascript' ; ctx . body = `const address = "${ instance . proxyAddress }";\n` ; ctx . body += `const commonJSInteropMode = ${ instance . config . commonJSInterop . mode };\n\n` ; ctx . body += importerCode ; } ; routedPaths [ '/unresolved' ] = ( ctx ) => { const { query } = ctx ; ctx . type = 'text/javascript' ; ctx . body = `
      console.error('[reboost] Unable to resolve import ${ JSON . stringify ( query . import ) } of ${ JSON . stringify ( query . importer ) }');
    ` . trim ( ) ; } ; routedPaths [ '/resolve' ] = async ( ctx ) => { const relativeTo = ctx . query . from as string ; const pathToResolve = ctx . query . to as string ; const finalPath = await resolveDependency ( instance , pathToResolve , relativeTo ) ; if ( finalPath ) { ctx . type = 'text/plain' ; ctx . body = finalPath ; } } ; return async ( ctx , next ) => { if ( routedPaths [ ctx . path ] ) { await routedPaths [ ctx . path ] ( ctx ) ; } return next ( ) ; } ; } ; export const createProxyServer = ( instance ) => { const proxyServer = new Koa ( ) ; const router = createRouter ( instance ) ; proxyServer . use ( cors ( { origin : '*' } ) ) . use ( router ) ; onServerCreated ( proxyServer , ( server ) => { const wss = new WebSocket . Server ( { server } ) ; wss . on ( 'connection' , ( socket ) => { webSockets . add ( socket ) ; socket . on ( 'close' , ( ) => webSockets . delete ( socket ) ) ; } ) ; instance . onStop ( "Closes proxy server's websocket" , ( ) => wss . close ( ) ) ; } ) ; return proxyServer ; } ; export const messageClient = ( message ) => { webSockets . forEach ( ( ws ) => ws . send ( JSON . stringify ( message ) ) ) ; } ;