diff --git a/src/Config/container.ts b/src/Config/container.ts index 48f6a683..6f288db8 100644 --- a/src/Config/container.ts +++ b/src/Config/container.ts @@ -50,7 +50,12 @@ import { ConfigReader } from './ConfigReader'; import { DefaultConfigReader } from './DefaultConfigReader'; import { CliInfo } from './CliInfo'; import { CliBuilder } from './CliBuilder'; -import { RepeaterServer, DefaultRepeaterServer } from '../Repeater'; +import { + RepeaterServer, + DefaultRepeaterServer, + RepeaterCommandHub, + DefaultRepeaterCommandHub +} from '../Repeater'; import { container, Lifecycle } from 'tsyringe'; container @@ -107,6 +112,13 @@ container }, { lifecycle: Lifecycle.Singleton } ) + .register( + RepeaterCommandHub, + { + useClass: DefaultRepeaterCommandHub + }, + { lifecycle: Lifecycle.Singleton } + ) .register( Tokens, { diff --git a/src/Handlers/Events/NetworkTest.ts b/src/Handlers/Events/NetworkTest.ts index 85126a3f..006b9ba9 100644 --- a/src/Handlers/Events/NetworkTest.ts +++ b/src/Handlers/Events/NetworkTest.ts @@ -1,9 +1,5 @@ import { Event } from '../../Bus'; - -export enum NetworkTestType { - PING = 'ping', - TRACEROUTE = 'traceroute' -} +import { NetworkTestType } from '../../Repeater'; export class NetworkTest implements Event { constructor( diff --git a/src/Handlers/Events/RegisterScripts.ts b/src/Handlers/Events/RegisterScripts.ts index 1523698b..f226215e 100644 --- a/src/Handlers/Events/RegisterScripts.ts +++ b/src/Handlers/Events/RegisterScripts.ts @@ -1,5 +1,5 @@ -import { Event } from '../../Bus/Event'; +import { Event } from '../../Bus'; export class RegisterScripts implements Event { - constructor(public readonly script: string | Record) {} + constructor(public readonly script?: string | Record) {} } diff --git a/src/Handlers/NetworkTestHandler.ts b/src/Handlers/NetworkTestHandler.ts index 448b7c4c..dfed601f 100644 --- a/src/Handlers/NetworkTestHandler.ts +++ b/src/Handlers/NetworkTestHandler.ts @@ -1,94 +1,37 @@ import { bind, Handler } from '../Bus'; import { NetworkTest } from './Events'; -import { NetworkTestResult } from '../Integrations'; -import { ReadlinePlatform } from '../Wizard'; -import { Helpers, logger } from '../Utils'; -import { injectable } from 'tsyringe'; -import { EOL } from 'os'; -import { URL } from 'url'; +import { RepeaterCommandHub } from '../Repeater'; +import { inject, injectable } from 'tsyringe'; + +type NetworkTestResult = + | { + output: string; + } + | { + error: string; + }; @injectable() @bind(NetworkTest) export class NetworkTestHandler implements Handler { - public async handle(config: NetworkTest): Promise { - const output = await this.getOutput(config); - - return { output, repeaterId: config.repeaterId }; - } - - private async getOutput(config: NetworkTest): Promise { - return new Promise((resolve, reject) => { - const args = ['configure', `--${config.type}`]; - - logger.debug('Launching "Network Diagnostic" process with cmd: %j', args); - - const child = Helpers.spawn({ - include: args, - exclude: ['repeater'] - }); - - child.unref(); - - const stdout: string[] = []; - - child.stdout.on('data', (data: Buffer) => { - const chunk = data.toString(); - const lines: string[] = chunk - .split('\n') - .filter((line: string) => line.length > 0); - - stdout.push(...lines); + constructor( + @inject(RepeaterCommandHub) private readonly commandHub: RepeaterCommandHub + ) {} - const [first, ...rest]: string[] = [].concat(config.input); - - if (chunk.indexOf(ReadlinePlatform.URLS_QUESTION) > -1) { - child.stdin.write(`${[first, ...rest].join(',')}${EOL}`); - } - - if (chunk.indexOf(ReadlinePlatform.HOST_OR_IP_QUESTION) > -1) { - child.stdin.write(`${new URL(first).hostname}${EOL}`); - } - - if (chunk.indexOf(ReadlinePlatform.COMPELED_MESSAGE) > -1) { - child.stdin.end(); - } - }); - - child.once('error', (err: Error) => { - logger.warn( - `Failed to start "Network Diagnostic" due to %s`, - err.message - ); - reject(err); - }); - - child.on('close', (code: number) => { - if (code !== 0 || stdout.length === 0) { - const msg = `"Network Diagnostic" did not start successfully. Process exited with code ${code}`; - - logger.warn(msg); - - return reject(new Error(msg)); - } - - resolve(this.processOutput(stdout)); - }); - }); - } - - // this is workaround of \x1B[1G control code that retype string in console - private processOutput(input: string[]): string { - return input - .filter( - (element, index, arr) => - !( - element.endsWith('\u001b[1G') || - (!!arr[index + 1] && arr[index + 1] === '\u001b[1G') - ) - ) - .filter((x) => !x.startsWith(ReadlinePlatform.URLS_QUESTION)) - .join('\n'); + public async handle(config: NetworkTest): Promise { + try { + const output = await this.commandHub.testNetwork( + config.type, + config.input + ); + + return { output }; + } catch (e) { + return { + error: typeof e === 'string' ? e : (e as Error).message + }; + } } } diff --git a/src/Handlers/RegisterScriptsHandler.ts b/src/Handlers/RegisterScriptsHandler.ts index 72e90bc7..6f283f3e 100644 --- a/src/Handlers/RegisterScriptsHandler.ts +++ b/src/Handlers/RegisterScriptsHandler.ts @@ -1,27 +1,19 @@ import { bind, Handler } from '../Bus'; -import { VirtualScripts, VirtualScriptType } from '../Scripts'; import { RegisterScripts } from './Events'; +import { RepeaterCommandHub } from '../Repeater'; import { inject, injectable } from 'tsyringe'; @injectable() @bind(RegisterScripts) export class RegisterScriptsHandler implements Handler { constructor( - @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts + @inject(RepeaterCommandHub) private readonly commandHub: RepeaterCommandHub ) {} // eslint-disable-next-line @typescript-eslint/require-await public async handle(event: RegisterScripts): Promise { - this.virtualScripts.clear(VirtualScriptType.REMOTE); - - const { script } = event; - - if (typeof script === 'string') { - this.virtualScripts.set('*', VirtualScriptType.REMOTE, script); - } else if (script) { - Object.entries(script).map(([wildcard, code]: [string, string]) => - this.virtualScripts.set(wildcard, VirtualScriptType.REMOTE, code) - ); + if (event.script) { + this.commandHub.compileScripts(event.script); } } } diff --git a/src/Handlers/SendRequestHandler.ts b/src/Handlers/SendRequestHandler.ts index 0ebbb4a7..0f7779e9 100644 --- a/src/Handlers/SendRequestHandler.ts +++ b/src/Handlers/SendRequestHandler.ts @@ -1,7 +1,8 @@ import { bind, Handler } from '../Bus'; -import { Request, RequestExecutor, Response } from '../RequestExecutor'; +import { Request } from '../RequestExecutor'; import { ExecuteScript, ForwardResponse } from './Events'; -import { injectable, injectAll } from 'tsyringe'; +import { RepeaterCommandHub } from '../Repeater'; +import { inject, injectable } from 'tsyringe'; @injectable() @bind(ExecuteScript) @@ -9,26 +10,17 @@ export class SendRequestHandler implements Handler { constructor( - @injectAll(RequestExecutor) - private readonly requestExecutors: RequestExecutor[] + @inject(RepeaterCommandHub) + private readonly commandHub: RepeaterCommandHub ) {} public async handle(event: ExecuteScript): Promise { - const { protocol } = event; - - const requestExecutor = this.requestExecutors.find( - (x) => x.protocol === protocol - ); - - if (!requestExecutor) { - throw new Error(`Unsupported protocol "${protocol}"`); - } - - const response: Response = await requestExecutor.execute( + const response = await this.commandHub.sendRequest( new Request({ ...event, correlationIdRegex: event.correlation_id_regex }) ); - const { statusCode, message, errorCode, body, headers } = response; + const { statusCode, message, errorCode, body, headers, protocol } = + response; return new ForwardResponse( protocol, diff --git a/src/Integrations/NetworkTestResult.ts b/src/Integrations/NetworkTestResult.ts deleted file mode 100644 index 8549a31e..00000000 --- a/src/Integrations/NetworkTestResult.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface NetworkTestResult { - output: string; - repeaterId: string; -} diff --git a/src/Integrations/index.ts b/src/Integrations/index.ts index a54f15c2..7082602b 100644 --- a/src/Integrations/index.ts +++ b/src/Integrations/index.ts @@ -6,4 +6,3 @@ export * from './IntegrationClient'; export * from './IntegrationType'; export * from './Project'; export * from './IntegrationOptions'; -export * from './NetworkTestResult'; diff --git a/src/Repeater/DefaultRepeaterCommandHub.ts b/src/Repeater/DefaultRepeaterCommandHub.ts new file mode 100644 index 00000000..0becf01c --- /dev/null +++ b/src/Repeater/DefaultRepeaterCommandHub.ts @@ -0,0 +1,129 @@ +import { RepeaterCommandHub } from './RepeaterCommandHub'; +import { NetworkTestType } from './NetworkTestType'; +import { VirtualScripts, VirtualScriptType } from '../Scripts'; +import { Helpers, logger } from '../Utils'; +import { ReadlinePlatform } from '../Wizard'; +import { Request, RequestExecutor, Response } from '../RequestExecutor'; +import { inject, injectable, injectAll } from 'tsyringe'; +import { EOL } from 'os'; +import { URL } from 'url'; + +@injectable() +export class DefaultRepeaterCommandHub implements RepeaterCommandHub { + constructor( + @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts, + @injectAll(RequestExecutor) + private readonly requestExecutors: RequestExecutor[] + ) {} + + public compileScripts(script: string | Record): void { + this.virtualScripts.clear(VirtualScriptType.REMOTE); + + if (this.virtualScripts.size) { + logger.warn( + 'Error Loading Script: Cannot accept scripts from the cloud when a local script is already loaded' + ); + + return; + } + + if (typeof script === 'string') { + this.virtualScripts.set('*', VirtualScriptType.REMOTE, script); + } else { + Object.entries(script).map(([wildcard, code]: [string, string]) => + this.virtualScripts.set(wildcard, VirtualScriptType.REMOTE, code) + ); + } + } + + public sendRequest(request: Request): Promise { + const { protocol } = request; + + const requestExecutor = this.requestExecutors.find( + (x) => x.protocol === protocol + ); + + if (!requestExecutor) { + throw new Error(`Unsupported protocol "${protocol}"`); + } + + return requestExecutor.execute(request); + } + + public testNetwork( + type: NetworkTestType, + input: string | string[] + ): Promise { + return new Promise((resolve, reject) => { + const args = ['configure', `--${type}`]; + + logger.debug('Launching "Network Diagnostic" process with cmd: %j', args); + + const child = Helpers.spawn({ + include: args, + exclude: ['repeater'] + }); + + child.unref(); + + const stdout: string[] = []; + + child.stdout.on('data', (data: Buffer) => { + const chunk = data.toString(); + const lines: string[] = chunk + .split('\n') + .filter((line: string) => line.length > 0); + + stdout.push(...lines); + + const [first, ...rest]: string[] = [].concat(input); + + if (chunk.indexOf(ReadlinePlatform.URLS_QUESTION) > -1) { + child.stdin.write(`${[first, ...rest].join(',')}${EOL}`); + } + + if (chunk.indexOf(ReadlinePlatform.HOST_OR_IP_QUESTION) > -1) { + child.stdin.write(`${new URL(first).hostname}${EOL}`); + } + + if (chunk.indexOf(ReadlinePlatform.COMPELED_MESSAGE) > -1) { + child.stdin.end(); + } + }); + + child.once('error', (err: Error) => { + logger.warn( + `Failed to start "Network Diagnostic" due to %s`, + err.message + ); + reject(err); + }); + + child.on('close', (code: number) => { + if (code !== 0 || stdout.length === 0) { + const msg = `"Network Diagnostic" did not start successfully. Process exited with code ${code}`; + + logger.warn(msg); + + return reject(new Error(msg)); + } + + resolve(this.processOutput(stdout)); + }); + }); + } + + // this is workaround of \x1B[1G control code that retype string in console + private processOutput(input: string[]): string { + return input + .filter( + (element, index, arr) => + !( + element.endsWith('\u001b[1G') || + (!!arr[index + 1] && arr[index + 1] === '\u001b[1G') + ) + ) + .filter((x) => !x.startsWith(ReadlinePlatform.URLS_QUESTION)) + .join('\n'); + } +} diff --git a/src/Repeater/DefaultRepeaterLauncher.ts b/src/Repeater/DefaultRepeaterLauncher.ts index 89505429..cd13661d 100644 --- a/src/Repeater/DefaultRepeaterLauncher.ts +++ b/src/Repeater/DefaultRepeaterLauncher.ts @@ -1,6 +1,6 @@ import { RepeaterLauncher } from './RepeaterLauncher'; import { Bus } from '../Bus'; -import { ScriptLoader, VirtualScripts, VirtualScriptType } from '../Scripts'; +import { ScriptLoader, VirtualScripts } from '../Scripts'; import { StartupManager } from '../StartupScripts'; import { Certificates } from '../RequestExecutor'; import { Helpers, logger } from '../Utils'; @@ -14,6 +14,7 @@ import { SendRequestHandler } from '../Handlers'; import { CliInfo } from '../Config'; +import { RepeaterCommandHub } from './RepeaterCommandHub'; import { gt } from 'semver'; import chalk from 'chalk'; import { delay, inject, injectable } from 'tsyringe'; @@ -28,6 +29,7 @@ export class DefaultRepeaterLauncher implements RepeaterLauncher { constructor( @inject(Bus) private readonly bus: Bus, + @inject(RepeaterCommandHub) private readonly commandHub: RepeaterCommandHub, @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts, @inject(StartupManager) private readonly startupManager: StartupManager, @@ -81,30 +83,6 @@ export class DefaultRepeaterLauncher implements RepeaterLauncher { return this.scriptLoader.load(scripts); } - public compileScripts(script: string | Record): void { - if (!script) { - return; - } - - this.virtualScripts.clear(VirtualScriptType.REMOTE); - - if (this.virtualScripts.size) { - logger.warn( - 'Error Loading Script: Cannot accept scripts from the cloud when a local script is already loaded' - ); - - return; - } - - if (typeof script === 'string') { - this.virtualScripts.set('*', VirtualScriptType.REMOTE, script); - } else { - Object.entries(script).map(([wildcard, code]: [string, string]) => - this.virtualScripts.set(wildcard, VirtualScriptType.REMOTE, code) - ); - } - } - public async uninstall(): Promise { await this.startupManager.uninstall(DefaultRepeaterLauncher.SERVICE_NAME); @@ -155,7 +133,7 @@ export class DefaultRepeaterLauncher implements RepeaterLauncher { } if (payload.script) { - this.compileScripts(payload.script); + this.commandHub.compileScripts(payload.script); } } diff --git a/src/Repeater/DefaultRepeaterServer.ts b/src/Repeater/DefaultRepeaterServer.ts index 8ee2fb1e..10a0cf85 100644 --- a/src/Repeater/DefaultRepeaterServer.ts +++ b/src/Repeater/DefaultRepeaterServer.ts @@ -6,7 +6,12 @@ import { RepeaterServerRequestEvent, RepeaterServerRequestResponse, RepeaterServerErrorEvent, - RepeaterServerReconnectionAttemptedEvent + RepeaterServerReconnectionAttemptedEvent, + RepeaterServerNetworkTestEvent, + RepeaterServerNetworkTestResult, + RepeaterServerScriptsUpdatedEvent, + DeployCommandOptions, + DeploymentRuntime } from './RepeaterServer'; import { inject, injectable } from 'tsyringe'; import io, { Socket } from 'socket.io-client'; @@ -48,11 +53,10 @@ export class DefaultRepeaterServer implements RepeaterServer { } public async deploy( - repeaterId?: string + options: DeployCommandOptions, + runtime: DeploymentRuntime ): Promise { - this.socket.emit('deploy', { - repeaterId - }); + process.nextTick(() => this.socket.emit('deploy', options, runtime)); const [result]: RepeaterServerDeployedEvent[] = await once( this.socket, @@ -82,9 +86,9 @@ export class DefaultRepeaterServer implements RepeaterServer { } }); - this.socket.on('connect_error', (error: Error) => { - logger.debug(`Unable to connect to the %s host`, this.options.uri, error); - }); + this.socket.on('connect_error', (error: Error) => + logger.debug(`Unable to connect to the %s host`, this.options.uri, error) + ); this.createPingTimer(); @@ -96,9 +100,29 @@ export class DefaultRepeaterServer implements RepeaterServer { event: RepeaterServerRequestEvent ) => RepeaterServerRequestResponse | Promise ): void { - this.socket.on('request', (payload, callback) => { - this.processEventHandler('request', payload, handler, callback); - }); + this.socket.on('request', (payload, callback) => + this.processEventHandler('request', payload, handler, callback) + ); + } + + public networkTesting( + handler: ( + event: RepeaterServerNetworkTestEvent + ) => + | RepeaterServerNetworkTestResult + | Promise + ): void { + this.socket.on('test-network', (payload, callback) => + this.processEventHandler('test-network', payload, handler, callback) + ); + } + + public scriptsUpdated( + handler: (event: RepeaterServerScriptsUpdatedEvent) => Promise | void + ): void { + this.socket.on('update-scripts', (payload, callback) => + this.processEventHandler('update-scripts', payload, handler, callback) + ); } public reconnectionFailed( @@ -110,27 +134,28 @@ export class DefaultRepeaterServer implements RepeaterServer { this.latestReconnectionError = undefined; }); - this.socket.io.on('reconnect_error', (error) => { - this.latestReconnectionError = error; - }); + this.socket.io.on( + 'reconnect_error', + (error) => (this.latestReconnectionError = error) + ); - this.socket.io.on('reconnect_failed', () => { + this.socket.io.on('reconnect_failed', () => this.processEventHandler( 'reconnection_failed', { error: this.latestReconnectionError }, handler - ); - }); + ) + ); } public errorOccurred( handler: (event: RepeaterServerErrorEvent) => void | Promise ): void { - this.socket.on('error', (payload, callback) => { - this.processEventHandler('error', payload, handler, callback); - }); + this.socket.on('error', (payload, callback) => + this.processEventHandler('error', payload, handler, callback) + ); } public reconnectionAttempted( @@ -138,19 +163,19 @@ export class DefaultRepeaterServer implements RepeaterServer { event: RepeaterServerReconnectionAttemptedEvent ) => void | Promise ): void { - this.socket.io.on('reconnect_attempt', (attempt) => { + this.socket.io.on('reconnect_attempt', (attempt) => this.processEventHandler( 'reconnect_attempt', { attempt, maxAttempts: this.MAX_RECONNECTION_ATTEMPTS }, handler - ); - }); + ) + ); } public reconnectionSucceeded(handler: () => void | Promise): void { - this.socket.io.on('reconnect', () => { - this.processEventHandler('reconnect', undefined, handler); - }); + this.socket.io.on('reconnect', () => + this.processEventHandler('reconnect', undefined, handler) + ); } private get socket() { @@ -163,31 +188,29 @@ export class DefaultRepeaterServer implements RepeaterServer { return this._socket; } - private processEventHandler

( + private async processEventHandler

( event: string, payload: P, handler: (payload: P) => unknown, callback?: unknown ) { - (async function () { - try { - const response = await handler(payload); - - if (typeof callback !== 'function') { - return; - } - - callback(response); - } catch (error) { - logger.debug( - 'Error processing event "%s" with the following payload: %s. Details: %s', - event, - payload, - error - ); - logger.error('Error: %s', error.message); + try { + const response = await handler(payload); + + if (typeof callback !== 'function') { + return; } - })(); + + callback(response); + } catch (error) { + logger.debug( + 'Error processing event "%s" with the following payload: %s. Details: %s', + event, + payload, + error + ); + logger.error('Error: %s', error.message); + } } private createPingTimer() { diff --git a/src/Repeater/NetworkTestType.ts b/src/Repeater/NetworkTestType.ts new file mode 100644 index 00000000..ac4032c0 --- /dev/null +++ b/src/Repeater/NetworkTestType.ts @@ -0,0 +1,4 @@ +export enum NetworkTestType { + PING = 'ping', + TRACEROUTE = 'traceroute' +} diff --git a/src/Repeater/RepeaterCommandHub.ts b/src/Repeater/RepeaterCommandHub.ts new file mode 100644 index 00000000..cb5602b7 --- /dev/null +++ b/src/Repeater/RepeaterCommandHub.ts @@ -0,0 +1,10 @@ +import { Request, Response } from '../RequestExecutor'; +import { NetworkTestType } from './NetworkTestType'; + +export interface RepeaterCommandHub { + compileScripts(script: string | Record): void; + testNetwork(type: NetworkTestType, input: string | string[]): Promise; + sendRequest(request: Request): Promise; +} + +export const RepeaterCommandHub: unique symbol = Symbol('RepeaterCommandHub'); diff --git a/src/Repeater/RepeaterLauncher.ts b/src/Repeater/RepeaterLauncher.ts index bbd9edc2..dc07a071 100644 --- a/src/Repeater/RepeaterLauncher.ts +++ b/src/Repeater/RepeaterLauncher.ts @@ -3,11 +3,6 @@ export interface RepeaterLauncher { loadScripts(scripts: Record): Promise; - /** - * @deprecated currently not supported by some implementations - */ - compileScripts?(scripts: string | Record): void; - run(repeaterId: string, asDaemon?: boolean): Promise; close(): Promise; diff --git a/src/Repeater/RepeaterServer.ts b/src/Repeater/RepeaterServer.ts index 2dd5bc46..edf922e8 100644 --- a/src/Repeater/RepeaterServer.ts +++ b/src/Repeater/RepeaterServer.ts @@ -1,4 +1,5 @@ import { Protocol } from '../RequestExecutor'; +import { NetworkTestType } from './NetworkTestType'; export interface RepeaterServerDeployedEvent { repeaterId: string; @@ -13,6 +14,24 @@ export interface RepeaterServerRequestEvent { body?: string; } +export type RepeaterServerNetworkTestEvent = + | { + type: NetworkTestType.PING; + input: string[]; + } + | { + type: NetworkTestType.TRACEROUTE; + input: string; + }; + +export type RepeaterServerNetworkTestResult = + | { + output: string; + } + | { + error: string; + }; + export type RepeaterServerRequestResponse = | { protocol: Protocol; @@ -41,12 +60,40 @@ export interface RepeaterServerErrorEvent { message: string; } +export interface RepeaterServerScriptsUpdatedEvent { + script: string | Record; +} + +export interface DeployCommandOptions { + repeaterId?: string; +} + +export interface DeploymentRuntime { + version: string; + scriptsLoaded: boolean; +} + export interface RepeaterServer { disconnect(): void; connect(hostname: string): void; - deploy(repeaterId?: string): Promise; + deploy( + options: DeployCommandOptions, + runtime?: DeploymentRuntime + ): Promise; + + scriptsUpdated( + handler: (event: RepeaterServerScriptsUpdatedEvent) => Promise | void + ): void; + + networkTesting( + handler: ( + event: RepeaterServerNetworkTestEvent + ) => + | RepeaterServerNetworkTestResult + | Promise + ): void; requestReceived( handler: ( diff --git a/src/Repeater/ServerRepeaterLauncher.ts b/src/Repeater/ServerRepeaterLauncher.ts index 1280e9bd..63c3d6c1 100644 --- a/src/Repeater/ServerRepeaterLauncher.ts +++ b/src/Repeater/ServerRepeaterLauncher.ts @@ -1,20 +1,18 @@ import { RepeaterLauncher } from './RepeaterLauncher'; import { + DeploymentRuntime, RepeaterServer, + RepeaterServerNetworkTestEvent, RepeaterServerReconnectionFailedEvent, RepeaterServerRequestEvent } from './RepeaterServer'; -import { ScriptLoader } from '../Scripts'; +import { ScriptLoader, VirtualScripts } from '../Scripts'; import { StartupManager } from '../StartupScripts'; -import { - Certificates, - Request, - RequestExecutor, - Response -} from '../RequestExecutor'; +import { Certificates, Request } from '../RequestExecutor'; import { Helpers, logger } from '../Utils'; import { CliInfo } from '../Config'; -import { delay, inject, injectAll, injectable } from 'tsyringe'; +import { RepeaterCommandHub } from './RepeaterCommandHub'; +import { delay, inject, injectable } from 'tsyringe'; @injectable() export class ServerRepeaterLauncher implements RepeaterLauncher { @@ -22,14 +20,15 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { private repeaterStarted: boolean = false; constructor( + @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts, @inject(RepeaterServer) private readonly repeaterServer: RepeaterServer, @inject(StartupManager) private readonly startupManager: StartupManager, + @inject(RepeaterCommandHub) + private readonly commandHub: RepeaterCommandHub, @inject(Certificates) private readonly certificates: Certificates, @inject(ScriptLoader) private readonly scriptLoader: ScriptLoader, - @inject(delay(() => CliInfo)) private readonly info: CliInfo, - @injectAll(RequestExecutor) - private readonly requestExecutors: RequestExecutor[] + @inject(delay(() => CliInfo)) private readonly info: CliInfo ) {} public close() { @@ -94,13 +93,25 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { this.subscribeToEvents(); - await this.repeaterServer.deploy(repeaterId); + await this.repeaterServer.deploy( + { + repeaterId + }, + this.getRuntime() + ); this.repeaterStarted = true; logger.log(`The Repeater (%s) started`, this.info.version); } + private getRuntime(): DeploymentRuntime { + return { + version: this.info.version, + scriptsLoaded: !!this.virtualScripts.size + }; + } + private subscribeToEvents() { this.repeaterServer.errorOccurred(({ message }) => { logger.error(message); @@ -111,6 +122,12 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { this.repeaterServer.requestReceived((payload) => this.requestReceived(payload) ); + this.repeaterServer.networkTesting((payload) => + this.testingNetwork(payload) + ); + this.repeaterServer.scriptsUpdated((payload) => + this.commandHub.compileScripts(payload.script) + ); this.repeaterServer.reconnectionAttempted(({ attempt, maxAttempts }) => logger.warn('Failed to connect (attempt %d/%d)', attempt, maxAttempts) ); @@ -125,22 +142,27 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { process.exit(1); } - private async requestReceived(event: RepeaterServerRequestEvent) { - const { protocol } = event; - - const requestExecutor = this.requestExecutors.find( - (x) => x.protocol === protocol - ); - - if (!requestExecutor) { - throw new Error(`Unsupported protocol "${protocol}"`); + private async testingNetwork(event: RepeaterServerNetworkTestEvent) { + try { + const output = await this.commandHub.testNetwork(event.type, event.input); + + return { + output + }; + } catch (e) { + return { + error: typeof e === 'string' ? e : (e as Error).message + }; } + } - const response: Response = await requestExecutor.execute( + private async requestReceived(event: RepeaterServerRequestEvent) { + const response = await this.commandHub.sendRequest( new Request({ ...event }) ); - const { statusCode, message, errorCode, body, headers } = response; + const { statusCode, message, errorCode, body, headers, protocol } = + response; return { protocol, diff --git a/src/Repeater/index.ts b/src/Repeater/index.ts index 7b012abb..d57d1d20 100644 --- a/src/Repeater/index.ts +++ b/src/Repeater/index.ts @@ -1,5 +1,8 @@ +export * from './RepeaterCommandHub'; +export * from './DefaultRepeaterCommandHub'; +export * from './NetworkTestType'; export * from './DefaultRepeaterLauncher'; -export * from './ServerRepeaterLauncher'; +export * from './DefaultRepeaterServer'; export * from './RepeaterLauncher'; export * from './RepeaterServer'; -export * from './DefaultRepeaterServer'; +export * from './ServerRepeaterLauncher'; diff --git a/src/Scan/Scans.ts b/src/Scan/Scans.ts index f10bff0c..f5295af9 100644 --- a/src/Scan/Scans.ts +++ b/src/Scan/Scans.ts @@ -137,8 +137,8 @@ export interface ScanConfig { authObjectId?: string; projectId?: string; discoveryTypes?: Discovery[]; - tests: TestType[]; - buckets: string[]; + tests?: TestType[]; + buckets?: string[]; poolSize?: number; fileId?: string; attackParamLocations?: AttackParamLocation[];