@styris-ame/y-engineio
v2.2.0
Published
Engine.IO provider for Yjs (Yjs v13 line)
Readme
@styris-ame/y-engineio :tophat:
Engine.IO Provider for Yjs
A port of y-websocket that syncs a
Y.Doc over Engine.IO (the transport
layer underneath Socket.IO) instead of a raw WebSocket. It keeps the same sync +
awareness protocol and the same cross-tab BroadcastChannel sync.
Why Engine.IO? It negotiates a transport (HTTP long-polling → WebSocket →
WebTransport) and transparently falls back when WebSockets are blocked by a
proxy, which a raw WebSocket cannot do.
This is the v4 / main-branch port: it targets Yjs v14 (@y/y,
@y/protocols) and includes the sync-status feature.
Yjs v13 vs v14
This 2.x line (dist-tag yjs14) targets Yjs v14 (@y/y, @y/protocols).
For Yjs v13 (yjs, y-protocols, lib0@^0.2) install the 1.x line
(dist-tag yjs13): npm i @styris-ame/y-engineio@yjs13 yjs.
Quick Start
npm i @styris-ame/y-engineio@yjs14 engine.io-client @y/yimport * as Y from '@y/y'
import { EngineIOProvider } from '@styris-ame/y-engineio'
const doc = new Y.Doc()
// note: serverUrl is just the origin — the room name is NOT part of the URL
const provider = new EngineIOProvider('http://localhost:1234', 'my-roomname', doc)
provider.on('status', event => {
console.log(event.status) // "connecting" | "connected" | "disconnected"
})How it differs from y-websocket
| | y-websocket | @styris-ame/y-engineio |
|---|---|---|
| transport | raw WebSocket | engine.io-client Socket (polling/ws/webtransport) |
| URL | serverUrl/roomname?params | serverUrl only; params become engine.io query |
| room selection | encoded in the URL path | sent as the room handshake query param (see below) |
| socket field | provider.ws | provider.engine |
Everything else — the messageSync / messageAwareness / messageAuth /
messageQueryAwareness protocol, exponential-backoff reconnect, the
socketTimeout watchdog, sync / sync-status events, and cross-tab
BroadcastChannel sync — is unchanged from y-websocket.
Wire protocol (for server authors)
Every frame is binary (engine.io binary message) and starts with a
lib0-encoded varUint message type:
| type | const | direction | payload |
|---|---|---|---|
| 0 | messageSync | both | @y/protocols/sync message |
| 1 | messageAwareness | both | varUint8Array awareness update |
| 2 | messageAuth | server→client | @y/protocols/auth message |
| 3 | messageQueryAwareness | both | (empty) |
Room handshake
Because engine.io has a single connection endpoint (no per-room URL), the client
declares its room as the room handshake query parameter, alongside any
user-supplied params. engine.io-client URL-encodes query values, so the room
name needs no manual escaping.
The server reads room from the handshake query (e.g. engine.io's
allowRequest middleware or socket.request) and binds the connection to that
room before any frames are exchanged. Because it is available at handshake time,
the server can reject an unknown or unauthorized room immediately instead of
accepting the socket and waiting for a first frame. After the connection opens
the client proceeds exactly like y-websocket: it sends sync step 1 and its local
awareness state.
API
import { EngineIOProvider } from '@styris-ame/y-engineio'new EngineIOProvider(serverUrl: string, room: string, ydoc: Y.Doc, opts?)
serverUrl is the engine.io server origin (e.g. http://localhost:1234). The
following opts are supported:
{
// connect immediately; set false to call provider.connect() yourself
connect: true,
// query-string params -> engine.io `query`
params: {}, // Object<string,string>
// forwarded verbatim to the engine.io-client Socket
// (e.g. path, transports, withCredentials, extraHeaders, ...)
engineOptions: {},
// override the engine.io-client Socket constructor (e.g. for tests)
EngineClass: Socket,
// existing awareness instance
awareness: new awarenessProtocol.Awareness(ydoc),
// resend sync step 1 every N ms (-1 disables)
resyncInterval: -1,
// max backoff between reconnects (exponential)
maxBackoffTime: 2500,
// disable cross-tab BroadcastChannel sync
disableBc: false,
// close + reconnect if no message received for this long
socketTimeout: /* ~1.5 * awareness outdatedTimeout */
}Instance fields & events match y-websocket, except the ws-prefixed fields are
renamed (connected, connecting, unsuccessfulReconnects,
lastMessageReceived): connected, connecting,
shouldConnect, bcconnected, synced, syncStatus, params,
connect(), disconnect(), destroy(), and the status, sync,
sync-status, connection-close, connection-error events. The live socket is
exposed as provider.engine (an engine.io-client Socket) rather than
provider.ws.
License
The MIT License — port of y-websocket © Kevin Jahns
