Giter Site home page Giter Site logo

ws-plugins's Introduction

ws-plugins WIP

WebSocket Plugins is a modulable plugin system for client and server WebSockets.

Without any breaking changes of the initial WebSocket API, you can add built-in plugins into your WebSockets or create your own plugins.

Quick Start

yarn add @ifnot/ws-plugins
(or) npm install @ifnot/ws-plugins --save

Server :

const { WebSocketServer } = require('ws')
const { server } = require('@ifnot/ws-plugins')

const wss = new WebSocketServer({ port: 8080 })
server(wss, {
  // Add plugins here
  catchErrors: true,
  log: 'info'
})

Client :

const WebSocket = require('ws')
const { client } = require('@ifnot/ws-plugins')

const ws = new WebSocket('ws://127.0.0.1:8080')
client(ws, {
  // Add plugins here
  catchErrors: true,
  log: 'info'
})

Built-in Plugins

Summary :

  • PingPong: Close broken connections.
  • Reconnect: Reconnect when connection is closed.
  • Stream: Multiple simultaneous streams through one connection.
  • Router: Namespace messages, handle them with a router
  • AsyncPayload: Send promise and streams into your object messages.

PingPong

Automatically close broken connections after a ping-pong timeout. Read more

// Server
server(wss, {
  pingpong: {
    interval: 30000, // In ms, the interval between each ping
    timeout: 1000, // In ms, the timeout where a client connection is considered broken
    onTimeout: (ws) => {}, // Replace the default behaviour of `ws.terminate()` when a timeout occurs
    onPong: (ws, ms) => {} // Add a custom handler when a pong is received
  }
})

// Client
client(ws, {
  pingpong: {
    interval: 30000, // In ms, should be same as server
    timeout: 1000, // In ms, should be same as server
    outage: 3, // The number of timeouts before the connection is considered broken
    onTimeout: () => {} // Replace the default behaviour of `ws.terminate()` when a timeout occurs
  }
})

Tips :

  • As the ping is sent by the server using a setInterval, when high delay occurs (WebSocket paused, NodeJs event loop stuck), the client may trigger a timeout and disconnect. Tune the outage option to avoid this.

Reconnect

Restart your client connection handler when the connection is closed. Read more

// Client
function connect () {
  const ws = new WebSocket('ws://127.0.0.1:8099')
  client(ws, {
    catchErrors: true, // catchErrors option is REQUIRED
    pingpong: true, // pingpong plugin is RECOMMENDED
    reconnect: {
      handler: connect
    }
  })
}
connect()

Stream

Write and read multiple simultaneous streams on a single WebSocket connection.

// Server
server(wss, {
  stream: {
    nameLength: 10 // The fixed amount of bytes allocated at the beginning of each chunks for storing the name of the stream.
  }
})

// Send two files at the same time to any connected client
wss.on('connection', (ws) => {
  ws.stream.readable('my-stream1').pipe(fs.createWriteStream('./from1.txt'))
  ws.stream.readable('my-stream2').pipe(fs.createWriteStream('./from2.txt'))
})

// Client
client(ws, {
  stream: {
    nameLength: 10 // Should be same as server
  }
})

// One connected, pipe the two files from the server stream (names must match)
ws.on('open', () => {
  fs.createReadStream('./to1.txt').pipe(socket.stream.writable('my-stream1'))
  fs.createReadStream('./to2.txt').pipe(socket.stream.writable('my-stream2'))
})

Tips:

  • Writable streams will add the name of the stream as prefix (adding an overload of nameLength bytes). When received, the prefix will be used to find the corresponding reading stream and then removed before piping.
  • The slowest reading stream will apply backpressure and slow down the entire WebSocket stream and not only the source writing stream.
  • You can open multiple reading stream with the same name to create copies. And opening multiple same writing stream to merge streams.

Router

Send named messages with router.send and read them using a router (like a REST API). You can also make router.query which wait for a response using a promise.

// Server
server(wss, {
  stream: true, // stream plugin is REQUIRED
  router: {
    delimiter: '/', // The delimiter used to make paths
    wildcard: false, // If true, the router will accept wildcard paths
  }
})

wss.on('connection', function connection (ws) {
  // Handle a message from client named `/hello/world`
  ws.router.on('/hello-world', (data) => {
    console.log('Websocket ping received')
  })
  
  // Handle a message query from client named `/users/create`
  ws.router.on('/users/create', user => {
    console.log('User received', user)
    return user
  })
  
  // You can also send a message to the client with the router
  ws.router.send('/foo')
})

// Client
client(ws, {
  stream: true, // stream plugin is REQUIRED
  router: {
    delimiter: '/', // Should be same as server
    wildcard: false, // If true, the router will accept wildcard paths
  }
})

ws.on('open', async () => {
  // Send a `hello-world` message to the server
  ws.router.send('/hello-world')
  
  // Send a `/users/create` query to the server and wait for the response
  const user = await ws.router.query('/users/create', { name: 'John' })
  
  // Catch all messages from the server (with wildcard)
  ws.router.on('/*', (data) => {
    console.log('Websocket message received', data)
  })
})

TODO

  • Add support for global routing on the server instead of runtime routing for each connection. We may add also a middleware plugin.

AsyncPayload

Send promises and streams through WebSocket messages.

// Server
server(wss, {
  stream: true, // stream plugin is REQUIRED
  asyncPayload: true
})

wss.on('connection', (ws) => {
  // You can send a file with metadata
  ws.send({
    name: 'my-file.tx',
    size: 12345,
    type: 'text/plain',
    stream: fs.createReadStream('./my-file.txt'),
  })
  
  // You can receive a promise  
  ws.on('message', async ({ name, completion }) => {
    try {
      await completion
      console.error('Task', name, 'suceeded.')
    } catch (e) {
      console.error('Task', name, 'failed', e)
    }
  })
})

// Client
client(ws, {
  stream: true, // stream plugin is REQUIRED
  asyncPayload: true
})

ws.on('open', () => {
  // And receive the file
  ws.on('message', async (data) => {
    const { name, size, type, stream } = data
    stream.pipe(fs.createWriteStream(name))
  })
  
  // Or send a promise
  ws.send({
    name: 'MY_LONG_TASK',
    completion: new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello world')
      }, 1000)
    })
  })
})

Tips :

  • You can also send promise/streams with router.send and router.query methods with the router plugin.

ws-plugins's People

Contributors

ifnotfr avatar

Watchers

 avatar

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.