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

WebSocket hooks don't fire when using the exported listen function from Listhen #174

Open
joshmossas opened this issue Mar 19, 2024 · 6 comments
Labels
documentation Improvements or additions to documentation enhancement New feature or request

Comments

@joshmossas
Copy link

Environment

NodeJS - v20.11.1
H3 - v1.11.1

Reproduction

https://github.com/joshmossas/h3-ws-listhen-bug

Describe the bug

If you start an h3 server like so:

listen(toNodeListener(app), {
  ws: true,
  public: true,
});

None of the websocket hooks in defineWebSocketHandler will fire. Oddly enough, clients are still able to connect and send messages. So listhen is upgrading the connection, but for some reason none of the hooks go off meaning you can't actually send messages back.

If you refactor the code to use the listhen cli. It starts working again:

// index.ts
export default app;
listhen ./index.ts --ws --public

Additional context

I'm unsure if the bug is happening on the H3 side or the Listhen side. I did try digging through both code bases for a while and couldn't figure out what was causing this behavior.

Logs

No response

@joshmossas joshmossas changed the title Websocket hooks don't fire when using the exported listen function from Listhen WebSocket hooks don't fire when using the exported listen function from Listhen Mar 19, 2024
@joshmossas
Copy link
Author

joshmossas commented Apr 4, 2024

Okay I've determined the issue. It turns out the listhen cli does some magic to assign the websocket hooks from the h3 app handler. The raw listen function doesn't do any of that magic so you have to tell it how to resolve the websocket hooks manually.

To get things working I had to change it from

listen(toNodeListener(app), {
  ws: true,
  public: true,
});

to the following

listen(toNodeListener(app), {
  ws: {
    async resolve(info) {
      if (app.websocket.resolve) {
        return app.websocket.resolve(info);
      }
      return app.websocket.hooks ?? app.handler.__websocket__ ?? {};
    },
  },
  public: true,
});

Perhaps there should be some documentation added regarding this? Or is the goal that the listhen listen function also handles this automatically out of the box?

@manniL manniL added documentation Improvements or additions to documentation enhancement New feature or request labels May 5, 2024
@manniL
Copy link
Member

manniL commented May 5, 2024

This seems more like a listhen issue then.

It is also denoted in the README that the CLI does enable it easily

@manniL manniL transferred this issue from unjs/h3 May 5, 2024
@joshmossas
Copy link
Author

joshmossas commented May 8, 2024

Hmm, so then I think the main thing that needs to be fixed is updating the exported ListenOptions type

Right now ws accepts boolean, CrossWSOptions, or ((req: IncomingMessage, head: Buffer) => void), but using a boolean doesn't actually work. So the typing is deceptive.

It looks like the main reason why the type allows for a boolean is because the CLI reuses the ListenOptions type so we would need to some slight modifications to the typing in the CLI to make it work. From an initial glance the cli could prolly use something like this

Omit<ListenOptions, "ws"> & { ws?: boolean | CrossWsOptions | ((req: IncomingMessage, head: Buffer) => void) }

(This is pseudo code I would need to actually test in an editor haha)

I'd be willing to open a PR for this if the team is open to my proposed (pseudo code) solution. Fixing this would remove future confusion surrounding this feature imo, especially since the README does outline the proper usage. Although a more detailed example probably wouldn't hurt either.

@pi0
Copy link
Member

pi0 commented May 9, 2024

Thanks for investigations and initial reproduction. I think we can do as you proposed in #174 (comment) also in programmatic when true value is passed.

@joshmossas
Copy link
Author

Okay sure. That would create a more seamless dev experience!

I suppose the only blocker for what I wrote in #174 (comment) is that it has access to the original H3 app giving me access to app.websocket.whatever. The listen() function would only have access to the result of toNodeListener(). Is there a way to get the original H3 app out of a NodeListener?

If not we can take a couple approaches:

  1. Have H3 modify the NodeListener by adding a webhook resolver that listhen can check for. This will make ws: true work but only for H3 apps.
  2. Make use of logic similar to what the CLI does. (It looks like it creates a parent H3 app which wraps the NodeListener and use that to resolve webhooks).

The second option is prolly more self contained. I can start exploring that unless you think the first option is preferable.

@pi0
Copy link
Member

pi0 commented May 9, 2024

Exploring with second is good start. We might find a lighter wrapper to replace h3 layer in both places

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants