diff --git a/packages/javascript-api/src/lib/services/graphql/__tests__/graphql-subscriptions.spec.ts b/packages/javascript-api/src/lib/services/graphql/__tests__/graphql-subscriptions.spec.ts index f13e138a..7220223f 100644 --- a/packages/javascript-api/src/lib/services/graphql/__tests__/graphql-subscriptions.spec.ts +++ b/packages/javascript-api/src/lib/services/graphql/__tests__/graphql-subscriptions.spec.ts @@ -245,6 +245,19 @@ describe('GraphQL subscriptions', () => { subscription.unsubscribe(); }); + it('replies with a pong when the server sends a ping', async () => { + const subscription = fixture.triggerSubscription(); + + await fixture.handleConnectionInit(); + await fixture.consumeSubscribeMessage(); + + fixture.sendMessageToClient({ type: 'ping' }); + + expect(await fixture.getNextMessage()).toEqual({ type: 'pong' }); + + subscription.unsubscribe(); + }); + it('when the server sends an error, it will reconnect and subscribe again', async () => { const subscription = fixture.triggerSubscription(); useFakeSetInterval(); diff --git a/packages/javascript-api/src/lib/services/graphql/__tests__/subscription-protocol.spec.ts b/packages/javascript-api/src/lib/services/graphql/__tests__/subscription-protocol.spec.ts index 01005f20..b52111c4 100644 --- a/packages/javascript-api/src/lib/services/graphql/__tests__/subscription-protocol.spec.ts +++ b/packages/javascript-api/src/lib/services/graphql/__tests__/subscription-protocol.spec.ts @@ -32,6 +32,10 @@ describe('LegacySubscriptionProtocol', () => { it('serializes ping without id and payload', () => { expect(JSON.parse(protocol.serializePing())).toEqual({ type: 'ping' }); }); + + it('serializes pong without id and payload', () => { + expect(JSON.parse(protocol.serializePong())).toEqual({ type: 'pong' }); + }); }); describe('incoming frames', () => { @@ -47,6 +51,12 @@ describe('LegacySubscriptionProtocol', () => { ).toEqual({ type: 'connection-ack' }); }); + it('parses a server-sent ping', () => { + expect(protocol.parseIncomingMessage('{"type":"ping"}')).toEqual({ + type: 'ping', + }); + }); + it('parses pong', () => { expect(protocol.parseIncomingMessage('{"type":"pong"}')).toEqual({ type: 'pong', diff --git a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts index b38c4980..702d50b3 100644 --- a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts +++ b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts @@ -542,6 +542,10 @@ export class GraphqlService { break; } + case 'ping': + this.sendRawMessage(this.protocol.serializePong()); + break; + case 'pong': clearTimeout(this.pongTimeout); this.connectionAttemptsCount = 0; diff --git a/packages/javascript-api/src/lib/services/graphql/subscription-protocol.ts b/packages/javascript-api/src/lib/services/graphql/subscription-protocol.ts index 9f9c8287..04441eee 100644 --- a/packages/javascript-api/src/lib/services/graphql/subscription-protocol.ts +++ b/packages/javascript-api/src/lib/services/graphql/subscription-protocol.ts @@ -17,6 +17,7 @@ export interface QminderGraphQLError { export type IncomingSubscriptionMessage = | { readonly type: 'connection-ack' } | { readonly type: 'keep-alive' } + | { readonly type: 'ping' } | { readonly type: 'pong' } | { readonly type: 'data'; @@ -45,6 +46,7 @@ export interface SubscriptionProtocol { serializeSubscribe(id: string, query: string): string; serializeUnsubscribe(id: string): string; serializePing(): string; + serializePong(): string; parseIncomingMessage(data: string): IncomingSubscriptionMessage; } @@ -106,6 +108,10 @@ export class LegacySubscriptionProtocol implements SubscriptionProtocol { return JSON.stringify({ type: LegacyMessageType.GQL_PING }); } + serializePong(): string { + return JSON.stringify({ type: LegacyMessageType.GQL_PONG }); + } + parseIncomingMessage(data: string): IncomingSubscriptionMessage { const message: LegacyMessage = JSON.parse(data); @@ -116,6 +122,9 @@ export class LegacySubscriptionProtocol implements SubscriptionProtocol { case LegacyMessageType.GQL_CONNECTION_ACK: return { type: 'connection-ack' }; + case LegacyMessageType.GQL_PING: + return { type: 'ping' }; + case LegacyMessageType.GQL_PONG: return { type: 'pong' };