Skip to content

Commit

Permalink
Proxy CFW DevTools for Workerd (#1518)
Browse files Browse the repository at this point in the history
* Proxy CFW modified DevTools

* Show link to DevTools

* Small refactor

* Update changeset

* Fix tab name for locales

* Fix unrelated test

* Improve message types

* Refactor: move code around and simplify inspector connections

* Moar refactoring
  • Loading branch information
frandiox authored Nov 21, 2023
1 parent 74ea1db commit 2c9c2b7
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 168 deletions.
4 changes: 3 additions & 1 deletion .changeset/silver-sheep-melt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Enable debugger connections by passing `--debug` flag to the `h2 dev` command:

You can then connect to the port `9229` (configurable with the new `--inspector-port` flag) to start step debugging.

For example, in Chrome you can go to `chrome://inspect` and make sure the inspector port is added to the network targets. In VSCode, you can add the following to your `.vscode/launch.json`:
When using `--worker-unstable`, an improved version of the DevTools will be available in `localhost:9229`. Otherwise, in Chrome you can go to `chrome://inspect` to open the DevTools -- make sure the inspector port is added to the network targets.

Alternatively, in VSCode, you can add the following to your `.vscode/launch.json`:

```
{
Expand Down
116 changes: 87 additions & 29 deletions packages/cli/src/lib/mini-oxygen/workerd-inspector-logs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,90 @@
import {type Protocol} from 'devtools-protocol';
import {type ErrorProperties} from './workerd-inspector.js';
import type {
InspectorConnection,
ErrorProperties,
MessageData,
} from './workerd-inspector.js';
import {SourceMapConsumer} from 'source-map';
import {parse as parseStackTrace} from 'stack-trace';

export function addInspectorConsoleLogger(inspector: InspectorConnection) {
inspector.ws.addEventListener('message', async (event) => {
if (typeof event.data !== 'string') {
// We should never get here, but who know...
console.error('Unrecognised devtools event:', event);
return;
}

const evt = JSON.parse(event.data) as MessageData;
inspector.cleanupMessageQueue(evt);

if (evt.method === 'Runtime.consoleAPICalled') {
await logConsoleMessage(evt.params, inspector);
} else if (evt.method === 'Runtime.exceptionThrown') {
console.error(
await createErrorFromException(evt.params.exceptionDetails, inspector),
);
}
});
}

export async function createErrorFromException(
exceptionDetails: Protocol.Runtime.ExceptionDetails,
inspector: InspectorConnection,
) {
const errorProperties: ErrorProperties = {};

const sourceMapConsumer = await inspector.getSourceMapConsumer();
if (sourceMapConsumer !== undefined) {
// Create the lines for the exception details log
const message = exceptionDetails.exception?.description?.split('\n')[0];
const stack = exceptionDetails.stackTrace?.callFrames;
const formatted = formatStructuredError(sourceMapConsumer, message, stack);

errorProperties.message = exceptionDetails.text;
errorProperties.stack = formatted;
} else {
errorProperties.message =
exceptionDetails.text +
' ' +
(exceptionDetails.exception?.description ?? '');
}

return inspector.reconstructError(
errorProperties,
exceptionDetails.exception,
);
}

export async function createErrorFromLog(
ro: Protocol.Runtime.RemoteObject,
inspector: InspectorConnection,
) {
if (ro.subtype !== 'error' || ro.preview?.subtype !== 'error') {
throw new Error('Not an error object');
}

const errorProperties = {
message:
ro.preview.description
?.split('\n')
.filter((line) => !/^\s+at\s/.test(line))
.join('\n') ??
ro.preview.properties.find(({name}) => name === 'message')?.value ??
'',
stack:
ro.preview.description ??
ro.description ??
ro.preview.properties.find(({name}) => name === 'stack')?.value,
cause: ro.preview.properties.find(({name}) => name === 'cause')
?.value as unknown,
} satisfies ErrorProperties;

// Even though we have gathered all the properties, they are likely
// truncated so we need to fetch their full version.
return inspector.reconstructError(errorProperties, ro);
}

/**
* This function converts a message serialised as a devtools event
* into arguments suitable to be called by a console method, and
Expand All @@ -11,7 +93,7 @@ import {parse as parseStackTrace} from 'stack-trace';
* directly in the terminal.
*/

export const mapConsoleAPIMessageTypeToConsoleMethod: {
const mapConsoleAPIMessageTypeToConsoleMethod: {
[key in Protocol.Runtime.ConsoleAPICalledEvent['type']]: Exclude<
keyof Console,
'Console'
Expand All @@ -37,12 +119,9 @@ export const mapConsoleAPIMessageTypeToConsoleMethod: {
endGroup: 'groupEnd',
};

export async function logConsoleMessage(
async function logConsoleMessage(
evt: Protocol.Runtime.ConsoleAPICalledEvent,
reconstructError: (
initialProperties: ErrorProperties,
ro: Protocol.Runtime.RemoteObject,
) => Promise<Error>,
inspector: InspectorConnection,
) {
const args: Array<string | Error> = [];
for (const ro of evt.args) {
Expand Down Expand Up @@ -141,28 +220,7 @@ export async function logConsoleMessage(
case 'wasmvalue':
break;
case 'error':
const errorProperties = {
message:
ro.preview.description
?.split('\n')
.filter((line) => !/^\s+at\s/.test(line))
.join('\n') ??
ro.preview.properties.find(({name}) => name === 'message')
?.value ??
'',
stack:
ro.preview.description ??
ro.description ??
ro.preview.properties.find(({name}) => name === 'stack')
?.value,
cause: ro.preview.properties.find(({name}) => name === 'cause')
?.value as unknown,
};

// Even though we have gathered all the properties, they are likely
// truncated so we need to fetch their full version.
const error = await reconstructError(errorProperties, ro);

const error = await createErrorFromLog(ro, inspector);
// Replace its description in args
args.splice(-1, 1, error);

Expand Down
Loading

0 comments on commit 2c9c2b7

Please sign in to comment.