diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c6ad2b2..abe498619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Added built-in support for `subscribe` from `graphql-js` [PR #133](https://github.com/apollographql/subscriptions-transport-ws/pull/133) - Fixed infinity reconnects when server accepts connections but its in an error state. [PR #135](https://github.com/apollographql/subscriptions-transport-ws/pull/135) - Force close client-side socket when using `close()`, and ignore reconnect logic. [PR #137](https://github.com/apollographql/subscriptions-transport-ws/pull/137) - +- Added new connection events to give a more accurate control over the connection state [PR #139]. Fixes [Issue #136]. ### 0.6.0 diff --git a/src/client.ts b/src/client.ts index 258c66bd5..d048a3a42 100644 --- a/src/client.ts +++ b/src/client.ts @@ -177,16 +177,54 @@ export class SubscriptionClient { }; } + /** + * @deprecated This method will become deprecated in the next release. + * You can use onConnecting and onConnected instead. + */ public onConnect(callback: ListenerFn, context?: any): Function { - return this.on('connect', callback, context); + this.logWarningOnNonProductionEnv('This method will become deprecated in the next release. ' + + 'You can use onConnecting and onConnected instead.'); + return this.onConnecting(callback, context); } + /** + * @deprecated This method will become deprecated in the next release. + * You can use onDisconnected instead. + */ public onDisconnect(callback: ListenerFn, context?: any): Function { - return this.on('disconnect', callback, context); + this.logWarningOnNonProductionEnv('This method will become deprecated in the next release. ' + + 'You can use onDisconnected instead.'); + return this.onDisconnected(callback, context); } + /** + * @deprecated This method will become deprecated in the next release. + * You can use onReconnecting and onReconnected instead. + */ public onReconnect(callback: ListenerFn, context?: any): Function { - return this.on('reconnect', callback, context); + this.logWarningOnNonProductionEnv('This method will become deprecated in the next release. ' + + 'You can use onReconnecting and onReconnected instead.'); + return this.onReconnecting(callback, context); + } + + public onConnected(callback: ListenerFn, context?: any): Function { + return this.on('connected', callback, context); + } + + public onConnecting(callback: ListenerFn, context?: any): Function { + return this.on('connecting', callback, context); + } + + public onDisconnected(callback: ListenerFn, context?: any): Function { + return this.on('disconnected', callback, context); + } + + public onReconnected(callback: ListenerFn, context?: any): Function { + return this.on('reconnected', callback, context); + } + + public onReconnecting(callback: ListenerFn, context?: any): Function { + return this.on('reconnecting', callback, context); } public unsubscribe(opId: number) { @@ -238,6 +276,12 @@ export class SubscriptionClient { return this; } + private logWarningOnNonProductionEnv(warning: string) { + if (process && process.env && process.env.NODE_ENV !== 'production') { + console.warn(warning); + } + } + private checkOperationOptions(options: OperationOptions, handler: (error: Error[], result?: any) => void) { const { query, variables, operationName } = options; @@ -386,7 +430,7 @@ export class SubscriptionClient { this.client = new this.wsImpl(this.url, GRAPHQL_WS); this.client.onopen = () => { - this.eventEmitter.emit(this.reconnecting ? 'reconnect' : 'connect'); + this.eventEmitter.emit(this.reconnecting ? 'reconnecting' : 'connecting'); const payload: ConnectionParams = typeof this.connectionParams === 'function' ? this.connectionParams() : this.connectionParams; @@ -396,7 +440,7 @@ export class SubscriptionClient { }; this.client.onclose = () => { - this.eventEmitter.emit('disconnect'); + this.eventEmitter.emit('disconnected'); if (this.forceClose) { this.forceClose = false; @@ -444,6 +488,7 @@ export class SubscriptionClient { break; case MessageTypes.GQL_CONNECTION_ACK: + this.eventEmitter.emit(this.reconnecting ? 'reconnected' : 'connected'); this.reconnecting = false; this.backoff.reset(); diff --git a/src/test/tests.ts b/src/test/tests.ts index 42ccd80b1..691b44afd 100644 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -302,6 +302,81 @@ describe('Client', function () { }); }); + it('should emit connected event for client side when socket closed', (done) => { + const client = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`); + const onConnectingSpy = sinon.spy(); + const unregisterOnConnecting = client.onConnecting(onConnectingSpy); + + const unregister = client.onConnected(() => { + unregisterOnConnecting(); + unregister(); + expect(onConnectingSpy.called).to.equal(true); + done(); + }); + }); + + it('should emit connecting event for client side when socket closed', (done) => { + const subscriptionsClient = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`); + const onConnectedSpy = sinon.spy(); + const unregisterOnConnected = subscriptionsClient.onConnected(onConnectedSpy); + const unregisterOnConnecting = subscriptionsClient.onConnecting(() => { + unregisterOnConnecting(); + unregisterOnConnected(); + expect(onConnectedSpy.called).to.equal(false); + done(); + }); + }); + + it('should emit disconnected event for client side when socket closed', (done) => { + const client = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`, { + connectionCallback: () => { + client.client.close(); + }, + }); + + const unregister = client.onDisconnected(() => { + unregister(); + done(); + }); + }); + + it('should emit reconnected event for client side when socket closed', (done) => { + const client = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`, { + reconnect: true, + reconnectionAttempts: 1, + connectionCallback: () => { + client.client.close(); + }, + }); + const onReconnectingSpy = sinon.spy(); + const unregisterOnReconnecting = client.onReconnecting(onReconnectingSpy); + + const unregister = client.onReconnected(() => { + unregisterOnReconnecting(); + unregister(); + expect(onReconnectingSpy.called).to.equal(true); + done(); + }); + }); + + it('should emit reconnecting event for client side when socket closed', (done) => { + const subscriptionsClient = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`, { + reconnect: true, + reconnectionAttempts: 1, + connectionCallback: () => { + subscriptionsClient.client.close(); + }, + }); + const onReconnectedSpy = sinon.spy(); + const unregisterOnReconnected = subscriptionsClient.onReconnected(onReconnectedSpy); + const unregisterOnReconnecting = subscriptionsClient.onReconnecting(() => { + unregisterOnReconnecting(); + unregisterOnReconnected(); + expect(onReconnectedSpy.called).to.equal(false); + done(); + }); + }); + it('should throw an exception when query is not provided', (done) => { const client = new SubscriptionClient(`ws://localhost:${TEST_PORT}/`); @@ -481,7 +556,7 @@ describe('Client', function () { }); }); - it('should handle init_fail message and handle server that closes connection', (done) => { + it('should handle connection_error message and handle server that closes connection', (done) => { let client: any = null; wsServer.on('connection', (connection: any) => {