Giter Site home page Giter Site logo

fastify-websocket's Introduction

@fastify/websocket

CI NPM version js-standard-style

WebSocket support for Fastify. Built upon ws@8.

Install

npm i @fastify/websocket
# or 
yarn add @fastify/websocket

If you're a TypeScript user, this package has its own TypeScript types built in, but you will also need to install the types for the ws package:

npm i @types/ws -D
# or
yarn add -D @types/ws

Usage

After registering this plugin, you can choose on which routes the WS server will respond. This can be achieved by adding websocket: true property to routeOptions on a fastify's .get route. In this case two arguments will be passed to the handler, the socket connection, and the fastify request object:

'use strict'

const fastify = require('fastify')()
fastify.register(require('@fastify/websocket'))
fastify.register(async function (fastify) {
  fastify.get('/', { websocket: true }, (socket /* WebSocket */, req /* FastifyRequest */) => {
    socket.on('message', message => {
      // message.toString() === 'hi from client'
      socket.send('hi from server')
    })
  })
})

fastify.listen({ port: 3000 }, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

In this case, it will respond with a 404 error on every unregistered route, closing the incoming upgrade connection requests.

However, you can still define a wildcard route, that will be used as default handler:

'use strict'

const fastify = require('fastify')()

fastify.register(require('@fastify/websocket'), {
  options: { maxPayload: 1048576 }
})

fastify.register(async function (fastify) {
  fastify.get('/*', { websocket: true }, (socket /* WebSocket */, req /* FastifyRequest */) => {
    socket.on('message', message => {
      // message.toString() === 'hi from client'
      socket.send('hi from wildcard route')
    })
  })

  fastify.get('/', { websocket: true }, (socket /* WebSocket */, req /* FastifyRequest */) => {
    socket.on('message', message => {
      // message.toString() === 'hi from client'
      socket.send('hi from server')
    })
  })
})

fastify.listen({ port: 3000 }, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

Attaching event handlers

It is important that websocket route handlers attach event handlers synchronously during handler execution to avoid accidentally dropping messages. If you want to do any async work in your websocket handler, say to authenticate a user or load data from a datastore, ensure you attach any on('message') handlers before you trigger this async work. Otherwise, messages might arrive whilst this async work is underway, and if there is no handler listening for this data it will be silently dropped.

Here is an example of how to attach message handlers synchronously while still accessing asynchronous resources. We store a promise for the async thing in a local variable, attach the message handler synchronously, and then make the message handler itself asynchronous to grab the async data and do some processing:

fastify.get('/*', { websocket: true }, (socket, request) => {
  const sessionPromise = request.getSession() // example async session getter, called synchronously to return a promise

  socket.on('message', async (message) => {
    const session = await sessionPromise()
    // do something with the message and session
  })
})

Using hooks

Routes registered with @fastify/websocket respect the Fastify plugin encapsulation contexts, and so will run any hooks that have been registered. This means the same route hooks you might use for authentication or error handling of plain old HTTP handlers will apply to websocket handlers as well.

fastify.addHook('preValidation', async (request, reply) => {
  // check if the request is authenticated
  if (!request.isAuthenticated()) {
    await reply.code(401).send("not authenticated");
  }
})
fastify.get('/', { websocket: true }, (socket, req) => {
  // the connection will only be opened for authenticated incoming requests
  socket.on('message', message => {
    // ...
  })
})

NB This plugin uses the same router as the fastify instance, this has a few implications to take into account:

  • Websocket route handlers follow the usual fastify request lifecycle, which means hooks, error handlers, and decorators all work the same way as other route handlers.
  • You can access the fastify server via this in your handlers
  • When using @fastify/websocket, it needs to be registered before all routes in order to be able to intercept websocket connections to existing routes and close the connection on non-websocket routes.
import Fastify from 'fastify'
import websocket from '@fastify/websocket'

const fastify = Fastify()
await fastify.register(websocket)

fastify.get('/', { websocket: true }, function wsHandler (socket, req) {
  // bound to fastify server
  this.myDecoration.someFunc()

  socket.on('message', message => {
    // message.toString() === 'hi from client'
    socket.send('hi from server')
  })
})

await fastify.listen({ port: 3000 })

If you need to handle both HTTP requests and incoming socket connections on the same route, you can still do it using the full declaration syntax, adding a wsHandler property.

'use strict'

const fastify = require('fastify')()

function handle (socket, req) {
  socket.on('message', (data) => socket.send(data)) // creates an echo server
}

fastify.register(require('@fastify/websocket'), {
  handle,
  options: { maxPayload: 1048576 }
})

fastify.register(async function () {
  fastify.route({
    method: 'GET',
    url: '/hello',
    handler: (req, reply) => {
      // this will handle http requests
      reply.send({ hello: 'world' })
    },
    wsHandler: (socket, req) => {
      // this will handle websockets connections
      socket.send('hello client')

      socket.once('message', chunk => {
        socket.close()
      })
    }
  })
})

fastify.listen({ port: 3000 }, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

Custom error handler:

You can optionally provide a custom errorHandler that will be used to handle any cleaning up of established websocket connections. The errorHandler will be called if any errors are thrown by your websocket route handler after the connection has been established. Note that neither Fastify's onError hook or functions registered with fastify.setErrorHandler will be called for errors thrown during a websocket request handler.

Neither the errorHandler passed to this plugin or fastify's onError hook will be called for errors encountered during message processing for your connection. If you want to handle unexpected errors within your message event handlers, you'll need to use your own try { } catch {} statements and decide what to send back over the websocket.

const fastify = require('fastify')()

fastify.register(require('@fastify/websocket'), {
  errorHandler: function (error, socket /* WebSocket */, req /* FastifyRequest */, reply /* FastifyReply */) {
    // Do stuff
    // destroy/close connection
    socket.terminate()
  },
  options: {
    maxPayload: 1048576, // we set the maximum allowed messages size to 1 MiB (1024 bytes * 1024 bytes)
    verifyClient: function (info, next) {
      if (info.req.headers['x-fastify-header'] !== 'fastify is awesome !') {
        return next(false) // the connection is not allowed
      }
      next(true) // the connection is allowed
    }
  }
})

fastify.get('/', { websocket: true }, (socket /* WebSocket */, req /* FastifyRequest */) => {
  socket.on('message', message => {
    // message.toString() === 'hi from client'
    socket.send('hi from server')
  })
})

fastify.listen({ port: 3000 }, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

Note: Fastify's onError and error handlers registered by setErrorHandler will still be called for errors encountered before the websocket connection is established. This means errors thrown by onRequest hooks, preValidation handlers, and hooks registered by plugins will use the normal error handling mechanisms in Fastify. Once the websocket is established and your websocket route handler is called, fastify-websocket's errorHandler takes over.

Custom preClose hook:

By default, all ws connections are closed when the server closes. If you wish to modify this behaviour, you can pass your own preClose function.

Note that preClose is responsible for closing all connections and closing the websocket server.

const fastify = require('fastify')()

fastify.register(require('@fastify/websocket'), {
  preClose: (done) => { // Note: can also use async style, without done-callback
    const server = this.websocketServer

    for (const socket of server.clients) {
      socket.close(1001, 'WS server is going offline in custom manner, sending a code + message')
    }

    server.close(done)
  }
})

Testing

Testing the ws handler can be quite tricky, luckily fastify-websocket decorates fastify instance with injectWS. It allows to test easily a websocket endpoint.

The signature of injectWS is the following: ([path], [upgradeContext]).

Creating a stream from the WebSocket

const Fastify = require('fastify')
const FastifyWebSocket = require('@fastify/websocket')
const ws = require('ws')

const fastify = Fastify()
await fastify.register(websocket)

fastify.get('/', { websocket: true }, (socket, req) => {
  const stream = ws.createWebSocketStream(socket, { /* options */ })
  stream.setEncoding('utf8')
  stream.write('hello client')
  
  stream.on('data', function (data) {
    // Make sure to set up a data handler or read all the incoming
    // data in another way, otherwise stream backpressure will cause
    // the underlying WebSocket object to get paused.
  })
})

await fastify.listen({ port: 3000 })

App.js

'use strict'

const Fastify = require('fastify')
const FastifyWebSocket = require('@fastify/websocket')

const App = Fastify()

App.register(FastifyWebSocket);

App.register(async function(fastify) {
  fastify.addHook('preValidation', async (request, reply) => {
    if (request.headers['api-key'] !== 'some-random-key') {
      return reply.code(401).send()
    }
  })

  fastify.get('/', { websocket: true }, (socket) => {
    socket.on('message', message => {
      socket.send('hi from server')
    })
  })
})

module.exports = App 

App.test.js

'use strict'

const { test } = require('tap')
const Fastify = require('fastify')
const App = require('./app.js')

test('connect to /', async (t) => {
  t.plan(1)

  const fastify = Fastify()
  fastify.register(App)
  t.teardown(fastify.close.bind(fastify))

  const ws = await fastify.injectWS('/', {headers: { "api-key" : "some-random-key" }})
  let resolve;
  const promise = new Promise(r => { resolve = r })

  ws.on('message', (data) => {
    resolve(data.toString());
  })
  ws.send('hi from client')

  t.assert(await promise, 'hi from server')
  // Remember to close the ws at the end
  ws.terminate()
})

Things to know

  • Websocket need to be closed manually at the end of each test.
  • fastify.ready() needs to be awaited to ensure that fastify has been decorated.
  • You need to register the event listener before sending the message if you need to process server response.

Options

@fastify/websocket accept these options for ws :

  • host - The hostname where to bind the server.
  • port - The port where to bind the server.
  • backlog - The maximum length of the queue of pending connections.
  • server - A pre-created Node.js HTTP/S server.
  • verifyClient - A function which can be used to validate incoming connections.
  • handleProtocols - A function which can be used to handle the WebSocket subprotocols.
  • clientTracking - Specifies whether or not to track clients.
  • perMessageDeflate - Enable/disable permessage-deflate.
  • maxPayload - The maximum allowed message size in bytes.

For more information, you can check ws options documentation.

NB By default if you do not provide a server option @fastify/websocket will bind your websocket server instance to the scoped fastify instance.

NB The path option from ws should not be provided since the routing is handled by fastify itself

NB The noServer option from ws should not be provided since the point of @fastify/websocket is to listen on the fastify server. If you want a custom server, you can use the server option, and if you want more control, you can use the ws library directly

ws does not allow you to set objectMode or writableObjectMode to true

Acknowledgements

This project is kindly sponsored by nearForm.

License

Licensed under MIT.

fastify-websocket's People

Contributors

a-a-github-a-a avatar aelnaiem avatar airhorns avatar darkgl0w avatar delvedor avatar dependabot-preview[bot] avatar dependabot[bot] avatar eomm avatar fdawgs avatar fox1t avatar frikille avatar greenkeeper[bot] avatar jsumners avatar marcolanaro avatar mat-sz avatar mcollina avatar momennano avatar mustardfrog avatar salmanm avatar shogunpanda avatar simenb avatar skellla avatar teastman avatar thegedge avatar tinchoz49 avatar tylercraft avatar uzlopak avatar vottuscode avatar yovach avatar zekth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastify-websocket's Issues

Bump ws to 8.5 for custom WebSocket class

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

๐Ÿš€ Feature Proposal

[email protected] allows for extending the WebSocket class.

Motivation

Issue discussed here: websockets/ws#2007

I forked myself and simply npm i -P ws, which seems to work fine. However I'm not sure how to navigate the typescript def.

Example

import fastify from 'fastify'
import websockets from 'fastify-websocket'
import WebSocket from 'ws'

class MyWebSocket extends WebSocket {
  foo() {
    return 'bar'
  }
}

const server = fastify()

server.register(websockets, {
  options: { WebSocket: MyWebSocket }
})

parametric routes broken

Long story short: It looks to me like parametric routes did not got parsed anymore.

fastify.get('/pools/:user', { websocket: true }, async (connection, req) => {
})

thats pretty odd

Cannot access websocketServer instance

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.24.1

Plugin version

4.0.0

Node.js version

14.15.4

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.0.1

Description

It seem impossible to access the decarated properties websocketServer (the istance of the WS lib).
I need it to access currently connected clients.

Steps to Reproduce

const fastify = require("fastify")();
fastify.register(require('fastify-websocket'));
console.log(server.websocketServer); //=> prints undefined

Expected Behavior

server.websocketServer is the real WebSocketServer instance (from ws lib)

Provides support for uWebSockets.js

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

๐Ÿš€ Feature Proposal

From the description and evaluation, uWebSockets.js has higher performance, whether to consider ws/uWebSockets.js as an optional engine

Motivation

more efficient performance

Example

No response

Types: WebsocketRouteOptions is not generic and results in handler not being typed properly

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure it has not already been reported

Fastify version

3.15.1

Node.js version

14.5.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

20.04

Description

The WebsocketRouteOptions doesn't accept generics, so WebsocketHandler is left with the defaults, not with the appropriate types.

Steps to Reproduce

import fastify from "fastify"
import fastifyWs from "fastify-websocket"

const fastify = fastify()
fastify.register(fastifyWs)

Let's say we have this interface declared:

interface AuthenticatedEndpoint extends RequestGenericInterface {
  Body: {
    auth: string
  }
}

When using it with app.route:

fastify.route<AuthenticatedEndpoint>({
  method: 'GET',
  url: '/ws',
  wsHandler(conn, req)  {
    req.body // typed as `unknown`
    req.body.auth // TypeError
  }
})

Expected Behavior

(assuming we have the same starting code as we had in Steps to Reproduce)

fastify.route<AuthenticatedEndpoint>({
  method: 'GET',
  url: '/ws',
  wsHandler(conn, req)  {
    req.body // typed as { auth: string }
    req.body.auth // string
  }
})

Importing from TS results in undefined variable

๐Ÿ› Bug Report

Importing fastify-websocket using typescript leads to undefined

To Reproduce

Steps to reproduce the behavior:

Simply importing fastify-websocket and attempt to register it

import fastify, { FastifyInstance } from 'fastify'

const server: FastifyInstance = fastify({})

import fastifyWebSocket from 'fastify-websocket'
server.register(fastifyWebSocket)

server.listen(8080, (err, address) => {
    // ...
})

Result

After building and attempting to run, fastify will throw an error,

Error: plugin must be a function or a promise

Doing a console.log reveals fastifyWebSocket is undefined

Your Environment

  • node version: 14.5.0
  • fastify version: 3.1.1
  • os: Windows, Linux

fastify http2

hello, my server http2, can i use this plugin for wss connection?

Message Validations

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

๐Ÿš€ Feature Proposal

What i'm suggesting is using the route schema available already in fastify route definition and use it to validate messages payload.

  const handleUpgrade = (rawRequest, validate, callback) => {
    wss.handleUpgrade(rawRequest, rawRequest[kWs], rawRequest[kWsHead], (socket) => {
      wss.emit('connection', socket, rawRequest)
      
      const connection = WebSocket.createWebSocketStream(socket, opts.connectionOptions)
      connection.socket = socket
      connection.socket.on('message',(message)=>{
        // we would need something like so
        if(!validate(message)) {
          connection.socket.send('RIP')
        } else {
          connection.socket.emit('parsedMessage', {foo:'bar'}) // we emit a parsed message event
        }
      })
      connection.socket.on('newListener', event => {
        if (event === 'message') {
          connection.resume()
        }
      })

      callback(connection)
    })
  }

Constraint here is we cannot unemit an event on the socket, i propose the parsedMessage event which is used for the validation. message event handler will still listen to all the events.

Problem currently is we cannot have access to the ajv-compiler from the fastify instance. If we can have access to it on within the instance or the onRoute hook we can compile the schema from the route option and create the validation function for the websocket connection.

What do you think?

note: also slight change needed in the onRoute hook

    if (routeOptions.websocket || routeOptions.wsHandler) {
      let wsSchema = routeOptions.schema // otherwise the onUpgrade fails with invalid body
      delete routeOptions.schema

Motivation

Reviving fastify/help#102
It would be great to have message validations based on the route schemas

Example

No response

Simple example of usage?

Hello everyone!
I need a simple example of this plugin to integrate to my chat application.

Can someone help me?
Thanks!

Support for routing

Hi, I just finished implementing routing plugin for WS server. I needed it for declaring multiple handlers and a "404 error" fallback on unregistered routes.
Before publishing on npm, I was wandering if it would be a PR for this repo, instead of creating a new package.
Here is the code: https://github.com/fox1t/fastify-websocket-router

I'll delay publishing till this issue will be closed.

Bump find-my-way from 3.0.4 to 3.0.5

This project uses find-my-way and is pinned to 3.0.4. Can you please bump it to 3.0.5 (which is what it is in the main fastify project).

This is currently causing dependency alerts in repo's that use this project due to this:
GHSA-jgrh-5m3h-9c5f

Fastify 3.10 breaks prefixed plugins

๐Ÿ› Bug Report

Fastify 3.10, together with the use of prefixes, breaks fastify-websocket.

fastify.register(
  (fastify, opts, done) => {
    fastify.get("/", { websocket: true }, (connection, req) => {
      connection.socket.send("This works");
    });

    done();
  },
  { prefix: "/activity" }
);

To Reproduce

Your Environment

  • node version: 15.3
  • fastify version: >=3.10
  • os: Mac

Some ideas as to what may be causing this

Prior to 3.10, this line was called twice per route:

fastify.addHook('onRoute', routeOptions => {

That's why this lines makes sure to ignore one of them:

if (routeOptions.path === routeOptions.prefix) {

Now with 3.10, the onRoute is called only once, and all of the routes are effectively ignored.

originally encountered in quirrel-dev/quirrel#49

Client loses the connection when the total amount of data sent exceeds 16384 bytes

๐Ÿ› Bug Report

When a client send more than a total of 16384 bytes to the server he is no longer able to send, but it can still receive data from the server. It's not necessary to send the 16384 bytes or more in a single message, this can be four times 5000 bytes for example. I also observed that the message that will exceed the 16384 bytes will be fully send, In the above example the fouth message of 5000 bytes will be entirely send but the next one won't.

To Reproduce

To Reproduce, just execute the code below

/* /// SERVER /// */

'use strict'

const fastify = require('fastify')({logger: true})

fastify.register(require('fastify-websocket'), {
  handle,
  options: {
    maxPayload: 1048576, // 1Mio
    path: '/', 
  }
})

function handle (conn) {
  conn.socket.on('message', (msg) => {
    console.log(msg)
    conn.socket.send(msg)
  })
}

const start = async () => {
  try {
    await fastify.listen(3000, "0.0.0.0")
    fastify.log.info(`server listening on ${fastify.server.address().port}`)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

/* /// CLIENT /// */

var socket = new WebSocket("ws://<ip adress>:3000");
socket.addEventListener('message', (msg) => {
	console.log(msg);
})

var data = "a".repeat(16384);
socket.send(data); // This one will be see in the server console and the client will receive the "echo"
socket.send(data); // This one won't be see in the server and client console

Your Environment

  • node version: 10.21.0
  • fastify version: >=2.0.0
  • os: Linux (Raspbian)

"Reply was already sent" error when connecting to websocket

๐Ÿ› Bug Report

Hey, I stumbled upon a small issue in my fastify API server... I've registered cors and websocket plugin as well as created some GET/POST routes.... the REST API call all work fine, but as soon as I try to connect to the WebSocket I see some errors from the cors plugin:

"reqId":2,"err":{"type":"FastifyError","message":"Reply was already sent.","stack":"FastifyError: Reply was already sent.
[...]
at ...webserver/node_modules/fastify-cors/index.js:85:129)\n    at onRequest (...webserver/node_modules/fastify-cors/index.js:87:3)","name":"FastifyError","code":"FST_ERR_REP_ALREADY_SENT","statusCode":500},"msg":"Reply already sent"}

the websocket client (wscat) says connected though, and communication via the websocket is working as well...

To Reproduce

I created a small sample showing the behavior: https://github.com/appelgriebsch/fastify-sample. When doing a GET / it will respond properly with OK and show only info level logs for the server. When connecting to the websocket via wscat /ws you will notice the above mentioned error in the log.

Expected behavior

Connecting to the websocket should not lead to an issue in the fastify processing pipeline.

Your Environment

  • node version: 14.15
  • fastify version: >=3.11
  • os: Linux

I need an example to connect to socket from froentend

๐Ÿ’ฌ Questions and Help

Hi,

I tried below code to run socket.


const fastify = require('fastify')()

function handle (conn) {
  conn.pipe(conn) // creates an echo server
}

fastify.register(require('fastify-websocket'), {
  handle,
  options: { maxPayload: 1048576 }
})

fastify.get('/', { websocket: true }, (connection, req) => {
  connection.socket.on('message', message => {
    // message === 'hi from client'
    connection.socket.send('hi from server')
  })
})

fastify.listen(3000, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

And from my, I connected to my socket by using var test = new WebSocket('ws://localhost:3000') . How I can trigger / API with my socket connection. Please help me with some examples.

Problems with params

HI!

I have this:

async getRealtimeOrdersByShop(connection, request, params) {
    try {
      
      const id = await params.id;

    } catch (error) {
      throw boom.boomify(error);
    }
  }

but params returns undefined. What is wrong?

Types: wsHandler missing in fastify.route

๐Ÿ› Bug Report

In index.d.ts RouteOptions is not augmented with wsHandler, so only short form (fastify.get) works, full one (the one below with fastify.route) doesn't compile because of missing wsHandler.

fastify.route({
  method: 'GET',
  url: '/hello',
  handler: (req, reply) => {
    // this will handle http requests
    reply.send({ hello: 'world' })
  },
  wsHandler: (conn, req) => {
    // this will handle websockets connections
    conn.setEncoding('utf8')
    conn.write('hello client')

    conn.once('data', chunk => {
      conn.end()
    })
  }
})

Your Environment

  • node version: 12
  • fastify version: 3.2.1
  • fastify-websocket version: 2.0.7
  • os: Mac

dynamic websocket routes

hi,

I need to generate new websocket routes after a post request, yet the client that tries to connect to it will get a 500 error.
I tried to put the route in beforehand, which worked well, but trying to create it on the fly, won't do. Should I be better using the ws package for this?


        const createWebsocketRoute = (listenKey) => {

            console.log('?CREATE?')

            // CRASHES HERE.

            fastify.get(`/ws/x`, { websocket: true }, ({socket}, res) => { // needs to close previous socket
                
                console.log('socket connected:', listenKey)
    
                setTimeout(() => {
    
                    socket.send(JSON.stringify({
                        d: "100.00000000",
                        T: 1573200697068,
                        E: 1573200697110,
                    }))
                    
                }, 500)
            })
        }


        fastify.route({ method: 'POST', websocket: false, url: '/api/v3/userDataStream', handler: async ({query}, res) => {

            listenKey = crypto.createHash('sha256').update(crypto.randomBytes(256)).digest("hex").slice(0, 64)

             console.log('new listenKey:', listenKey)

             createWebsocketRoute(listenKey)

            // CLIENT CRASHES

            await new Promise(rs => setTimeout(rs, 1000)) // tried to add some timeout for maybe the route has to start up=? 
            res.send({listenKey})

        }})

noServer option has no effect

๐Ÿ› Bug Report

The setting noServer = true in options has no effect. I was trying to implement a websocket authorization, based on the examples provided in the ws repository. To handle the upgrade of the connection, the setting noServer must be set to true. If i do so in fastify-websocket, this has no effect.

The issue is, that ws checks for the setting, but this is overruled by a check of the property server of the ws instance. This property is always set in fastify-websocket regardless of the noServer setting.

See ws/lib/websocket-server.js Line 81

{
   ... check for parmeter noServer
} else if (options.server) {
   this._server = options.server;
}

if (this._server) {
   ...

This leads to the following error in my case:

Error: server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration
    at WebSocketServer.completeUpgrade (/workspace/backend/node_modules/ws/lib/websocket-server.js:267:13)
    at WebSocketServer.handleUpgrade (/workspace/backend/node_modules/ws/lib/websocket-server.js:245:10)
    at Server.<anonymous> (/workspace/backend/src/index.ts:197:28)
    at Server.emit (events.js:326:22)
    at Server.EventEmitter.emit (domain.js:486:12)
    at onParserExecuteCommon (_http_server.js:644:14)
    at onParserExecute (_http_server.js:577:3)
    at HTTPParser.callbackTrampoline (internal/async_hooks.js:129:14) uncaughtException

To Reproduce

Steps to reproduce the behavior:

Set the parameter noServer to true

// Paste your code here

Expected behavior

The server property of ws should only be set when noServer is null or false.

// Paste the expected results here

Your Environment

  • node version: 14
  • fastify version: >=2.0.0
  • os: Linux

Add readableObjectMode as an option for createWebSocketStream

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

๐Ÿš€ Feature Proposal

Adding readableObjectMode as an option for fastify-websocket.

I noticed that currently in the documentation, it says that fastify-websocket supports objectMode as an option for [ws](https://github.com/websockets/ws/), but objectMode is not listed in the ws options documentation.

It seems reasonable to add readableObjectMode as an option to fastify-websocket as it is an option for createWebSocketStream and achieves a similar goal. It may also make sense to allow users to change other connection (duplex) options like readableHighWaterMark and writableHighWaterMark, but I haven't needed those options yet.

Motivation

There is currently no way to set readableObjectMode and this can lead to a TypeError for certain WebSocket applications, specifically:

TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of 
Buffer or Uint8Array. Received an instance of ArrayBuffer

That being said, this issue which led to the inclusion of readableObjectMode as an option for createWebSocketStream suggests there are issues with objectMode and backpressure handling, and the option could be a footgun.

This error also didn't occur when using express-ws so I wonder if it's related to how Fastify works as the issue also highlights a bug in Node.js core. I noticed that express-ws doesn't seem to call createWebSocketStream at all and so I'm curious why there is a difference in implementation and that might reflect if this feature is needed.

Example

fastify.register(require('fastify-websocket'), {
  options: { maxPayload: 1048576 },
  connectionOptions: { readableObjectMode: true } // can include other duplex options 
})

Then these options can be passed to WebSocket.createWebSocketStream

const connection = WebSocket.createWebSocketStream(socket, opts.connectionOptions)

Your .dependabot/config.yml contained invalid details

Dependabot encountered the following error when parsing your .dependabot/config.yml:

The property '#/' did not contain a required property of 'update_configs'
The property '#/' contains additional properties ["update_conigs"] outside of the schema when none are allowed

Please update the config file to conform with Dependabot's specification using our docs and online validator.

TypeScript Typing for second param (request) is wrong

๐Ÿ› Bug Report

It seems not to be FastifyRequest

  • It does not have req.params. (I now know it is in the third (3rd) params)
  • req.log is undefined, not Pino Logger. I have to import Pino (and install both pino and @types/pino) directly to activate logging.

Expected behavior

  • Please append logger to req.
  • Do not use Typing for FastifyRequest if it does not have params nor log.

Your Environment

  • node version: 10, 12 (tested with both)
  • fastify version:
    "fastify": "^2.14.1",
    "fastify-cors": "^3.0.3",
    "fastify-file-upload": "^2.0.1",
    "fastify-helmet": "^3.0.2",
    "fastify-oas": "^2.7.0",
    "fastify-static": "^2.7.0",
    "fastify-websocket": "^1.1.2",
  • os: macOS Catalina

Websocket stuck after receiving around ~16 kb of data from client

๐Ÿ› Bug Report

After client sends ~100 messages of ~160 bytes each to websocket, connection.socket.on('message') is no longer being triggered.

It seems to be size-dependent, I can send thousands of very short (a few bytes) messages, but only around 100 lines of 160 bytes.

To Reproduce

In new dir:

yarn init
yarn add fastify fastify-websocket

Create server.js:

'use strict'

const fastify = require('fastify')()

fastify.register(require('fastify-websocket'))

fastify.get('/', { websocket: true }, (connection, req) => {
  connection.socket.on('message', message => {
    // message === 'hi from client'
    connection.socket.send('hi from server')
  })
})

fastify.listen(5000, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }

Run node server.js

In another window, run:

wscat --show-ping-pong -c "ws://localhost:5000"

Create a file with ~150 lines of 160 bytes each. Copy-paste contents into wscat.

The server will stop responding.

Expected behavior

Should keep returning responses.

Your Environment

  • node version: 12
  • fastify version: >=2.0.0
  • os: Mac, Linux

Check if a request was websocket in `onRequest` hook

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

๐Ÿš€ Feature Proposal

I would like to see if a request was websocket in the onRequest hook, maybe through a req.ws boolean.

Motivation

I have a chat which uses the full declaration syntax with the websocket. However, I want my request to log differently if the request was a websocket.

Example

Maybe like this:

fastify.addHook("onRequest", async (req, res) => {
  if (req.ws) { //this is if it's a websocket
    req.log.info({req}, "websocket tryna connect");
  } else {
    req.log.info({req}, "regular old request");
  }
});

100% code coverage

As titled, this would be awesome.

At the time of this writing:


Time:     1s
----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |       96 |    86.36 |       90 |    97.96 |                   |
 index.js |       96 |    86.36 |       90 |    97.96 |                59 |
----------|----------|----------|----------|----------|-------------------|

How can I broadcast to all connected sockets?

I have no problems with the connection, all clients connect without problem. My question is how can I send a message to everyone at the same time since I have tried the examples of the 'ws' library but I have not succeeded. Someone has an example code, here I leave part of my code on the server.

  fastify.route({
    method: 'GET',
    url: '/',
    handler: (req, reply) => {
      reply.send({ ruta: 'OK' })
    },
    wsHandler: (conn, req, params) => {
      conn.setEncoding('utf8')
      conn.on('data', chunk => {
        console.log(chunk)
        conn.socket.send('message OK')
      })
    }
  })

Add Typescript Support

๐Ÿš€ Feature Proposal

fastify-websocket cant be used in a typescript file

Motivation

fastify supports typescript but not websocket

Example

npm install @types/fastify-websocket

Migrating to socketio v3

๐Ÿš€ Feature Proposal

Migrate or offer an option for socketio v3
Right now, connection from socketio.client v3 results in

 Error: It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible

Bootstrap is done with the following parameters

  fastify.register(require('fastify-websocket'), {
    function(conn) {
      conn.pipe(conn); // creates an echo server
    },
    options: {
      maxPayload: 1048576, // we set the maximum allowed messages size to 1 MiB (1024 bytes * 1024 bytes)
      path: '/ws',
      verifyClient: (info, next) => {
        if (!info.req.headers['x-nao-auth']) {
          logg(`x-nao-auth header not found > `, info.req.headers);
          // -->Drop: if header isn't there
          return next(false);
        }
        next(true) // the connection is allowed
      }
    }
  });

And connecting from the client side

    this.io = io(environment.API.wsUsers.url, {
      // path: environment.API.wsUsers.url + environment.API.wsUsers.options.path,
      path: environment.API.wsUsers.options.path,

Motivation

v3 adds a lot of value with the shared pipes and ability to use one connection with multiple namespaces

Example

Feature will be use same as v2 but better :)

Add an option to not wrap the websocket in a stream

๐Ÿš€ Feature Proposal

Currently we wrap every websocket connection in a Node Stream, however it might be handy to not do that in certain cases. We could add an option to avoid that.

Example

const fastify = require('fastify')()

fastify.register(require('fastify-websocket'), {
  handle,
  stream: false
})

Expose decorated fastify server in websocket handler

๐Ÿš€ Feature Proposal

Expose decorated fastify server in websocket handler

Motivation

Currently, taking advantage of one of the use cases of decorations and the plugin system is not possible inside a websocket handler.

Decoration use case from Fastify docs:

fastify.decorate('db', new DbConnection())

fastify.get('/', async function (request, reply) {
  reply({hello: await this.db.query('world')})
})

This means that we can not take advantage of other plugins that might have been setup in our Fastify server, and must find another way to expose this functionality to the websocket handler, possibly resulting in duplicated efforts.

I looked at the source of this plugin and understand that it uses its own independent router and therefore, most of the facilities we expect inside a regular request handler are not available. I believe we can expose the Fastify server to the websocket handler with little effort to take advantage of the decorations introduced by other plugins.

Example

In Fastify handlers, this is bound to the server. I believe we can use the same strategy here, for consistency's sake, though I admit I'm not familiarized with the memory/performance implications.

fastify.get('/', { websocket: true }, function myWSHandler(connection, req) {
  // this is bound to Fastify server
  this.myPlugin.someFunc()

  connection.socket.on('message', message => {
    // message === 'hi from client'
    connection.socket.send('hi from server')
  })
})

Possible implementation?

https://github.com/fastify/fastify-websocket/blob/master/index.js#L55

      // ...
      router.on('GET', routeOptions.path, (req, _, params) => {
-      const result = wsHandler(req[kWs], req, params)
+      const result = wsHandler.call(fastify, req[kWs], req, params)

        if (result && typeof result.catch === 'function') {
          result.catch(err => req[kWs].destroy(err))
        }
      })

I'm willing to help with the implementation if you agree that this would be useful.

Missing declaration file for module 'ws'

๐Ÿ› Bug Report

Compiling my fastify server using Typescript, I am getting an error message pointing out the missing the 'ws' module declaration file. When manually installing the corresponding DefinitelyTyped (@types/ws), the error disappears.

To Reproduce

  1. npm install
  2. npx tsc -w
[05:36:04] Starting compilation in watch mode...

node_modules/fastify-websocket/index.d.ts:5:28 - error TS7016: Could not find a declaration file for module 'ws'.
'D:/_software/electronic-publishing/social/node_modules/ws/index.js' implicitly has an 'any' type.
Try `npm install @types/ws` if it exists or add a new declaration (.d.ts) file containing `declare module 'ws';

5 import * as WebSocket from 'ws';

[05:36:07] Found 1 error. Watching for file changes.

Expected behavior

I believe that this dependency should be determined in fastify-websocket and not directly in my project.

Your Environment

  • node version: 14.10.1

  • fastify version: >=3.8.0

  • os: Windows

  • project dependencies:

    "dependencies": {
    "dotenv": "^8.2.0",
    "fastify": "^3.8.0",
    "fastify-compress": "^3.4.0",
    "fastify-cors": "^4.1.0",
    "fastify-helmet": "^5.0.3",
    "mercurius": "^6.2.0",
    "pg": "^8.4.2",
    "pg-format": "^1.0.4",
    "uuid": "^8.3.1"
    },
    "devDependencies": {
    "@types/node": "^14.14.7",
    "@types/pg": "^7.14.6",
    "@types/pg-format": "^1.0.1",
    "@types/uuid": "^8.3.0",
    "typescript": "^4.0.5"
    }

How to handle ``on("message"`` for both binary and json messages

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Hello,

I'm having an issue where I'm sending both json strings as well as flatbuffer/uin8 arrays to my websocket connection.

In on("message" I'm calling toString but that doesnt work for the Uint8Arrays being sent. How should I handle both message types / is there a flag to enable?

Current version: 4.2.2

error when calling toString on a message that is actually an Uint8Array

SyntaxError: Unexpected token ยถ in JSON at position 0
    at JSON.parse (<anonymous>)
    at C:\git\needle-tiny-networking-package\packages\websocket\src\networking.js:52:28
    at WebSocket.<anonymous> (C:\git\needle-tiny-networking-package\packages\websocket\src\proxy.js:31:17)
    at WebSocket.emit (events.js:388:22)
    at Receiver.receiverOnMessage (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\websocket.js:1137:20)
    at Receiver.emit (events.js:376:20)
    at Receiver.dataMessage (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:513:14)
    at Receiver.getData (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:446:17)
    at Receiver.startLoop (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:148:22)
    at Receiver._write (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\receiver.js:83:10)
    at writeOrBuffer (internal/streams/writable.js:358:12)
    at Receiver.Writable.write (internal/streams/writable.js:303:10)
    at Socket.socketOnData (C:\git\needle-tiny-networking-package\packages\websocket\node_modules\fastify-websocket\node_modules\ws\lib\websocket.js:1231:35)
    at Socket.emit (events.js:376:20)
    at addChunk (internal/streams/readable.js:309:12)
    at readableAddChunk (internal/streams/readable.js:284:9)

`verifyClient` isn't working and support should likely be removed

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.x.x

Plugin version

No response

Node.js version

unknown

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

unknown

Description

When I tried using verifyClient in my server, Fastify kept sending Reply already sent errors. And in the ws documentation, it literally encourages not to use it. I believe this plugin should throw an error or deprecation warning when attempting to use verifyClient to avoid these issues in the future.

Steps to Reproduce

Start a Fastify server using this plugin with a verifyClient option and logging on.

Expected Behavior

I believe this plugin should throw an error or deprecation warning when attempting to use verifyClient to avoid these issues in the future.

ws not working with empty get path when using prefix

๐Ÿ› Bug Report

when I register api module and give it a prefix option and then in the module file I can't set get route without any additional path name fastify.get('/', { websocket: true }, <function>)
and it gives me the error

AssertionError [ERR_ASSERTION]: Method 'GET' already declared for route '/api/'

it works when I add something to get path like fastify.get('/hi', { websocket: true }, <function>)
app.js

fastify.register(require('fastify-websocket'))

fastify.register(require('./api), { prefix: '/api' }

api.js

fastify.get('/', { websocket: true }, <function>)

Expected behavior

It should work in websocket. like it works fine without websocket (regular get '/' path)

Your Environment

  • node version: 12
  • fastify version: >=2.0.0
  • os: Windows

Start using FastifyRequest objects for the upgrade request so hooks fire and decorators can be used

๐Ÿš€ Feature Proposal

fastify-websocket currently passes the raw http.IncomingMessage into websocket route handlers, not a FastifyRequest object. I think instead passing a FastifyRequest to handlers would improve a number of things.

Motivation

First off, it is unfortunate to not have access to FastifyRequest's basic helpers. The higher level object is more useful than the raw IncomingMessage and has some handy doodads for getting say route params or the logger to work with. Second, fastify promotes and has a really healthy ecosystem of things that decorate FastifyRequest that websocket handlers also can't access. Things like a session from fastify-secure-session, utilities from fastify-url-data, or user-land decorators to get a database client or what have you aren't available in route handlers.

Finally, it's unfortunate that hooks don't run for websocket handlers. We'd have to discuss semantically how this might work, but, if we could come up with sane semantics for this, it'd be really handy. I want to use hooks to implement authorization for example -- deny requests based on the presence of a session value or whatever. This same logic needs to apply to work done over normal HTTP requests as well as work done over websocket connections, but currently I have to duplicate it in a hook and in a verifyClient option. One place (especially one place that had access to decorators) would be simpler.

I also think there's an argument to be made that websocket handlers being different like this violates the principle of least surprise.

How

This'd definitely be a breaking change, so knowing that, we'd either introduce a new API or just do a major version bump. With that, I think it'd be possible to actually implement this by using ws ability to bind to an existing http.Server, as is detailed here: https://www.npmjs.com/package/ws#external-https-server

I think for an UPGRADE request we'd probably not want to provide a FastifyReply object, or we'd want to change the API to use FastifyReply to actually start the websocket connection. I think there's really only one thing you want to do with an upgrade request, which is delegate it to the websocket server, so I think having to do that manually isn't really worth it, and that it's a special enough scenario that having a different handler signature is actually a good thing anyways. So I'd suggest having websocket handlers look pretty similar to how the do right now.

Hooks wise, the semantics that make sense to me are firing everything up until the reply is sending. So, onRequest, preParsing, preValidation, preHandler, preSerialization, and onError could all be relied on, but onSend and onResponse would never be called and would be documented as such. I am not really familiar with Fastify's internals enough to say how hard it would be to actually do this weird half-chain of hooks, but were that easy enough, this makes sense to me.

Context

I don't really know why fastify-websocket works the way it does right now, but if I had to guess, I'd say it's because its really simple to implement. That's not a bad reason. I am finding that I have to do more work in userland because of this though, and I think implementing this inside the library instead of in bespoke and crappy ways outside it each time would save devs lots of time.

Thoughts?

Is websocketServer implemented for TypeScript?

Using typescript, as I wanted to access websocketServer the type says that it is not defined. I am wondering If the type is defined.

This gives error:
fastify.websocketServer.clients

So I opt to any which works cause it is there.
(fastifyServer as any).websocketServer.clients

@fox1t as I saw that you implemented the types, can you help me on the matter, please?

Graceful exit - onClose hook is never called when connections are active

๐Ÿ› Bug Report

I'm trying to setup a graceful exit strategy by closing connections to other services (eg: database, redis cache) in an onClose hook, using fastify-graceful-shutdown.

However, when there are active websocket connections, the onClose hooks are never called, even though fastify.close() was called upon reception of the exit signal by fastify-graceful-shutdown.

This is probably linked to the fact that graceful shutdown waits for requests to complete (while refusing new ones, which works ok), and websocket requests never actually complete on their own..

To Reproduce

Using this example on CodeSandbox:

  1. The server should start automatically
  2. Run the client code locally (see below), the client stays connected.
  3. Restart the server in CodeSandbox using the Server Control Panel > Control Container > Restart Server button.
  4. Observe the logs: the onClose log line is not printed out.

Server code:

const Fastify = require("fastify");

const server = Fastify({ logger: true });

// The order does not seem to matter here:
server.register(require("fastify-graceful-shutdown"));
server.register(require("fastify-websocket"));

server.get("/foo", { websocket: true }, (connection, req) => {
  const socketID = req.headers["sec-websocket-key"];
  server.log.info({
    msg: "Client connected",
    socket: socketID
  });
  connection.socket.on("message", message => {
    server.log.info({
      msg: message,
      socket: socketID
    });
  });
  connection.socket.on("close", () => {
    server.log.info({
      msg: "Client disconnected",
      socket: socketID
    });
  });
});

server.addHook("onClose", (server, done) => {
  server.log.info("in the onClose hook");
  done();
});

server.listen({ port: 8080 });

Test client code:

const WebSocket = require('isomorphic-ws')

const client = new WebSocket('wss://uvgpw.sse.codesandbox.io/foo')

client.on('open', () => {
  console.log('Connected')
  client.send('foo')
})

client.on('close', () => {
  console.log('Disconnected')
})

Expected behavior

The onClose hook should be called when fastify.close() is called.

Closing the open websocket connections could be done manually here using whatever strategy makes sense for the app (sending a batch of data, a special message to the client before closing the connection...).

Your Environment

  • node version: 13.7.0
  • fastify version: 2.11.0
  • os: macOS
  • fastify-graceful-shutdown: 2.0.1
  • fastify-websocket: 1.1.0

fastify websocket handler with typescript

๐Ÿ’ฌ Question here

How to implement a websocket route using fastify-websocket in typescript?

Your Environment

  • node version: 14.15
  • fastify version: 3.17
  • os: Windows

TypeError: request.getSession()

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.24.0

Plugin version

4.0.0

Node.js version

16

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

11.6.1

Description

Hi,
I have tried to implement an asynchronous task in a wsHandler as described in the documentation. However, I get a TypeError as soon as I call request.getSession(). Either I have misunderstood something or the method is no longer available in the current version?

const { QueryService } = require('@lib/infrastructure/database')
const enums = require('@lib/common/enums')

module.exports = function (fastify) {
  return {
    global: function (connection, request) {
      const sessionPromise = request.getSession() // <-- TYPE_ERROR

      connection.socket.on('close', async function (message) {
        await sessionPromise()
        const queryService = new QueryService()
        const client = await fastify.pg.connect()
        await client.query(queryService.players.updateState, [enums.playerStates.offline, request.query.playerId, request.query.gameId])
        fastify.websocketServer.clients.forEach(function (client) {
          if (client.readyState === 1) {
            client.send(JSON.stringify({ ...request.query, type: 'close' }))
          }
        })
      })
    }
  }
}

Reference: Repository

Steps to Reproduce

Use request.getSession()

Expected Behavior

No Error.

WebsocketHandler Request generics don't work the same as http Handlers

๐Ÿ› Bug Report

In a TypeScript project, I am using the new 3.0.0 WebsocketHandler interface that provides FastifyRequest. I want to declare the request Header type so I can access them without casting or using //@ts-ignore as I do with HTTP request handlers.

Here is an example

  server.get("/ws",  {
      websocket: true,
      schema: {
        headers: TenantHeaderSchema
      }
    },
    // compiler error, see below
    createWSHandler())

...

function createWSHandler() {
  return (conn:     SocketStream,
          request:  FastifyRequest<{Headers: TenantHTTPHeader}>) => {
    const {tenant} = request.headers
...

Here is the compiler error:

 Overload 1 of 4, '(path: string, opts: RouteShorthandOptions<Server, IncomingMessage, ServerResponse, RequestGenericInterface, unknown> & { ...; }, handler?: WebsocketHandler | undefined): FastifyInstance<...>', gave the following error.
    Argument of type '(conn: SocketStream, request: FastifyRequest<{    Headers: TenantHTTPHeader;}>) => void' is not assignable to parameter of type 'WebsocketHandler'.
      Types of parameters 'request' and 'request' are incompatible.
        Type 'FastifyRequest<RouteGenericInterface, Server, IncomingMessage>' is not assignable to type 'FastifyRequest<{ Headers: TenantHTTPHeader; }, Server, IncomingMessage>'.
          Type 'RouteGenericInterface' is not assignable to type '{ Headers: TenantHTTPHeader; }'.
            Types of property 'Headers' are incompatible.
              Type 'unknown' is not assignable to type 'TenantHTTPHeader'.

Expected behavior

I expect the FastifyRequest declaration to accept Headers declarations as they do in other get() handlers. An example that works with the same schema and types is below:

  server.get("/data", {
      schema: {
        headers: TenantHeaderSchema
      }
    },
    createGetDataHandler())

...

function createGetDataHandler() {
  return async (request:  FastifyRequest<{Headers: TenantHTTPHeader}>,
                reply:    FastifyReply) => {
    const {tenant} = request.headers

Your Environment

  • node version: 12.20.0
  • fastify version: >=3.11.0
  • fastify-websocket version: 3.0.0
  • os: Mac

fastify.inject like testing for WS?

Hey guys,
for faking HTTP requests, there is ab option to inject fake HTTP requests via light-my-request. Looking in the tests of this repo, I can see that Fastify really needs to listen on some port in order to create a WS client connetion.

Is there a way to Inject a WS connection without actually making Fastify server listen on a port?

I'm asking because right now we many HTTP tests using Ava testing framework, that are in many separate files (due to concurrent testing). So far for HTTP testing we were not forcing the Fastify server to listen on any port. Now we are at the point where we want to start cover the WS scenarios with such tests and it seems that we are going to need to actually make Fastify server listen on certain port.

The issue we are seeing is that each test file will need to have a separate port, because they run concurrently. I know it's doable like that, but I'm just currious if it's possible to fake WS connections as well as WS data being sent?

Thanks in advance.

/GJ

Use Set-Cookie header within websocket request

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Dear folks,

I've recently done some research on setting headers within websocket requests.

The idea is to have a websocket connection to a server, while being able to (re-)authenticate via a https-only cookie.

For non-ws requests you would use reply.header(key, value).
The therefore needed reply object is nowhere accessible in the wsHandler.

I've tried creating a fork and include the reply.header() call within the onRoute hook, but neither before or after the reply.hijack() call the inserted call to reply.header() has any effect.

I'm wondering if you can help me?

Further assumptions I have:

  1. It is not possible at all to use Set-Cookie header within an upgrade request, so Chrome Dev Tools automatically skip this entry, although it was sent by the server.
  2. The line I have inserted the reply.header() call was wrongly chosen. In this case please push me to the right location.

I really hope somebody can help me :-)

Kind regard
ulrichrobin

EDIT: Another solution I have come up with would be setting the header on a separate non-ws route, and read the set cookie via request.header['Cookie']. Is this the way my issue is resolved out there yet?

An error inside the WebSocket handler results in "Reply was already sent" and not the actual error

๐Ÿ› Bug Report

#103 (comment)

Errors inside the handler are currently swallowed, which for me is unexpected behavior and inconsistent with the behavior for regular non-ws routes.

To Reproduce

Steps to reproduce the behavior:

'use strict'

const fastify = require('fastify')({
  logger: true
})

fastify.register(require('fastify-websocket'))

fastify.get('/ws', { websocket: true }, () => {
  throw new Error('demonstration')
})

// Declare a route
fastify.get('/', (request, reply) => {
  throw new Error('demonstration')
})

fastify.listen(3000, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

wscat --connect ws://localhost:3000/ws results in :

{
  "level": 40,
  "time": 1615569367599,
  "pid": 28959,
  "hostname": "alex-desktop",
  "reqId": "req-1",
  "err": {
    "type": "FastifyError",
    "message": "Reply was already sent.",
    "stack": "FastifyError: Reply was already sent.\n    at _Reply.Reply.send (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/reply.js:118:26)\n    at preHandlerCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:126:11)\n    at preValidationCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:107:5)\n    at handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:70:7)\n    at handleRequest (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:18:5)\n    at runPreParsing (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:421:5)\n    at Object.routeHandler [as handler] (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:379:7)\n    at Router.lookup (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/find-my-way/index.js:339:14)\n    at Server.<anonymous> (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify-websocket/index.js:50:15)\n    at Server.emit (events.js:315:20)",
    "name": "FastifyError",
    "code": "FST_ERR_REP_ALREADY_SENT",
    "statusCode": 500
  },
  "msg": "Reply already sent"
}

curl http://localhost:3000 :

{
  "level": 50,
  "time": 1615569798536,
  "pid": 30071,
  "hostname": "alex-desktop",
  "reqId": "req-1",
  "req": {
    "method": "GET",
    "url": "/",
    "hostname": "localhost:3000",
    "remoteAddress": "127.0.0.1",
    "remotePort": 50970
  },
  "res": { "statusCode": 500 },
  "err": {
    "type": "Error",
    "message": "demonstration",
    "stack": "Error: demonstration\n    at Object.<anonymous> (/home/alex/src/issues/issue-256-websocket-swallows-error/index.js:16:9)\n    at Object.routeOptions.handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify-websocket/index.js:125:24)\n    at preHandlerCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:124:28)\n    at preValidationCallback (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:107:5)\n    at handler (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:70:7)\n    at handleRequest (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/handleRequest.js:18:5)\n    at runPreParsing (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:421:5)\n    at Object.routeHandler [as handler] (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/fastify/lib/route.js:379:7)\n    at Router.lookup (/home/alex/src/issues/issue-256-websocket-swallows-error/node_modules/find-my-way/index.js:339:14)\n    at Server.emit (events.js:315:20)"
  },
  "msg": "demonstration"
}

For the ws handler the information about the original new Error('demonstration') is completely lost.

Expected behavior

Errors in my code are logged and I get a chance to track them down.

Your Environment

  • node version: v14.16.0
  • fastify version: 3.14.0, ws: 3.1.0
  • os: Ubuntu

Global Handler parameter problem?

๐Ÿ› Bug Report

The second parameter of the Global Handler should be ServerRequest instead of ServerResponse.

To Reproduce

app.register(require("fastify-websocket"), {
  handle: (ws,req) => {
    console.log(req);  // This should be Request, not Response
    wsHandle(ws);
  },
  options: {
    maxPayload: 1048576,
    path: "/websocket",
  },
});

image

image

Expected behavior

The second parameter of the Global Handler is ServerRequest

app.register(require("fastify-websocket"), {
  handle: (ws,req) => {
    console.log(req);  // Here is the Request
    wsHandle(ws);
  },
  options: {
    maxPayload: 1048576,
    path: "/websocket",
  },
});

return handle.call(fastify, req[kWs], res)

Your Environment

  • node version: v14.15.4
  • fastify version: >=3.10.1
  • os: Mac, Windows, Linux
  • fastify-websocket version:>=2.1.0

Socket stops accepting messages

I'm kind of loosing my mind over this...

๐Ÿ› Bug Report

Server-side socket stops accepting messages, even though the connection remains open and it is still able to send messages.

To Reproduce

See https://github.com/cope/wtfws for an extremely detail description and ability to clone and repro the issue.

Expected behavior

Server-side socket to KEEP accepting messages.

Your Environment

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.