Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: inspect/debugging as useInspector #199

Merged
merged 1 commit into from
Jan 11, 2022
Merged

Conversation

threepointone
Copy link
Contributor

This PR rewrites the logic for establishing a connection with the debugging socket, and adds some enhancements along the way. Part of solving #188. Some highlights -

  • I've installed devtools-protocol, a convenient package that has the static types for the devtools protocol (duh) autogenerated from chrome's devtools codebase.
  • We now log messages and exceptions into the terminal directly, so you don't have to open devtools to see those messages.
  • Messages are now buffered until a devtools instance connects, so you won't lose any messages while devtools isn't connected.
  • We don't lose the connection on making changes to the worker, removing the need for the kludgy hack on the devtools side (where we refresh the whole page when there's a change)

Some things that I still have to do in followup PRs -

  • clear the console whenever we make a change to the worker
  • stay connected when we shift between local/remote mode

@changeset-bot
Copy link

changeset-bot bot commented Jan 6, 2022

🦋 Changeset detected

Latest commit: e3fa749

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
wrangler Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

useInspector(token ? token.inspectorUrl.href : undefined);
useInspector({
inspectorUrl: token ? token.inspectorUrl.href : undefined,
port: 9229,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a followup PR, we'll make this port configurable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And/or also if not configured, dynamic to avoid an already open port?

Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the use of hooks here to keep dependencies in sync. Very nice!

I made a bunch of comments, but I don't think any are blocking this PR, assuming that you have actually checked it works as expected!

Can you provide some thoughts on how you propose we write tests for all this functionality?

@@ -196,7 +162,11 @@ function Remote(props: {

useProxy({ token, publicRoot: props.public, port: props.port });

useInspector(token ? token.inspectorUrl.href : undefined);
useInspector({
inspectorUrl: token ? token.inspectorUrl.href : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
inspectorUrl: token ? token.inspectorUrl.href : undefined,
inspectorUrl: token?.inspectorUrl.href,

* When we start a session with `wrangler dev`, the Workers platform
* also exposes a debugging websocket that implements the DevTools
* Protocol. While we could just start up DevTools and connect to this
* URL, however that URL changes every time we make a change to the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* URL, however that URL changes every time we make a change to the
* URL, that URL changes every time we make a change to the

useInspector(token ? token.inspectorUrl.href : undefined);
useInspector({
inspectorUrl: token ? token.inspectorUrl.href : undefined,
port: 9229,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And/or also if not configured, dynamic to avoid an already open port?

// Let's create the websocket server on top of the proxy server
wsServerRef.current = new WebSocketServer({ server: serverRef.current });
wsServerRef.current.on("connection", (ws: WebSocket) => {
if (isConnected) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you do something like the following?

if (wsServerRef.current.clients.size > 1) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. will do.

*/
let isConnected = false;
// Let's create the websocket server on top of the proxy server
wsServerRef.current = new WebSocketServer({ server: serverRef.current });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this https://github.com/websockets/ws/blob/HEAD/doc/ws.md#serverclients we need to set clientTracking: true in these options so that the clients property used below is active.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I could swear I tested this. Thanks for the catch!

Comment on lines 386 to 388
messageBufferRef.current.forEach((evt) => {
localWebSocket.send(evt);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if we renamed remoteMessageListener to sendMessageToLocal then it would have been more obvious to reuse that here:

Suggested change
messageBufferRef.current.forEach((evt) => {
localWebSocket.send(evt);
});
messageBufferRef.current.forEach(sendMessageToLocal);


/**
* This function converts a message serialised as a devtools event
* into aarguments uitable to be called by a console method, and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* into aarguments uitable to be called by a console method, and
* into arguments suitable to be called by a console method, and

}
}

console[evt.type](...args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is running in ink, I wonder if we could pass these messages out to be rendered in an Ink component, rather than just dumping them out using console.xxx?
(I appreciate that Ink patches console but I think we could be more elegant here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revisit it. I actually try to avoid Ink for anything that doesn't feel like UI (and before you say it, I understand the boudnary is deeply fuzzy.) For example, in this usecase, I want the messages to persist no matter what, even if the process exits, etc. For now I think calling console methods makes sense, and matches behaviour closer to node (or at least, is already better than what we have, which is nothing.)

wsServerRef.current = new WebSocketServer({ server: serverRef.current });
wsServerRef.current.on("connection", (ws: WebSocket) => {
if (isConnected) {
console.log(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.warn or console.error here?

}, 10_000);
});

ws.on("unexpected-response", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just something that you have noticed or is it documented somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed! It actually happens. Tho, good point, it's not in the spec. I wonder whether we do anything special here for this message. I'll investigate.

Copy link
Contributor Author

@threepointone threepointone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the PR with suggested changes. I'm going to write down some followup tasks, including testing plan, before I land this. Thanks @petebacondarwin!

}, 10_000);
});

ws.on("unexpected-response", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed! It actually happens. Tho, good point, it's not in the spec. I wonder whether we do anything special here for this message. I'll investigate.

// TODO: maybe we should have a max limit on this?
// if so, we should be careful when removing messages
// from the front, because they could be critical for
// devtools (like execution context creation, etc)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually in my tests, there don't seem to be any critical messages per se. But I could be mistaken. Let's revisit this when it becomes a problem.

*/
let isConnected = false;
// Let's create the websocket server on top of the proxy server
wsServerRef.current = new WebSocketServer({ server: serverRef.current });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I could swear I tested this. Thanks for the catch!

// Let's create the websocket server on top of the proxy server
wsServerRef.current = new WebSocketServer({ server: serverRef.current });
wsServerRef.current.on("connection", (ws: WebSocket) => {
if (isConnected) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. will do.

* of the effect that connects to the remote websocket.
*/
const [
retryRemoteWebSocketConnectionSigil,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want a good word for something that's used to trigger the effect again. A sigil is used through history as a sign/seal of something. I would say counter, but that's the implementation detail. Got any suggestions for something better?

try {
remoteWebSocket.send(event.data);
} catch (e) {
if (e.message !== "WebSocket is not open: readyState 0 (CONNECTING)") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is brittle. I highly dount it's part of the spec. Honestly maybe this can just be silent. When I add a logger for #230 I'll revisit this. Leaving this conversation unresolved so I can find it later.

}
}

console[evt.type](...args);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revisit it. I actually try to avoid Ink for anything that doesn't feel like UI (and before you say it, I understand the boudnary is deeply fuzzy.) For example, in this usecase, I want the messages to persist no matter what, even if the process exits, etc. For now I think calling console methods makes sense, and matches behaviour closer to node (or at least, is already better than what we have, which is nothing.)

This PR rewrites the logic for establishing a connection with the debugging socket, and adds some enhancements along the way.  Part of solving #188. Some highlights -

- I've installed `"devtools-protocol`, a convenient package that has the static types for the devtools protocol (duh) autogenerated from chrome's devtools codebase.
- We now log messages and exceptions into the terminal directly, so you don't have to open devtools to see those messages.
- Messages are now buffered until a devtools instance connects, so you won't lose any messages while devtools isn't connected.
- We don't lose the connection on making changes to the worker, removing the need for the kludgy hack on the devtools side (where we refresh the whole page when there's a change)

Some things that I still have to do, and will do so in followup PRs -
- clear the console whenever we make a change to the worker
- stay connected when we shift between local/remote mode
- eventually, move to using the devtools hosted at cloudflareworkers.com.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants