UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

274 lines (234 loc) 8.42 kB
#!/usr/bin/env node import * as fs from 'fs'; import * as http from 'http'; import * as path from 'path'; import { promisify } from 'util'; import { device } from 'aws-iot-device-sdk'; import * as progam from 'caporal'; import * as open from 'open'; import { WebSocketServer } from 'ws'; // @ts-ignore import { getConnection } from '../listener/iot-connection'; import { getTopic } from '../listener/topic'; const readFileAsync = promisify(fs.readFile); //resolve issue with module import let opener = open; if ((open as any).default) { opener = (open as any).default; } async function run() { let stackList: string[] | undefined; let cdkOutput: Record<string, Record<string, string>>; let options: any; progam .description('ServerlessSpy web console') .option('--ws <ws>', 'Websocket link') .option( '--cdkoutput <cdkoutput>', 'CDK output file that contains IoT Endpoint link in a property ServerlessSpyWsUrl' ) .option( '--cdkstack <cdkstack>', 'CDK stack in cdk output file. If not specified the first one is picked.' ) .option('--open <open>', 'Open browser', progam.BOOL, true) .option( '--port <p>', `A port on localhost where ServerlessSpy web console is accessible.`, progam.INT, '3456' ) .option( '--wsport <wsp>', `A port on localhost where ServerlessSpy websocket is accessible.`, progam.INT, '3457' ) .action((_args, opt, _logger) => { options = opt; }); progam.parse(process.argv); if (!options.ws && !options.cdkoutput) { throw new Error('--ws or --cdkoutput parameter not specified'); } if (options.cdkoutput) { const rawdata = fs.readFileSync(options.cdkoutput); cdkOutput = JSON.parse(rawdata.toString()); stackList = Object.keys(cdkOutput); } const wss = new WebSocketServer({ port: options.wsport }); let connection: device | undefined = undefined; wss.on('close', async () => { if (connection) connection.end(true); }); wss.on('connection', async function connect(ws) { console.log('Connection'); ws.on('message', function message(data) { console.log('received: %s', data); }); let wsUrl: string | undefined; if (options.ws) { wsUrl = options.ws; } else if (cdkOutput) { if (cdkOutput[options.cdkstack]) { wsUrl = cdkOutput[options.cdkstack].ServerlessSpyWsUrl; } else if (cdkOutput[Object.keys(cdkOutput)[0]]) { wsUrl = cdkOutput[Object.keys(cdkOutput)[0]].ServerlessSpyWsUrl; } } if (!wsUrl) { throw new Error('Missing IoT endpoint url'); } const wsUrlWithoutScope = wsUrl.split('/')[0]; connection = await getConnection(true, wsUrlWithoutScope); const topic = getTopic('#'); console.log(`Subscribing to ${topic}`); connection.on('connect', () => { console.log('Connection opened'); if (connection) { connection.subscribe(topic); } }); connection.on('message', (topic: string, data: Buffer) => { ws.send( JSON.stringify({ ...JSON.parse(JSON.parse(data.toString()).data), topic, }) ); }); }); http .createServer((request, response) => { void (async () => { try { //console.log('request ', request.url); let filePath: string = `.${request.url}`; //remove query parameters filePath = filePath.split('?')[0]; let rootFolder = __dirname; if (request.url?.startsWith('/webServerlessSpy.js')) { //get transpiled TS to JS files rootFolder = getCompiledJsPath(); } else if (request.url?.startsWith('/bootstrap/')) { filePath = filePath.substring('/bootstrap/'.length); const bootstrapFolder = await getNpmModuleInstalledPath( 'bootstrap' ); rootFolder = bootstrapFolder; } else if (request.url?.startsWith('/bootstrap-icons/')) { filePath = filePath.substring('/bootstrap-icons/'.length); const bootstrapFolder = await getNpmModuleInstalledPath( 'bootstrap-icons' ); rootFolder = bootstrapFolder; } else { if (filePath === './') { filePath = './index.html'; } } filePath = path.join(rootFolder, filePath); //console.log(`${request.url} --> ${filePath}`); const extname = String(path.extname(filePath)).toLowerCase(); const mimeTypes: any = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.woff': 'application/font-woff', '.ttf': 'application/font-ttf', '.eot': 'application/vnd.ms-fontobject', '.otf': 'application/font-otf', '.wasm': 'application/wasm', }; const contentType = mimeTypes[extname] || 'application/octet-stream'; if (request.url === '/stackList') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(JSON.stringify(stackList), 'utf-8'); } else if (request.url === '/stackTopicMappings') { response.writeHead(200, { 'Content-Type': 'application/json' }); const mappings: Record<string, string> = {}; if (cdkOutput) { for (const [stackName, stack] of Object.entries(cdkOutput)) { if (stack.ServerlessSpyWsUrl) { const [_, scope] = stack.ServerlessSpyWsUrl.split('/'); if (scope) { mappings[stackName] = scope; } } } } response.end(JSON.stringify(mappings), 'utf-8'); } else if (request.url?.match('^/wsUrl')) { response.writeHead(200, { 'Content-Type': 'text/html' }); response.end(`ws:localhost:${options.wsport}`, 'utf-8'); } else { try { const content = await readFileAsync(filePath); response.writeHead(200, { 'Content-Type': contentType }); response.end(content, 'utf-8'); } catch (error: any) { if (error.code === 'ENOENT') { response.writeHead(404, { 'Content-Type': 'text/html' }); response.end( `No such file or directory ${request.url}`, 'utf-8' ); } else { response.writeHead(500); response.end(`Error: ${error.code} ..\n`); } } } } catch (err: any) { response.writeHead(500, { 'Content-Type': 'text/html' }); response.end(err.message, 'utf-8'); } })(); }) .listen(options.port); console.log( `ServerlessSpy console runing at http://localhost:${options.port}` ); if (options.open) { await opener(`http://localhost:${options.port}`); } } run().catch(console.error); function getNpmModuleInstalledPath(npm: string) { let folder = path.join(__dirname, '../', 'node_modules', npm); if (fs.existsSync(folder)) { return folder; } let folderAsPackage = path.join(__dirname, '../../', 'node_modules', npm); if (fs.existsSync(folderAsPackage)) { return folderAsPackage; } // When boostrap ends up in importing projects root node_modules and not in serverless-spys node_modules folderAsPackage = path.join(__dirname, '../../../../', 'node_modules', npm); if (fs.existsSync(folderAsPackage)) { return folderAsPackage; } throw new Error( `Can not find package in folder ${folder} and ${folderAsPackage}` ); } function getCompiledJsPath() { let folder = path.join(__dirname, '../', 'lib/cli'); if (fs.existsSync(folder)) { return folder; } let folderAsPackage = path.join(__dirname, '../../', 'lib/cli'); if (fs.existsSync(folderAsPackage)) { return folderAsPackage; } throw new Error( `Can not find compiled files in folder ${folder} and ${folderAsPackage}` ); }