Skip to content

Commit

Permalink
feat: implement graphql-ws ping/pong
Browse files Browse the repository at this point in the history
  • Loading branch information
andreialecu committed Aug 27, 2022
1 parent f25d973 commit 7b4f4d0
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 11 deletions.
26 changes: 23 additions & 3 deletions lib/subscription-client.js
Expand Up @@ -220,7 +220,11 @@ class SubscriptionClient {
if (operation) {
operation.handler(null)
this.operations.delete(operationId)
this.sendMessage(operationId, this.protocolMessageTypes.GQL_ERROR, data.payload)
this.sendMessage(
operationId,
this.protocolMessageTypes.GQL_ERROR,
data.payload
)
}
break
case this.protocolMessageTypes.GQL_COMPLETE:
Expand All @@ -238,11 +242,27 @@ class SubscriptionClient {
}
break
case this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE:
if (this.socket) {
this.sendMessage(
operationId,
this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE_ACK
)
}
break
/* istanbul ignore next */
default:
// GQL_CONNECTION_KEEP_ALIVE_ACK is only defined in the graphql-ws protocol
/* istanbul ignore next */
if (
this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE_ACK &&
data.type === this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE_ACK
) {
break
}

/* istanbul ignore next */
throw new MER_ERR_GQL_SUBSCRIPTION_MESSAGE_INVALID(`Invalid message type "${data.type}"`)
throw new MER_ERR_GQL_SUBSCRIPTION_MESSAGE_INVALID(
`Invalid message type "${data.type}"`
)
}
}

Expand Down
30 changes: 26 additions & 4 deletions lib/subscription-connection.js
Expand Up @@ -74,20 +74,42 @@ module.exports = class SubscriptionConnection {
break
case this.protocolMessageTypes.GQL_START: {
if (this.isReady) {
this.handleGQLStart(data).catch(e => {
this.sendMessage(this.protocolMessageTypes.GQL_ERROR, id, e.message)
this.handleGQLStart(data).catch((e) => {
this.sendMessage(
this.protocolMessageTypes.GQL_ERROR,
id,
e.message
)
})
} else {
this.sendMessage(this.protocolMessageTypes.GQL_CONNECTION_ERROR, undefined, { message: 'Connection has not been established yet.' })
this.sendMessage(
this.protocolMessageTypes.GQL_CONNECTION_ERROR,
undefined,
{ message: 'Connection has not been established yet.' }
)
return this.handleConnectionClose()
}
break
}
case this.protocolMessageTypes.GQL_STOP:
await this.handleGQLStop(data)
break
case this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE:
// GQL_CONNECTION_KEEP_ALIVE_ACK is only defined in the graphql-ws protocol
/* istanbul ignore next */
if (this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE_ACK) {
this.sendMessage(
this.protocolMessageTypes.GQL_CONNECTION_KEEP_ALIVE_ACK,
id
)
}
break
default:
this.sendMessage(this.protocolMessageTypes.GQL_ERROR, id, 'Invalid payload type')
this.sendMessage(
this.protocolMessageTypes.GQL_ERROR,
id,
'Invalid payload type'
)
}
}

Expand Down
3 changes: 2 additions & 1 deletion lib/subscription-protocol.js
Expand Up @@ -13,7 +13,8 @@ module.exports.getProtocolByName = function (name) {
GQL_CONNECTION_INIT: 'connection_init', // Client -> Server
GQL_CONNECTION_ACK: 'connection_ack', // Server -> Client
GQL_CONNECTION_ERROR: 'connection_error', // Server -> Client
GQL_CONNECTION_KEEP_ALIVE: 'ka', // Server -> Client
GQL_CONNECTION_KEEP_ALIVE: 'ping', // Bidirectional
GQL_CONNECTION_KEEP_ALIVE_ACK: 'pong', // Bidirectional
GQL_CONNECTION_TERMINATE: 'connection_terminate', // Client -> Server
GQL_START: 'subscribe', // Client -> Server
GQL_DATA: 'next', // Server -> Client
Expand Down
11 changes: 9 additions & 2 deletions test/subscription-client.js
Expand Up @@ -379,7 +379,14 @@ test('subscription client sends GQL_CONNECTION_KEEP_ALIVE when the keep alive op
ws.send(JSON.stringify({ id: '1', type: 'connection_ack' }))
} else if (data.type === 'start') {
ws.send(JSON.stringify({ id: '2', type: 'complete' }))
} else if (data.type === 'ka') {
} else if (data.type === 'ping') {
// this is a client sent ping, we reply with our pong
ws.send(JSON.stringify({ id: '3', type: 'pong' }))

// send a ping to the client so that it replies with its own pong
// pings and pongs are bidirectional
ws.send(JSON.stringify({ id: '4', type: 'ping' }))
} else if (data.type === 'pong') {
client.close()
server.close()
t.end()
Expand Down Expand Up @@ -414,7 +421,7 @@ test('subscription client not throwing error on GQL_CONNECTION_KEEP_ALIVE type p
ws.send(JSON.stringify({ id: undefined, type: 'connection_ack' }))

clock.setInterval(() => {
ws.send(JSON.stringify({ type: 'ka' }))
ws.send(JSON.stringify({ type: 'ping' }))
}, 200)
})

Expand Down
30 changes: 30 additions & 0 deletions test/subscription-connection.js
Expand Up @@ -341,6 +341,36 @@ test('subscription connection handles when GQL_START is called before GQL_INIT',
}))
})

test('subscription connection replies to GQL_CONNECTION_KEEP_ALIVE message with GQL_CONNECTION_KEEP_ALIVE_ACK', async (t) => {
t.plan(1)

const sc = new SubscriptionConnection(
{
on () {},
close () {},
send (message) {
t.equal(
JSON.stringify({
type: 'pong',
id: 1
}),
message
)
},
protocol: GRAPHQL_TRANSPORT_WS
},
{}
)

await sc.handleMessage(
JSON.stringify({
id: 1,
type: 'ping',
payload: {}
})
)
})

test('subscription connection extends context with onConnect return value', async (t) => {
t.plan(3)

Expand Down
3 changes: 2 additions & 1 deletion test/subscription-protocol.js
Expand Up @@ -20,7 +20,8 @@ test('getProtocolByName returns correct protocol message types', t => {
GQL_CONNECTION_INIT: 'connection_init',
GQL_CONNECTION_ACK: 'connection_ack',
GQL_CONNECTION_ERROR: 'connection_error',
GQL_CONNECTION_KEEP_ALIVE: 'ka',
GQL_CONNECTION_KEEP_ALIVE: 'ping',
GQL_CONNECTION_KEEP_ALIVE_ACK: 'pong',
GQL_CONNECTION_TERMINATE: 'connection_terminate',
GQL_START: 'subscribe',
GQL_DATA: 'next',
Expand Down

0 comments on commit 7b4f4d0

Please sign in to comment.