@y-sweet/client
JavaScript/TypeScript library for connecting a Yjs Doc with a Y-Sweet server in order to sync and persist document updates.
Installation
npm install @y-sweet/client
The @y-sweet/client
library is primarily used to connect a given Yjs document to a Y-Sweet server so that changes to the Yjs document
are synced across all clients and, optionally, so that the document is persisted to S3 storage.
createYjsProvider(doc, docId, authEndpoint, [options])
Connecting the Yjs document to Y-Sweet is done with the createYjsProvider()
function, which takes a Yjs document, a doc ID, an AuthEndpoint
, and some optional parameters. It returns a promise which, when await
ed, resolves to a YSweetProvider
. Note that it is often not necessary to directly interact with the YSweetProvider
.
import { createYjsProvider } from '@y-sweet/client'
const doc = new Y.Doc()
const docId = 'my-doc-id'
await createYjsProvider(doc, docId, '/api/auth')
The AuthEndpoint
is used by the provider to get a ClientToken
that authorizes the client to read and write to the shared document. The AuthEndpoint
may either be a
string (a full URL or just a path) or an async function that returns a ClientToken
. If you are using a meta-framework like NextJS or Remix, you can implement this auth endpoint as an API route, or you
can use a server framework like ExpressJS or Hono.
Here’s an example of how the auth endpoint might be implemented using Hono:
import { DocumentManager } from '@y-sweet/sdk'
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { serve } from '@hono/node-server'
const docManager = new DocumentManager(process.env.CONNECTION_STRING)
const app = new Hono()
app.use('/api/auth', cors())
app.post('/api/auth', async (c) => {
const body = await c.req.json()
const docId = body.docId
// At this point, in a production app, you'd want to authenticate the user
// and make sure they have access to the given doc
const clientToken = await docManager.getOrCreateDocAndToken(docId)
return c.json(clientToken)
})
serve(app)
Comparison to WebsocketProvider
(y-websocket
)
y-websocket
is a popular WebSocket provider for Yjs.
YSweetProvider
aims to provide a compatible interface to WebsocketProvider
where it makes sense. This section outlines
the differences between the two providers.
Constructor arguments
As its first argument, WebsocketProvider
takes a WebSocket URL. YSweetProvider
takes an auth URL, which is a POST
endpoint that it expects to return a ClientToken
. It is that ClientToken
that contains the WebSocket URL. This approach
allows YSweetProvider
to reauthenticate when its token expires. Read more about auth endpoints.
Both constructors take an optional params
object, and most of WebsocketProvider
parameters also work with YSweetProvider
(except for some that don’t apply to Y-Sweet).
WebsocketProvider Parameter | Supported in YSweetProvider | Meaning |
---|---|---|
connect | ✅ | Automatically connect upon initialization. |
params | ❌ | URL parameters to add to the WebSocket URL. Not applicable to Y-Sweet. |
awareness | ✅ | Yjs Awareness instance. |
WebSocketPolyfill | ✅ | WebSocket constructor to use (normally for non-browser environments) |
resyncInterval | ❌ | Resync is not necessary over a proper TCP connection. |
disableBc | ❌ | BroadcastChannel is enabled automatically if offline support is enabled. |
YSweetProvider
additionally supports several other parameters:
initialClientToken
- provides aClientToken
to avoid the initial trip to the auth endpoint. If a connection does not succeed (e.g. the token has expired), the provider will still attempt to connect via the auth endpoint.offlineSupport
- enables storing and loading document data locally from IndexedDB. When the user reconnects to the Y-Sweet server, all their local changes will be synced.
Provider fields
WebsocketProvider Field | Supported in YSweetProvider | Meaning |
---|---|---|
wsconnected | ✅ | Whether the WebSocket is connected. Equivalent to this.status === STATUS_CONNECTED || this.status === STATUS_HANDSHAKING |
wsconnecting | ✅ | Whether the WebSocket is connecting. Equivalent to this.status === STATUS_CONNECTING |
synced | ✅ | Whether the document is synced. Equivalent to this.status === STATUS_CONNECTED |
shouldConnect | ✅ | Whether the WebSocket should connect. Equivalent to this.status !== STATUS_OFFLINE |
bcConnected | ❌ | Broadcast channel is not supported. |
params | ❌ | URL parameters to add to the WebSocket URL. Not applicable to Y-Sweet. |
YSweetProvider
additionally supports a field called hasLocalChanges
, which is a boolean that indicates whether there are
local changes that have not yet been synced to the server.
Provider methods
WebsocketProvider Method | Supported in YSweetProvider | Meaning |
---|---|---|
connect | ✅ | Connect to the WebSocket. |
disconnect | ✅ | Disconnect from the WebSocket. |
destroy | ✅ | Cleanup before the provider is destroyed. |
on | ✅ | Add an event listener. |
All WebsocketProvider
events are supported in .on()
(sync
, status
, connection-close
, connection-error
).
Two new events are additionally available:
local-changes
- emitted with a boolean when the document’shasLocalChanges
state changesconnection-status
- emitted when the connection status changes
Note that for consistency with WebsocketProvider
, the sync
event is only concerned with the initial handshake sync of each
connection lifecycle. The local-changes
event is probably what you’re looking for if you want to show the user whether their changes have
been synced to the server.
Connection statuses
The connection-status
events provide a richer set of statuses than the status
events, which are limited to y-websocket
statuses. The equivalents are shown below:
YSweetProvider Status | WebsocketProvider Status |
---|---|
offline | disconnected |
connecting | connecting |
handshaking | connecting |
error | disconnected |
connected | connected |
Types
createYjsProvider(
doc: Y.Doc,
docId: string,
authEndpoint: string | () => Promise<ClientToken>,
options: YSweetProviderParams
): YSweetProvider
type AuthEndpoint = string | () => Promise<ClientToken>
type ClientToken = {
url: string
baseUrl: string
docId: string
token?: string
}
type YSweetProviderParams = {
connect?: boolean
awareness?: Awareness
WebSocketPolyfill?: WebSocketPolyfillType
initialClientToken?: ClientToken
}