UNPKG

@hocuspocus/provider

Version:

hocuspocus provider

1 lines 75.4 kB
{"version":3,"file":"hocuspocus-provider.cjs","names":["time","encoding","WsReadyStates","messageYjsSyncStep2","awarenessProtocol","Y","Awareness"],"sources":["../src/EventEmitter.ts","../src/IncomingMessage.ts","../src/types.ts","../src/OutgoingMessage.ts","../src/OutgoingMessages/CloseMessage.ts","../src/HocuspocusProviderWebsocket.ts","../src/MessageReceiver.ts","../src/MessageSender.ts","../src/version.ts","../src/OutgoingMessages/AuthenticationMessage.ts","../src/OutgoingMessages/AwarenessMessage.ts","../src/OutgoingMessages/StatelessMessage.ts","../src/OutgoingMessages/SyncStepOneMessage.ts","../src/OutgoingMessages/UpdateMessage.ts","../src/HocuspocusProvider.ts"],"sourcesContent":["export default class EventEmitter {\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tpublic callbacks: { [key: string]: Function[] } = {};\n\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tpublic on(event: string, fn: Function): this {\n\t\tif (!this.callbacks[event]) {\n\t\t\tthis.callbacks[event] = [];\n\t\t}\n\n\t\tthis.callbacks[event].push(fn);\n\n\t\treturn this;\n\t}\n\n\tprotected emit(event: string, ...args: any): this {\n\t\tconst callbacks = this.callbacks[event];\n\n\t\tif (callbacks) {\n\t\t\tcallbacks.forEach((callback) => callback.apply(this, args));\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tpublic off(event: string, fn?: Function): this {\n\t\tconst callbacks = this.callbacks[event];\n\n\t\tif (callbacks) {\n\t\t\tif (fn) {\n\t\t\t\tthis.callbacks[event] = callbacks.filter((callback) => callback !== fn);\n\t\t\t} else {\n\t\t\t\tdelete this.callbacks[event];\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tremoveAllListeners(): void {\n\t\tthis.callbacks = {};\n\t}\n}\n","import type { Decoder } from \"lib0/decoding\";\nimport {\n\tcreateDecoder,\n\tpeekVarString,\n\treadVarUint,\n\treadVarUint8Array,\n\treadVarString,\n} from \"lib0/decoding\";\nimport type { Encoder } from \"lib0/encoding\";\nimport {\n\tcreateEncoder,\n\twriteVarUint,\n\twriteVarUint8Array,\n\twriteVarString,\n\tlength,\n} from \"lib0/encoding\";\nimport type { MessageType } from \"./types.ts\";\n\nexport class IncomingMessage {\n\tdata: any;\n\n\tencoder: Encoder;\n\n\tdecoder: Decoder;\n\n\tconstructor(data: any) {\n\t\tthis.data = data;\n\t\tthis.encoder = createEncoder();\n\t\tthis.decoder = createDecoder(new Uint8Array(this.data));\n\t}\n\n\tpeekVarString(): string {\n\t\treturn peekVarString(this.decoder);\n\t}\n\n\treadVarUint(): MessageType {\n\t\treturn readVarUint(this.decoder);\n\t}\n\n\treadVarString(): string {\n\t\treturn readVarString(this.decoder);\n\t}\n\n\treadVarUint8Array() {\n\t\treturn readVarUint8Array(this.decoder);\n\t}\n\n\twriteVarUint(type: MessageType) {\n\t\treturn writeVarUint(this.encoder, type);\n\t}\n\n\twriteVarString(string: string) {\n\t\treturn writeVarString(this.encoder, string);\n\t}\n\n\twriteVarUint8Array(data: Uint8Array) {\n\t\treturn writeVarUint8Array(this.encoder, data);\n\t}\n\n\tlength() {\n\t\treturn length(this.encoder);\n\t}\n}\n","import type { Encoder } from \"lib0/encoding\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport type * as Y from \"yjs\";\nimport type { CloseEvent } from \"@hocuspocus/common\";\nimport type { IncomingMessage } from \"./IncomingMessage.ts\";\nimport type { OutgoingMessage } from \"./OutgoingMessage.ts\";\nimport type { AuthenticationMessage } from \"./OutgoingMessages/AuthenticationMessage.ts\";\nimport type { AwarenessMessage } from \"./OutgoingMessages/AwarenessMessage.ts\";\nimport type { QueryAwarenessMessage } from \"./OutgoingMessages/QueryAwarenessMessage.ts\";\nimport type { SyncStepOneMessage } from \"./OutgoingMessages/SyncStepOneMessage.ts\";\nimport type { SyncStepTwoMessage } from \"./OutgoingMessages/SyncStepTwoMessage.ts\";\nimport type { UpdateMessage } from \"./OutgoingMessages/UpdateMessage.ts\";\n\nexport enum MessageType {\n\tSync = 0,\n\tAwareness = 1,\n\tAuth = 2,\n\tQueryAwareness = 3,\n\tStateless = 5,\n\tCLOSE = 7,\n\tSyncStatus = 8,\n\tPing = 9,\n\tPong = 10,\n}\n\nexport enum WebSocketStatus {\n\tConnecting = \"connecting\",\n\tConnected = \"connected\",\n\tDisconnected = \"disconnected\",\n}\n\nexport type AuthorizedScope = \"read-write\" | \"readonly\";\n\nexport interface OutgoingMessageInterface {\n\tencoder: Encoder;\n\ttype?: MessageType;\n}\n\nexport interface OutgoingMessageArguments {\n\tdocumentName: string;\n\ttoken: string;\n\tdocument: Y.Doc;\n\tawareness: Awareness;\n\tclients: number[];\n\tstates: Map<number, { [key: string]: any }>;\n\tupdate: any;\n\tpayload: string;\n\tencoder: Encoder;\n}\n\nexport interface Constructable<T> {\n\tnew (...args: any): T;\n}\n\nexport type ConstructableOutgoingMessage =\n\t| Constructable<AuthenticationMessage>\n\t| Constructable<AwarenessMessage>\n\t| Constructable<QueryAwarenessMessage>\n\t| Constructable<SyncStepOneMessage>\n\t| Constructable<SyncStepTwoMessage>\n\t| Constructable<UpdateMessage>;\n\nexport type onAuthenticationFailedParameters = {\n\treason: string;\n};\n\nexport type onAuthenticatedParameters = {\n\tscope: AuthorizedScope;\n};\n\nexport type onOpenParameters = {\n\tevent: Event;\n};\n\nexport type onMessageParameters = {\n\tevent: MessageEvent;\n\tmessage: IncomingMessage;\n};\n\nexport type onOutgoingMessageParameters = {\n\tmessage: OutgoingMessage;\n};\n\nexport type onStatusParameters = {\n\tstatus: WebSocketStatus;\n};\n\nexport type onSyncedParameters = {\n\tstate: boolean;\n};\n\nexport type onUnsyncedChangesParameters = {\n\tnumber: number;\n};\n\nexport type onDisconnectParameters = {\n\tevent: CloseEvent;\n};\n\nexport type onCloseParameters = {\n\tevent: CloseEvent;\n};\n\nexport type onAwarenessUpdateParameters = {\n\tstates: StatesArray;\n};\n\nexport type onAwarenessChangeParameters = {\n\tstates: StatesArray;\n};\n\nexport type onStatelessParameters = {\n\tpayload: string;\n};\n\nexport type StatesArray = { clientId: number; [key: string | number]: any }[];\n","import type { Encoder } from \"lib0/encoding\";\nimport { createEncoder, toUint8Array } from \"lib0/encoding\";\nimport type {\n\tMessageType,\n\tOutgoingMessageArguments,\n\tOutgoingMessageInterface,\n} from \"./types.ts\";\n\nexport class OutgoingMessage implements OutgoingMessageInterface {\n\tencoder: Encoder;\n\n\ttype?: MessageType;\n\n\tconstructor() {\n\t\tthis.encoder = createEncoder();\n\t}\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\treturn args.encoder;\n\t}\n\n\ttoUint8Array() {\n\t\treturn toUint8Array(this.encoder);\n\t}\n}\n","import * as encoding from \"lib0/encoding\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\n\nexport class CloseMessage extends OutgoingMessage {\n\ttype = MessageType.CLOSE;\n\n\tdescription = \"Ask the server to close the connection\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\tencoding.writeVarString(this.encoder, args.documentName!);\n\t\tencoding.writeVarUint(this.encoder, this.type);\n\n\t\treturn this.encoder;\n\t}\n}\n","import { WsReadyStates } from \"@hocuspocus/common\";\nimport { retry } from \"@lifeomic/attempt\";\nimport { createDecoder, readVarString, readVarUint } from \"lib0/decoding\";\nimport * as encoding from \"lib0/encoding\";\nimport * as time from \"lib0/time\";\nimport EventEmitter from \"./EventEmitter.ts\";\nimport type { HocuspocusProvider } from \"./HocuspocusProvider.ts\";\nimport { IncomingMessage } from \"./IncomingMessage.ts\";\nimport { CloseMessage } from \"./OutgoingMessages/CloseMessage.ts\";\nimport {\n\tMessageType,\n\ttype onAwarenessChangeParameters,\n\ttype onAwarenessUpdateParameters,\n\ttype onCloseParameters,\n\ttype onDisconnectParameters,\n\ttype onMessageParameters,\n\ttype onOpenParameters,\n\ttype onOutgoingMessageParameters,\n\ttype onStatusParameters,\n\tWebSocketStatus,\n} from \"./types.ts\";\n\nexport type HocuspocusWebSocket = WebSocket & { identifier: string };\nexport type HocusPocusWebSocket = HocuspocusWebSocket;\n\nexport type HocuspocusProviderWebsocketConfiguration = Required<\n\tPick<CompleteHocuspocusProviderWebsocketConfiguration, \"url\">\n> &\n\tPartial<CompleteHocuspocusProviderWebsocketConfiguration>;\n\nexport interface CompleteHocuspocusProviderWebsocketConfiguration {\n\t/**\n\t * Whether to connect automatically when creating the provider instance. Default=true\n\t */\n\tautoConnect: boolean;\n\n\t/**\n\t * URL of your @hocuspocus/server instance\n\t */\n\turl: string;\n\n\t/**\n\t * By default, trailing slashes are removed from the URL. Set this to true\n\t * to preserve trailing slashes if your server configuration requires them.\n\t */\n\tpreserveTrailingSlash: boolean;\n\n\t/**\n\t * An optional WebSocket polyfill, for example for Node.js\n\t */\n\tWebSocketPolyfill: any;\n\n\t/**\n\t * Disconnect when no message is received for the defined amount of milliseconds.\n\t */\n\tmessageReconnectTimeout: number;\n\t/**\n\t * The delay between each attempt in milliseconds. You can provide a factor to have the delay grow exponentially.\n\t */\n\tdelay: number;\n\t/**\n\t * The initialDelay is the amount of time to wait before making the first attempt. This option should typically be 0 since you typically want the first attempt to happen immediately.\n\t */\n\tinitialDelay: number;\n\t/**\n\t * The factor option is used to grow the delay exponentially.\n\t */\n\tfactor: number;\n\t/**\n\t * The maximum number of attempts or 0 if there is no limit on number of attempts.\n\t */\n\tmaxAttempts: number;\n\t/**\n\t * minDelay is used to set a lower bound of delay when jitter is enabled. This property has no effect if jitter is disabled.\n\t */\n\tminDelay: number;\n\t/**\n\t * The maxDelay option is used to set an upper bound for the delay when factor is enabled. A value of 0 can be provided if there should be no upper bound when calculating delay.\n\t */\n\tmaxDelay: number;\n\t/**\n\t * If jitter is true then the calculated delay will be a random integer value between minDelay and the calculated delay for the current iteration.\n\t */\n\tjitter: boolean;\n\t/**\n\t * A timeout in milliseconds. If timeout is non-zero then a timer is set using setTimeout. If the timeout is triggered then future attempts will be aborted.\n\t */\n\ttimeout: number;\n\thandleTimeout: (() => Promise<unknown>) | null;\n\tonOpen: (data: onOpenParameters) => void;\n\tonConnect: () => void;\n\tonMessage: (data: onMessageParameters) => void;\n\tonOutgoingMessage: (data: onOutgoingMessageParameters) => void;\n\tonStatus: (data: onStatusParameters) => void;\n\tonDisconnect: (data: onDisconnectParameters) => void;\n\tonClose: (data: onCloseParameters) => void;\n\tonDestroy: () => void;\n\tonAwarenessUpdate: (data: onAwarenessUpdateParameters) => void;\n\tonAwarenessChange: (data: onAwarenessChangeParameters) => void;\n\n\t/**\n\t * Map of attached providers keyed by documentName.\n\t */\n\tproviderMap: Map<string, HocuspocusProvider>;\n}\n\nexport class HocuspocusProviderWebsocket extends EventEmitter {\n\tprivate static readonly DEDUPLICATABLE_TYPES = new Set([\n\t\tMessageType.Awareness,\n\t\tMessageType.QueryAwareness,\n\t]);\n\n\tprivate messageQueue: any[] = [];\n\n\tpublic configuration: CompleteHocuspocusProviderWebsocketConfiguration = {\n\t\turl: \"\",\n\t\tautoConnect: true,\n\t\tpreserveTrailingSlash: false,\n\t\t// @ts-expect-error\n\t\tdocument: undefined,\n\t\tWebSocketPolyfill: undefined,\n\t\t// TODO: this should depend on awareness.outdatedTime\n\t\tmessageReconnectTimeout: 30000,\n\t\t// 1 second\n\t\tdelay: 1000,\n\t\t// instant\n\t\tinitialDelay: 0,\n\t\t// double the delay each time\n\t\tfactor: 2,\n\t\t// unlimited retries\n\t\tmaxAttempts: 0,\n\t\t// wait at least 1 second\n\t\tminDelay: 1000,\n\t\t// at least every 30 seconds\n\t\tmaxDelay: 30000,\n\t\t// randomize\n\t\tjitter: true,\n\t\t// retry forever\n\t\ttimeout: 0,\n\t\tonOpen: () => null,\n\t\tonConnect: () => null,\n\t\tonMessage: () => null,\n\t\tonOutgoingMessage: () => null,\n\t\tonStatus: () => null,\n\t\tonDisconnect: () => null,\n\t\tonClose: () => null,\n\t\tonDestroy: () => null,\n\t\tonAwarenessUpdate: () => null,\n\t\tonAwarenessChange: () => null,\n\t\thandleTimeout: null,\n\t\tproviderMap: new Map(),\n\t};\n\n\twebSocket: HocusPocusWebSocket | null = null;\n\n\twebSocketHandlers: { [key: string]: any } = {};\n\n\tshouldConnect = true;\n\n\tstatus = WebSocketStatus.Disconnected;\n\n\tlastMessageReceived = 0;\n\n\tidentifier = 0;\n\n\tintervals: any = {\n\t\tconnectionChecker: null,\n\t};\n\n\tconnectionAttempt: {\n\t\tresolve: (value?: any) => void;\n\t\treject: (reason?: any) => void;\n\t} | null = null;\n\n\tconstructor(configuration: HocuspocusProviderWebsocketConfiguration) {\n\t\tsuper();\n\t\tthis.setConfiguration(configuration);\n\n\t\tthis.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill\n\t\t\t? configuration.WebSocketPolyfill\n\t\t\t: WebSocket;\n\n\t\tthis.on(\"open\", this.configuration.onOpen);\n\t\tthis.on(\"open\", this.onOpen.bind(this));\n\t\tthis.on(\"connect\", this.configuration.onConnect);\n\t\tthis.on(\"message\", this.configuration.onMessage);\n\t\tthis.on(\"outgoingMessage\", this.configuration.onOutgoingMessage);\n\t\tthis.on(\"status\", this.configuration.onStatus);\n\t\tthis.on(\"disconnect\", this.configuration.onDisconnect);\n\t\tthis.on(\"close\", this.configuration.onClose);\n\t\tthis.on(\"destroy\", this.configuration.onDestroy);\n\t\tthis.on(\"awarenessUpdate\", this.configuration.onAwarenessUpdate);\n\t\tthis.on(\"awarenessChange\", this.configuration.onAwarenessChange);\n\n\t\tthis.on(\"close\", this.onClose.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\n\t\tthis.intervals.connectionChecker = setInterval(\n\t\t\tthis.checkConnection.bind(this),\n\t\t\tthis.configuration.messageReconnectTimeout / 10,\n\t\t);\n\n\t\tif (this.shouldConnect) {\n\t\t\tthis.connect();\n\t\t}\n\t}\n\n\treceivedOnOpenPayload?: Event | undefined = undefined;\n\n\tasync onOpen(event: Event) {\n\t\tthis.status = WebSocketStatus.Connected;\n\t\tthis.emit(\"status\", { status: WebSocketStatus.Connected });\n\n\t\tthis.cancelWebsocketRetry = undefined;\n\t\tthis.receivedOnOpenPayload = event;\n\t}\n\n\tattach(provider: HocuspocusProvider) {\n\t\tconst key = provider.effectiveName;\n\t\tconst existing = this.configuration.providerMap.get(key);\n\n\t\tif (existing && existing !== provider) {\n\t\t\t// Allow replacing a provider that hasn't authenticated (e.g., after auth failure retry)\n\t\t\tif (existing.isAuthenticated) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cannot attach two providers with the same effective name \"${key}\". ` +\n\t\t\t\t\t\t\"Use sessionAwareness: true to multiplex providers with the same document name.\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.configuration.providerMap.set(key, provider);\n\n\t\tif (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {\n\t\t\tthis.connect();\n\t\t}\n\n\t\tif (\n\t\t\tthis.receivedOnOpenPayload &&\n\t\t\tthis.status === WebSocketStatus.Connected\n\t\t) {\n\t\t\tprovider.onOpen(this.receivedOnOpenPayload);\n\t\t}\n\t}\n\n\tdetach(provider: HocuspocusProvider) {\n\t\tconst key = provider.effectiveName;\n\t\tif (this.configuration.providerMap.has(key)) {\n\t\t\tprovider.send(CloseMessage, {\n\t\t\t\tdocumentName: key,\n\t\t\t});\n\t\t\tthis.configuration.providerMap.delete(key);\n\t\t}\n\t}\n\n\tpublic setConfiguration(\n\t\tconfiguration: Partial<HocuspocusProviderWebsocketConfiguration> = {},\n\t): void {\n\t\tthis.configuration = { ...this.configuration, ...configuration };\n\n\t\tif (!this.configuration.autoConnect) {\n\t\t\tthis.shouldConnect = false;\n\t\t}\n\t}\n\n\tcancelWebsocketRetry?: () => void;\n\n\tasync connect() {\n\t\tif (this.status === WebSocketStatus.Connected) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Always cancel any previously initiated connection retryer instances\n\t\tif (this.cancelWebsocketRetry) {\n\t\t\tthis.cancelWebsocketRetry();\n\t\t\tthis.cancelWebsocketRetry = undefined;\n\t\t}\n\n\t\tthis.receivedOnOpenPayload = undefined;\n\t\tthis.shouldConnect = true;\n\n\t\tconst abortableRetry = () => {\n\t\t\tlet cancelAttempt = false;\n\n\t\t\tconst retryPromise = retry(this.createWebSocketConnection.bind(this), {\n\t\t\t\tdelay: this.configuration.delay,\n\t\t\t\tinitialDelay: this.configuration.initialDelay,\n\t\t\t\tfactor: this.configuration.factor,\n\t\t\t\tmaxAttempts: this.configuration.maxAttempts,\n\t\t\t\tminDelay: this.configuration.minDelay,\n\t\t\t\tmaxDelay: this.configuration.maxDelay,\n\t\t\t\tjitter: this.configuration.jitter,\n\t\t\t\ttimeout: this.configuration.timeout,\n\t\t\t\thandleTimeout: this.configuration.handleTimeout,\n\t\t\t\tbeforeAttempt: (context) => {\n\t\t\t\t\tif (!this.shouldConnect || cancelAttempt) {\n\t\t\t\t\t\tcontext.abort();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}).catch((error: any) => {\n\t\t\t\t// If we aborted the connection attempt then don’t throw an error\n\t\t\t\t// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136\n\t\t\t\tif (error && error.code !== \"ATTEMPT_ABORTED\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tretryPromise,\n\t\t\t\tcancelFunc: () => {\n\t\t\t\t\tcancelAttempt = true;\n\t\t\t\t},\n\t\t\t};\n\t\t};\n\n\t\tconst { retryPromise, cancelFunc } = abortableRetry();\n\t\tthis.cancelWebsocketRetry = cancelFunc;\n\n\t\treturn retryPromise;\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tattachWebSocketListeners(ws: HocusPocusWebSocket, reject: Function) {\n\t\tconst { identifier } = ws;\n\t\tconst onMessageHandler = (payload: any) => this.emit(\"message\", payload);\n\t\tconst onCloseHandler = (payload: any) =>\n\t\t\tthis.emit(\"close\", { event: payload });\n\t\tconst onOpenHandler = (payload: any) => this.emit(\"open\", payload);\n\t\tconst onErrorHandler = (err: any) => {\n\t\t\treject(err);\n\t\t};\n\n\t\tthis.webSocketHandlers[identifier] = {\n\t\t\tmessage: onMessageHandler,\n\t\t\tclose: onCloseHandler,\n\t\t\topen: onOpenHandler,\n\t\t\terror: onErrorHandler,\n\t\t};\n\n\t\tconst handlers = this.webSocketHandlers[ws.identifier];\n\n\t\tObject.keys(handlers).forEach((name) => {\n\t\t\tws.addEventListener(name, handlers[name]);\n\t\t});\n\t}\n\n\tcleanupWebSocket() {\n\t\tif (!this.webSocket) {\n\t\t\treturn;\n\t\t}\n\t\tconst { identifier } = this.webSocket;\n\t\tconst handlers = this.webSocketHandlers[identifier];\n\n\t\tObject.keys(handlers).forEach((name) => {\n\t\t\tthis.webSocket?.removeEventListener(name, handlers[name]);\n\t\t\tdelete this.webSocketHandlers[identifier];\n\t\t});\n\t\tthis.webSocket.close();\n\t\tthis.webSocket = null;\n\t}\n\n\tcreateWebSocketConnection() {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (this.webSocket) {\n\t\t\t\tthis.messageQueue = [];\n\t\t\t\tthis.cleanupWebSocket();\n\t\t\t}\n\t\t\tthis.lastMessageReceived = 0;\n\t\t\tthis.identifier += 1;\n\n\t\t\t// Init the WebSocket connection\n\t\t\tconst ws = new this.configuration.WebSocketPolyfill(this.url);\n\t\t\tws.binaryType = \"arraybuffer\";\n\t\t\tws.identifier = this.identifier;\n\n\t\t\tthis.attachWebSocketListeners(ws, reject);\n\n\t\t\tthis.webSocket = ws;\n\n\t\t\t// Reset the status\n\t\t\tthis.status = WebSocketStatus.Connecting;\n\t\t\tthis.emit(\"status\", { status: WebSocketStatus.Connecting });\n\n\t\t\t// Store resolve/reject for later use\n\t\t\tthis.connectionAttempt = {\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t};\n\t\t});\n\t}\n\n\tonMessage(event: MessageEvent) {\n\t\tthis.resolveConnectionAttempt();\n\n\t\tthis.lastMessageReceived = time.getUnixTime();\n\n\t\tconst data = new Uint8Array(event.data as ArrayBuffer);\n\n\t\t// Check for connection-level Ping message (no document name prefix)\n\t\t// Ping messages are sent as just the message type byte (length 1)\n\t\t// We check length to avoid confusing with regular messages that happen to have\n\t\t// a document name length of 9 as the first byte\n\t\tif (data.length === 1 && data[0] === MessageType.Ping) {\n\t\t\tthis.sendPong();\n\t\t\treturn;\n\t\t}\n\n\t\tconst message = new IncomingMessage(data);\n\t\tconst rawKey = message.peekVarString();\n\n\t\tconst provider = this.configuration.providerMap.get(rawKey);\n\t\tprovider?.onMessage(event);\n\t}\n\n\t/**\n\t * Send application-level Pong response to server Ping\n\t */\n\tprivate sendPong() {\n\t\tconst encoder = encoding.createEncoder();\n\t\tencoding.writeVarUint(encoder, MessageType.Pong);\n\t\tthis.send(encoding.toUint8Array(encoder));\n\t}\n\n\tresolveConnectionAttempt() {\n\t\tif (this.connectionAttempt) {\n\t\t\tthis.connectionAttempt.resolve();\n\t\t\tthis.connectionAttempt = null;\n\n\t\t\tthis.status = WebSocketStatus.Connected;\n\t\t\tthis.emit(\"status\", { status: WebSocketStatus.Connected });\n\t\t\tthis.emit(\"connect\");\n\t\t\tthis.messageQueue.forEach((message) => this.send(message));\n\t\t\tthis.messageQueue = [];\n\t\t}\n\t}\n\n\tstopConnectionAttempt() {\n\t\tthis.connectionAttempt = null;\n\t}\n\n\trejectConnectionAttempt() {\n\t\tthis.connectionAttempt?.reject();\n\t\tthis.connectionAttempt = null;\n\t}\n\n\tcloseTries = 0;\n\n\tcheckConnection() {\n\t\t// Don’t check the connection when it’s not even established\n\t\tif (this.status !== WebSocketStatus.Connected) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Don’t close the connection while waiting for the first message\n\t\tif (!this.lastMessageReceived) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Don’t close the connection when a message was received recently\n\t\tif (\n\t\t\tthis.configuration.messageReconnectTimeout >=\n\t\t\ttime.getUnixTime() - this.lastMessageReceived\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\t// No message received in a long time, not even your own\n\t\t// Awareness updates, which are updated every 15 seconds\n\t\t// if awareness is enabled.\n\t\tthis.closeTries += 1;\n\t\t// https://bugs.webkit.org/show_bug.cgi?id=247943\n\t\tif (this.closeTries > 2) {\n\t\t\tthis.onClose({\n\t\t\t\tevent: {\n\t\t\t\t\tcode: 4408,\n\t\t\t\t\treason: \"forced\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.closeTries = 0;\n\t\t} else {\n\t\t\tthis.webSocket?.close();\n\t\t\tthis.messageQueue = [];\n\t\t}\n\t}\n\n\tget serverUrl() {\n\t\tif (this.configuration.preserveTrailingSlash) {\n\t\t\treturn this.configuration.url;\n\t\t}\n\n\t\t// By default, ensure that the URL never ends with /\n\t\tlet url = this.configuration.url;\n\t\twhile (url[url.length - 1] === \"/\") {\n\t\t\turl = url.slice(0, url.length - 1);\n\t\t}\n\n\t\treturn url;\n\t}\n\n\tget url() {\n\t\treturn this.serverUrl;\n\t}\n\n\tdisconnect() {\n\t\tthis.shouldConnect = false;\n\n\t\tif (this.webSocket === null) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.webSocket.close();\n\t\t\tthis.messageQueue = [];\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\t}\n\n\tprivate parseQueuedMessage(\n\t\tmessage: Uint8Array,\n\t): { documentName: string; messageType: number } | null {\n\t\ttry {\n\t\t\tconst decoder = createDecoder(message);\n\t\t\tconst documentName = readVarString(decoder);\n\t\t\tconst messageType = readVarUint(decoder);\n\t\t\treturn { documentName, messageType };\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate addToQueue(message: any) {\n\t\tif (message instanceof Uint8Array) {\n\t\t\tconst parsed = this.parseQueuedMessage(message);\n\t\t\tif (\n\t\t\t\tparsed &&\n\t\t\t\tHocuspocusProviderWebsocket.DEDUPLICATABLE_TYPES.has(parsed.messageType)\n\t\t\t) {\n\t\t\t\tthis.messageQueue = this.messageQueue.filter((queued) => {\n\t\t\t\t\tif (!(queued instanceof Uint8Array)) return true;\n\t\t\t\t\tconst queuedParsed = this.parseQueuedMessage(queued);\n\t\t\t\t\tif (!queuedParsed) return true;\n\t\t\t\t\treturn !(\n\t\t\t\t\t\tqueuedParsed.documentName === parsed.documentName &&\n\t\t\t\t\t\tqueuedParsed.messageType === parsed.messageType\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.messageQueue.push(message);\n\t}\n\n\tsend(message: any) {\n\t\tif (this.webSocket?.readyState === WsReadyStates.Open) {\n\t\t\tthis.webSocket.send(message);\n\t\t} else {\n\t\t\tthis.addToQueue(message);\n\t\t}\n\t}\n\n\tonClose({ event }: onCloseParameters) {\n\t\tthis.closeTries = 0;\n\t\tthis.cleanupWebSocket();\n\n\t\tif (this.connectionAttempt) {\n\t\t\t// That connection attempt failed.\n\t\t\tthis.rejectConnectionAttempt();\n\t\t}\n\n\t\t// Let’s update the connection status.\n\t\tthis.status = WebSocketStatus.Disconnected;\n\t\tthis.emit(\"status\", { status: WebSocketStatus.Disconnected });\n\t\tthis.emit(\"disconnect\", { event });\n\n\t\t// trigger connect if no retry is running and we want to have a connection\n\t\tif (!this.cancelWebsocketRetry && this.shouldConnect) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tthis.connect();\n\t\t\t}, this.configuration.delay);\n\t\t}\n\t}\n\n\tdestroy() {\n\t\tthis.emit(\"destroy\");\n\n\t\tclearInterval(this.intervals.connectionChecker);\n\n\t\t// If there is still a connection attempt outstanding then we should stop\n\t\t// it before calling disconnect, otherwise it will be rejected in the onClose\n\t\t// handler and trigger a retry\n\t\tthis.stopConnectionAttempt();\n\n\t\tthis.disconnect();\n\n\t\tthis.removeAllListeners();\n\n\t\tthis.cleanupWebSocket();\n\t}\n}\n","import { type CloseEvent, readAuthMessage } from \"@hocuspocus/common\";\nimport { readVarInt, readVarString } from \"lib0/decoding\";\nimport * as awarenessProtocol from \"y-protocols/awareness\";\nimport { messageYjsSyncStep2, readSyncMessage } from \"y-protocols/sync\";\nimport type { HocuspocusProvider } from \"./HocuspocusProvider.ts\";\nimport type { IncomingMessage } from \"./IncomingMessage.ts\";\nimport { OutgoingMessage } from \"./OutgoingMessage.ts\";\nimport { MessageType } from \"./types.ts\";\n\nexport class MessageReceiver {\n\tmessage: IncomingMessage;\n\n\tconstructor(message: IncomingMessage) {\n\t\tthis.message = message;\n\t}\n\n\tpublic apply(provider: HocuspocusProvider, emitSynced: boolean) {\n\t\tconst { message } = this;\n\t\tconst type = message.readVarUint();\n\n\t\tconst emptyMessageLength = message.length();\n\n\t\tswitch (type) {\n\t\t\tcase MessageType.Sync:\n\t\t\t\tthis.applySyncMessage(provider, emitSynced);\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.Awareness:\n\t\t\t\tthis.applyAwarenessMessage(provider);\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.Auth:\n\t\t\t\tthis.applyAuthMessage(provider);\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.QueryAwareness:\n\t\t\t\tthis.applyQueryAwarenessMessage(provider);\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.Stateless:\n\t\t\t\tprovider.receiveStateless(readVarString(message.decoder));\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.SyncStatus:\n\t\t\t\tthis.applySyncStatusMessage(\n\t\t\t\t\tprovider,\n\t\t\t\t\treadVarInt(message.decoder) === 1,\n\t\t\t\t);\n\t\t\t\tbreak;\n\n\t\t\tcase MessageType.CLOSE: {\n\t\t\t\t// eslint-disable-next-line no-case-declarations\n\t\t\t\tconst event: CloseEvent = {\n\t\t\t\t\tcode: 1000,\n\t\t\t\t\treason: readVarString(message.decoder),\n\t\t\t\t};\n\t\t\t\tprovider.onClose();\n\t\t\t\tprovider.configuration.onClose({ event });\n\t\t\t\tprovider.forwardClose({ event });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tconsole.error(`Can’t apply message of unknown type: ${type}`);\n\t\t}\n\n\t\t// Reply\n\t\tif (message.length() > emptyMessageLength + 1) {\n\t\t\t// length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)\n\t\t\t// @ts-expect-error\n\t\t\tprovider.send(OutgoingMessage, { encoder: message.encoder });\n\t\t}\n\t}\n\n\tprivate applySyncMessage(provider: HocuspocusProvider, emitSynced: boolean) {\n\t\tconst { message } = this;\n\n\t\tmessage.writeVarUint(MessageType.Sync);\n\n\t\t// Apply update\n\t\tconst syncMessageType = readSyncMessage(\n\t\t\tmessage.decoder,\n\t\t\tmessage.encoder,\n\t\t\tprovider.document,\n\t\t\tprovider,\n\t\t);\n\n\t\t// Synced once we receive Step2\n\t\tif (emitSynced && syncMessageType === messageYjsSyncStep2) {\n\t\t\tprovider.synced = true;\n\t\t}\n\t}\n\n\tapplySyncStatusMessage(provider: HocuspocusProvider, applied: boolean) {\n\t\tif (applied) {\n\t\t\tprovider.decrementUnsyncedChanges();\n\t\t}\n\t}\n\n\tprivate applyAwarenessMessage(provider: HocuspocusProvider) {\n\t\tif (!provider.awareness) return;\n\n\t\tconst { message } = this;\n\n\t\tawarenessProtocol.applyAwarenessUpdate(\n\t\t\tprovider.awareness,\n\t\t\tmessage.readVarUint8Array(),\n\t\t\tprovider,\n\t\t);\n\t}\n\n\tprivate applyAuthMessage(provider: HocuspocusProvider) {\n\t\tconst { message } = this;\n\n\t\treadAuthMessage(\n\t\t\tmessage.decoder,\n\t\t\tprovider.sendToken.bind(provider),\n\t\t\tprovider.permissionDeniedHandler.bind(provider),\n\t\t\tprovider.authenticatedHandler.bind(provider),\n\t\t);\n\t}\n\n\tprivate applyQueryAwarenessMessage(provider: HocuspocusProvider) {\n\t\tif (!provider.awareness) return;\n\n\t\tconst { message } = this;\n\n\t\tmessage.writeVarUint(MessageType.Awareness);\n\t\tmessage.writeVarUint8Array(\n\t\t\tawarenessProtocol.encodeAwarenessUpdate(\n\t\t\t\tprovider.awareness,\n\t\t\t\tArray.from(provider.awareness.getStates().keys()),\n\t\t\t),\n\t\t);\n\t}\n}\n","import type { Encoder } from \"lib0/encoding\";\nimport { toUint8Array } from \"lib0/encoding\";\nimport type { ConstructableOutgoingMessage } from \"./types.ts\";\n\nexport class MessageSender {\n\tencoder: Encoder;\n\n\tmessage: any;\n\n\tconstructor(Message: ConstructableOutgoingMessage, args: any = {}) {\n\t\tthis.message = new Message();\n\t\tthis.encoder = this.message.get(args);\n\t}\n\n\tcreate() {\n\t\treturn toUint8Array(this.encoder);\n\t}\n\n\tsend(webSocket: any) {\n\t\twebSocket?.send(this.create());\n\t}\n}\n","export const version: string =\n\t// @ts-expect-error - __HOCUSPOCUS_VERSION__ is replaced at build time by rolldown\n\ttypeof __HOCUSPOCUS_VERSION__ !== \"undefined\"\n\t\t? // @ts-expect-error - __HOCUSPOCUS_VERSION__ is replaced at build time by rolldown\n\t\t\t__HOCUSPOCUS_VERSION__\n\t\t: \"unknown\";\n","import { writeVarString, writeVarUint } from \"lib0/encoding\";\nimport { writeAuthentication } from \"@hocuspocus/common\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\nimport { version } from \"../version.ts\";\n\nexport class AuthenticationMessage extends OutgoingMessage {\n\ttype = MessageType.Auth;\n\n\tdescription = \"Authentication\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\tif (typeof args.token === \"undefined\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"The authentication message requires `token` as an argument.\",\n\t\t\t);\n\t\t}\n\n\t\twriteVarString(this.encoder, args.documentName!);\n\t\twriteVarUint(this.encoder, this.type);\n\t\twriteAuthentication(this.encoder, args.token);\n\t\twriteVarString(this.encoder, version);\n\n\t\treturn this.encoder;\n\t}\n}\n","import * as encoding from \"lib0/encoding\";\nimport { encodeAwarenessUpdate } from \"y-protocols/awareness\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\n\nexport class AwarenessMessage extends OutgoingMessage {\n\ttype = MessageType.Awareness;\n\n\tdescription = \"Awareness states update\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\tif (typeof args.awareness === \"undefined\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"The awareness message requires awareness as an argument\",\n\t\t\t);\n\t\t}\n\n\t\tif (typeof args.clients === \"undefined\") {\n\t\t\tthrow new Error(\"The awareness message requires clients as an argument\");\n\t\t}\n\n\t\tencoding.writeVarString(this.encoder, args.documentName!);\n\t\tencoding.writeVarUint(this.encoder, this.type);\n\n\t\tlet awarenessUpdate;\n\t\tif (args.states === undefined) {\n\t\t\tawarenessUpdate = encodeAwarenessUpdate(args.awareness, args.clients);\n\t\t} else {\n\t\t\tawarenessUpdate = encodeAwarenessUpdate(\n\t\t\t\targs.awareness,\n\t\t\t\targs.clients,\n\t\t\t\targs.states,\n\t\t\t);\n\t\t}\n\n\t\tencoding.writeVarUint8Array(this.encoder, awarenessUpdate);\n\n\t\treturn this.encoder;\n\t}\n}\n","import { writeVarString, writeVarUint } from \"lib0/encoding\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\n\nexport class StatelessMessage extends OutgoingMessage {\n\ttype = MessageType.Stateless;\n\n\tdescription = \"A stateless message\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\twriteVarString(this.encoder, args.documentName!);\n\t\twriteVarUint(this.encoder, this.type);\n\t\twriteVarString(this.encoder, args.payload ?? \"\");\n\n\t\treturn this.encoder;\n\t}\n}\n","import * as encoding from \"lib0/encoding\";\nimport * as syncProtocol from \"y-protocols/sync\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\n\nexport class SyncStepOneMessage extends OutgoingMessage {\n\ttype = MessageType.Sync;\n\n\tdescription = \"First sync step\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\tif (typeof args.document === \"undefined\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"The sync step one message requires document as an argument\",\n\t\t\t);\n\t\t}\n\n\t\tencoding.writeVarString(this.encoder, args.documentName!);\n\t\tencoding.writeVarUint(this.encoder, this.type);\n\t\tsyncProtocol.writeSyncStep1(this.encoder, args.document);\n\n\t\treturn this.encoder;\n\t}\n}\n","import { writeVarString, writeVarUint } from \"lib0/encoding\";\nimport { writeUpdate } from \"y-protocols/sync\";\nimport type { OutgoingMessageArguments } from \"../types.ts\";\nimport { MessageType } from \"../types.ts\";\nimport { OutgoingMessage } from \"../OutgoingMessage.ts\";\n\nexport class UpdateMessage extends OutgoingMessage {\n\ttype = MessageType.Sync;\n\n\tdescription = \"A document update\";\n\n\tget(args: Partial<OutgoingMessageArguments>) {\n\t\twriteVarString(this.encoder, args.documentName!);\n\t\twriteVarUint(this.encoder, this.type);\n\n\t\twriteUpdate(this.encoder, args.update);\n\n\t\treturn this.encoder;\n\t}\n}\n","import {\n\tawarenessStatesToArray,\n\tmakeRoutingKey,\n\tparseRoutingKey,\n} from \"@hocuspocus/common\";\nimport { Awareness, removeAwarenessStates } from \"y-protocols/awareness\";\nimport * as Y from \"yjs\";\nimport EventEmitter from \"./EventEmitter.ts\";\nimport type { CompleteHocuspocusProviderWebsocketConfiguration } from \"./HocuspocusProviderWebsocket.ts\";\nimport { HocuspocusProviderWebsocket } from \"./HocuspocusProviderWebsocket.ts\";\nimport { IncomingMessage } from \"./IncomingMessage.ts\";\nimport { MessageReceiver } from \"./MessageReceiver.ts\";\nimport { MessageSender } from \"./MessageSender.ts\";\nimport { AuthenticationMessage } from \"./OutgoingMessages/AuthenticationMessage.ts\";\nimport { AwarenessMessage } from \"./OutgoingMessages/AwarenessMessage.ts\";\nimport { StatelessMessage } from \"./OutgoingMessages/StatelessMessage.ts\";\nimport { SyncStepOneMessage } from \"./OutgoingMessages/SyncStepOneMessage.ts\";\nimport { UpdateMessage } from \"./OutgoingMessages/UpdateMessage.ts\";\nimport type {\n\tAuthorizedScope,\n\tConstructableOutgoingMessage,\n\tonAuthenticatedParameters,\n\tonAuthenticationFailedParameters,\n\tonAwarenessChangeParameters,\n\tonAwarenessUpdateParameters,\n\tonCloseParameters,\n\tonDisconnectParameters,\n\tonMessageParameters,\n\tonOpenParameters,\n\tonOutgoingMessageParameters,\n\tonStatelessParameters,\n\tonStatusParameters,\n\tonSyncedParameters,\n\tonUnsyncedChangesParameters,\n} from \"./types.ts\";\n\nexport type HocuspocusProviderConfiguration = Required<\n\tPick<CompleteHocuspocusProviderConfiguration, \"name\">\n> &\n\tPartial<CompleteHocuspocusProviderConfiguration> &\n\t(\n\t\t| (Required<Pick<CompleteHocuspocusProviderWebsocketConfiguration, \"url\">> &\n\t\t\t\tPartial<\n\t\t\t\t\tPick<\n\t\t\t\t\t\tCompleteHocuspocusProviderWebsocketConfiguration,\n\t\t\t\t\t\t\"preserveTrailingSlash\"\n\t\t\t\t\t>\n\t\t\t\t>)\n\t\t| Required<\n\t\t\t\tPick<CompleteHocuspocusProviderConfiguration, \"websocketProvider\">\n\t\t >\n\t);\n\nexport interface CompleteHocuspocusProviderConfiguration {\n\t/**\n\t * The identifier/name of your document\n\t */\n\tname: string;\n\t/**\n\t * The actual Y.js document\n\t */\n\tdocument: Y.Doc;\n\n\t/**\n\t * An Awareness instance to keep the presence state of all clients.\n\t *\n\t * You can disable sharing awareness information by passing `null`.\n\t * Note that having no awareness information shared across all connections will break our ping checks\n\t * and thus trigger reconnects. You should always have at least one Provider with enabled awareness per\n\t * socket connection, or ensure that the Provider receives messages before running into `HocuspocusProviderWebsocket.messageReconnectTimeout`.\n\t */\n\tawareness: Awareness | null;\n\n\t/**\n\t * A token that’s sent to the backend for authentication purposes.\n\t */\n\ttoken: string | (() => string) | (() => Promise<string>) | null;\n\n\t/**\n\t * Hocuspocus websocket provider\n\t */\n\twebsocketProvider: HocuspocusProviderWebsocket;\n\n\t/**\n\t * Enable session-aware multiplexing. When true, the provider embeds a unique\n\t * sessionId in the documentName field of every message, allowing multiple\n\t * providers with the same document name on a single WebSocket connection.\n\t *\n\t * Only set this to `true` when connecting to a v4 server that does\n\t * support session awareness.\n\t *\n\t * Default: false\n\t */\n\tsessionAwareness: boolean;\n\n\t/**\n\t * Force syncing the document in the defined interval.\n\t */\n\tforceSyncInterval: false | number;\n\n\t/**\n\t * Batch outgoing document and awareness updates over a short window (in\n\t * milliseconds) instead of sending one message per change. During heavy\n\t * editing this drastically reduces the number of websocket messages: Yjs\n\t * updates collected in the window are merged with `Y.mergeUpdates` into a\n\t * single message, and awareness collapses to the latest state of each\n\t * changed client.\n\t *\n\t * The window is a fixed batch, not a resetting debounce, so the added\n\t * latency is capped at `flushDelay` even while the user keeps typing. Keep\n\t * it small (e.g. 500) to avoid delaying what other clients see.\n\t *\n\t * Set to `false` (the default) to send every change immediately.\n\t */\n\tflushDelay: false | number;\n\n\tonAuthenticated: (data: onAuthenticatedParameters) => void;\n\tonAuthenticationFailed: (data: onAuthenticationFailedParameters) => void;\n\tonOpen: (data: onOpenParameters) => void;\n\tonConnect: () => void;\n\tonStatus: (data: onStatusParameters) => void;\n\tonMessage: (data: onMessageParameters) => void;\n\tonOutgoingMessage: (data: onOutgoingMessageParameters) => void;\n\tonSynced: (data: onSyncedParameters) => void;\n\tonDisconnect: (data: onDisconnectParameters) => void;\n\tonClose: (data: onCloseParameters) => void;\n\tonDestroy: () => void;\n\tonAwarenessUpdate: (data: onAwarenessUpdateParameters) => void;\n\tonAwarenessChange: (data: onAwarenessChangeParameters) => void;\n\tonStateless: (data: onStatelessParameters) => void;\n\tonUnsyncedChanges: (data: onUnsyncedChangesParameters) => void;\n}\n\nexport class AwarenessError extends Error {\n\tcode = 1001;\n}\n\nexport class HocuspocusProvider extends EventEmitter {\n\tpublic configuration: CompleteHocuspocusProviderConfiguration = {\n\t\tname: \"\",\n\t\t// @ts-expect-error\n\t\tdocument: undefined,\n\t\t// @ts-expect-error\n\t\tawareness: undefined,\n\t\ttoken: null,\n\t\tsessionAwareness: false,\n\t\tforceSyncInterval: false,\n\t\tflushDelay: false,\n\t\tonAuthenticated: () => null,\n\t\tonAuthenticationFailed: () => null,\n\t\tonOpen: () => null,\n\t\tonConnect: () => null,\n\t\tonMessage: () => null,\n\t\tonOutgoingMessage: () => null,\n\t\tonSynced: () => null,\n\t\tonStatus: () => null,\n\t\tonDisconnect: () => null,\n\t\tonClose: () => null,\n\t\tonDestroy: () => null,\n\t\tonAwarenessUpdate: () => null,\n\t\tonAwarenessChange: () => null,\n\t\tonStateless: () => null,\n\t\tonUnsyncedChanges: () => null,\n\t};\n\n\tisSynced = false;\n\n\tunsyncedChanges = 0;\n\n\tisAuthenticated = false;\n\n\tauthorizedScope: AuthorizedScope | undefined = undefined;\n\n\t// @internal\n\tmanageSocket = false;\n\n\tprivate _isAttached = false;\n\n\t/**\n\t * Outgoing document updates buffered for the current `flushDelay` window.\n\t */\n\tprivate pendingUpdates: Uint8Array[] = [];\n\n\t/**\n\t * Awareness client ids whose latest state should be sent on the next flush.\n\t */\n\tprivate pendingAwarenessClients = new Set<number>();\n\n\tprivate flushTimeout: ReturnType<typeof setTimeout> | null = null;\n\n\t/**\n\t * Unique session identifier for this provider instance.\n\t * Used for multiplexing multiple providers with the same document name on a single WebSocket.\n\t */\n\tsessionId: string = Math.random().toString(36).slice(2);\n\n\t/**\n\t * The effective name used as the first VarString in messages.\n\t * When `sessionAwareness` is enabled, returns a composite key (documentName\\0sessionId).\n\t * Otherwise, returns the plain document name.\n\t */\n\tget effectiveName(): string {\n\t\treturn this.configuration.sessionAwareness\n\t\t\t? makeRoutingKey(this.configuration.name, this.sessionId)\n\t\t\t: this.configuration.name;\n\t}\n\n\tintervals: any = {\n\t\tforceSync: null,\n\t};\n\n\tconstructor(configuration: HocuspocusProviderConfiguration) {\n\t\tsuper();\n\t\tthis.setConfiguration(configuration);\n\n\t\tthis.configuration.document = configuration.document\n\t\t\t? configuration.document\n\t\t\t: new Y.Doc();\n\t\tthis.configuration.awareness =\n\t\t\tconfiguration.awareness !== undefined\n\t\t\t\t? configuration.awareness\n\t\t\t\t: new Awareness(this.document);\n\n\t\tthis.on(\"open\", this.configuration.onOpen);\n\t\tthis.on(\"message\", this.configuration.onMessage);\n\t\tthis.on(\"outgoingMessage\", this.configuration.onOutgoingMessage);\n\t\tthis.on(\"synced\", this.configuration.onSynced);\n\t\tthis.on(\"destroy\", this.configuration.onDestroy);\n\t\tthis.on(\"awarenessUpdate\", this.configuration.onAwarenessUpdate);\n\t\tthis.on(\"awarenessChange\", this.configuration.onAwarenessChange);\n\t\tthis.on(\"stateless\", this.configuration.onStateless);\n\t\tthis.on(\"unsyncedChanges\", this.configuration.onUnsyncedChanges);\n\n\t\tthis.on(\"authenticated\", this.configuration.onAuthenticated);\n\t\tthis.on(\"authenticationFailed\", this.configuration.onAuthenticationFailed);\n\n\t\tthis.awareness?.on(\"update\", () => {\n\t\t\tthis.emit(\"awarenessUpdate\", {\n\t\t\t\tstates: awarenessStatesToArray(this.awareness!.getStates()),\n\t\t\t});\n\t\t});\n\n\t\tthis.awareness?.on(\"change\", () => {\n\t\t\tthis.emit(\"awarenessChange\", {\n\t\t\t\tstates: awarenessStatesToArray(this.awareness!.getStates()),\n\t\t\t});\n\t\t});\n\n\t\tthis.document.on(\"update\", this.boundDocumentUpdateHandler);\n\t\tthis.awareness?.on(\"update\", this.boundAwarenessUpdateHandler);\n\n\t\tthis.registerEventListeners();\n\n\t\tif (\n\t\t\tthis.configuration.forceSyncInterval &&\n\t\t\ttypeof this.configuration.forceSyncInterval === \"number\"\n\t\t) {\n\t\t\tthis.intervals.forceSync = setInterval(\n\t\t\t\tthis.forceSync.bind(this),\n\t\t\t\tthis.configuration.forceSyncInterval,\n\t\t\t);\n\t\t}\n\n\t\tif (this.manageSocket) {\n\t\t\tthis.attach();\n\t\t}\n\t}\n\n\tboundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);\n\n\tboundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);\n\n\tboundPageHide = this.pageHide.bind(this);\n\n\tboundOnOpen = this.onOpen.bind(this);\n\n\tboundOnClose = this.onClose.bind(this);\n\n\tforwardConnect = () => this.emit(\"connect\");\n\n\tforwardStatus = (e: onStatusParameters) => this.emit(\"status\", e);\n\n\tforwardClose = (e: onCloseParameters) => this.emit(\"close\", e);\n\n\tforwardDisconnect = (e: onDisconnectParameters) => this.emit(\"disconnect\", e);\n\n\tforwardDestroy = () => this.emit(\"destroy\");\n\n\tpublic setConfiguration(\n\t\tconfiguration: Partial<HocuspocusProviderConfiguration> = {},\n\t): void {\n\t\tif (!configuration.websocketProvider) {\n\t\t\tthis.manageSocket = true;\n\t\t\tthis.configuration.websocketProvider = new HocuspocusProviderWebsocket(\n\t\t\t\tconfiguration as CompleteHocuspocusProviderWebsocketConfiguration,\n\t\t\t);\n\t\t}\n\n\t\tthis.configuration = { ...this.configuration, ...configuration };\n\t}\n\n\tget document() {\n\t\treturn this.configuration.document;\n\t}\n\n\tpublic get isAttached() {\n\t\treturn this._isAttached;\n\t}\n\n\tget awareness() {\n\t\treturn this.configuration.awareness;\n\t}\n\n\tget hasUnsyncedChanges(): boolean {\n\t\treturn this.unsyncedChanges > 0;\n\t}\n\n\tprivate resetUnsyncedChanges() {\n\t\tthis.unsyncedChanges = 1;\n\t\tthis.emit(\"unsyncedChanges\", { number: this.unsyncedChanges });\n\t}\n\n\tincrementUnsyncedChanges() {\n\t\tthis.unsyncedChanges += 1;\n\t\tthis.emit(\"unsyncedChanges\", { number: this.unsyncedChanges });\n\t}\n\n\tdecrementUnsyncedChanges() {\n\t\tif (this.unsyncedChanges > 0) {\n\t\t\tthis.unsyncedChanges -= 1;\n\t\t}\n\n\t\tif (this.unsyncedChanges === 0) {\n\t\t\tthis.synced = true;\n\t\t}\n\n\t\tthis.emit(\"unsyncedChanges\", { number: this.unsyncedChanges });\n\t}\n\n\tforceSync() {\n\t\tthis.resetUnsyncedChanges();\n\n\t\tthis.send(SyncStepOneMessage, {\n\t\t\tdocument: this.document,\n\t\t\tdocumentName: this.effectiveName,\n\t\t});\n\t}\n\n\tpageHide() {\n\t\tif (this.awareness) {\n\t\t\tremoveAwarenessStates(\n\t\t\t\tthis.awareness,\n\t\t\t\t[this.document.clientID],\n\t\t\t\t\"page hide\",\n\t\t\t);\n\t\t}\n\t}\n\n\tregisterEventListeners() {\n\t\tif (typeof window === \"undefined\" || !(\"addEventListener\" in window)) {\n\t\t\treturn;\n\t\t}\n\n\t\twindow.addEventListener(\"pagehide\", this.boundPageHide);\n\t}\n\n\tsendStateless(payload: string) {\n\t\tthis.send(StatelessMessage, {\n\t\t\tdocumentName: this.effectiveName,\n\t\t\tpayload,\n\t\t});\n\t}\n\n\tasync sendToken() {\n\t\tlet token: string | null;\n\t\ttry {\n\t\t\ttoken = await this.getToken();\n\t\t} catch (error) {\n\t\t\tthis.permissionDeniedHandler(\n\t\t\t\t`Failed to get token during sendToken(): ${error}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.send(AuthenticationMessage, {\n\t\t\ttoken: token ?? \"\",\n\t\t\tdocumentName: this.effectiveName,\n\t\t});\n\t}\n\n\tprivate get batchingEnabled(): boolean {\n\t\treturn (\n\t\t\t!!this.configuration.flushDelay &&\n\t\t\ttypeof this.configuration.flushDelay === \"number\"\n\t\t);\n\t}\n\n\tdocumentUpdateHandler(update: Uint8Array, origin: any) {\n\t\tif (origin === this) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.batchingEnabled) {\n\t\t\tthis.incrementUnsyncedChanges();\n\t\t\tthis.send(UpdateMessage, { update, documentName: this.effectiveName });\n\t\t\treturn;\n\t\t}\n\n\t\t// Count one outstanding change per batch: the server acks once per\n\t\t// merged message, so incrementing per buffered update would never balance.\n\t\tif (this.pendingUpdates.length === 0) {\n\t\t\tthis.incrementUnsyncedChanges();\n\t\t}\n\n\t\tthis.pendingUpdates.push(update);\n\t\tthis.scheduleFlush();\n\t}\n\n\tawarenessUpdateHandler({ added, updated, removed }: any, origin: any) {\n\t\tconst changedClients = added.concat(updated).concat(removed);\n\n\t\tif (!this.batchingEnabled) {\n\t\t\tthis.send(AwarenessMessage, {\n\t\t\t\tawareness: this.awareness,\n\t\t\t\tclients: changedClients,\n\t\t\t\tdocumentName: this.effectiveName,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const client of changedClients) {\n\t\t\tthis.pendingAwarenessClients.add(client);\n\t\t}\n\n\t\tthis.scheduleFlush();\n\t}\n\n\tprivate scheduleFlush() {\n\t\t// Fixed-window batching: the first pending change starts the timer and\n\t\t// everything until it fires is flushed together, so latency stays capped\n\t\t// at `flushDelay` instead of growing while the user keeps typing.\n\t\tif (this.flushTimeout !== null) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.flushTimeout = setTimeout(() => {\n\t\t\tthis.flushTimeout = null;\n\t\t\tthis.flushPendingUpdates();\n\t\t}, this.configuration.flushDelay as number);\n\t}\n\n\t/**\n\t * Send everything buffered for the current `flushDelay` window right away.\n\t * Buffered document updates are merged into a single message and awareness\n\t * collapses to the latest state of each changed client. Safe to call when\n\t * nothing is pending.\n\t */\n\tflushPendingUpdates() {\n\t\tif (this.flushTimeout !== null) {\n\t\t\tclearTimeout(this.flushTimeout);\n\t\t\tthis.flushTimeout = null;\n\t\t}\n\n\t\tif (this.pendingUpdates.length > 0) {\n\t\t\tconst update =\n\t\t\t\tthis.pendingUpdates.length === 1\n\t\t\t\t\t? this.pendingUpdates[0]\n\t\t\t\t\t: Y.mergeUpdates(this.pendingUpdates);\n\n\t\t\tthis.pendingUpdates = [];\n\t\t\tthis.send(UpdateMessage, { update, documentName: this.effectiveName });\n\t\t}\n\n\t\tif (this.pendingAwarenessClients.size > 0) {\n\t\t\tconst clients = Array.from(this.pendingAwarenessClients);\n\n\t\t\tthis.pendingAwarenessClients.clear();\n\t\t\tthis.send(AwarenessMessage, {\n\t\t\t\tawareness: this.awareness,\n\t\t\t\tclients,\n\t\t\t\tdocumentName: this.effectiveName,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Indicates whether a first handshake with the server has been established\n\t *\n\t * Note: this does not mean all updates from the client have been persisted to the backend. For this,\n\t * use `hasUnsyncedChanges`.\n\t */\n\tget synced(): boolean {\n\t\treturn this.isSynced;\n\t}\n\n\tset synced(state) {\n\t\tif (this.isSynced === state) {\n\t\t\treturn;\n