-
Notifications
You must be signed in to change notification settings - Fork 341
Conversation
some cleanup for old code
some cleanup for old code
public client: any; | ||
public subscriptions: Subscriptions; | ||
private url: string; | ||
private maxId: number; | ||
private connectionParams: ConnectionParams; | ||
private subscriptionTimeout: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pre-existing issue but one worth considering: if you use reconnect, then subscriptionTimeout (which cannot be disabled) seems broken. Like, any subscribe is going to time out if you call it while you're offline, even though you might later reconnect! Will apps actually respond usefully to failed subscriptions by trying to subscribe again later? Tracing the timeout error looks like it would eventually get passed to an onError handler passed to subscribeToMore, which neither your GS PR nor GitHunt-react passes. Hmm...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, need to handle this with onError
, will update the GS PR.
- `options?: Object` : optional object to modify default client behavior | ||
* `timeout: number` : how long the client should wait in ms for a subscription to be started (default 5000 ms) | ||
- `options?: Object` : optional, object to modify default client behavior | ||
* `timeout?: number` : how long the client should wait in ms for a subscription to be started (default 5000 ms)how long the client should wait in ms for a subscription to be started (default 5000 ms) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is doubled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
README.md
Outdated
* `onSubscribe?: (message: SubscribeMessage, params: SubscriptionOptions, webSocketRequest: WebSocketRequest)` : optional method to create custom params that will be used when resolving this subscription | ||
* `onSubscribe?: (message: SubscribeMessage, params: SubscriptionOptions, webSocket: WebSocket)` : optional method to create custom params that will be used when resolving this subscription | ||
* `onUnsubscribe?: (webSocket: WebSocket)` : optional method that called when a client unsubscribe | ||
* `onConnect?: (connectionParams: Object, webSocket: WebSocket)` : optional method that called when a client connects to the socket, called with the `connectionParams` from the client, the return value of this callback will be available as part of the `context` of the subscription. return `false` or throw and exception to reject the connection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document that it may return a Promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also "an exception" not "and"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and be more specific about how the result is used: "if the return value is an object, its elements will be added to the context
" or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
## Client-server messages | ||
Each message has a type, as well as associated fields depending on the message type. | ||
### Client -> Server | ||
|
||
#### INIT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So interesting naming wrinkle here. The existing messages all start with SUBSCRIPTION_ and your new ones don't. Abstractly I think it's great to separate the INIT messages from the SUBSCRIPTION messages. But now SUBSCRIPTION_KEEPALIVE is weirdly named. Since we're doing enough backwards-incompatible stuff here maybe rename that one to KEEPALIVE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to KEEPALIVE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good but there are a couple remaining references to SUBSCRIPTION_KEEPALIVE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/client-websocket.ts
Outdated
declare let window: any; | ||
let _global = typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window: {}); | ||
|
||
import * as NodeSocket from 'ws'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the old websocket package has a browser
field in its package.json which points to a tiny file which just exports the native WebSocket object. This means that when, eg, webpack (probably Meteor too, I only tested with GitHunt-react) bundles up subscriptions-transport-ws, it won't actually include all of the Node-oriented code from websocket in the client bundle.
That doesn't seem to work here: ws
doesn't have the browser field so you're always going to bundle the entire ws
library into the client bundle even though none of it is used. This seems bad. I think this would happen even if you converted the import into a require and put it under a conditional?
I think you can probably fix this by using the "browser" field yourself in this package's package.json somehow? I know we want to be able to use Client on the server too (for tests if nothing else) but there's probably some way to do it that keeps code splitting working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous package, websocket
had the logic that inside the file client-websocket
internally, and this was the browser
file.
I changed the usage so ws
will be required only when there is no native WebSocket available, as suggested here: websockets/ws#837 , and also added browser
property to expose ./dist/client.js
only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. dist/client.js still pulls in dist/client-websocket.js which has a require('ws')
written in it (as opposed to in the old websocket module whose lib/client.js literally doesn't mention require). Are you sure this will work properly when webpacked? I admit I forget how I tested this a few weeks ago...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. I will verify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do verify this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the implementation of the client package, and now the client package look for the native WebSocket
object, and you can provide an optional implementation if you are using Node.
This way, ws
package is not part of the Meteor client bundle, and not loaded at all.
Other option, is to use object for browser
field and specify that ws
is ignored for browsers. But this is not supported yet for Meteor build (meteor/meteor#6890).
src/server.ts
Outdated
return; | ||
} | ||
|
||
// accept connection | ||
const connection: Connection = request.accept(GRAPHQL_SUBSCRIPTIONS, request.origin); | ||
request.resume(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, we call resume synchronously with pause? That seems weird... why call pause in the first place? Also pause/resume is the ancient streams API, is this really the right thing to use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we don't need it at all.. removed the pause
and resume
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
case INIT: | ||
let onConnectPromise = Promise.resolve(true); | ||
if (this.onConnect) { | ||
onConnectPromise = Promise.resolve(this.onConnect(parsedMessage.payload, connection)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good that onConnect doesn't have to be synchronous, but you shouldn't process other messages until it's done running! As far as I can tell more onMessages can be invoked while onConnect is running. (This should be easy to write a test for.)
Perhaps store the Promise that contains the initResult on this, and then
it before doing anything else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be simpler to get rid of initResult and just resolve the initPromise with initResult directly? You don't use initResult anywhere that's not already inside a initPromise.then...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/server.ts
Outdated
|
||
case SUBSCRIPTION_START: | ||
const baseParams: SubscriptionOptions = { | ||
query: parsedMessage.query, | ||
variables: parsedMessage.variables, | ||
operationName: parsedMessage.operationName, | ||
context: {}, | ||
context: Object.assign({}, this.initResult), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't this.initResult
be true
? Maaaybe that works with Object.assign but it sure feels weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? Still looks the same to me...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see, you only set initResult if it's an object. Maybe this is also the answer to my last question about why initResult isn't the value of initPromise? (But you still could probably make it be the value of initPromise by converting non-objects to {}
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/server.ts
Outdated
delete connectionSubscriptions[subId]; | ||
} | ||
break; | ||
|
||
default: | ||
this.sendSubscriptionFail(connection, subId, { | ||
errors: [{ | ||
message: 'Invalid message type. Message type must be `subscription_start` or `subscription_end`.' | ||
}] | ||
message: 'Invalid message type!.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
! or . not both
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/server.ts
Outdated
private sendInitResult(connection: WebSocket, result: any): void { | ||
connection.send(JSON.stringify(result), () => { | ||
if (result.type === INIT_FAIL) { | ||
connection.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why call both? Deserves at least a comment on why it's not appropriate to call just one of the similar methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment about this.
close
closes the socket and sends the error code, while terminate
sends the actual FIN packet and terminates the entire WS connection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that this actually works? Does the close packet actually get sent out over the network or does calling terminate synchronously close the socket too fast (I think it does). You can test this with wireshark or tcpdump or something...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be verified too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested it and it looks fine, the client side gets the close message with the code, and the the socket terminates. Also, this is how ws
uses it in it's unit tests.
@Urigo I merged some PRs on the other branch, but then I saw that you guys are still working here, so I'll let you publish the new version. Because of the new typings a minor version bump will be required. |
# Conflicts: # src/server.ts # src/test/tests.ts # tsconfig.json
@mlp5ab this is the pr for auth! |
# Conflicts: # src/index.ts
* typings.json: shut up typings install * package.json: bump all dependencies Signed-off-by: Pierre Carrier <pierre@meteor.com>
@Urigo Any documentation related to the changes by this PR? |
ws
package instead ofwebsocket
(https://npmcompare.com/compare/websocket,ws)Related: