Skip to content

Commit 0016bdd

Browse files
authored
add ping(websocket, payload) util (#4325)
1 parent 33274d0 commit 0016bdd

5 files changed

Lines changed: 116 additions & 2 deletions

File tree

docs/docs/api/WebSocket.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,33 @@ setInterval(() => write(), 5000)
7878

7979
```
8080

81+
## ping(websocket, payload)
82+
Arguments:
83+
84+
* **websocket** `WebSocket` - The WebSocket instance to send the ping frame on
85+
* **payload** `Buffer|undefined` (optional) - Optional payload data to include with the ping frame. Must not exceed 125 bytes.
86+
87+
Sends a ping frame to the WebSocket server. The server must respond with a pong frame containing the same payload data. This can be used for keepalive purposes or to verify that the connection is still active.
88+
89+
### Example:
90+
91+
```js
92+
import { WebSocket, ping } from 'undici'
93+
94+
const ws = new WebSocket('wss://echo.websocket.events')
95+
96+
ws.addEventListener('open', () => {
97+
// Send ping with no payload
98+
ping(ws)
99+
100+
// Send ping with payload
101+
const payload = Buffer.from('hello')
102+
ping(ws, payload)
103+
})
104+
```
105+
106+
**Note**: A ping frame cannot have a payload larger than 125 bytes. The ping will only be sent if the WebSocket connection is in the OPEN state.
107+
81108
## Read More
82109

83110
- [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)

index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,12 @@ module.exports.parseMIMEType = parseMIMEType
157157
module.exports.serializeAMimeType = serializeAMimeType
158158

159159
const { CloseEvent, ErrorEvent, MessageEvent } = require('./lib/web/websocket/events')
160-
module.exports.WebSocket = require('./lib/web/websocket/websocket').WebSocket
160+
const { WebSocket, ping } = require('./lib/web/websocket/websocket')
161+
module.exports.WebSocket = WebSocket
161162
module.exports.CloseEvent = CloseEvent
162163
module.exports.ErrorEvent = ErrorEvent
163164
module.exports.MessageEvent = MessageEvent
165+
module.exports.ping = ping
164166

165167
module.exports.WebSocketStream = require('./lib/web/websocket/stream/websocketstream').WebSocketStream
166168
module.exports.WebSocketError = require('./lib/web/websocket/stream/websocketerror').WebSocketError

lib/web/websocket/websocket.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
isConnecting,
99
isEstablished,
1010
isClosing,
11+
isClosed,
1112
isValidSubprotocol,
1213
fireEvent,
1314
utf8Decode,
@@ -21,6 +22,7 @@ const { getGlobalDispatcher } = require('../../global')
2122
const { types } = require('node:util')
2223
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
2324
const { SendQueue } = require('./sender')
25+
const { WebsocketFrameSend } = require('./frame')
2426
const { channels } = require('../../core/diagnostics')
2527

2628
/**
@@ -586,8 +588,32 @@ class WebSocket extends EventTarget {
586588
})
587589
}
588590
}
591+
592+
/**
593+
* @param {WebSocket} ws
594+
* @param {Buffer|undefined} buffer
595+
*/
596+
static ping (ws, buffer) {
597+
if (!Buffer.isBuffer(buffer)) {
598+
throw new TypeError('Expected buffer payload')
599+
} else if (buffer.length > 125) {
600+
throw new TypeError('A PING frame cannot have a body larger than 125 bytes.')
601+
}
602+
603+
// An endpoint MAY send a Ping frame any time after the connection is
604+
// established and before the connection is closed.
605+
const readyState = ws.#handler.readyState
606+
607+
if (isEstablished(readyState) && !isClosing(readyState) && !isClosed(readyState)) {
608+
const frame = new WebsocketFrameSend(buffer)
609+
ws.#handler.socket.write(frame.createFrame(opcodes.PING))
610+
}
611+
}
589612
}
590613

614+
const { ping } = WebSocket
615+
Reflect.deleteProperty(WebSocket, 'ping')
616+
591617
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
592618
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING
593619
// https://websockets.spec.whatwg.org/#dom-websocket-open
@@ -682,5 +708,6 @@ webidl.converters.WebSocketSendData = function (V) {
682708
}
683709

684710
module.exports = {
685-
WebSocket
711+
WebSocket,
712+
ping
686713
}

test/websocket/ping-util.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict'
2+
3+
const { test } = require('node:test')
4+
const { WebSocketServer } = require('ws')
5+
const { WebSocket, ping } = require('../..')
6+
const { tspl } = require('@matteo.collina/tspl')
7+
8+
test('ping', async (t) => {
9+
const { deepStrictEqual, completed } = tspl(t, { plan: 1 })
10+
11+
const pingBody = Buffer.from('ping body')
12+
const wss = new WebSocketServer({ port: 0 })
13+
14+
wss.on('connection', (ws) => {
15+
ws.on('ping', (b) => {
16+
deepStrictEqual(b, pingBody)
17+
})
18+
})
19+
20+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`)
21+
22+
ws.addEventListener('open', () => {
23+
ping(ws, pingBody)
24+
})
25+
26+
t.after(() => {
27+
ws.close()
28+
wss.close()
29+
})
30+
31+
await completed
32+
})
33+
34+
test('attempting to send invalid ping body', async (t) => {
35+
const { completed, throws, fail } = tspl(t, { plan: 2 })
36+
37+
const wss = new WebSocketServer({ port: 0 })
38+
39+
wss.on('connection', (ws) => {
40+
ws.on('ping', () => {
41+
fail('Received unexpected ping')
42+
})
43+
})
44+
45+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`)
46+
47+
throws(() => ping(ws, Buffer.from('a'.repeat(126))))
48+
throws(() => ping(ws, 'a'.repeat(125)))
49+
50+
t.after(() => {
51+
ws.close()
52+
wss.close()
53+
})
54+
55+
await completed
56+
})

types/websocket.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,5 @@ export declare const WebSocketError: {
182182
prototype: WebSocketError
183183
new (type: string, init?: WebSocketCloseInfo): WebSocketError
184184
}
185+
186+
export declare const ping: (ws: WebSocket, body?: Buffer) => void

0 commit comments

Comments
 (0)