Skip to content

Commit 261300d

Browse files
authored
feat: provide lifecycle hooks for injectWS (#341)
* feat: provide lifecycle hooks for injectWS * test: add typescript test
1 parent fd08ce2 commit 261300d

File tree

4 files changed

+76
-15
lines changed

4 files changed

+76
-15
lines changed

index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ function fastifyWebsocket (fastify, opts, next) {
5151
const wss = new WebSocket.Server(wssOptions)
5252
fastify.decorate('websocketServer', wss)
5353

54-
async function injectWS (path = '/', upgradeContext = {}) {
54+
// TODO: place upgrade context as options
55+
async function injectWS (path = '/', upgradeContext = {}, options = {}) {
5556
const server2Client = new PassThrough()
5657
const client2Server = new PassThrough()
5758

@@ -64,7 +65,10 @@ function fastifyWebsocket (fastify, opts, next) {
6465
let resolve, reject
6566
const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject })
6667

68+
typeof options.onInit === 'function' && options.onInit(ws)
69+
6770
ws.on('open', () => {
71+
typeof options.onOpen === 'function' && options.onOpen(ws)
6872
clientStream.removeListener('data', onData)
6973
resolve(ws)
7074
})

test/inject.test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,43 @@ test('rejects if the websocket is not upgraded', async (t) => {
132132
await fastify.ready()
133133
await t.assert.rejects(fastify.injectWS('/'), new Error('Unexpected server response: 401'))
134134
})
135+
136+
test('inject hooks', async (t) => {
137+
const fastify = buildFastify(t)
138+
const message = 'hi from client'
139+
140+
let _resolve
141+
const promise = new Promise((resolve) => { _resolve = resolve })
142+
143+
fastify.register(
144+
async function (instance) {
145+
instance.get('/ws', { websocket: true }, function (socket) {
146+
socket.once('message', chunk => {
147+
_resolve(chunk.toString())
148+
})
149+
})
150+
})
151+
152+
await fastify.ready()
153+
154+
let order = 0
155+
let initWS, openWS
156+
const ws = await fastify.injectWS('/ws', {}, {
157+
onInit (ws) {
158+
t.assert.strictEqual(order, 0)
159+
order++
160+
initWS = ws
161+
},
162+
onOpen (ws) {
163+
t.assert.strictEqual(order, 1)
164+
order++
165+
openWS = ws
166+
}
167+
})
168+
ws.send(message)
169+
t.assert.strictEqual(order, 2)
170+
t.assert.deepStrictEqual(ws, initWS)
171+
t.assert.deepStrictEqual(ws, openWS)
172+
t.assert.deepStrictEqual(await promise, message)
173+
ws.terminate()
174+
})

types/index.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/// <reference types="node" />
2-
import { IncomingMessage, ServerResponse, Server } from 'node:http'
3-
import { FastifyRequest, FastifyPluginCallback, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RequestGenericInterface, ContextConfigDefault, FastifyInstance, FastifySchema, FastifyTypeProvider, FastifyTypeProviderDefault, FastifyBaseLogger } from 'fastify'
42
import * as fastify from 'fastify'
5-
import * as WebSocket from 'ws'
3+
import { ContextConfigDefault, FastifyBaseLogger, FastifyInstance, FastifyPluginCallback, FastifyRequest, FastifySchema, FastifyTypeProvider, FastifyTypeProviderDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestGenericInterface } from 'fastify'
4+
import { preCloseAsyncHookHandler, preCloseHookHandler } from 'fastify/types/hooks'
65
import { FastifyReply } from 'fastify/types/reply'
7-
import { preCloseHookHandler, preCloseAsyncHookHandler } from 'fastify/types/hooks'
86
import { RouteGenericInterface } from 'fastify/types/route'
7+
import { IncomingMessage, Server, ServerResponse } from 'node:http'
8+
import * as WebSocket from 'ws'
99

1010
interface WebsocketRouteOptions<
1111
RawServer extends RawServerBase = RawServerDefault,
@@ -27,8 +27,13 @@ declare module 'fastify' {
2727
websocket?: boolean;
2828
}
2929

30+
interface InjectWSOption {
31+
onInit?: (ws: WebSocket.WebSocket) => void
32+
onOpen?: (ws: WebSocket.WebSocket) => void
33+
}
34+
3035
type InjectWSFn<RawRequest> =
31-
((path?: string, upgradeContext?: Partial<RawRequest>) => Promise<WebSocket>)
36+
((path?: string, upgradeContext?: Partial<RawRequest>, options?: InjectWSOption) => Promise<WebSocket>)
3237

3338
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3439
interface FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider> {

types/index.test-d.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
// eslint-disable-next-line import-x/no-named-default -- Testing default export
2-
import fastifyWebsocket, { WebsocketHandler, fastifyWebsocket as namedFastifyWebsocket, default as defaultFastifyWebsocket, WebSocket } from '..'
1+
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
2+
import { Type } from '@sinclair/typebox'
3+
import fastify, { FastifyBaseLogger, FastifyInstance, FastifyReply, FastifyRequest, FastifySchema, RawRequestDefaultExpression, RawServerDefault, RequestGenericInterface, RouteOptions } from 'fastify'
4+
import { RouteGenericInterface } from 'fastify/types/route'
35
import type { IncomingMessage } from 'node:http'
4-
import fastify, { RouteOptions, FastifyRequest, FastifyInstance, FastifyReply, RequestGenericInterface, FastifyBaseLogger, RawServerDefault, FastifySchema, RawRequestDefaultExpression } from 'fastify'
56
import { expectType } from 'tsd'
67
import { Server } from 'ws'
7-
import { RouteGenericInterface } from 'fastify/types/route'
8-
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
9-
import { Type } from '@sinclair/typebox'
8+
// eslint-disable-next-line import-x/no-named-default -- Test default export
9+
import fastifyWebsocket, { default as defaultFastifyWebsocket, fastifyWebsocket as namedFastifyWebsocket, WebSocket, WebsocketHandler } from '..'
1010

1111
const app: FastifyInstance = fastify()
1212
app.register(fastifyWebsocket)
@@ -22,8 +22,8 @@ app.register(fastifyWebsocket, {
2222
}
2323
})
2424
app.register(fastifyWebsocket, { options: { perMessageDeflate: true } })
25-
app.register(fastifyWebsocket, { preClose: function syncPreclose () {} })
26-
app.register(fastifyWebsocket, { preClose: async function asyncPreclose () {} })
25+
app.register(fastifyWebsocket, { preClose: function syncPreclose () { } })
26+
app.register(fastifyWebsocket, { preClose: async function asyncPreclose () { } })
2727

2828
app.get('/websockets-via-inferrence', { websocket: true }, async function (socket, request) {
2929
expectType<FastifyInstance>(this)
@@ -89,7 +89,7 @@ app.get<{ Params: { foo: string }, Body: { bar: string }, Querystring: { search:
8989
expectType<{ foo: string }>(request.params)
9090
expectType<{ bar: string }>(request.body)
9191
expectType<{ search: string }>(request.query)
92-
expectType< IncomingMessage['headers'] & { auth: string }>(request.headers)
92+
expectType<IncomingMessage['headers'] & { auth: string }>(request.headers)
9393
})
9494

9595
app.route<{ Params: { foo: string }, Body: { bar: string }, Querystring: { search: string }, Headers: { auth: string } }>({
@@ -162,3 +162,15 @@ server.get('/websockets-no-type-inference',
162162

163163
expectType<typeof fastifyWebsocket>(namedFastifyWebsocket)
164164
expectType<typeof fastifyWebsocket>(defaultFastifyWebsocket)
165+
166+
app.injectWS('/', {}, {})
167+
app.injectWS('/', {}, {
168+
onInit (ws) {
169+
expectType<WebSocket>(ws)
170+
},
171+
})
172+
app.injectWS('/', {}, {
173+
onOpen (ws) {
174+
expectType<WebSocket>(ws)
175+
},
176+
})

0 commit comments

Comments
 (0)