import { CloseEvent } from "@hocuspocus/common"; import { Mutex } from "async-mutex"; import { Awareness } from "y-protocols/awareness"; import { Doc } from "yjs"; import { IncomingMessage as IncomingMessage$1, Server as Server$1, ServerResponse } from "node:http"; import { AddressInfo } from "node:net"; import { Decoder } from "lib0/decoding"; import { Encoder } from "lib0/encoding"; //#region packages/server/src/Document.d.ts declare class Document extends Doc { awareness: Awareness; callbacks: { onUpdate: (document: Document, origin: unknown, update: Uint8Array) => void; beforeBroadcastStateless: (document: Document, stateless: string) => void; }; connections: Map; }>; directConnectionsCount: number; name: string; isLoading: boolean; isDestroyed: boolean; saveMutex: Mutex; lastChangeTime: number; /** * Constructor. */ constructor(name: string, yDocOptions?: object); /** * Check if the Document (XMLFragment or Map) is empty */ isEmpty(fieldName: string): boolean; /** * Merge the given document(s) into this one */ merge(documents: Doc | Array): Document; /** * Set a callback that will be triggered when the document is updated */ onUpdate(callback: (document: Document, origin: unknown, update: Uint8Array) => void): Document; /** * Set a callback that will be triggered before a stateless message is broadcasted */ beforeBroadcastStateless(callback: (document: Document, stateless: string) => void): Document; /** * Register a connection and a set of clients on this document keyed by the * underlying websocket connection */ addConnection(connection: Connection): Document; /** * Is the given connection registered on this document */ hasConnection(connection: Connection): boolean; /** * Remove the given connection from this document */ removeConnection(connection: Connection): Document; addDirectConnection(): Document; removeDirectConnection(): Document; /** * Get the number of active connections for this document */ getConnectionsCount(): number; /** * Get an array of registered connections */ getConnections(): Array; /** * Get the client ids for the given connection instance */ getClients(connection: Connection): Set; /** * Has the document awareness states */ hasAwarenessStates(): boolean; /** * Apply the given awareness update */ applyAwarenessUpdate(connection: Connection, update: Uint8Array): Document; /** * Handle an awareness update and sync changes to clients * @private */ private handleAwarenessUpdate; /** * Handle an updated document and sync changes to clients */ private handleUpdate; /** * Broadcast stateless message to all connections */ broadcastStateless(payload: string, filter?: (conn: Connection) => boolean): void; destroy(): void; } //#endregion //#region packages/server/src/ClientConnection.d.ts /** * The `ClientConnection` class is responsible for handling an incoming WebSocket * * TODO-refactor: * - use event handlers instead of calling hooks directly, hooks should probably be called from Hocuspocus.ts */ declare class ClientConnection { private readonly websocket; private readonly request; private readonly documentProvider; private readonly hooks; private readonly opts; private readonly defaultContext; private readonly documentConnections; private readonly incomingMessageQueue; private readonly documentConnectionsEstablished; private readonly hookPayloads; private readonly callbacks; private readonly socketId; timeout: number; pingInterval: NodeJS.Timeout; lastMessageReceivedAt: number; /** * The `ClientConnection` class receives incoming WebSocket connections, * runs all hooks: * * - onConnect for all connections * - onAuthenticate only if required * * … and if nothings fails it'll fully establish the connection and * load the Document then. */ constructor(websocket: WebSocketLike, request: Request, documentProvider: { createDocument: Hocuspocus["createDocument"]; }, hooks: Hocuspocus["hooks"], opts: { timeout: number; }, defaultContext?: Context); /** * Handle WebSocket close event. Call this from your integration * when the WebSocket connection closes. */ handleClose(event?: CloseEvent): void; private close; /** * Close the connection if no messages have been received within the timeout period. * This replaces application-level ping/pong to maintain backward compatibility * with older provider versions that don't understand Ping/Pong message types. * Awareness updates (~every 30s) keep active connections alive. */ private check; /** * Set a callback that will be triggered when the connection is closed */ onClose(callback: (document: Document, payload: onDisconnectPayload) => void): ClientConnection; /** * Create a new connection by the given request and document */ private createConnection; private setUpNewConnection; private handleQueueingMessage; /** * Handle an incoming WebSocket message. Call this from your integration * when the WebSocket receives a binary message. */ handleMessage: (data: Uint8Array) => void; } //#endregion //#region packages/server/src/DirectConnection.d.ts declare class DirectConnection$1 implements DirectConnection { document: Document | null; instance: Hocuspocus; context: Context; /** * Constructor. */ constructor(document: Document, instance: Hocuspocus, context?: Context); transact(transaction: (document: Document) => void): Promise; disconnect(): Promise; } //#endregion //#region packages/server/src/Server.d.ts interface ServerConfiguration extends Configuration { port?: number; address?: string; stopOnSignals?: boolean; /** * Options passed to the underlying WebSocket server (ws). * Supports all ws ServerOptions, e.g. { maxPayload: 1024 * 1024 } */ websocketOptions?: Record; } declare const defaultServerConfiguration: { port: number; address: string; stopOnSignals: boolean; }; declare class Server { httpServer: Server$1; private crossws; hocuspocus: Hocuspocus; configuration: ServerConfiguration; constructor(configuration?: Partial>); private setupHttpUpgrade; requestHandler: (request: IncomingMessage$1, response: ServerResponse) => Promise; listen(port?: number, callback?: any): Promise>; get address(): AddressInfo; destroy(): Promise; get URL(): string; get webSocketURL(): string; get httpURL(): string; private showStartScreen; } //#endregion //#region packages/server/src/Hocuspocus.d.ts declare const defaultConfiguration: { name: null; timeout: number; debounce: number; maxDebounce: number; quiet: boolean; yDocOptions: { gc: boolean; gcFilter: () => boolean; }; unloadImmediately: boolean; }; declare class Hocuspocus { configuration: Configuration; loadingDocuments: Map>; unloadingDocuments: Map>; documents: Map; server?: Server; debouncer: { debounce: (id: string, func: () => any | Promise<() => any>, debounce: number, maxDebounce: number) => Promise; isDebounced: (id: string) => boolean; isCurrentlyExecuting: (id: string) => boolean; executeNow: (id: string) => any; }; constructor(configuration?: Partial>); /** * Configure Hocuspocus */ configure(configuration: Partial>): Hocuspocus; /** * Get the total number of active documents */ getDocumentsCount(): number; /** * Get the total number of active connections */ getConnectionsCount(): number; /** * Immediately execute all pending debounced onStoreDocument calls. * Useful during shutdown to ensure documents are persisted and unloaded * before the server exits, even when unloadImmediately is false. */ flushPendingStores(): void; /** * Force close one or more connections */ closeConnections(documentName?: string): void; /** * The `handleConnection` method receives incoming WebSocket connections, * runs all hooks: * * - onConnect for all connections * - onAuthenticate only if required * * … and if nothing fails it'll fully establish the connection and * load the Document then. */ handleConnection(incoming: WebSocket | WebSocketLike, request: Request, defaultContext?: Context): ClientConnection; /** * Handle update of the given document * * "connection" is not necessarily type "Connection", it's the Yjs "origin" (which is "Connection" if * the update is incoming from the provider, but can be anything if the updates is originated from an extension. */ private handleDocumentUpdate; /** * Create a new document by the given request */ createDocument(documentName: string, request: Request, socketId: string, connection: ConnectionConfiguration, context?: Context): Promise; loadDocument(documentName: string, request: Request, socketId: string, connectionConfig: ConnectionConfiguration, context?: Context): Promise; storeDocumentHooks(document: Document, hookPayload: onStoreDocumentPayload, immediately?: boolean): Promise; /** * Run the given hook on all configured extensions. * Runs the given callback after each hook. */ hooks(name: T, payload: HookPayloadByName[T], callback?: Function | null): Promise; shouldUnloadDocument(document: Document): boolean; unloadDocument(document: Document): Promise; openDirectConnection(documentName: string, context?: Context): Promise>; } //#endregion //#region packages/server/src/types.d.ts interface ConnectionTransactionOrigin { source: "connection"; connection: Connection; } interface RedisTransactionOrigin { source: "redis"; } interface LocalTransactionOrigin { source: "local"; skipStoreHooks?: boolean; context?: any; } type TransactionOrigin = ConnectionTransactionOrigin | RedisTransactionOrigin | LocalTransactionOrigin; declare function isTransactionOrigin(origin: unknown): origin is TransactionOrigin; declare function shouldSkipStoreHooks(origin: unknown): boolean; /** * Minimal interface for any WebSocket-like object for WebSocket, Bun's ServerWebSocket, ws, Deno, etc. */ interface WebSocketLike { send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void; close(code?: number, reason?: string): void; readyState: number; } declare enum MessageType { Unknown = -1, Sync = 0, Awareness = 1, Auth = 2, QueryAwareness = 3, SyncReply = 4, // same as Sync, but won't trigger another 'SyncStep1' Stateless = 5, BroadcastStateless = 6, CLOSE = 7, SyncStatus = 8, Ping = 9, Pong = 10 } interface AwarenessUpdate { added: Array; updated: Array; removed: Array; } interface ConnectionConfiguration { readOnly: boolean; isAuthenticated: boolean; } interface Extension { priority?: number; extensionName?: string; onConfigure?(data: onConfigurePayload): Promise; onListen?(data: onListenPayload): Promise; onUpgrade?(data: onUpgradePayload): Promise; onConnect?(data: onConnectPayload): Promise; connected?(data: connectedPayload): Promise; onAuthenticate?(data: onAuthenticatePayload): Promise; onTokenSync?(data: onTokenSyncPayload): Promise; onCreateDocument?(data: onCreateDocumentPayload): Promise; onLoadDocument?(data: onLoadDocumentPayload): Promise; afterLoadDocument?(data: afterLoadDocumentPayload): Promise; beforeHandleMessage?(data: beforeHandleMessagePayload): Promise; beforeSync?(data: beforeSyncPayload): Promise; beforeBroadcastStateless?(data: beforeBroadcastStatelessPayload): Promise; onStateless?(payload: onStatelessPayload): Promise; onChange?(data: onChangePayload): Promise; onStoreDocument?(data: onStoreDocumentPayload): Promise; afterStoreDocument?(data: afterStoreDocumentPayload): Promise; onAwarenessUpdate?(data: onAwarenessUpdatePayload): Promise; onRequest?(data: onRequestPayload): Promise; onDisconnect?(data: onDisconnectPayload): Promise; beforeUnloadDocument?(data: beforeUnloadDocumentPayload): Promise; afterUnloadDocument?(data: afterUnloadDocumentPayload): Promise; onDestroy?(data: onDestroyPayload): Promise; } type HookName = "onConfigure" | "onListen" | "onUpgrade" | "onConnect" | "connected" | "onAuthenticate" | "onTokenSync" | "onCreateDocument" | "onLoadDocument" | "afterLoadDocument" | "beforeHandleMessage" | "beforeBroadcastStateless" | "beforeSync" | "onStateless" | "onChange" | "onStoreDocument" | "afterStoreDocument" | "onAwarenessUpdate" | "onRequest" | "onDisconnect" | "beforeUnloadDocument" | "afterUnloadDocument" | "onDestroy"; type HookPayloadByName = { onConfigure: onConfigurePayload; onListen: onListenPayload; onUpgrade: onUpgradePayload; onConnect: onConnectPayload; connected: connectedPayload; onAuthenticate: onAuthenticatePayload; onTokenSync: onTokenSyncPayload; onCreateDocument: onCreateDocumentPayload; onLoadDocument: onLoadDocumentPayload; afterLoadDocument: afterLoadDocumentPayload; beforeHandleMessage: beforeHandleMessagePayload; beforeBroadcastStateless: beforeBroadcastStatelessPayload; beforeSync: beforeSyncPayload; onStateless: onStatelessPayload; onChange: onChangePayload; onStoreDocument: onStoreDocumentPayload; afterStoreDocument: afterStoreDocumentPayload; onAwarenessUpdate: onAwarenessUpdatePayload; onRequest: onRequestPayload; onDisconnect: onDisconnectPayload; afterUnloadDocument: afterUnloadDocumentPayload; beforeUnloadDocument: beforeUnloadDocumentPayload; onDestroy: onDestroyPayload; }; interface Configuration extends Extension { /** * A name for the instance, used for logging. */ name: string | null; /** * A list of hocuspocus extensions. */ extensions: Array; /** * Defines in which interval the server sends a ping, and closes the connection when no pong is sent back. */ timeout: number; /** * Debounces the call of the `onStoreDocument` hook for the given amount of time in ms. * Otherwise every single update would be persisted. */ debounce: number; /** * Makes sure to call `onStoreDocument` at least in the given amount of time (ms). */ maxDebounce: number; /** * By default, the servers show a start screen. If passed false, the server will start quietly. */ quiet: boolean; /** * If set to false, respects the debounce time of `onStoreDocument` before unloading a document. * Otherwise, the document will be unloaded immediately. * * This prevents a client from DOSing the server by repeatedly connecting and disconnecting when * your onStoreDocument is rate-limited. */ unloadImmediately: boolean; /** * options to pass to the ydoc document */ yDocOptions: { gc: boolean; gcFilter: () => boolean; }; } interface onStatelessPayload { connection: Connection; documentName: string; document: Document; payload: string; } interface onAuthenticatePayload { context: Context; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; request: Request; socketId: string; token: string; connectionConfig: ConnectionConfiguration; providerVersion: string | null; } interface onTokenSyncPayload { context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; token: string; connectionConfig: ConnectionConfiguration; connection: Connection; } interface onCreateDocumentPayload { context: Context; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; } interface onConnectPayload { context: Context; documentName: string; instance: Hocuspocus; request: Request; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; providerVersion: string | null; } interface connectedPayload { context: Context; documentName: string; instance: Hocuspocus; request: Request; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; connection: Connection; providerVersion: string | null; } interface onLoadDocumentPayload { context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; } interface afterLoadDocumentPayload { context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; } interface onChangePayload { clientsCount: number; context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; update: Uint8Array; socketId: string; transactionOrigin: unknown; connection?: Connection; } interface beforeHandleMessagePayload { clientsCount: number; context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; update: Uint8Array; socketId: string; connection: Connection; } interface beforeSyncPayload { clientsCount: number; context: Context; document: Document; documentName: string; connection: Connection; /** * The y-protocols/sync message type * @example * 0: SyncStep1 * 1: SyncStep2 * 2: YjsUpdate * * @see https://github.com/yjs/y-protocols/blob/master/sync.js#L13-L40 */ type: number; /** * The payload of the y-sync message. */ payload: Uint8Array; } interface beforeBroadcastStatelessPayload { document: Document; documentName: string; payload: string; } interface onStoreDocumentPayload { clientsCount: number; document: Document; lastContext: Context; lastTransactionOrigin: unknown; documentName: string; instance: Hocuspocus; } interface afterStoreDocumentPayload extends onStoreDocumentPayload {} interface onAwarenessUpdatePayload { document: Document; documentName: string; instance: Hocuspocus; transactionOrigin: unknown; connection?: Connection; added: number[]; updated: number[]; removed: number[]; awareness: Awareness; states: StatesArray; } type StatesArray = { clientId: number; [key: string | number]: any; }[]; interface fetchPayload { context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; connectionConfig: ConnectionConfiguration; } interface storePayload extends onStoreDocumentPayload { state: Buffer; } interface onDisconnectPayload { clientsCount: number; context: Context; document: Document; documentName: string; instance: Hocuspocus; requestHeaders: Headers; requestParameters: URLSearchParams; socketId: string; } interface onRequestPayload { request: IncomingMessage$1; response: ServerResponse; instance: Hocuspocus; } interface onUpgradePayload { request: IncomingMessage$1; socket: any; head: any; instance: Hocuspocus; } interface onListenPayload { instance: Hocuspocus; configuration: Configuration; port: number; } interface onDestroyPayload { instance: Hocuspocus; } interface onConfigurePayload { instance: Hocuspocus; configuration: Configuration; version: string; } interface afterUnloadDocumentPayload { instance: Hocuspocus; documentName: string; } interface beforeUnloadDocumentPayload { instance: Hocuspocus; documentName: string; document: Document; } interface DirectConnection { transact(transaction: (document: Document) => void): Promise; disconnect(): void; } //#endregion //#region packages/server/src/Connection.d.ts declare class Connection { webSocket: WebSocketLike; context: Context; document: Document; request: Request; callbacks: { onClose: ((document: Document, event?: CloseEvent) => void)[]; beforeHandleMessage: (connection: Connection, update: Uint8Array) => Promise; beforeSync: (connection: Connection, payload: Pick) => Promise; statelessCallback: (payload: onStatelessPayload) => Promise; onTokenSyncCallback: (payload: { token: string; }) => Promise; }; socketId: string; readOnly: boolean; sessionId: string | null; providerVersion: string | null; /** * The address string prefixed to outgoing messages. * Session-aware clients get `documentName\0sessionId`; legacy clients get plain `documentName`. */ get messageAddress(): string; private messageQueue; private processingPromise; /** * Constructor. */ constructor(connection: WebSocketLike, request: Request, document: Document, socketId: string, context: Context, readOnly?: boolean, sessionId?: string | null, providerVersion?: string | null); /** * Set a callback that will be triggered when the connection is closed */ onClose(callback: (document: Document, event?: CloseEvent) => void): Connection; /** * Set a callback that will be triggered when an stateless message is received */ onStatelessCallback(callback: (payload: onStatelessPayload) => Promise): Connection; /** * Set a callback that will be triggered before an message is handled */ beforeHandleMessage(callback: (connection: Connection, update: Uint8Array) => Promise): Connection; /** * Set a callback that will be triggered before a sync message is handled */ beforeSync(callback: (connection: Connection, payload: Pick) => Promise): Connection; /** * Set a callback that will be triggered when on token sync message is received */ onTokenSyncCallback(callback: (payload: { token: string; }) => Promise): Connection; /** * Returns a promise that resolves when all queued messages have been processed. */ waitForPendingMessages(): Promise; /** * Send the given message */ send(message: Uint8Array): void; /** * Send a stateless message with payload */ sendStateless(payload: string): void; /** * Request current token from the client */ requestToken(): void; /** * Graceful wrapper around the WebSocket close method. */ close(event?: CloseEvent): void; /** * Send the current document awareness to the client, if any * @private */ private sendCurrentAwareness; /** * Handle an incoming message * @public */ handleMessage(data: Uint8Array): void; private processMessages; } //#endregion //#region packages/server/src/IncomingMessage.d.ts declare class IncomingMessage { /** * Access to the received message. */ decoder: Decoder; /** * Private encoder; can be undefined. * * Lazy creation of the encoder speeds up IncomingMessages that need only a decoder. */ private encoderInternal?; constructor(input: any); get encoder(): Encoder; readVarUint8Array(): Uint8Array; peekVarUint8Array(): Uint8Array; readVarUint(): number; readVarString(): string; toUint8Array(): Uint8Array; writeVarUint(type: MessageType): void; writeVarString(string: string): void; get length(): number; } //#endregion //#region packages/server/src/MessageReceiver.d.ts declare class MessageReceiver { message: IncomingMessage; defaultTransactionOrigin?: TransactionOrigin; constructor(message: IncomingMessage, defaultTransactionOrigin?: TransactionOrigin); apply(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): Promise; readSyncMessage(message: IncomingMessage, document: Document, connection?: Connection, reply?: (message: Uint8Array) => void, requestFirstSync?: boolean): Promise<0 | 2 | 1>; applyQueryAwarenessMessage(document: Document, connection?: Connection, reply?: (message: Uint8Array) => void): void; } //#endregion //#region packages/server/src/OutgoingMessage.d.ts declare class OutgoingMessage { encoder: Encoder; type?: number; category?: string; constructor(documentName: string); createSyncMessage(): OutgoingMessage; createSyncReplyMessage(): OutgoingMessage; createAwarenessUpdateMessage(awareness: Awareness, changedClients?: Array): OutgoingMessage; writeQueryAwareness(): OutgoingMessage; writeTokenSyncRequest(): OutgoingMessage; writeAuthenticated(readonly: boolean): OutgoingMessage; writePermissionDenied(reason: string): OutgoingMessage; writeFirstSyncStepFor(document: Document): OutgoingMessage; writeUpdate(update: Uint8Array): OutgoingMessage; writeStateless(payload: string): OutgoingMessage; writeBroadcastStateless(payload: string): OutgoingMessage; writeSyncStatus(updateSaved: boolean): OutgoingMessage; writeCloseMessage(reason: string): OutgoingMessage; toUint8Array(): Uint8Array; } //#endregion //#region packages/server/src/util/debounce.d.ts declare const useDebounce: () => { debounce: (id: string, func: () => any | Promise<() => any>, debounce: number, maxDebounce: number) => Promise; isDebounced: (id: string) => boolean; isCurrentlyExecuting: (id: string) => boolean; executeNow: (id: string) => any; }; //#endregion export { AwarenessUpdate, Configuration, Connection, ConnectionConfiguration, ConnectionTransactionOrigin, DirectConnection, Document, Extension, Hocuspocus, HookName, HookPayloadByName, IncomingMessage, LocalTransactionOrigin, MessageReceiver, MessageType, OutgoingMessage, RedisTransactionOrigin, Server, ServerConfiguration, StatesArray, TransactionOrigin, WebSocketLike, afterLoadDocumentPayload, afterStoreDocumentPayload, afterUnloadDocumentPayload, beforeBroadcastStatelessPayload, beforeHandleMessagePayload, beforeSyncPayload, beforeUnloadDocumentPayload, connectedPayload, defaultConfiguration, defaultServerConfiguration, fetchPayload, isTransactionOrigin, onAuthenticatePayload, onAwarenessUpdatePayload, onChangePayload, onConfigurePayload, onConnectPayload, onCreateDocumentPayload, onDestroyPayload, onDisconnectPayload, onListenPayload, onLoadDocumentPayload, onRequestPayload, onStatelessPayload, onStoreDocumentPayload, onTokenSyncPayload, onUpgradePayload, shouldSkipStoreHooks, storePayload, useDebounce };