snapshot of https://github.com/ueberdosis/hocuspocus.git @ cf3cfa355b4d3bb61c6b6c80071f71783e32470e (2026-09-15)
Some checks failed
build / lint (22) (push) Failing after 17s
build / test (22) (push) Failing after 8s
build / lint (23) (push) Failing after 2s
build / build (22) (push) Has been skipped
build / build (23) (push) Has been skipped
build / test (23) (push) Failing after 4s
docsearch / docsearch (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 17:21:35 +05:00
commit e0c588ca1c
265 changed files with 55259 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.7.0](https://github.com/ueberdosis/hocuspocus/compare/v4.6.0...v4.7.0) (2026-09-09)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.6.0](https://github.com/ueberdosis/hocuspocus/compare/v4.5.0...v4.6.0) (2026-08-10)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.5.0](https://github.com/ueberdosis/hocuspocus/compare/v4.4.0...v4.5.0) (2026-08-04)
### Bug Fixes
* update packages via audit --fix ([1dc9ca0](https://github.com/ueberdosis/hocuspocus/commit/1dc9ca0ff35f1033136473d134cee8cb6b336281))
# [4.4.0](https://github.com/ueberdosis/hocuspocus/compare/v4.3.0...v4.4.0) (2026-07-13)
### Features
* add `flushDelay` option for batching updates to reduce websocket traffic during heavy editing ([#1118](https://github.com/ueberdosis/hocuspocus/issues/1118)) ([75594c0](https://github.com/ueberdosis/hocuspocus/commit/75594c05d57d48f2f70d4c9440c28b8226bf95ac))
# [4.3.0](https://github.com/ueberdosis/hocuspocus/compare/v4.2.0...v4.3.0) (2026-06-18)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.2.0](https://github.com/ueberdosis/hocuspocus/compare/v4.1.2...v4.2.0) (2026-06-12)
**Note:** Version bump only for package @hocuspocus/provider-react
## [4.1.2](https://github.com/ueberdosis/hocuspocus/compare/v4.1.1...v4.1.2) (2026-06-11)
**Note:** Version bump only for package @hocuspocus/provider-react
## [4.1.1](https://github.com/ueberdosis/hocuspocus/compare/v4.1.0...v4.1.1) (2026-06-10)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.1.0](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0...v4.1.0) (2026-05-20)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.0.0](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.7...v4.0.0) (2026-04-23)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.0.0-rc.7](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.6...v4.0.0-rc.7) (2026-04-22)
**Note:** Version bump only for package @hocuspocus/provider-react
# [4.0.0-rc.6](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.5...v4.0.0-rc.6) (2026-04-20)
### Bug Fixes
* react provider in strict mode ([4f27b7f](https://github.com/ueberdosis/hocuspocus/commit/4f27b7f0e57423b08fabb59cfa6d0a2cc67bc996))
# [4.0.0-rc.5](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.4...v4.0.0-rc.5) (2026-04-16)
### Features
* adds package readmes ([c7d63f7](https://github.com/ueberdosis/hocuspocus/commit/c7d63f79980de3c34e63b60073f22774bf27ac13))
# [4.0.0-rc.4](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.3...v4.0.0-rc.4) (2026-04-16)
### Bug Fixes
* publishConfig for provider-react ([376765f](https://github.com/ueberdosis/hocuspocus/commit/376765f6623259c79940da67a1f970ac9a1e84b1))
# [4.0.0-rc.3](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.2...v4.0.0-rc.3) (2026-04-16)
### Features
* first draft of @hocuspocus/provider-react package ([#1059](https://github.com/ueberdosis/hocuspocus/issues/1059)) ([cbc9977](https://github.com/ueberdosis/hocuspocus/commit/cbc99770096e8c674dae304c7e1a16e0fbec827b))

View File

@@ -0,0 +1,70 @@
# @hocuspocus/provider-react
[![Version](https://img.shields.io/npm/v/@hocuspocus/provider-react.svg?label=version)](https://www.npmjs.com/package/@hocuspocus/provider-react)
[![Downloads](https://img.shields.io/npm/dm/@hocuspocus/provider-react.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![License](https://img.shields.io/npm/l/@hocuspocus/provider-react.svg)](https://www.npmjs.com/package/@hocuspocus/provider-react)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
React bindings for the [Hocuspocus provider](../provider). Wraps the provider in components and hooks so React handles the lifecycle — including StrictMode double-mounts. Built on `useSyncExternalStore`.
## Installation
```bash
npm install @hocuspocus/provider @hocuspocus/provider-react yjs
```
Requires React 18 or 19.
## Usage
Wrap your collaborative subtree with `HocuspocusProviderWebsocketComponent` (shared WebSocket) and one or more `HocuspocusRoom`s (one per document). Inside the room, hooks give you the provider and its state:
```tsx
import {
HocuspocusProviderWebsocketComponent,
HocuspocusRoom,
useHocuspocusProvider,
useHocuspocusConnectionStatus,
} from "@hocuspocus/provider-react"
function Editor() {
const provider = useHocuspocusProvider()
const status = useHocuspocusConnectionStatus()
// wire provider.document / provider.awareness into your editor
return <div>{status}</div>
}
export function App() {
return (
<HocuspocusProviderWebsocketComponent url="ws://127.0.0.1:1234">
<HocuspocusRoom name="example-document" token="super-secret-token">
<Editor />
</HocuspocusRoom>
</HocuspocusProviderWebsocketComponent>
)
}
```
To use with Tiptap, pass `provider.document` into the `Collaboration` extension and `provider` into `CollaborationCaret` — see the [collaborative editing guide](https://tiptap.dev/docs/hocuspocus/guides/collaborative-editing) for a full example.
## Exports
**Components**
- `HocuspocusProviderWebsocketComponent` — manages the shared WebSocket
- `HocuspocusRoom` — creates a per-document provider on the shared socket; StrictMode-safe
**Hooks** (must be used inside a `HocuspocusRoom`)
- `useHocuspocusProvider()` — the `HocuspocusProvider` instance
- `useHocuspocusConnectionStatus()` — `'connecting' | 'connected' | 'disconnected'`
- `useHocuspocusSyncStatus()` — `'synced' | 'syncing'`
- `useHocuspocusAwareness()` — array of connected users' awareness state
- `useHocuspocusEvent(name, handler)` — subscribe to any provider event
## Documentation
Full components, hooks, and patterns reference: [tiptap.dev/docs/hocuspocus/provider/react](https://tiptap.dev/docs/hocuspocus/provider/react).
## License
MIT — see [LICENSE.md](https://github.com/ueberdosis/hocuspocus/blob/main/LICENSE.md).

View File

@@ -0,0 +1,47 @@
{
"name": "@hocuspocus/provider-react",
"version": "4.7.0",
"description": "React bindings for Hocuspocus provider",
"homepage": "https://hocuspocus.dev",
"keywords": [
"hocuspocus",
"websocket",
"provider",
"react",
"yjs"
],
"license": "MIT",
"type": "module",
"main": "dist/hocuspocus-provider-react.cjs",
"module": "dist/hocuspocus-provider-react.esm.js",
"types": "dist/index.d.ts",
"exports": {
"source": {
"import": "./src/index.ts"
},
"default": {
"import": "./dist/hocuspocus-provider-react.esm.js",
"require": "./dist/hocuspocus-provider-react.cjs",
"types": "./dist/index.d.ts"
}
},
"files": [
"src",
"dist"
],
"peerDependencies": {
"@hocuspocus/provider": "workspace:^",
"react": "^18.0.0 || ^19.0.0",
"yjs": "^13.6.8"
},
"devDependencies": {
"@types/react": "^19.2.14",
"react": "^19.2.4"
},
"repository": {
"url": "https://github.com/ueberdosis/hocuspocus"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1,78 @@
"use client";
import { HocuspocusProviderWebsocket } from "@hocuspocus/provider";
import { useEffect, useMemo, useRef } from "react";
import { HocuspocusContext } from "./context.ts";
import type { HocuspocusProviderWebsocketComponentProps } from "./types.ts";
/**
* HocuspocusProviderWebsocketComponent manages the WebSocket connection that is shared across all rooms.
*
* This component creates a single WebSocket connection that can be used by multiple
* HocuspocusRoom components, preventing connection overhead when switching between documents.
*
* @example
* ```tsx
* <HocuspocusProviderWebsocketComponent url="ws://localhost:1234">
* <HocuspocusRoom name="document-1">
* <Editor />
* </HocuspocusRoom>
* </HocuspocusProviderWebsocketComponent>
* ```
*/
export function HocuspocusProviderWebsocketComponent({
children,
url,
websocketProvider: externalWebsocketProvider,
}: HocuspocusProviderWebsocketComponentProps) {
const websocketRef = useRef<HocuspocusProviderWebsocket | null>(null);
const destroyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Create WebSocket provider once on mount.
// Safe in StrictMode: useRef persists across double-renders, so the instance
// is created only once. The deferred destruction in the effect below ensures
// the second mount cancels the pending destroy before it fires.
if (!websocketRef.current && !externalWebsocketProvider) {
websocketRef.current = new HocuspocusProviderWebsocket({
url: url!,
});
}
const websocketProvider =
externalWebsocketProvider ??
(websocketRef.current as HocuspocusProviderWebsocket);
// Cleanup on unmount with deferred destruction to handle StrictMode double-mount
useEffect(() => {
if (destroyTimeoutRef.current) {
clearTimeout(destroyTimeoutRef.current);
destroyTimeoutRef.current = null;
}
return () => {
// Only destroy if we created the websocket (not externally provided)
if (!externalWebsocketProvider) {
destroyTimeoutRef.current = setTimeout(() => {
if (websocketRef.current) {
websocketRef.current.destroy();
websocketRef.current = null;
}
}, 0);
}
};
}, [externalWebsocketProvider]);
const contextValue = useMemo(
() => ({
websocketProvider,
}),
[websocketProvider],
);
return (
<HocuspocusContext.Provider value={contextValue}>
{children}
</HocuspocusContext.Provider>
);
}

View File

@@ -0,0 +1,168 @@
"use client";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { HocuspocusContext, HocuspocusRoomContext } from "./context.ts";
import type { HocuspocusProviderEvents, HocuspocusRoomProps } from "./types.ts";
/**
* Mapping from HocuspocusRoom `on*` prop names to provider event names.
*/
const EVENT_PROP_MAP: Record<string, keyof HocuspocusProviderEvents> = {
onOpen: "open",
onConnect: "connect",
onClose: "close",
onDisconnect: "disconnect",
onStatus: "status",
onSynced: "synced",
onUnsyncedChanges: "unsyncedChanges",
onMessage: "message",
onOutgoingMessage: "outgoingMessage",
onStateless: "stateless",
onAuthenticated: "authenticated",
onAuthenticationFailed: "authenticationFailed",
onAwarenessUpdate: "awarenessUpdate",
onAwarenessChange: "awarenessChange",
onDestroy: "destroy",
};
/**
* HocuspocusRoom manages the connection to a specific document.
*
* It uses the shared WebSocket from HocuspocusProviderWebsocketComponent and creates a document-specific
* provider that connects on mount and disconnects on unmount.
*
* This component handles React's StrictMode gracefully by using deferred destruction,
* preventing unnecessary reconnections during development double-mounts.
*
* @example
* ```tsx
* <HocuspocusProviderWebsocketComponent url="ws://localhost:1234">
* <HocuspocusRoom
* name="document-1"
* onAuthenticationFailed={(data) => console.error(data.reason)}
* >
* <Editor />
* </HocuspocusRoom>
* </HocuspocusProviderWebsocketComponent>
* ```
*/
export function HocuspocusRoom({
children,
name,
document,
token,
sessionAwareness,
flushDelay,
...eventHandlers
}: HocuspocusRoomProps) {
const hocuspocusContext = useContext(HocuspocusContext);
if (!hocuspocusContext) {
throw new Error(
"HocuspocusRoom must be used within a HocuspocusProviderWebsocketComponent",
);
}
const { websocketProvider } = hocuspocusContext;
const [provider, setProvider] = useState(
() =>
new HocuspocusProvider({
name,
websocketProvider,
document,
token,
sessionAwareness,
flushDelay,
}),
);
const destroyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Recreate provider when name, document, or token changes.
// Only compare `document`/`token` when the caller actually provided them —
// otherwise the provider's constructor-initialized defaults (auto-created
// Y.Doc, null token) will never match the undefined prop, causing a destroy
// on every render (and in StrictMode, a stray CloseMessage for the current
// doc once the provider is already attached).
// biome-ignore lint/correctness/useExhaustiveDependencies: provider.configuration holds the previous values we compare against — not a reactive dependency
useEffect(() => {
const shouldRecreate =
provider.configuration.name !== name ||
(document !== undefined &&
provider.configuration.document !== document) ||
(token !== undefined && provider.configuration.token !== token) ||
provider.configuration.websocketProvider !== websocketProvider;
if (shouldRecreate) {
provider.destroy();
setProvider(
new HocuspocusProvider({
name,
websocketProvider,
document,
token,
sessionAwareness,
flushDelay,
}),
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [name, document, token, websocketProvider]);
// Attach/detach lifecycle with deferred destruction for StrictMode
useEffect(() => {
if (destroyTimeoutRef.current) {
clearTimeout(destroyTimeoutRef.current);
destroyTimeoutRef.current = null;
}
provider.attach();
return () => {
destroyTimeoutRef.current = setTimeout(() => {
provider.destroy();
}, 0);
};
}, [provider]);
// Wire up on* event handler props with stable refs
const handlersRef = useRef(eventHandlers);
handlersRef.current = eventHandlers;
useEffect(() => {
const cleanups: (() => void)[] = [];
for (const [propName, eventName] of Object.entries(EVENT_PROP_MAP)) {
const listener = (...args: unknown[]) => {
const handler = handlersRef.current[
propName as keyof typeof handlersRef.current
] as ((...a: unknown[]) => void) | undefined;
handler?.(...args);
};
provider.on(eventName, listener);
cleanups.push(() => provider.off(eventName, listener));
}
return () => {
for (const cleanup of cleanups) {
cleanup();
}
};
}, [provider]);
const contextValue = useMemo(
() => ({
provider,
}),
[provider],
);
return (
<HocuspocusRoomContext.Provider value={contextValue}>
{children}
</HocuspocusRoomContext.Provider>
);
}

View File

@@ -0,0 +1,18 @@
import { createContext } from "react";
import type {
HocuspocusContextValue,
HocuspocusRoomContextValue,
} from "./types.ts";
/**
* Context for the WebSocket connection shared across rooms
*/
export const HocuspocusContext =
createContext<HocuspocusContextValue | null>(null);
/**
* Context for the room/document provider
*/
export const HocuspocusRoomContext =
createContext<HocuspocusRoomContextValue | null>(null);

View File

@@ -0,0 +1,5 @@
export { useHocuspocusAwareness } from "./useHocuspocusAwareness.ts";
export { useHocuspocusConnectionStatus } from "./useHocuspocusConnectionStatus.ts";
export { useHocuspocusEvent } from "./useHocuspocusEvent.ts";
export { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
export { useHocuspocusSyncStatus } from "./useHocuspocusSyncStatus.ts";

View File

@@ -0,0 +1,89 @@
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
import type { CollabUser } from "../types.ts";
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
/**
* Subscribe to the list of connected users in the document.
*
* This hook uses the Yjs awareness protocol to track users currently
* connected to the document.
*
* @returns Array of user objects with their awareness state
*
* @example
* ```tsx
* function UserList() {
* const users = useHocuspocusAwareness()
*
* return (
* <div className="avatars">
* {users.map(user => (
* <div
* key={user.clientId}
* style={{ backgroundColor: user.color }}
* title={user.name}
* >
* {user.name?.[0]}
* </div>
* ))}
* </div>
* )
* }
* ```
*/
export function useHocuspocusAwareness(): CollabUser[] {
const provider = useHocuspocusProvider();
const versionRef = useRef(0);
const cacheRef = useRef<{ users: CollabUser[]; version: number }>({
users: [],
version: -1,
});
// Reset cache when provider changes so we don't serve stale users
// from a previous provider instance.
useEffect(() => {
versionRef.current++;
cacheRef.current = { users: [], version: -1 };
}, [provider]);
const subscribe = useCallback(
(onStoreChange: () => void) => {
const onChange = () => {
versionRef.current++;
onStoreChange();
};
provider.awareness?.on("change", onChange);
return () => {
provider.awareness?.off("change", onChange);
};
},
[provider],
);
const getSnapshot = useCallback(() => {
const awareness = provider.awareness;
if (!awareness) {
return [];
}
// Return cached value if awareness hasn't changed since last snapshot
if (cacheRef.current.version === versionRef.current) {
return cacheRef.current.users;
}
const users: CollabUser[] = [];
awareness.getStates().forEach((state, clientId) => {
users.push({
clientId,
...state,
});
});
cacheRef.current = { users, version: versionRef.current };
return users;
}, [provider]);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}

View File

@@ -0,0 +1,47 @@
import { useCallback, useSyncExternalStore } from "react";
import type { ConnectionStatus } from "../types.ts";
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
/**
* Subscribe to the connection status of the collaboration provider.
*
* This hook uses React's useSyncExternalStore for optimal integration with
* concurrent rendering features.
*
* @returns The current connection status: 'connecting', 'connected', or 'disconnected'
*
* @example
* ```tsx
* function ConnectionIndicator() {
* const status = useHocuspocusConnectionStatus()
*
* return (
* <div className={`status-${status}`}>
* {status === 'connected' ? 'Online' : status === 'connecting' ? 'Connecting...' : 'Offline'}
* </div>
* )
* }
* ```
*/
export function useHocuspocusConnectionStatus(): ConnectionStatus {
const provider = useHocuspocusProvider();
const subscribe = useCallback(
(onStoreChange: () => void) => {
provider.on("status", onStoreChange);
return () => {
provider.off("status", onStoreChange);
};
},
[provider],
);
const getSnapshot = useCallback(
(): ConnectionStatus =>
provider.configuration.websocketProvider.status as ConnectionStatus,
[provider],
);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}

View File

@@ -0,0 +1,52 @@
import { useEffect, useRef } from "react";
import type { HocuspocusProviderEvents } from "../types.ts";
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
/**
* Subscribe to events from the HocuspocusProvider for the current room.
*
* The handler is stored in a ref so the subscription stays stable even if
* the handler identity changes between renders.
*
* Must be used within a HocuspocusRoom component.
*
* @param event - The event name to subscribe to
* @param handler - Callback invoked when the event fires
*
* @example
* ```tsx
* function AuthGuard() {
* useHocuspocusEvent('authenticationFailed', (data) => {
* console.error('Auth failed:', data.reason)
* redirectToLogin()
* })
*
* useHocuspocusEvent('close', (data) => {
* console.log('Connection closed', data.event)
* })
*
* return null
* }
* ```
*/
export function useHocuspocusEvent<E extends keyof HocuspocusProviderEvents>(
event: E,
handler: HocuspocusProviderEvents[E] extends undefined
? () => void
: (data: HocuspocusProviderEvents[E]) => void,
): void {
const provider = useHocuspocusProvider();
const handlerRef = useRef(handler);
handlerRef.current = handler;
useEffect(() => {
const listener = (...args: unknown[]) => {
(handlerRef.current as (...a: unknown[]) => void)(...args);
};
provider.on(event, listener);
return () => {
provider.off(event, listener);
};
}, [provider, event]);
}

View File

@@ -0,0 +1,37 @@
import { useContext } from "react";
import { HocuspocusRoomContext } from "../context.ts";
/**
* Access the HocuspocusProvider instance for the current room.
*
* Must be used within a HocuspocusRoom component.
*
* @returns The HocuspocusProvider instance
* @throws Error if used outside of HocuspocusRoom
*
* @example
* ```tsx
* function Editor() {
* const provider = useHocuspocusProvider()
*
* const editor = useEditor({
* extensions: [
* Collaboration.configure({ document: provider.document }),
* CollaborationCursor.configure({ provider }),
* ],
* })
*
* return <EditorContent editor={editor} />
* }
* ```
*/
export function useHocuspocusProvider() {
const context = useContext(HocuspocusRoomContext);
if (!context) {
throw new Error("useHocuspocusProvider must be used within a HocuspocusRoom");
}
return context.provider;
}

View File

@@ -0,0 +1,51 @@
import { useCallback, useSyncExternalStore } from "react";
import type { SyncStatus } from "../types.ts";
import { useHocuspocusProvider } from "./useHocuspocusProvider.ts";
/**
* Subscribe to the sync status of local changes.
*
* This hook indicates whether local changes have been synced with the server.
*
* @returns The current sync status: 'synced' or 'syncing'
*
* @example
* ```tsx
* function SaveIndicator() {
* const syncStatus = useHocuspocusSyncStatus()
*
* return (
* <div>
* {syncStatus === 'syncing' ? 'Saving...' : 'All changes saved'}
* </div>
* )
* }
* ```
*/
export function useHocuspocusSyncStatus(): SyncStatus {
const provider = useHocuspocusProvider();
const subscribe = useCallback(
(onStoreChange: () => void) => {
provider.on("synced", onStoreChange);
provider.on("unsyncedChanges", onStoreChange);
return () => {
provider.off("synced", onStoreChange);
provider.off("unsyncedChanges", onStoreChange);
};
},
[provider],
);
const getSnapshot = useCallback((): SyncStatus => {
// Check if there are unsynced changes
if (provider.unsyncedChanges > 0) {
return "syncing";
}
return "synced";
}, [provider]);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}

View File

@@ -0,0 +1,29 @@
"use client";
// Contexts
export { HocuspocusContext, HocuspocusRoomContext } from "./context.ts";
// Components
export { HocuspocusProviderWebsocketComponent } from "./HocuspocusProviderWebsocketComponent.tsx";
export { HocuspocusRoom } from "./HocuspocusRoom.tsx";
// Hooks
export {
useHocuspocusAwareness,
useHocuspocusConnectionStatus,
useHocuspocusEvent,
useHocuspocusProvider,
useHocuspocusSyncStatus,
} from "./hooks/index.ts";
// Types
export type {
CollabUser,
ConnectionStatus,
HocuspocusContextValue,
HocuspocusProviderEvents,
HocuspocusProviderWebsocketComponentProps,
HocuspocusRoomContextValue,
HocuspocusRoomProps,
SyncStatus,
} from "./types.ts";

View File

@@ -0,0 +1,155 @@
import type {
HocuspocusProvider,
HocuspocusProviderWebsocket,
onAuthenticatedParameters,
onAuthenticationFailedParameters,
onAwarenessChangeParameters,
onAwarenessUpdateParameters,
onCloseParameters,
onDisconnectParameters,
onMessageParameters,
onOpenParameters,
onOutgoingMessageParameters,
onStatelessParameters,
onStatusParameters,
onSyncedParameters,
onUnsyncedChangesParameters,
} from "@hocuspocus/provider";
import type { ReactNode } from "react";
import type * as Y from "yjs";
/**
* Configuration for HocuspocusProvider component.
*
* Provide either a `url` to create a managed WebSocket connection,
* or a `websocketProvider` instance for full control — but not both.
*/
export type HocuspocusProviderWebsocketComponentProps = {
children: ReactNode;
} & (
| {
/**
* URL of your @hocuspocus/server instance
*/
url: string;
websocketProvider?: never;
}
| {
url?: never;
/**
* Provide your own websocket instance for full control
*/
websocketProvider: HocuspocusProviderWebsocket;
}
);
/**
* Map of HocuspocusProvider event names to their payload types.
* Used by `useHocuspocusEvent` and the `HocuspocusRoom` event handler props.
*/
export interface HocuspocusProviderEvents {
open: onOpenParameters;
connect: undefined;
close: onCloseParameters;
disconnect: onDisconnectParameters;
status: onStatusParameters;
synced: onSyncedParameters;
unsyncedChanges: onUnsyncedChangesParameters;
message: onMessageParameters;
outgoingMessage: onOutgoingMessageParameters;
stateless: onStatelessParameters;
authenticated: onAuthenticatedParameters;
authenticationFailed: onAuthenticationFailedParameters;
awarenessUpdate: onAwarenessUpdateParameters;
awarenessChange: onAwarenessChangeParameters;
destroy: undefined;
}
/**
* Configuration for HocuspocusRoom component
*/
export interface HocuspocusRoomProps {
children: ReactNode;
/**
* The document name (required)
*/
name: string;
/**
* Optional: bring your own Y.Doc
*/
document?: Y.Doc;
/**
* JWT token or function that returns a promise resolving to a token
*/
token?: string | (() => Promise<string>) | (() => string);
/**
* Enable session-aware multiplexing. When true, the provider embeds a unique
* sessionId in the documentName field of every message, allowing multiple
* providers with the same document name on a single WebSocket connection.
*
* Only set this to `true` when connecting to a v4 server that does
* support session awareness.
*
* Default: false
*/
sessionAwareness?: boolean;
/**
* Batch outgoing document and awareness updates over a short window (in
* milliseconds) instead of sending one message per change, to reduce the
* number of websocket messages during heavy editing. The added latency is
* capped at `flushDelay`, so keep it small (e.g. 500).
*
* Default: false (send every change immediately)
*/
flushDelay?: false | number;
// Event handlers — all optional, called when the provider emits the corresponding event.
onOpen?: (data: onOpenParameters) => void;
onConnect?: () => void;
onClose?: (data: onCloseParameters) => void;
onDisconnect?: (data: onDisconnectParameters) => void;
onStatus?: (data: onStatusParameters) => void;
onSynced?: (data: onSyncedParameters) => void;
onUnsyncedChanges?: (data: onUnsyncedChangesParameters) => void;
onMessage?: (data: onMessageParameters) => void;
onOutgoingMessage?: (data: onOutgoingMessageParameters) => void;
onStateless?: (data: onStatelessParameters) => void;
onAuthenticated?: (data: onAuthenticatedParameters) => void;
onAuthenticationFailed?: (data: onAuthenticationFailedParameters) => void;
onAwarenessUpdate?: (data: onAwarenessUpdateParameters) => void;
onAwarenessChange?: (data: onAwarenessChangeParameters) => void;
onDestroy?: () => void;
}
/**
* Context value for the WebSocket provider
*/
export interface HocuspocusContextValue {
websocketProvider: HocuspocusProviderWebsocket;
}
/**
* Context value for the room/document provider
*/
export interface HocuspocusRoomContextValue {
provider: HocuspocusProvider;
}
/**
* Connection status for the collaboration provider
*/
export type ConnectionStatus = "connecting" | "connected" | "disconnected";
/**
* Sync status indicating whether local changes are synced with server
*/
export type SyncStatus = "synced" | "syncing";
/**
* User information from awareness
*/
export interface CollabUser {
clientId: number;
[key: string]: unknown;
}

View File

@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}