# bidi-sse
**Repository Path**: mirrors_WebReflection/bidi-sse
## Basic Information
- **Project Name**: bidi-sse
- **Description**: Bidirectional Server-sent Events
- **Primary Language**: Unknown
- **License**: ISC
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-11-27
- **Last Updated**: 2026-07-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# bidi-sse

**Social Media Photo by [Ian Taylor](https://unsplash.com/@carrier_lost) on [Unsplash](https://unsplash.com/)**
Bidirectional [Server-sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
### About
Heavily inspired by the awesome [ws module](https://github.com/websockets/ws#readme), *bidi-sse* provides a *Web Sockets* friendly API, although based on both [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) primitives.
Thanks to its different approach, both client side and server side code are minimal (~0.7K on the client, few lines on the server), and there is no need to roundtrip *ping* / *pong* to know whenever a client is gone, as that happens pretty much instantly, or better, as soon as the alive connection gets closed.
### Example
Following a very simple setup to explain the basics behind this module, one for the *client side*, and one for the *server side*.
Client
```js
import BidiSSE from 'bidi-sse/client';
// or import BidiSSE from 'https://unpkg.com/bidi-sse';
const bidi = new BidiSSE('/bidi-path');
// use it like a socket
bidi.on('open', () => {
console.log('open');
// it can send data once connected
bidi.send({some: 'data'});
});
bidi.on('message', console.log);
bidi.on('error', console.error);
bidi.on('close', () => console.log('closed'));
```
Server
```js
const express = require('express');
const BidiSSE = require('bidi-sse/server');
// or import BidiSSE from 'https://unpkg.com/bidi-sse/esm/server.js';
const bidi = new BidiSSE('/bidi-path');
// use it as handler or check bidi.handler(req, res) directly
const app = express();
app.use(bidi.handler);
app.use(express.static(__dirname));
app.listen(8080);
// and set it up like a socket
bidi.on('connection', client => {
// all clients via .clients for broadcast
console.log('clients', bidi.clients.size);
// setup clients also like sockets
client.on('message', data => {
console.log('client', data);
client.send(data);
});
client.on('close', () => {
console.log('client is gone');
});
});
```
## API
Both *client* and *server* constructors accept a `path` to enable as *bidi-sse*, and an optional `options` object.
Client - a simplified emitter
```js
const bidi = new BidiSSE('/some-path', {
// optional fetch options to merge per each send
// using credentials 'omit' set withCredentials
// for EventSource as `false`: it's `true` by default.
fetch: {credentials: 'omit'},
// default JSON serializer to send/receive data
JSON
});
// readyState is one of the static BidiSSE values:
bidi.readyState;
// BidiSSE.CONNECTING ➡ open event not fired yet: cannot send
// BidiSSE.OPEN ➡ open fired: can now send
// BidiSSE.CLOSING ➡ connection error occurred
// BidiSSE.CLOSED ➡ bidi.close(); or after connection error
// events + chainable .on(type, fn) method
bidi.once('open', () => console.info('open'));
bidi.on('message', console.log);
bidi.on('error', console.error);
bidi.once('close', () => console.info('close'));
// methods: send throws if readyState is not OPEN
bidi.send({any: 'data'});
bidi.close();
// extra
bidi.emit('type', ...[{any: 'data'}]);
```
Server - an event emitter with clients notified via connection
```js
const bidi = new BidiSSE('/some-path', {
// if its value is `"cors"` it enables CORS via headers
mode: '',
// optional headers to include per each SSE initialization
// or further posted data via send(...)
headers: {},
// default JSON serializer to send/receive data
JSON
});
// a read only *Set* of clients, where each client has
// the same properties and methods of the client side one
bidi.clients;
// an auto-bound method usable as express handler or within
// basic nodejs createServer logic. Returns true if the request
// was handled as Server-sent Event
bidi.handler;
// events + chainable .on(type, fn) method
bidi.on('connection', client => {
// client is unique per visitor and it has all features
// a client-side bidi instance has
});
bidi.on('close', () => { console.log('all gone'); });
// methods: close throw away all connected clients, then resolves
bidi.close();
```
## Use cases
It is very important to understand *where* this module can easily *fail*, as opposite of being a solution ...
* this module assumes *every request passes through the same stack*, meaning that *cluster*, *serverless*, *load balance*, or any stack that might diverge the request somewhere else, will easily fail if the browser client `EventSource` points at a different end of the spectrum, and further *UUIDs related* request are sent elsewhere
* this module was mostly born to satisfy [proxied-node](https://github.com/WebReflection/proxied-node#readme) constrains and architecrture, among IoT caveats, so *don't use this in production unless you really [understand how this module works](https://github.com/WebReflection/bidi-sse#how-it-works) 👍*
### How it works
* an *EventSource* client request is intercepted and handled on the server:
* the response object is trapped until the client disconnects
* a server side *client* is created and the long living response object is associated with it
* the very first server-sent event is a unique identifier
* the server side *client* is associated to this unique identifier and a *connection* event emitted, passing such *client* as ready to communicate
* the client stores internally such unique identifier and emit it's *open* listener, enabling its communication ability
* each time the client instance `.send(data)` is invoked, the same *EventSource's href* plus the unique identifier is used to *POST* the data as serialized format
* the server intercepts *POST* requests and handle these internally if:
* the url is *the same as the initial one defined to trap responses*
* there is *a known UUID* associated with the url as `bidi-sse` query string
* the sent data is built as string via all its chunks, and then deserialized through the same `stringify` and `parse` mechanism used on the client. This is *JSON* by default, but [it could be any different library](https://github.com/WebReflection/bidi-sse#using-different-serialization)
* if the `parse(postedData)` operation fails, an *error* is triggered on the client side, but only if it's still connected
* if the operation is successful, a *message* event with the parsed data is invoked on the server "*client's counterpart*"
* when the server side *client* `.send(data)` is invoked, a *message* event is emitted in the browser's *client* side, and through the same `stringify` and `parse` procedure
#### Using different serialization
Please note that the **JSON** reference library to *stringify* and *parse* must be the same for both *client* and *server*.
[flatted](https://www.npmjs.com/package/flatted) and [@ungap/structured-clone/json](https://github.com/ungap/structured-clone#tojson) are just two of the many possible parser alternatives, able to deal with recursion and, in the structured clone case, with also more data types and primitives.