Documentation
¶
Overview ¶
Package deepws is a websocket sync provider for collaborative documents: a hub that relays crdt.Document updates and presence between the clients of a room, and a client that keeps a local document converged with it.
It is a separate module on purpose. A transport means owning a network dependency, and most users of deep never open a socket — the core stays dependency-free, and the integration point this package fills is the one examples/websocket_sync sketches.
The protocol is three frame kinds over binary websocket messages, each one byte of type followed by its payload:
- state vector — the compact binary form; "this is what I have seen"
- update — the compact binary form; "this is what you are missing"
- presence — opaque bytes relayed to the room, carrying an crdt.AwarenessUpdate the server never decodes
A connecting client sends its state vector; the hub answers with what the client is missing and its own vector, the client sends back what the hub is missing — offline edits survive a reconnect — and from there both sides stream deltas as they happen. Presence is relayed, never stored durably: the hub caches each connection's last announcement so a joiner sees the room, and expiry is every client's own affair, which is safe because presence is ephemeral.
Index ¶
- type Client
- func (c *Client[P]) Announce(ctx context.Context, state P) error
- func (c *Client[P]) Awareness() *crdt.Awareness[P]
- func (c *Client[P]) Close(ctx context.Context) error
- func (c *Client[P]) Detach() *crdt.Document
- func (c *Client[P]) Done() <-chan struct{}
- func (c *Client[P]) Edit(fn func(*crdt.Document))
- func (c *Client[P]) Err() error
- func (c *Client[P]) Len() int
- func (c *Client[P]) OnUpdate(fn func())
- func (c *Client[P]) Publish(ctx context.Context) error
- func (c *Client[P]) Text() string
- type ClientOption
- type Hub
- type HubOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client[P any] struct { // contains filtered or unexported fields }
Client keeps a local document converged with a hub room, and carries presence for the peers editing alongside. P is the presence state — a cursor, a name — and is this client's business alone: the hub relays it without looking.
func Dial ¶
Dial connects to a hub and completes the sync handshake: whatever the room has that this client does not arrives before Dial returns, and whatever this client has that the room does not — offline edits, on a reconnect — is sent up.
node identifies this client's edits and presence; reuse the same id across reconnects so its clock keeps counting from where it left off.
func (*Client[P]) Announce ¶
Announce records this client's presence state and sends it to the room. It is also the heartbeat: call it periodically — and on every cursor move — so peers keep drawing this client.
func (*Client[P]) Awareness ¶
Awareness returns the presence view: peers appear, update and expire here. Announce this client through Client.Announce rather than SetLocal, so the announcement also reaches the room.
func (*Client[P]) Close ¶
Close says goodbye — so peers drop this client at once instead of waiting out the presence timeout — and closes the connection.
func (*Client[P]) Detach ¶ added in v1.2.0
Detach hands over the client's document once the connection is over — for offline editing and a later resume via WithDocument. It returns nil while the client is still live: sharing a document with a running read loop is a data race, so a live client's document is reachable only through Client.Edit.
func (*Client[P]) Done ¶
func (c *Client[P]) Done() <-chan struct{}
Done is closed when the connection is over.
func (*Client[P]) Edit ¶
Edit runs fn with the document, holding the client's lock: remote updates land on the same document from the read loop, and crdt.Document is not safe for unsynchronised concurrent use. Call Client.Publish afterwards to send what fn changed.
func (*Client[P]) OnUpdate ¶
func (c *Client[P]) OnUpdate(fn func())
OnUpdate registers fn to run after a remote update lands in the document. It runs on the read goroutine; keep it short and hand real work elsewhere.
type ClientOption ¶ added in v1.1.0
type ClientOption func(*clientConfig)
ClientOption configures a client.
func WithClientPingInterval ¶ added in v1.1.0
func WithClientPingInterval(d time.Duration) ClientOption
WithClientPingInterval sets how often the client probes the connection. A hub that stops answering gets the connection closed and Client.Done closed with it — without the probe, a silently dead TCP connection leaves the client waiting forever for updates that cannot come. The default is 20 seconds; zero disables the probe.
func WithDialOptions ¶ added in v1.1.0
func WithDialOptions(opts *websocket.DialOptions) ClientOption
WithDialOptions sets the websocket dial options — headers for authentication, an HTTP client, a subprotocol.
func WithDocument ¶ added in v1.2.0
func WithDocument(doc *crdt.Document) ClientOption
WithDocument resumes from an existing document instead of starting empty. This is the offline story: keep editing a previous client's document after the connection is gone (through Client.Edit it stays valid), then hand it to the next Dial — the handshake sends everything the room has not seen, offline edits included, and pulls down what the room gained meanwhile.
The document must not be shared with another live client, and node should be the same identity that produced its edits.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub serves rooms, each holding one document. It is an http.Handler; the room is named by the request's "room" query parameter.
func (*Hub) Room ¶
Room runs fn with the named room's document under the room's lock, creating the room if needed. It is how a host application seeds a room before the first client arrives, or snapshots one for persistence — clients apply updates to the same document concurrently, so access goes through here.
fn runs under the room's lock, so it must not call back into the hub — that inverts the lock order the eviction timer takes and can deadlock.
Looking at a room does not keep it alive: being in one does. A host that reads its rooms on a schedule — a document listing, a metrics sweep — would otherwise postpone every eviction it touched. The consequence for seeding is that a room seeded a moment before a client joins may be evicted in between, and the client then finds an empty room; hosts seed on every join for that reason, which the CRDT makes free.
type HubOption ¶
type HubOption func(*Hub)
HubOption configures a Hub.
func WithAcceptOptions ¶
func WithAcceptOptions(opts *websocket.AcceptOptions) HubOption
WithAcceptOptions sets the websocket accept options — origin patterns, compression — used for every connection.
func WithAuth ¶ added in v1.1.0
WithAuth installs a per-request check, called before the websocket upgrade with the request and the room it names. A non-nil error refuses the connection with 403. Origin policy belongs in WithAcceptOptions; this is for the authorization the request itself carries — a token in a header, a session cookie.
func WithPingInterval ¶ added in v1.1.0
WithPingInterval sets how often the hub probes each connection for liveness. A connection whose peer stops answering is closed, which frees its room slot — without the probe, a silently dead TCP connection holds it until the operating system gives up, which can be never. The default is 20 seconds; zero disables the probe.
func WithRoomEviction ¶ added in v1.1.0
WithRoomEviction drops a room after it has sat empty for idle, calling onEvict with the document first so the host can persist it. Without this a hub keeps every room it has ever served; with it, a room's state lives in the host's store between sessions and the next joiner starts a fresh room the host can seed from that store via Hub.Room.
Every room is on the clock, including one that no client ever joined — seeded ahead of time, or created by a request that never became a websocket. Reaching for a room through Hub.Room postpones its eviction but cannot cancel it: a host that lists or snapshots its rooms is not thereby keeping them alive forever.