Server

Server-side implementation of DocSync

DocSyncServer

Creates a DocSync server that handles WebSocket connections, authentication, authorization, and document synchronization.

import {
  DocSyncServer,
  inMemoryServerProvider, // don't use in production
} from "@docukit/docsync/server";

const server = new DocSyncServer({
  port: 8080,
  docBinding,
  provider: inMemoryServerProvider(),
  async authenticate({ request, token }) {
    // Authenticate via request, commonly with cookies.
    const session = await getSessionFromCookie(request.headers);
    if (session) {
      return { userId: session.user.id, context: { role: session.user.role } };
    }

    // Or authenticate via token.
    if (!token) return undefined;

    const tokenUser = await verifyToken(token);
    if (!tokenUser) return undefined;
    return { userId: tokenUser.id, context: { role: tokenUser.role } };
  },
  async authorize({ type, req, userId, context }) {
    return true;
  },
});

See Providers to bring your own storage backend (Postgres, MySQL, etc.).

Props

Prop

Type


authenticate

Called once per WebSocket connection attempt. Must validate the handshake request, optional token, or both, and resolve the canonical userId.

For browser apps with HttpOnly session cookies, prefer reading the cookie from request.headers first. Keep the token branch for non-browser clients and short-lived scoped grants.

async authenticate({ request, token }) {
  // Authenticate via request, commonly with cookies.
  const session = await getSessionFromCookie(request.headers);
  if (session) return { userId: session.user.id };

  // Or authenticate via token.
  if (!token) return undefined;

  const tokenUser = await verifyToken(token);
  if (!tokenUser) return undefined;

  return {
    userId: tokenUser.id,
    context: { role: tokenUser.role, orgId: tokenUser.orgId },
  };
}

Props

Prop

Type

Returns

Prop

Type

Return undefined to reject the connection.


authorize

Called for each operation after authentication. Use this to implement fine-grained access control.

async authorize({ type, req, userId, context }) {
  if (type === "sync") {
    const { docId } = req;
    return await canUserAccessDoc(userId, docId, context.orgId);
  }
  return true;
}

Props

Prop

Type

Returns

Return true to allow the operation, false to deny.


Events

onClientConnect

Called when a client successfully connects after authentication.

server.onClientConnect((event) => {
  console.log(`User ${event.userId} connected from device ${event.deviceId}`);
});

Prop

Type

onClientDisconnect

Called when a client disconnects.

server.onClientDisconnect((event) => {
  console.log(`User ${event.userId} disconnected: ${event.reason}`);
});

Prop

Type

onDocSubscribe

Called when a connected client starts syncing a document.

server.onDocSubscribe((event) => {
  console.log(`Client ${event.clientId} opened doc ${event.docId}`);
});

Prop

Type

onDocUnsubscribe

Called when a connected client stops syncing a document, either by unsubscribing or disconnecting.

server.onDocUnsubscribe((event) => {
  console.log(
    `Client ${event.clientId} closed doc ${event.docId}: ${event.reason}`,
  );
});

Prop

Type

onSyncRequest

Called after each sync request completes. Follows "wide events" philosophy: one event per operation with all context.

server.onSyncRequest((event) => {
  console.log(
    `Sync ${event.status} for doc ${event.req.docId} in ${event.durationMs}ms`,
  );
});

Prop

Type


close

Closes the server and all connections.

await server.close();

On this page

Edit on GitHub