diff --git a/package-lock.json b/package-lock.json index b4c80c4..44e2914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1289,15 +1289,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1308,6 +1299,15 @@ "strip-ansi": "4.0.0" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", diff --git a/proxy/index.js b/proxy/index.js index 6771f99..afb2d42 100644 --- a/proxy/index.js +++ b/proxy/index.js @@ -1,9 +1,9 @@ - const cp = require('child_process'); const WebSocket = require('ws'); const http = require('http'); const types = require('../lib/messageTypes'); +// parse handler name const handlerEnv = process.env._HANDLER && process.env._HANDLER.indexOf('.') > -1 ? process.env._HANDLER.split('.') : [null, 'defaultHandler']; // eslint-disable-line const HANDLER_NAME = handlerEnv[1];// eslint-disable-line @@ -11,13 +11,14 @@ let child; let childSocket; let brokerSocket; -function currentTimeInMillis () { - const hrtime = process.hrtime() - return hrtime[0] * 1000 + Math.floor(hrtime[1] / 1000000) +function currentTimeInMilliseconds() { + const hrtime = process.hrtime(); + return (hrtime[0] * 1000) + Math.floor(hrtime[1] / 1000000); } function runAsProxy() { if (!process.env.DEBUGGER_ACTIVE || process.env.DEBUGGER_ACTIVE === 'false') return; + console.log('[AWS Lambda Debugger] Debugger Status: ACTIVE'); let childResolver; let debuggerUrl; const childPromise = new Promise((resolve) => { childResolver = resolve; }); @@ -36,6 +37,7 @@ function runAsProxy() { const message = JSON.parse(messageString); switch (message.type) { case types.CHILD_READY: { + console.log('[AWS Lambda Debugger] Child ready.'); debuggerUrl = message.debuggerUrl; // eslint-disable-line childResolver(); break; @@ -46,6 +48,7 @@ function runAsProxy() { } }); + // set child stream encodings child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); } @@ -59,6 +62,7 @@ function runAsProxy() { // this is a requirement to keep function from running to full timeout context.callbackWaitsForEmptyEventLoop = false; // eslint-disable-line + // handle shims for callback and callbackWaitsForEmptyEventLoop function childMessageHandler(messageString) { const message = JSON.parse(messageString); switch (message.type) { @@ -94,9 +98,14 @@ function runAsProxy() { brokerSocket.on('message', (messageString) => { const message = JSON.parse(messageString); switch (message.type) { + // handle notification from broker that user has connected case types.USER_CONNECTED: { - console.log('user connected via broker. invoking child.'); + console.log('[AWS Lambda Debugger] User connected via broker. Invoking child.'); + + // open socket to child's debugger endpoint childSocket = new WebSocket(debuggerUrl); + + // proxy V8 debugger messages from child childSocket.on('message', (rawInspectorMessage) => { if (brokerSocket.readyState === WebSocket.OPEN) { brokerSocket.send(JSON.stringify({ @@ -105,9 +114,11 @@ function runAsProxy() { })); } }); + + // send invoke message to child childSocket.on('open', () => { child.send(JSON.stringify({ - timestamp: currentTimeInMillis(), + timestamp: currentTimeInMilliseconds(), remainingTime: context.getRemainingTimeInMillis(), type: types.INVOKE_HANDLER, event, @@ -116,6 +127,8 @@ function runAsProxy() { }); break; } + + // proxy V8 debugger messages to child case types.V8_INSPECTOR_MESSAGE: { if (childSocket.readyState === WebSocket.OPEN) { childSocket.send(message.payload); @@ -128,6 +141,7 @@ function runAsProxy() { } }); + // clean up on broker socket close brokerSocket.on('close', () => { if (childSocket && childSocket.readyState === WebSocket.OPEN) { childSocket.close(); @@ -138,6 +152,7 @@ function runAsProxy() { } function runAsChild() { + // shim callback const callback = (err, response) => process.send(JSON.stringify({ type: types.LAMBDA_CALLBACK, err, response })); @@ -145,8 +160,9 @@ function runAsChild() { process.on('message', (messageString) => { const message = JSON.parse(messageString); switch (message.type) { + // handle invoke message from proxy case types.INVOKE_HANDLER: { - // shimming context + // shim context let _callbackWaitsForEmptyEventLoop = true; // eslint-disable-line Object.defineProperties(message.context, { callbackWaitsForEmptyEventLoop: { @@ -160,8 +176,11 @@ function runAsChild() { } } }); + // emulate getRemainingTimeInMillis by taking the known remaining time and subtracting the + // delta between now and when that known remaining time was captured. Result should be + // very close since both parent and child share the same system clock. message.context.getRemainingTimeInMillis = - () => message.remainingTime - (currentTimeInMillis() - message.timestamp); + () => message.remainingTime - (currentTimeInMilliseconds() - message.timestamp); message.context.done = (err, response) => process.send(JSON.stringify({ type: types.LAMBDA_CALLBACK, @@ -173,12 +192,18 @@ function runAsChild() { message.context.fail = err => message.context.done(err, null); // get ready for the user - console.log(`Current AWS request ID: ${message.context.awsRequestId}`); + console.log(`[AWS Lambda Debugger] Current AWS request ID: ${message.context.awsRequestId}`); setTimeout( // this is a hack to get around the delay before the debugger fully kicks in () => { + const handler = module.parent.exports[HANDLER_NAME]; debugger; // *** STEP INTO THE FOLLOWING LINE TO BEGIN STEPPING THROUGH YOUR FUNCTION *** - module.parent.exports[HANDLER_NAME](message.event, message.context, callback); + const response = handler(message.event, message.context, callback); + // handle promise returns - required for async handler support + if (response instanceof Promise) { + response.then(message.context.succeed); + response.catch(message.context.fail); + } }, 1000 ); @@ -197,7 +222,7 @@ function runAsChild() { responseString += chunk; }); responseStream.on('end', () => { - console.log(responseString); + console.log(`[AWS Lambda Debugger] Child process info: ${responseString}`); const response = JSON.parse(responseString); process.send(JSON.stringify({ type: types.CHILD_READY, @@ -207,6 +232,7 @@ function runAsChild() { }); } +// process.send only exists on a child process if (process.send) { runAsChild(); } else {