Skip to content

Conversation

m1212e
Copy link
Contributor

@m1212e m1212e commented Mar 19, 2025

Adds an adapter for crossws.

I know this has been discussed in #546 but with SvelteKit on the way of implementing native WS support via crossws I thought this might be useful nonetheless. We need to wait for h3js/crossws#149 before proceeding with this.

@enisdenjo
Copy link
Owner

Great stuff, thanks! Please make sure to also add it to the test servers so that the full server test suite can make sure it works as expected.

@m1212e
Copy link
Contributor Author

m1212e commented Mar 23, 2025

Hey, I'm trying to wrap around how the tests work and have two questions:

  1. https://github.com/m1212e/graphql-ws/blob/cca14ac3a3e9b3a8ea869aca2b7be52a97527620/tests/utils/tservers.ts#L966 What exactly are these config test things for and how do they have to be set?

  2. What is a proper way of converting the NodeAdapter from crossws to some form the test setup can work with? https://github.com/m1212e/graphql-ws/blob/cca14ac3a3e9b3a8ea869aca2b7be52a97527620/tests/utils/tservers.ts#L768 As far as I can see the crossws NodeAdapter is a reduced version of WebSocketServer but I can't really figure a way to make this work.

I'd be very thankful for your input on this!

@enisdenjo
Copy link
Owner

  1. The skipX and itForX are tests that either skip or run only for the given server. If you do skipWS('should do things', () => {}) the test will run for all servers except the WS server; the inverse is true for itForWS('should do things', () => {}), the test will run only for WS skipping other servers.
  2. Sorry but I am not familiar with crossws so I cant really answer your question. If crossws wraps the ws module in Node environments, then there should be a way. However, if there really isnt, you can set null and update the comment here - and of course make sure the tests needing the server pass.

Thank you for your work on this!

@m1212e
Copy link
Contributor Author

m1212e commented Mar 25, 2025

Ok, thanks! I tested a lot locally and with various example projects and it works pretty nice like this. Unfortunately I did not fully understand the test setup yet so I'll have to look into that when I find the time.

@m1212e m1212e marked this pull request as ready for review March 26, 2025 00:02
@enisdenjo
Copy link
Owner

Thanks! The server testing suite is not all that complicated, you have to:

Having the tests is a necessity for merging this PR. No hurry though, whenever you get time.

P.S. there are other failing checks, please see the CI

@m1212e
Copy link
Contributor Author

m1212e commented Mar 27, 2025

Hey, thanks for your fast response times, really helps me out/lets me stay motivated. Probably also not beneficial to do this after a full 8h work day all the time so please excuse all those questions.
But I think I might have a quick and not totally stupid solution for the tests? Maybe not, curious what your take on this is but I kinda just hijacked the existing uws test, put the already existing uws crossw adapter in between and got nearly all tests passing, only 2 are left failing:

 FAIL  tests/use.test.ts > crossws > Keep-Alive > should dispatch pings after the timeout has passed
 FAIL  tests/use.test.ts > crossws > Keep-Alive > should terminate the socket if no pong is sent in response to a ping

I know this isn't 'really' testing the specifics of the crossws adapter but since these are integration tests anyway and crossws isn't a real adapter for a runtime on its own but more of a glue between the two systems this might suffice your requirements. Let me hear what you think!

Regarding the failed tests: These refer to this part of the spec, right? I'm not tooo into WS and graphql-transport-ws so I'm wondering if this a specific graphql-transport-ws or a general WS thing. I'm asking because the deno and bun client do not have explicit logic on this, it seems to me like the clients do that automatically? Would the crossws adapter be the right place to implement a ping logic and what if the client already does(like bun?), should the adapter check that and prevent double pings in these cases?

@m1212e
Copy link
Contributor Author

m1212e commented May 6, 2025

Hey, did you by any chance have the time to take a look or some feedback on this?

@pi0
Copy link

pi0 commented May 6, 2025

(crossws maintainer here) LMK if could do any help on this ❤️

Copy link
Owner

@enisdenjo enisdenjo left a comment

Choose a reason for hiding this comment

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

Other than this one remark, this looks good to me.

Please create a changeset using yarn changeset in order to preare a release.

I'll also have to check why's the CI not running here. 🤔

Copy link
Owner

@enisdenjo enisdenjo left a comment

Choose a reason for hiding this comment

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

Aside from adding the example in the changelog, it'd be a good idea to add it to the get started documentation too to advertise the support.

In the get-started.mdx, add a new entry under "Start the server" called "With crossws".

@enisdenjo
Copy link
Owner

Oh and don't forget to run yarn and update the lockfile, so that we can have the CI run.

@enisdenjo
Copy link
Owner

enisdenjo commented May 7, 2025

Please fix the formatting, run yarn format.

Regarding the failing tests and your comment, those tests are important and should not be ignored. Those keep-alive pings and pongs are not from the GraphQL over WS spec, but are from the WebSocket spec itself.

You can advise the ws implementation to see how the keepalive is implemented there:

graphql-ws/src/use/ws.ts

Lines 108 to 131 in 5161bd9

// keep alive through ping-pong messages
let pongWait: ReturnType<typeof setTimeout> | null = null;
const pingInterval =
keepAlive > 0 && isFinite(keepAlive)
? setInterval(() => {
// ping pong on open sockets only
if (socket.readyState === socket.OPEN) {
// terminate the connection after pong wait has passed because the client is idle
pongWait = setTimeout(() => {
socket.terminate();
}, keepAlive);
// listen for client's pong and stop socket termination
socket.once('pong', () => {
if (pongWait) {
clearTimeout(pongWait);
pongWait = null;
}
});
socket.ping();
}
}, keepAlive)
: null;

Disclaimer, I don't fully understand crossws and how/whether this can be implemented in crossws, maybe @pi0 can help? What are your thoughts here? How do you see handling keepalive?

@pi0
Copy link

pi0 commented May 7, 2025

Escalated to h3js/crossws#154 for keep alive support

@enisdenjo
Copy link
Owner

Ok cool, thanks @pi0.

@m1212e since this is a missing feature in crossws, you can go ahead and skip those two tests for crossws until #154 gets resolved and then we can implement it here if necessary. Thank you for your hard work here! We're close, lets get this to the finish line!

@enisdenjo
Copy link
Owner

Great work @m1212e! Thank you!

@enisdenjo enisdenjo merged commit fce94fa into enisdenjo:master May 9, 2025
15 checks passed
@m1212e m1212e deleted the crossws-adapter branch May 13, 2025 12:11
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.

3 participants