import { Adapter, PeerID, GeneratedChannel, Channel, ChannelMsg } from '@loro-extended/repo'; import { Request, Router } from 'express'; /** * Represents an active HTTP polling connection to a peer. * This class manages the lifecycle of a single peer connection, * including message queuing and long-polling support. */ declare class HttpPollingConnection { readonly peerId: PeerID; readonly channelId: number; private _channel; private _messageQueue; private _lastActivity; private _waitingResolver; private _waitingTimeout; constructor(peerId: PeerID, channelId: number); /** * Internal: Set the channel reference (called by adapter). */ _setChannel(channel: Channel): void; /** * Get the timestamp of the last activity on this connection. */ get lastActivity(): number; /** * Update the last activity timestamp. */ touch(): void; /** * Enqueue a message to be sent on the next poll. * If a poll request is currently waiting (long-polling), resolves it immediately. */ enqueue(msg: ChannelMsg): void; /** * Drain all queued messages (called on poll). * Returns the messages and clears the queue. */ drain(): ChannelMsg[]; /** * Wait for messages with timeout (for long-polling). * Returns immediately if messages are already queued. * * @param timeoutMs Maximum time to wait for messages (0 = return immediately) * @returns Promise that resolves with queued messages */ waitForMessages(timeoutMs: number): Promise; /** * Cancel any waiting poll request. * Called when the connection is being cleaned up. */ cancelWait(): void; /** * Receive a message from the client and route it to the channel. */ receive(msg: ChannelMsg): void; /** * Check if this connection has a waiting poll request. */ get isWaiting(): boolean; /** * Get the number of queued messages. */ get queueLength(): number; } /** * HTTP Polling server network adapter that manages peer connections. * This adapter is framework-agnostic and does not depend on Express or any HTTP framework. * * Use a router factory (like createHttpPollingExpressRouter) to integrate with your HTTP framework. */ declare class HttpPollingServerNetworkAdapter extends Adapter { private connections; private cleanupInterval; private _connectionTimeout; constructor(options?: { connectionTimeout?: number; }); protected generate(peerId: PeerID): GeneratedChannel; onStart(): Promise; onStop(): Promise; /** * Register a new peer connection. * This should be called when a peer first polls. * * @param peerId The unique identifier for the peer * @returns An HttpPollingConnection object that can be used to manage the connection */ registerConnection(peerId: PeerID): HttpPollingConnection; /** * Unregister a peer connection. * This should be called when a peer disconnects or times out. * * @param peerId The unique identifier for the peer */ unregisterConnection(peerId: PeerID): void; /** * Get an active connection by peer ID. * * @param peerId The unique identifier for the peer * @returns The HttpPollingConnection if found, undefined otherwise */ getConnection(peerId: PeerID): HttpPollingConnection | undefined; /** * Get all active connections. * * @returns An array of all active HttpPollingConnection objects */ getAllConnections(): HttpPollingConnection[]; /** * Check if a peer is currently connected. * * @param peerId The unique identifier for the peer * @returns true if the peer is connected, false otherwise */ isConnected(peerId: PeerID): boolean; /** * Clean up connections that have been inactive for too long. */ private cleanupStaleConnections; /** * Get the connection timeout setting. */ get connectionTimeout(): number; } interface HttpPollingExpressRouterOptions { /** * Path for the poll endpoint where clients GET messages. * @default "/poll" */ pollPath?: string; /** * Path for the sync endpoint where clients POST messages. * @default "/sync" */ syncPath?: string; /** * Maximum time server will hold a long-poll request (caps client's serverWaitHint). * @default 60000 (60 seconds) */ maxServerWait?: number; /** * Custom function to extract peerId from the poll request. * By default, reads from the "peerId" query parameter. */ getPeerIdFromPollRequest?: (req: Request) => PeerID | undefined; /** * Custom function to extract peerId from the sync request. * By default, reads from the "x-peer-id" header. */ getPeerIdFromSyncRequest?: (req: Request) => PeerID | undefined; } /** * Create an Express router for HTTP polling server adapter. * * This factory function creates Express routes that integrate with the * HttpPollingServerNetworkAdapter. It handles: * - GET endpoint for clients to poll for messages (with long-polling support) * - POST endpoint for clients to send messages to the server * - DELETE endpoint for clients to explicitly disconnect * - Message serialization/deserialization * * @param adapter The HttpPollingServerNetworkAdapter instance * @param options Configuration options for the router * @returns An Express Router ready to be mounted * * @example * ```typescript * const adapter = new HttpPollingServerNetworkAdapter() * const repo = new Repo({ adapters: [adapter, storageAdapter] }) * * app.use("/loro", createHttpPollingExpressRouter(adapter, { * pollPath: "/poll", * syncPath: "/sync", * maxServerWait: 60000 * })) * ``` */ declare function createHttpPollingExpressRouter(adapter: HttpPollingServerNetworkAdapter, options?: HttpPollingExpressRouterOptions): Router; export { HttpPollingConnection, type HttpPollingExpressRouterOptions, HttpPollingServerNetworkAdapter, createHttpPollingExpressRouter };