Configure Hocuspocus Server

Introduction

There are only a few settings to pass for now. Most things are controlled through hooks.

Settings

SettingDescriptionDefault value
nameA name for the instance, used for logging.
portThe port the server should listen on.80
timeoutA connection healthcheck interval in milliseconds. Increased from 30s to 60s in v4.60000 (= 60s)
debounceDebounces the call of the onStoreDocument hook for the given amount of time in ms. Otherwise every single update would be persisted.2000 (= 2s)
maxDebounceMakes sure to call onStoreDocument at least in the given amount of time (ms).10000 (= 10s)
quietBy default, the servers show a start screen. If passed false, the server will start quietly.false
websocketOptionsOptions forwarded to the underlying WebSocket server (e.g. { maxPayload: 1024 * 1024 }). Moved inside the config object in v4.{}
maxUnauthenticatedQueueSizeMaximum number of bytes buffered (across all documents) for a single connection while it is still unauthenticated. The connection is closed when exceeded. See Pre-authentication resource limits.5242880 (= 5 MiB)
maxUnauthenticatedQueueMessagesMaximum number of messages buffered (across all documents) for a single connection while it is still unauthenticated. The connection is closed when exceeded.1000
maxPendingDocumentsMaximum number of distinct documents a single connection may open before any of them is authenticated. The connection is closed when exceeded.100

Usage

import { Server } from '@hocuspocus/server'

const server = new Server({
  name: 'hocuspocus-fra1-01',
  port: 1234,
  timeout: 60000,
  debounce: 5000,
  maxDebounce: 30000,
  quiet: true,
  websocketOptions: { maxPayload: 1024 * 1024 },
})

server.listen()

Generic Context type

Since v4, the server accepts a generic Context type parameter for end-to-end type safety across all hooks:

import { Server } from '@hocuspocus/server'

interface MyContext {
  userId: string
  permissions: string[]
}

const server = new Server<MyContext>({
  async onAuthenticate({ token }) {
    // Return value is typed as MyContext
    return { userId: '123', permissions: ['read', 'write'] }
  },
  async onChange({ context }) {
    // context.userId is typed as string
    console.log(context.userId)
  },
})

The generic defaults to any, so existing code without explicit typing continues to work.

Pre-authentication resource limits

Messages received before authentication completes are buffered in memory per connection. Without a bound, an unauthenticated client could exhaust server memory (see GHSA-xwhh-v746-pj9m). Three settings cap this buffering, and the connection is closed as soon as any limit is exceeded:

  • maxUnauthenticatedQueueSize (default 5 MiB) — total buffered bytes per connection.
  • maxUnauthenticatedQueueMessages (default 1000) — total buffered messages per connection.
  • maxPendingDocuments (default 100) — distinct not-yet-authenticated documents a single connection may have open at once. Each pending document holds its own queue and hook payload, so this stops a client from fanning out across many document names while staying under the per-connection queue limits.

In addition, the idle timeout is enforced as an absolute pre-authentication deadline (measured from when the connection was established and not refreshed by inbound traffic) until the connection authenticates its first document. After the first successful authentication, timeout reverts to the normal idle behavior for the rest of the socket's life.

import { Server } from '@hocuspocus/server'

const server = new Server({
  maxUnauthenticatedQueueSize: 5 * 1024 * 1024, // 5 MiB
  maxUnauthenticatedQueueMessages: 1000,
  maxPendingDocuments: 100,
})

server.listen()