@dropgate/core 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/utils/network.ts","../../src/adapters/defaults.ts","../../src/p2p/utils.ts","../../src/p2p/helpers.ts","../../src/p2p/protocol.ts","../../src/p2p/send.ts","../../src/p2p/receive.ts"],"sourcesContent":["export interface DropgateErrorOptions {\r\n code?: string;\r\n details?: unknown;\r\n cause?: unknown;\r\n}\r\n\r\n/**\r\n * Base error class for all Dropgate errors\r\n */\r\nexport class DropgateError extends Error {\r\n readonly code: string;\r\n readonly details?: unknown;\r\n\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\r\n this.name = this.constructor.name;\r\n this.code = opts.code || 'DROPGATE_ERROR';\r\n this.details = opts.details;\r\n }\r\n}\r\n\r\n/**\r\n * Validation error for invalid inputs\r\n */\r\nexport class DropgateValidationError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'VALIDATION_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Network error for connection issues\r\n */\r\nexport class DropgateNetworkError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'NETWORK_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Protocol error for server communication issues\r\n */\r\nexport class DropgateProtocolError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'PROTOCOL_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Abort error - replacement for DOMException with AbortError name\r\n * Used when operations are cancelled\r\n */\r\nexport class DropgateAbortError extends DropgateError {\r\n constructor(message = 'Operation aborted') {\r\n super(message, { code: 'ABORT_ERROR' });\r\n this.name = 'AbortError';\r\n }\r\n}\r\n\r\n/**\r\n * Timeout error - replacement for DOMException with TimeoutError name\r\n * Used when operations exceed their time limit\r\n */\r\nexport class DropgateTimeoutError extends DropgateError {\r\n constructor(message = 'Request timed out') {\r\n super(message, { code: 'TIMEOUT_ERROR' });\r\n this.name = 'TimeoutError';\r\n }\r\n}\r\n","import { DropgateAbortError, DropgateTimeoutError, DropgateValidationError } from '../errors.js';\r\nimport type { FetchFn, ServerTarget } from '../types.js';\r\n\r\n/**\r\n * Parse a server URL string into host, port, and secure components.\r\n * If no protocol is specified, defaults to HTTPS.\r\n */\r\nexport function parseServerUrl(urlStr: string): ServerTarget {\r\n let normalized = urlStr.trim();\r\n if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {\r\n normalized = 'https://' + normalized;\r\n }\r\n const url = new URL(normalized);\r\n return {\r\n host: url.hostname,\r\n port: url.port ? Number(url.port) : undefined,\r\n secure: url.protocol === 'https:',\r\n };\r\n}\r\n\r\n/**\r\n * Build a base URL from host, port, and secure options.\r\n */\r\nexport function buildBaseUrl(opts: ServerTarget): string {\r\n const { host, port, secure } = opts;\r\n\r\n if (!host || typeof host !== 'string') {\r\n throw new DropgateValidationError('Server host is required.');\r\n }\r\n\r\n const protocol = secure === false ? 'http' : 'https';\r\n const portSuffix = port ? `:${port}` : '';\r\n\r\n return `${protocol}://${host}${portSuffix}`;\r\n}\r\n\r\n/**\r\n * Sleep for a specified duration, with optional abort signal support.\r\n */\r\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (signal?.aborted) {\r\n return reject(signal.reason || new DropgateAbortError());\r\n }\r\n\r\n const t = setTimeout(resolve, ms);\r\n\r\n if (signal) {\r\n signal.addEventListener(\r\n 'abort',\r\n () => {\r\n clearTimeout(t);\r\n reject(signal.reason || new DropgateAbortError());\r\n },\r\n { once: true }\r\n );\r\n }\r\n });\r\n}\r\n\r\nexport interface AbortSignalWithCleanup {\r\n signal: AbortSignal;\r\n cleanup: () => void;\r\n}\r\n\r\n/**\r\n * Create an AbortSignal that combines a parent signal with a timeout.\r\n */\r\nexport function makeAbortSignal(\r\n parentSignal?: AbortSignal | null,\r\n timeoutMs?: number\r\n): AbortSignalWithCleanup {\r\n const controller = new AbortController();\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n\r\n const abort = (reason?: unknown): void => {\r\n if (!controller.signal.aborted) {\r\n controller.abort(reason);\r\n }\r\n };\r\n\r\n if (parentSignal) {\r\n if (parentSignal.aborted) {\r\n abort(parentSignal.reason);\r\n } else {\r\n parentSignal.addEventListener('abort', () => abort(parentSignal.reason), {\r\n once: true,\r\n });\r\n }\r\n }\r\n\r\n if (Number.isFinite(timeoutMs) && timeoutMs! > 0) {\r\n timeoutId = setTimeout(() => {\r\n abort(new DropgateTimeoutError());\r\n }, timeoutMs);\r\n }\r\n\r\n return {\r\n signal: controller.signal,\r\n cleanup: () => {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n },\r\n };\r\n}\r\n\r\nexport interface FetchJsonResult {\r\n res: Response;\r\n json: unknown;\r\n text: string;\r\n}\r\n\r\nexport interface FetchJsonOptions extends Omit<RequestInit, 'signal'> {\r\n timeoutMs?: number;\r\n signal?: AbortSignal;\r\n}\r\n\r\n/**\r\n * Fetch JSON from a URL with timeout and error handling.\r\n */\r\nexport async function fetchJson(\r\n fetchFn: FetchFn,\r\n url: string,\r\n opts: FetchJsonOptions = {}\r\n): Promise<FetchJsonResult> {\r\n const { timeoutMs, signal, ...rest } = opts;\r\n const { signal: s, cleanup } = makeAbortSignal(signal, timeoutMs);\r\n\r\n try {\r\n const res = await fetchFn(url, { ...rest, signal: s });\r\n const text = await res.text();\r\n\r\n let json: unknown = null;\r\n try {\r\n json = text ? JSON.parse(text) : null;\r\n } catch {\r\n // Ignore parse errors - json will remain null\r\n }\r\n\r\n return { res, json, text };\r\n } finally {\r\n cleanup();\r\n }\r\n}\r\n","import type { Base64Adapter, CryptoAdapter, FetchFn } from '../types.js';\r\n\r\n/**\r\n * Get the default Base64 adapter for the current environment.\r\n * Automatically detects Node.js Buffer vs browser btoa/atob.\r\n */\r\nexport function getDefaultBase64(): Base64Adapter {\r\n // Check for Node.js Buffer (works in Node.js and some bundlers)\r\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\r\n return {\r\n encode(bytes: Uint8Array): string {\r\n return Buffer.from(bytes).toString('base64');\r\n },\r\n decode(b64: string): Uint8Array {\r\n return new Uint8Array(Buffer.from(b64, 'base64'));\r\n },\r\n };\r\n }\r\n\r\n // Browser fallback using btoa/atob\r\n if (typeof btoa === 'function' && typeof atob === 'function') {\r\n return {\r\n encode(bytes: Uint8Array): string {\r\n let binary = '';\r\n for (let i = 0; i < bytes.length; i++) {\r\n binary += String.fromCharCode(bytes[i]);\r\n }\r\n return btoa(binary);\r\n },\r\n decode(b64: string): Uint8Array {\r\n const binary = atob(b64);\r\n const out = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i++) {\r\n out[i] = binary.charCodeAt(i);\r\n }\r\n return out;\r\n },\r\n };\r\n }\r\n\r\n throw new Error(\r\n 'No Base64 implementation available. Provide a Base64Adapter via options.'\r\n );\r\n}\r\n\r\n/**\r\n * Get the default crypto object for the current environment.\r\n * Returns globalThis.crypto if available.\r\n */\r\nexport function getDefaultCrypto(): CryptoAdapter | undefined {\r\n return globalThis.crypto as CryptoAdapter | undefined;\r\n}\r\n\r\n/**\r\n * Get the default fetch function for the current environment.\r\n * Returns globalThis.fetch if available.\r\n */\r\nexport function getDefaultFetch(): FetchFn | undefined {\r\n return globalThis.fetch?.bind(globalThis) as FetchFn | undefined;\r\n}\r\n","import type { CryptoAdapter } from '../types.js';\r\nimport { getDefaultCrypto } from '../adapters/defaults.js';\r\n\r\n/**\r\n * Check if a hostname is localhost\r\n */\r\nexport function isLocalhostHostname(hostname: string): boolean {\r\n const host = String(hostname || '').toLowerCase();\r\n return host === 'localhost' || host === '127.0.0.1' || host === '::1';\r\n}\r\n\r\n/**\r\n * Check if the current context allows P2P (HTTPS or localhost)\r\n */\r\nexport function isSecureContextForP2P(\r\n hostname?: string,\r\n isSecureContext?: boolean\r\n): boolean {\r\n return Boolean(isSecureContext) || isLocalhostHostname(hostname || '');\r\n}\r\n\r\n/**\r\n * Generate a P2P sharing code using cryptographically secure random.\r\n * Format: XXXX-0000 (4 letters + 4 digits)\r\n */\r\nexport function generateP2PCode(cryptoObj?: CryptoAdapter): string {\r\n const crypto = cryptoObj || getDefaultCrypto();\r\n const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Excluded I and O to avoid confusion\r\n\r\n if (crypto) {\r\n const randomBytes = new Uint8Array(8);\r\n crypto.getRandomValues(randomBytes);\r\n\r\n let letterPart = '';\r\n for (let i = 0; i < 4; i++) {\r\n letterPart += letters[randomBytes[i] % letters.length];\r\n }\r\n\r\n let numberPart = '';\r\n for (let i = 4; i < 8; i++) {\r\n numberPart += (randomBytes[i] % 10).toString();\r\n }\r\n\r\n return `${letterPart}-${numberPart}`;\r\n }\r\n\r\n // Fallback to Math.random (less secure, but works everywhere)\r\n let a = '';\r\n for (let i = 0; i < 4; i++) {\r\n a += letters[Math.floor(Math.random() * letters.length)];\r\n }\r\n let b = '';\r\n for (let i = 0; i < 4; i++) {\r\n b += Math.floor(Math.random() * 10);\r\n }\r\n return `${a}-${b}`;\r\n}\r\n\r\n/**\r\n * Check if a string looks like a P2P sharing code\r\n */\r\nexport function isP2PCodeLike(code: string): boolean {\r\n return /^[A-Z]{4}-\\d{4}$/.test(String(code || '').trim());\r\n}\r\n","import { DropgateNetworkError } from '../errors.js';\r\nimport type { P2PCapabilities } from '../types.js';\r\nimport type { PeerInstance, PeerOptions, P2PServerConfig } from './types.js';\r\n\r\n/**\r\n * Resolve P2P server configuration from user options and server capabilities.\r\n * User-provided values take precedence over server capabilities.\r\n */\r\nexport function resolvePeerConfig(\r\n userConfig: P2PServerConfig,\r\n serverCaps?: P2PCapabilities\r\n): { path: string; iceServers: RTCIceServer[] } {\r\n return {\r\n path: userConfig.peerjsPath ?? serverCaps?.peerjsPath ?? '/peerjs',\r\n iceServers: userConfig.iceServers ?? serverCaps?.iceServers ?? [],\r\n };\r\n}\r\n\r\n/**\r\n * Build PeerJS connection options from P2P server configuration.\r\n */\r\nexport function buildPeerOptions(config: P2PServerConfig = {}): PeerOptions {\r\n const { host, port, peerjsPath = '/peerjs', secure = false, iceServers = [] } = config;\r\n\r\n const peerOpts: PeerOptions = {\r\n host,\r\n path: peerjsPath,\r\n secure,\r\n config: { iceServers },\r\n debug: 0,\r\n };\r\n\r\n if (port) {\r\n peerOpts.port = port;\r\n }\r\n\r\n return peerOpts;\r\n}\r\n\r\nexport interface CreatePeerWithRetriesOptions {\r\n code?: string | null;\r\n codeGenerator: () => string;\r\n maxAttempts: number;\r\n buildPeer: (id: string) => PeerInstance;\r\n onCode?: (code: string, attempt: number) => void;\r\n}\r\n\r\n/**\r\n * Create a peer with retries if the code is already taken\r\n */\r\nexport async function createPeerWithRetries(\r\n opts: CreatePeerWithRetriesOptions\r\n): Promise<{ peer: PeerInstance; code: string }> {\r\n const { code, codeGenerator, maxAttempts, buildPeer, onCode } = opts;\r\n\r\n let nextCode = code || codeGenerator();\r\n let peer: PeerInstance | null = null;\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\r\n onCode?.(nextCode, attempt);\r\n\r\n try {\r\n peer = await new Promise<PeerInstance>((resolve, reject) => {\r\n const instance = buildPeer(nextCode);\r\n instance.on('open', () => resolve(instance));\r\n instance.on('error', (err: Error) => {\r\n try {\r\n instance.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n reject(err);\r\n });\r\n });\r\n\r\n return { peer, code: nextCode };\r\n } catch (err) {\r\n lastError = err as Error;\r\n nextCode = codeGenerator();\r\n }\r\n }\r\n\r\n throw lastError || new DropgateNetworkError('Could not establish PeerJS connection.');\r\n}\r\n","/**\r\n * P2P Protocol Definitions\r\n *\r\n * This file defines the application-level protocol for P2P file transfers.\r\n * Protocol version 2 introduces:\r\n * - Explicit handshake with version negotiation\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack retries for reliability\r\n * - Resume capability support\r\n *\r\n * Protocol version 3 introduces:\r\n * - Multi-file transfers via file_list, file_end, file_end_ack messages\r\n * - Sequential file-by-file transfer within a single session\r\n */\r\n\r\n// Protocol version for forward compatibility\r\nexport const P2P_PROTOCOL_VERSION = 3;\r\n\r\n/**\r\n * All possible P2P message types.\r\n */\r\nexport type P2PMessageType =\r\n | 'hello' // Initial handshake with protocol version\r\n | 'file_list' // v3: List of files in multi-file transfer\r\n | 'meta' // File metadata (name, size, mime)\r\n | 'ready' // Receiver is ready to receive\r\n | 'chunk' // Data chunk with sequence number\r\n | 'chunk_ack' // Chunk acknowledgment (for flow control)\r\n | 'file_end' // v3: Current file fully sent\r\n | 'file_end_ack' // v3: Current file receipt confirmed\r\n | 'end' // All chunks/files sent\r\n | 'end_ack' // Transfer verified complete\r\n | 'ping' // Heartbeat\r\n | 'pong' // Heartbeat response\r\n | 'error' // Error occurred\r\n | 'cancelled' // User cancelled\r\n | 'resume' // Request to resume from offset\r\n | 'resume_ack'; // Resume position confirmed\r\n\r\n/**\r\n * Base interface for all P2P messages.\r\n */\r\nexport interface P2PMessageBase {\r\n t: P2PMessageType;\r\n}\r\n\r\n/**\r\n * Initial handshake message exchanged between sender and receiver.\r\n */\r\nexport interface P2PHelloMessage extends P2PMessageBase {\r\n t: 'hello';\r\n protocolVersion: number;\r\n sessionId: string;\r\n}\r\n\r\n/**\r\n * v3: File list sent by sender after handshake for multi-file transfers.\r\n */\r\nexport interface P2PFileListMessage extends P2PMessageBase {\r\n t: 'file_list';\r\n fileCount: number;\r\n files: Array<{ name: string; size: number; mime: string }>;\r\n totalSize: number;\r\n}\r\n\r\n/**\r\n * File metadata sent by sender after handshake (single file)\r\n * or before each file's chunks in multi-file mode.\r\n */\r\nexport interface P2PMetaMessage extends P2PMessageBase {\r\n t: 'meta';\r\n sessionId: string;\r\n name: string;\r\n size: number;\r\n mime: string;\r\n /** v3: File index within the file list (0-based). Absent for single-file transfers. */\r\n fileIndex?: number;\r\n}\r\n\r\n/**\r\n * Receiver signals readiness to receive data.\r\n */\r\nexport interface P2PReadyMessage extends P2PMessageBase {\r\n t: 'ready';\r\n}\r\n\r\n/**\r\n * Chunk header sent before binary data.\r\n * The actual binary data follows immediately after this message.\r\n */\r\nexport interface P2PChunkMessage extends P2PMessageBase {\r\n t: 'chunk';\r\n seq: number; // Sequence number for ordering/ack\r\n offset: number; // Byte offset in file\r\n size: number; // Size of this chunk\r\n total: number; // Total file size\r\n}\r\n\r\n/**\r\n * Acknowledgment for a received chunk.\r\n */\r\nexport interface P2PChunkAckMessage extends P2PMessageBase {\r\n t: 'chunk_ack';\r\n seq: number; // Acknowledged sequence number\r\n received: number; // Total bytes received so far\r\n}\r\n\r\n/**\r\n * v3: Sender signals all chunks for the current file have been sent.\r\n */\r\nexport interface P2PFileEndMessage extends P2PMessageBase {\r\n t: 'file_end';\r\n fileIndex: number;\r\n attempt?: number;\r\n}\r\n\r\n/**\r\n * v3: Receiver confirms current file receipt.\r\n */\r\nexport interface P2PFileEndAckMessage extends P2PMessageBase {\r\n t: 'file_end_ack';\r\n fileIndex: number;\r\n received: number;\r\n size: number;\r\n}\r\n\r\n/**\r\n * Sender signals all chunks have been sent.\r\n */\r\nexport interface P2PEndMessage extends P2PMessageBase {\r\n t: 'end';\r\n attempt?: number; // Retry attempt number\r\n}\r\n\r\n/**\r\n * Receiver confirms transfer completion.\r\n */\r\nexport interface P2PEndAckMessage extends P2PMessageBase {\r\n t: 'end_ack';\r\n received: number;\r\n total: number;\r\n}\r\n\r\n/**\r\n * Heartbeat ping.\r\n */\r\nexport interface P2PPingMessage extends P2PMessageBase {\r\n t: 'ping';\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Heartbeat pong response.\r\n */\r\nexport interface P2PPongMessage extends P2PMessageBase {\r\n t: 'pong';\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Error message sent when something goes wrong.\r\n */\r\nexport interface P2PErrorMessage extends P2PMessageBase {\r\n t: 'error';\r\n message: string;\r\n code?: string;\r\n}\r\n\r\n/**\r\n * Cancellation message sent when user cancels transfer.\r\n */\r\nexport interface P2PCancelledMessage extends P2PMessageBase {\r\n t: 'cancelled';\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Resume request sent by receiver to continue interrupted transfer.\r\n */\r\nexport interface P2PResumeMessage extends P2PMessageBase {\r\n t: 'resume';\r\n sessionId: string;\r\n receivedBytes: number;\r\n}\r\n\r\n/**\r\n * Resume acknowledgment from sender.\r\n */\r\nexport interface P2PResumeAckMessage extends P2PMessageBase {\r\n t: 'resume_ack';\r\n resumeFromOffset: number;\r\n accepted: boolean;\r\n}\r\n\r\n/**\r\n * Union type of all possible P2P messages.\r\n */\r\nexport type P2PMessage =\r\n | P2PHelloMessage\r\n | P2PFileListMessage\r\n | P2PMetaMessage\r\n | P2PReadyMessage\r\n | P2PChunkMessage\r\n | P2PChunkAckMessage\r\n | P2PFileEndMessage\r\n | P2PFileEndAckMessage\r\n | P2PEndMessage\r\n | P2PEndAckMessage\r\n | P2PPingMessage\r\n | P2PPongMessage\r\n | P2PErrorMessage\r\n | P2PCancelledMessage\r\n | P2PResumeMessage\r\n | P2PResumeAckMessage;\r\n\r\n/**\r\n * Type guard to check if a value is a valid P2P message.\r\n */\r\nexport function isP2PMessage(value: unknown): value is P2PMessage {\r\n if (!value || typeof value !== 'object') return false;\r\n const msg = value as Record<string, unknown>;\r\n return typeof msg.t === 'string' && [\r\n 'hello', 'file_list', 'meta', 'ready', 'chunk', 'chunk_ack',\r\n 'file_end', 'file_end_ack', 'end', 'end_ack', 'ping', 'pong',\r\n 'error', 'cancelled', 'resume', 'resume_ack'\r\n ].includes(msg.t);\r\n}\r\n\r\n/**\r\n * Check if protocol versions are compatible.\r\n */\r\nexport function isProtocolCompatible(\r\n senderVersion: number,\r\n receiverVersion: number\r\n): boolean {\r\n return senderVersion === receiverVersion;\r\n}\r\n\r\n/**\r\n * Default chunk size for P2P transfers (64KB).\r\n * Smaller than standard upload to reduce latency and improve flow control.\r\n */\r\nexport const P2P_CHUNK_SIZE = 64 * 1024;\r\n\r\n/**\r\n * Default maximum unacknowledged chunks before sender pauses.\r\n * This creates backpressure when receiver is slow.\r\n */\r\nexport const P2P_MAX_UNACKED_CHUNKS = 32;\r\n\r\n/**\r\n * Default timeout for waiting on end acknowledgment (ms).\r\n */\r\nexport const P2P_END_ACK_TIMEOUT_MS = 15000;\r\n\r\n/**\r\n * Number of times to retry sending end message.\r\n */\r\nexport const P2P_END_ACK_RETRIES = 3;\r\n\r\n/**\r\n * Delay between multiple end_ack sends from receiver (ms).\r\n */\r\nexport const P2P_END_ACK_RETRY_DELAY_MS = 100;\r\n\r\n/**\r\n * Grace period after connection close before declaring failure (ms).\r\n * Allows for brief reconnection attempts.\r\n */\r\nexport const P2P_CLOSE_GRACE_PERIOD_MS = 2000;\r\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\r\nimport { sleep } from '../utils/network.js';\r\nimport type {\r\n P2PSendOptions,\r\n P2PSendSession,\r\n P2PSendState,\r\n DataConnection,\r\n P2PConnectionHealthEvent,\r\n} from './types.js';\r\nimport { generateP2PCode } from './utils.js';\r\nimport { buildPeerOptions, createPeerWithRetries, resolvePeerConfig } from './helpers.js';\r\nimport type { FileSource } from '../types.js';\r\nimport {\r\n P2P_PROTOCOL_VERSION,\r\n P2P_CHUNK_SIZE,\r\n P2P_MAX_UNACKED_CHUNKS,\r\n P2P_END_ACK_TIMEOUT_MS,\r\n P2P_END_ACK_RETRIES,\r\n P2P_CLOSE_GRACE_PERIOD_MS,\r\n isP2PMessage,\r\n type P2PChunkAckMessage,\r\n type P2PEndAckMessage,\r\n type P2PFileEndAckMessage,\r\n} from './protocol.js';\r\n\r\n/**\r\n * Generate a unique session ID for transfer tracking.\r\n */\r\nfunction generateSessionId(): string {\r\n return crypto.randomUUID();\r\n}\r\n\r\n/**\r\n * Allowed state transitions to prevent invalid state changes.\r\n * This enforces a strict state machine where transitions only happen\r\n * in the expected order, preventing race conditions.\r\n */\r\nconst ALLOWED_TRANSITIONS: Record<P2PSendState, P2PSendState[]> = {\r\n initializing: ['listening', 'closed'],\r\n listening: ['handshaking', 'closed', 'cancelled'],\r\n handshaking: ['negotiating', 'closed', 'cancelled'],\r\n negotiating: ['transferring', 'closed', 'cancelled'],\r\n transferring: ['finishing', 'closed', 'cancelled'],\r\n finishing: ['awaiting_ack', 'closed', 'cancelled'],\r\n awaiting_ack: ['completed', 'closed', 'cancelled'],\r\n completed: ['closed'],\r\n cancelled: ['closed'],\r\n closed: [],\r\n};\r\n\r\n/**\r\n * Start a direct transfer (P2P) sender session.\r\n *\r\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor.\r\n * This removes DOM coupling (no script injection).\r\n *\r\n * Protocol v2 features:\r\n * - Explicit version handshake\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack retries for reliability\r\n * - Stream-through design for unlimited file sizes\r\n *\r\n * Example:\r\n * ```js\r\n * import Peer from 'peerjs';\r\n * import { startP2PSend } from '@dropgate/core/p2p';\r\n *\r\n * const session = await startP2PSend({\r\n * file: myFile,\r\n * Peer,\r\n * host: 'dropgate.link',\r\n * secure: true,\r\n * onCode: (code) => console.log('Share this code:', code),\r\n * onProgress: (evt) => console.log(`${evt.percent}% sent`),\r\n * onComplete: () => console.log('Done!'),\r\n * });\r\n * ```\r\n */\r\nexport async function startP2PSend(opts: P2PSendOptions): Promise<P2PSendSession> {\r\n const {\r\n file,\r\n Peer,\r\n serverInfo,\r\n host,\r\n port,\r\n peerjsPath,\r\n secure = false,\r\n iceServers,\r\n codeGenerator,\r\n cryptoObj,\r\n maxAttempts = 4,\r\n chunkSize = P2P_CHUNK_SIZE,\r\n endAckTimeoutMs = P2P_END_ACK_TIMEOUT_MS,\r\n bufferHighWaterMark = 8 * 1024 * 1024,\r\n bufferLowWaterMark = 2 * 1024 * 1024,\r\n heartbeatIntervalMs = 5000,\r\n chunkAcknowledgments = true,\r\n maxUnackedChunks = P2P_MAX_UNACKED_CHUNKS,\r\n onCode,\r\n onStatus,\r\n onProgress,\r\n onComplete,\r\n onError,\r\n onDisconnect,\r\n onCancel,\r\n onConnectionHealth,\r\n } = opts;\r\n\r\n // Normalize to files array\r\n const files: FileSource[] = Array.isArray(file) ? file : [file];\r\n const isMultiFile = files.length > 1;\r\n const totalSize = files.reduce((sum, f) => sum + f.size, 0);\r\n\r\n // Validate required options\r\n if (!files.length) {\r\n throw new DropgateValidationError('At least one file is required.');\r\n }\r\n\r\n if (!Peer) {\r\n throw new DropgateValidationError(\r\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\r\n );\r\n }\r\n\r\n // Check P2P capabilities if serverInfo is provided\r\n const p2pCaps = serverInfo?.capabilities?.p2p;\r\n if (serverInfo && !p2pCaps?.enabled) {\r\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\r\n }\r\n\r\n // Resolve config from user options and server capabilities\r\n const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(\r\n { peerjsPath, iceServers },\r\n p2pCaps\r\n );\r\n\r\n // Build peer options\r\n const peerOpts = buildPeerOptions({\r\n host,\r\n port,\r\n peerjsPath: finalPath,\r\n secure,\r\n iceServers: finalIceServers,\r\n });\r\n\r\n // Create the code generator\r\n const finalCodeGenerator = codeGenerator || (() => generateP2PCode(cryptoObj));\r\n\r\n // Create peer with retries\r\n const buildPeer = (id: string) => new Peer(id, peerOpts);\r\n const { peer, code } = await createPeerWithRetries({\r\n code: null,\r\n codeGenerator: finalCodeGenerator,\r\n maxAttempts,\r\n buildPeer,\r\n onCode,\r\n });\r\n\r\n // Generate unique session ID for this transfer\r\n const sessionId = generateSessionId();\r\n\r\n // State machine - replaces boolean flags to prevent race conditions\r\n let state: P2PSendState = 'listening';\r\n let activeConn: DataConnection | null = null;\r\n let sentBytes = 0;\r\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n let healthCheckTimer: ReturnType<typeof setInterval> | null = null;\r\n let lastActivityTime = Date.now();\r\n\r\n // Chunk acknowledgment tracking\r\n const unackedChunks = new Map<number, { offset: number; size: number; sentAt: number }>();\r\n let nextSeq = 0;\r\n let ackResolvers: Array<() => void> = [];\r\n\r\n /**\r\n * Attempt a state transition. Returns true if transition was valid.\r\n * Logs a warning for invalid transitions but doesn't throw.\r\n */\r\n const transitionTo = (newState: P2PSendState): boolean => {\r\n if (!ALLOWED_TRANSITIONS[state].includes(newState)) {\r\n console.warn(`[P2P Send] Invalid state transition: ${state} -> ${newState}`);\r\n return false;\r\n }\r\n state = newState;\r\n return true;\r\n };\r\n\r\n const reportProgress = (data: { received: number; total: number }): void => {\r\n if (isStopped()) return;\r\n const safeTotal =\r\n Number.isFinite(data.total) && data.total > 0 ? data.total : totalSize;\r\n const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);\r\n const percent = safeTotal ? (safeReceived / safeTotal) * 100 : 0;\r\n onProgress?.({ processedBytes: safeReceived, totalBytes: safeTotal, percent });\r\n };\r\n\r\n // Safe error handler - prevents calling onError after completion or cancellation\r\n const safeError = (err: Error): void => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') return;\r\n transitionTo('closed');\r\n onError?.(err);\r\n cleanup();\r\n };\r\n\r\n // Safe complete handler - only fires from awaiting_ack state\r\n const safeComplete = (): void => {\r\n if (state !== 'awaiting_ack' && state !== 'finishing') return;\r\n transitionTo('completed');\r\n onComplete?.();\r\n cleanup();\r\n };\r\n\r\n // Cleanup all resources\r\n const cleanup = (): void => {\r\n // Clear heartbeat timer\r\n if (heartbeatTimer) {\r\n clearInterval(heartbeatTimer);\r\n heartbeatTimer = null;\r\n }\r\n\r\n // Clear health check timer\r\n if (healthCheckTimer) {\r\n clearInterval(healthCheckTimer);\r\n healthCheckTimer = null;\r\n }\r\n\r\n // Clear any pending ack resolvers\r\n ackResolvers.forEach((resolve) => resolve());\r\n ackResolvers = [];\r\n unackedChunks.clear();\r\n\r\n // Remove beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n try {\r\n activeConn?.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n try {\r\n peer.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n };\r\n\r\n // Handle browser tab close/refresh\r\n const handleUnload = (): void => {\r\n try {\r\n activeConn?.send({ t: 'error', message: 'Sender closed the connection.' });\r\n } catch {\r\n // Best effort\r\n }\r\n stop();\r\n };\r\n\r\n // Add beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n const stop = (): void => {\r\n if (state === 'closed' || state === 'cancelled') return;\r\n\r\n // If already completed, just cleanup without callbacks\r\n if (state === 'completed') {\r\n cleanup();\r\n return;\r\n }\r\n\r\n const wasActive = state === 'transferring' || state === 'finishing' || state === 'awaiting_ack';\r\n transitionTo('cancelled');\r\n\r\n // Notify peer before cleanup\r\n try {\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n if (activeConn && activeConn.open) {\r\n activeConn.send({ t: 'cancelled', message: 'Sender cancelled the transfer.' });\r\n }\r\n } catch {\r\n // Best effort\r\n }\r\n\r\n if (wasActive && onCancel) {\r\n onCancel({ cancelledBy: 'sender' });\r\n }\r\n\r\n cleanup();\r\n };\r\n\r\n // Helper to check if session is stopped - bypasses TypeScript narrowing\r\n // which doesn't understand state can change asynchronously\r\n const isStopped = (): boolean => state === 'closed' || state === 'cancelled';\r\n\r\n // Connection health monitoring\r\n const startHealthMonitoring = (conn: DataConnection): void => {\r\n if (!onConnectionHealth) return;\r\n\r\n healthCheckTimer = setInterval(() => {\r\n if (isStopped()) return;\r\n const dc = conn._dc;\r\n if (!dc) return;\r\n\r\n // Note: iceConnectionState is on RTCPeerConnection, not RTCDataChannel\r\n // We can only report bufferedAmount and readyState from the data channel\r\n const health: P2PConnectionHealthEvent = {\r\n iceConnectionState: (dc.readyState === 'open' ? 'connected' : 'disconnected') as P2PConnectionHealthEvent['iceConnectionState'],\r\n bufferedAmount: dc.bufferedAmount,\r\n lastActivityMs: Date.now() - lastActivityTime,\r\n };\r\n\r\n onConnectionHealth(health);\r\n }, 2000);\r\n };\r\n\r\n // Handle chunk acknowledgment\r\n const handleChunkAck = (msg: P2PChunkAckMessage): void => {\r\n lastActivityTime = Date.now();\r\n unackedChunks.delete(msg.seq);\r\n reportProgress({ received: msg.received, total: totalSize });\r\n\r\n // Resolve any pending waitForAck promises\r\n const resolver = ackResolvers.shift();\r\n if (resolver) resolver();\r\n };\r\n\r\n // Wait for chunk acknowledgment when too many unacked\r\n const waitForAck = (): Promise<void> => {\r\n return new Promise((resolve) => {\r\n ackResolvers.push(resolve);\r\n });\r\n };\r\n\r\n // Send chunk with sequence tracking\r\n const sendChunk = async (conn: DataConnection, data: ArrayBuffer, offset: number, fileTotal?: number): Promise<void> => {\r\n // Wait if too many unacknowledged chunks (flow control)\r\n if (chunkAcknowledgments) {\r\n while (unackedChunks.size >= maxUnackedChunks) {\r\n await Promise.race([\r\n waitForAck(),\r\n sleep(1000), // Timeout to prevent deadlock\r\n ]);\r\n if (isStopped()) return;\r\n }\r\n }\r\n\r\n const seq = nextSeq++;\r\n if (chunkAcknowledgments) {\r\n unackedChunks.set(seq, { offset, size: data.byteLength, sentAt: Date.now() });\r\n }\r\n\r\n // Send chunk header then binary data\r\n conn.send({ t: 'chunk', seq, offset, size: data.byteLength, total: fileTotal ?? totalSize });\r\n conn.send(data);\r\n sentBytes += data.byteLength;\r\n\r\n // Buffer-based flow control using data channel thresholds\r\n const dc = conn._dc;\r\n if (dc && bufferHighWaterMark > 0) {\r\n while (dc.bufferedAmount > bufferHighWaterMark) {\r\n await new Promise<void>((resolve) => {\r\n const fallback = setTimeout(resolve, 60);\r\n try {\r\n dc.addEventListener(\r\n 'bufferedamountlow',\r\n () => {\r\n clearTimeout(fallback);\r\n resolve();\r\n },\r\n { once: true }\r\n );\r\n } catch {\r\n // Fallback only\r\n }\r\n });\r\n if (isStopped()) return;\r\n }\r\n }\r\n };\r\n\r\n // Robust end-ack with retries\r\n const waitForEndAck = async (\r\n conn: DataConnection,\r\n ackPromise: Promise<P2PEndAckMessage>\r\n ): Promise<P2PEndAckMessage> => {\r\n const baseTimeout = endAckTimeoutMs;\r\n\r\n for (let attempt = 0; attempt < P2P_END_ACK_RETRIES; attempt++) {\r\n conn.send({ t: 'end', attempt });\r\n\r\n const timeout = baseTimeout * Math.pow(1.5, attempt);\r\n const result = await Promise.race([\r\n ackPromise,\r\n sleep(timeout).then(() => null as P2PEndAckMessage | null),\r\n ]);\r\n\r\n if (result && result.t === 'end_ack') {\r\n return result;\r\n }\r\n\r\n // Check if connection is still alive\r\n if (isStopped()) {\r\n throw new DropgateNetworkError('Connection closed during completion.');\r\n }\r\n }\r\n\r\n throw new DropgateNetworkError('Receiver did not confirm completion after retries.');\r\n };\r\n\r\n peer.on('connection', (conn: DataConnection) => {\r\n if (isStopped()) return;\r\n\r\n // Connection replacement logic - allow new connections if old one is dead\r\n if (activeConn) {\r\n // Check if existing connection is actually still open\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n const isOldConnOpen = activeConn.open !== false;\r\n\r\n if (isOldConnOpen && state === 'transferring') {\r\n // Actively transferring, reject new connection\r\n try {\r\n conn.send({ t: 'error', message: 'Transfer already in progress.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n } else if (!isOldConnOpen) {\r\n // Old connection is dead, clean it up and accept new one\r\n try {\r\n activeConn.close();\r\n } catch {\r\n // Ignore\r\n }\r\n activeConn = null;\r\n // Reset state to allow new transfer\r\n state = 'listening';\r\n sentBytes = 0;\r\n nextSeq = 0;\r\n unackedChunks.clear();\r\n } else {\r\n // Connection exists but not transferring (maybe in negotiating state)\r\n // Reject to avoid confusion\r\n try {\r\n conn.send({ t: 'error', message: 'Another receiver is already connected.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n }\r\n }\r\n\r\n activeConn = conn;\r\n transitionTo('handshaking');\r\n if (!isStopped()) onStatus?.({ phase: 'connected', message: 'Receiver connected.' });\r\n lastActivityTime = Date.now();\r\n\r\n let helloResolve: ((version: number) => void) | null = null;\r\n let readyResolve: (() => void) | null = null;\r\n let endAckResolve: ((msg: P2PEndAckMessage) => void) | null = null;\r\n let fileEndAckResolve: ((msg: P2PFileEndAckMessage) => void) | null = null;\r\n\r\n const helloPromise = new Promise<number>((resolve) => {\r\n helloResolve = resolve;\r\n });\r\n\r\n const readyPromise = new Promise<void>((resolve) => {\r\n readyResolve = resolve;\r\n });\r\n\r\n const endAckPromise = new Promise<P2PEndAckMessage>((resolve) => {\r\n endAckResolve = resolve;\r\n });\r\n\r\n conn.on('data', (data: unknown) => {\r\n lastActivityTime = Date.now();\r\n\r\n // Handle binary data (we don't expect binary from receiver)\r\n if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\r\n return;\r\n }\r\n\r\n if (!isP2PMessage(data)) return;\r\n\r\n const msg = data;\r\n\r\n switch (msg.t) {\r\n case 'hello':\r\n helloResolve?.(msg.protocolVersion);\r\n break;\r\n\r\n case 'ready':\r\n if (!isStopped()) onStatus?.({ phase: 'transferring', message: 'Receiver accepted. Starting transfer...' });\r\n readyResolve?.();\r\n break;\r\n\r\n case 'chunk_ack':\r\n handleChunkAck(msg as P2PChunkAckMessage);\r\n break;\r\n\r\n case 'file_end_ack':\r\n fileEndAckResolve?.(msg as P2PFileEndAckMessage);\r\n break;\r\n\r\n case 'end_ack':\r\n endAckResolve?.(msg as P2PEndAckMessage);\r\n break;\r\n\r\n case 'pong':\r\n // Heartbeat response received, connection is alive\r\n break;\r\n\r\n case 'error':\r\n safeError(new DropgateNetworkError(msg.message || 'Receiver reported an error.'));\r\n break;\r\n\r\n case 'cancelled':\r\n if (state === 'cancelled' || state === 'closed' || state === 'completed') return;\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'receiver', message: msg.reason });\r\n cleanup();\r\n break;\r\n }\r\n });\r\n\r\n conn.on('open', async () => {\r\n try {\r\n if (isStopped()) return;\r\n\r\n // Start health monitoring\r\n startHealthMonitoring(conn);\r\n\r\n // Protocol v2: Send hello first\r\n conn.send({\r\n t: 'hello',\r\n protocolVersion: P2P_PROTOCOL_VERSION,\r\n sessionId,\r\n });\r\n\r\n // Wait for receiver's hello (with timeout)\r\n const receiverVersion = await Promise.race([\r\n helloPromise,\r\n sleep(10000).then(() => null as number | null),\r\n ]);\r\n\r\n if (isStopped()) return;\r\n\r\n if (receiverVersion === null) {\r\n throw new DropgateNetworkError('Receiver did not respond to handshake.');\r\n } else if (receiverVersion !== P2P_PROTOCOL_VERSION) {\r\n throw new DropgateNetworkError(\r\n `Protocol version mismatch: sender v${P2P_PROTOCOL_VERSION}, receiver v${receiverVersion}`\r\n );\r\n }\r\n\r\n transitionTo('negotiating');\r\n if (!isStopped()) onStatus?.({ phase: 'waiting', message: 'Connected. Waiting for receiver to accept...' });\r\n\r\n // v3: Send file_list for multi-file transfers\r\n if (isMultiFile) {\r\n conn.send({\r\n t: 'file_list',\r\n fileCount: files.length,\r\n files: files.map(f => ({ name: f.name, size: f.size, mime: f.type || 'application/octet-stream' })),\r\n totalSize,\r\n });\r\n }\r\n\r\n // Send metadata for the first file (or the only file)\r\n conn.send({\r\n t: 'meta',\r\n sessionId,\r\n name: files[0].name,\r\n size: files[0].size,\r\n mime: files[0].type || 'application/octet-stream',\r\n ...(isMultiFile ? { fileIndex: 0 } : {}),\r\n });\r\n\r\n const dc = conn._dc;\r\n\r\n if (dc && Number.isFinite(bufferLowWaterMark)) {\r\n try {\r\n dc.bufferedAmountLowThreshold = bufferLowWaterMark;\r\n } catch {\r\n // Ignore threshold setting errors\r\n }\r\n }\r\n\r\n // Wait for ready signal\r\n await readyPromise;\r\n if (isStopped()) return;\r\n\r\n // Start heartbeat for long transfers\r\n if (heartbeatIntervalMs > 0) {\r\n heartbeatTimer = setInterval(() => {\r\n if (state === 'transferring' || state === 'finishing' || state === 'awaiting_ack') {\r\n try {\r\n conn.send({ t: 'ping', timestamp: Date.now() });\r\n } catch {\r\n // Ignore ping errors\r\n }\r\n }\r\n }, heartbeatIntervalMs);\r\n }\r\n\r\n transitionTo('transferring');\r\n\r\n let overallSentBytes = 0;\r\n\r\n // Send file(s) in chunks\r\n for (let fi = 0; fi < files.length; fi++) {\r\n const currentFile = files[fi];\r\n\r\n // For multi-file (after first file), send meta for subsequent files\r\n if (isMultiFile && fi > 0) {\r\n conn.send({\r\n t: 'meta',\r\n sessionId,\r\n name: currentFile.name,\r\n size: currentFile.size,\r\n mime: currentFile.type || 'application/octet-stream',\r\n fileIndex: fi,\r\n });\r\n }\r\n\r\n // Send this file's chunks\r\n for (let offset = 0; offset < currentFile.size; offset += chunkSize) {\r\n if (isStopped()) return;\r\n\r\n const slice = currentFile.slice(offset, offset + chunkSize);\r\n const buf = await slice.arrayBuffer();\r\n if (isStopped()) return;\r\n\r\n await sendChunk(conn, buf, offset, currentFile.size);\r\n overallSentBytes += buf.byteLength;\r\n reportProgress({ received: overallSentBytes, total: totalSize });\r\n }\r\n\r\n if (isStopped()) return;\r\n\r\n // For multi-file: send file_end and wait for file_end_ack\r\n if (isMultiFile) {\r\n const fileEndAckPromise = new Promise<P2PFileEndAckMessage>((resolve) => {\r\n fileEndAckResolve = resolve;\r\n });\r\n\r\n conn.send({ t: 'file_end', fileIndex: fi });\r\n\r\n const feAck = await Promise.race([\r\n fileEndAckPromise,\r\n sleep(endAckTimeoutMs).then(() => null as P2PFileEndAckMessage | null),\r\n ]);\r\n\r\n if (isStopped()) return;\r\n\r\n if (!feAck) {\r\n throw new DropgateNetworkError(`Receiver did not confirm receipt of file ${fi + 1}/${files.length}.`);\r\n }\r\n }\r\n }\r\n\r\n if (isStopped()) return;\r\n\r\n transitionTo('finishing');\r\n transitionTo('awaiting_ack');\r\n\r\n // Wait for end acknowledgment with retries\r\n const ackResult = await waitForEndAck(conn, endAckPromise);\r\n\r\n if (isStopped()) return;\r\n\r\n const ackTotal = Number(ackResult.total) || totalSize;\r\n const ackReceived = Number(ackResult.received) || 0;\r\n\r\n if (ackTotal && ackReceived < ackTotal) {\r\n throw new DropgateNetworkError('Receiver reported an incomplete transfer.');\r\n }\r\n\r\n reportProgress({ received: ackReceived || ackTotal, total: ackTotal });\r\n safeComplete();\r\n } catch (err) {\r\n safeError(err as Error);\r\n }\r\n });\r\n\r\n conn.on('error', (err: Error) => {\r\n safeError(err);\r\n });\r\n\r\n conn.on('close', () => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') {\r\n // Clean shutdown or already cancelled, ensure full cleanup\r\n cleanup();\r\n return;\r\n }\r\n\r\n // Special handling for awaiting_ack state - give grace period\r\n if (state === 'awaiting_ack') {\r\n // Connection closed while waiting for end_ack\r\n // Give a grace period for the ack to have been processed\r\n setTimeout(() => {\r\n if (state === 'awaiting_ack') {\r\n // Still waiting, treat as failure\r\n safeError(new DropgateNetworkError('Connection closed while awaiting confirmation.'));\r\n }\r\n }, P2P_CLOSE_GRACE_PERIOD_MS);\r\n return;\r\n }\r\n\r\n if (state === 'transferring' || state === 'finishing') {\r\n // Connection closed during active transfer — the receiver either cancelled\r\n // or disconnected. Treat as a receiver-initiated cancellation so the UI\r\n // can reset cleanly instead of showing a raw error.\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'receiver' });\r\n cleanup();\r\n } else {\r\n // Disconnected before transfer started (during waiting/negotiating phase)\r\n // Reset state to allow reconnection\r\n activeConn = null;\r\n state = 'listening';\r\n sentBytes = 0;\r\n nextSeq = 0;\r\n unackedChunks.clear();\r\n onDisconnect?.();\r\n }\r\n });\r\n });\r\n\r\n return {\r\n peer,\r\n code,\r\n sessionId,\r\n stop,\r\n getStatus: () => state,\r\n getBytesSent: () => sentBytes,\r\n getConnectedPeerId: () => {\r\n if (!activeConn) return null;\r\n // @ts-expect-error - peer property exists on PeerJS DataConnection\r\n return activeConn.peer || null;\r\n },\r\n };\r\n}\r\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\r\nimport { sleep } from '../utils/network.js';\r\nimport type { P2PReceiveOptions, P2PReceiveSession, P2PReceiveState, DataConnection } from './types.js';\r\nimport { isP2PCodeLike } from './utils.js';\r\nimport { buildPeerOptions, resolvePeerConfig } from './helpers.js';\r\nimport {\r\n P2P_PROTOCOL_VERSION,\r\n P2P_END_ACK_RETRY_DELAY_MS,\r\n isP2PMessage,\r\n type P2PChunkMessage,\r\n type P2PFileListMessage,\r\n} from './protocol.js';\r\n\r\n/**\r\n * Allowed state transitions to prevent invalid state changes.\r\n */\r\nconst ALLOWED_TRANSITIONS: Record<P2PReceiveState, P2PReceiveState[]> = {\r\n initializing: ['connecting', 'closed'],\r\n connecting: ['handshaking', 'closed', 'cancelled'],\r\n handshaking: ['negotiating', 'closed', 'cancelled'],\r\n negotiating: ['transferring', 'closed', 'cancelled'],\r\n transferring: ['completed', 'closed', 'cancelled'],\r\n completed: ['closed'],\r\n cancelled: ['closed'],\r\n closed: [],\r\n};\r\n\r\n/**\r\n * Start a direct transfer (P2P) receiver session.\r\n *\r\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor and handle file writing.\r\n * This removes DOM coupling (no streamSaver).\r\n *\r\n * Protocol v2 features:\r\n * - Explicit version handshake\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack sends for reliability\r\n * - Stream-through design for unlimited file sizes\r\n *\r\n * Example:\r\n * ```js\r\n * import Peer from 'peerjs';\r\n * import { startP2PReceive } from '@dropgate/core/p2p';\r\n *\r\n * let writer;\r\n * const session = await startP2PReceive({\r\n * code: 'ABCD-1234',\r\n * Peer,\r\n * host: 'dropgate.link',\r\n * secure: true,\r\n * onMeta: ({ name, total }) => {\r\n * // Consumer creates file writer\r\n * writer = createWriteStream(name);\r\n * },\r\n * onData: async (chunk) => {\r\n * // Consumer writes data\r\n * await writer.write(chunk);\r\n * },\r\n * onComplete: () => {\r\n * writer.close();\r\n * console.log('Done!');\r\n * },\r\n * });\r\n * ```\r\n */\r\nexport async function startP2PReceive(opts: P2PReceiveOptions): Promise<P2PReceiveSession> {\r\n const {\r\n code,\r\n Peer,\r\n serverInfo,\r\n host,\r\n port,\r\n peerjsPath,\r\n secure = false,\r\n iceServers,\r\n autoReady = true,\r\n watchdogTimeoutMs = 15000,\r\n onStatus,\r\n onMeta,\r\n onData,\r\n onProgress,\r\n onFileStart,\r\n onFileEnd,\r\n onComplete,\r\n onError,\r\n onDisconnect,\r\n onCancel,\r\n } = opts;\r\n\r\n // Validate required options\r\n if (!code) {\r\n throw new DropgateValidationError('No sharing code was provided.');\r\n }\r\n\r\n if (!Peer) {\r\n throw new DropgateValidationError(\r\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\r\n );\r\n }\r\n\r\n // Check P2P capabilities if serverInfo is provided\r\n const p2pCaps = serverInfo?.capabilities?.p2p;\r\n if (serverInfo && !p2pCaps?.enabled) {\r\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\r\n }\r\n\r\n // Validate and normalize code\r\n const normalizedCode = String(code).trim().replace(/\\s+/g, '').toUpperCase();\r\n if (!isP2PCodeLike(normalizedCode)) {\r\n throw new DropgateValidationError('Invalid direct transfer code.');\r\n }\r\n\r\n // Resolve config from user options and server capabilities\r\n const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(\r\n { peerjsPath, iceServers },\r\n p2pCaps\r\n );\r\n\r\n // Build peer options\r\n const peerOpts = buildPeerOptions({\r\n host,\r\n port,\r\n peerjsPath: finalPath,\r\n secure,\r\n iceServers: finalIceServers,\r\n });\r\n\r\n // Create peer (receiver doesn't need a specific ID)\r\n const peer = new Peer(undefined, peerOpts);\r\n\r\n // State machine - replaces boolean flags to prevent race conditions\r\n let state: P2PReceiveState = 'initializing';\r\n let total = 0;\r\n let received = 0;\r\n let currentSessionId: string | null = null;\r\n let writeQueue = Promise.resolve();\r\n let watchdogTimer: ReturnType<typeof setTimeout> | null = null;\r\n let activeConn: DataConnection | null = null;\r\n\r\n let pendingChunk: P2PChunkMessage | null = null;\r\n\r\n // Multi-file tracking (v3)\r\n let fileList: P2PFileListMessage | null = null;\r\n let currentFileReceived = 0;\r\n let totalReceivedAllFiles = 0;\r\n\r\n /**\r\n * Attempt a state transition. Returns true if transition was valid.\r\n */\r\n const transitionTo = (newState: P2PReceiveState): boolean => {\r\n if (!ALLOWED_TRANSITIONS[state].includes(newState)) {\r\n console.warn(`[P2P Receive] Invalid state transition: ${state} -> ${newState}`);\r\n return false;\r\n }\r\n state = newState;\r\n return true;\r\n };\r\n\r\n // Helper to check if session is stopped\r\n const isStopped = (): boolean => state === 'closed' || state === 'cancelled';\r\n\r\n // Watchdog - detects dead connections during transfer\r\n const resetWatchdog = (): void => {\r\n if (watchdogTimeoutMs <= 0) return;\r\n\r\n if (watchdogTimer) {\r\n clearTimeout(watchdogTimer);\r\n }\r\n\r\n watchdogTimer = setTimeout(() => {\r\n if (state === 'transferring') {\r\n safeError(new DropgateNetworkError('Connection timed out (no data received).'));\r\n }\r\n }, watchdogTimeoutMs);\r\n };\r\n\r\n const clearWatchdog = (): void => {\r\n if (watchdogTimer) {\r\n clearTimeout(watchdogTimer);\r\n watchdogTimer = null;\r\n }\r\n };\r\n\r\n // Safe error handler - prevents calling onError after completion or cancellation\r\n const safeError = (err: Error): void => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') return;\r\n transitionTo('closed');\r\n onError?.(err);\r\n cleanup();\r\n };\r\n\r\n // Safe complete handler - only fires from transferring state\r\n const safeComplete = (completeData: { received: number; total: number }): void => {\r\n if (state !== 'transferring') return;\r\n transitionTo('completed');\r\n onComplete?.(completeData);\r\n // Don't immediately cleanup - let acks be sent first\r\n // The sender will close the connection after receiving ack\r\n // Our close handler will call cleanup when that happens\r\n };\r\n\r\n // Cleanup all resources\r\n const cleanup = (): void => {\r\n clearWatchdog();\r\n\r\n // Remove beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n try {\r\n peer.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n };\r\n\r\n // Handle browser tab close/refresh\r\n const handleUnload = (): void => {\r\n try {\r\n activeConn?.send({ t: 'error', message: 'Receiver closed the connection.' });\r\n } catch {\r\n // Best effort\r\n }\r\n stop();\r\n };\r\n\r\n // Add beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n const stop = (): void => {\r\n if (state === 'closed' || state === 'cancelled') return;\r\n\r\n // If already completed, just cleanup without callbacks\r\n if (state === 'completed') {\r\n cleanup();\r\n return;\r\n }\r\n\r\n const wasActive = state === 'transferring';\r\n transitionTo('cancelled');\r\n\r\n // Notify peer before cleanup\r\n try {\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n if (activeConn && activeConn.open) {\r\n activeConn.send({ t: 'cancelled', reason: 'Receiver cancelled the transfer.' });\r\n }\r\n } catch {\r\n // Best effort\r\n }\r\n\r\n if (wasActive && onCancel) {\r\n onCancel({ cancelledBy: 'receiver' });\r\n }\r\n\r\n cleanup();\r\n };\r\n\r\n // Send chunk acknowledgment\r\n const sendChunkAck = (conn: DataConnection, seq: number): void => {\r\n try {\r\n conn.send({ t: 'chunk_ack', seq, received });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n };\r\n\r\n peer.on('error', (err: Error) => {\r\n safeError(err);\r\n });\r\n\r\n peer.on('open', () => {\r\n transitionTo('connecting');\r\n const conn = peer.connect(normalizedCode, { reliable: true });\r\n activeConn = conn;\r\n\r\n conn.on('open', () => {\r\n transitionTo('handshaking');\r\n onStatus?.({ phase: 'connected', message: 'Connected.' });\r\n\r\n // Send our hello immediately\r\n conn.send({\r\n t: 'hello',\r\n protocolVersion: P2P_PROTOCOL_VERSION,\r\n sessionId: '',\r\n });\r\n });\r\n\r\n conn.on('data', async (data: unknown) => {\r\n try {\r\n // Reset watchdog on any data received\r\n resetWatchdog();\r\n\r\n // Handle binary data - this is file content\r\n if (data instanceof ArrayBuffer || ArrayBuffer.isView(data) ||\r\n (typeof Blob !== 'undefined' && data instanceof Blob)) {\r\n\r\n // Process the binary chunk\r\n let bufPromise: Promise<Uint8Array>;\r\n\r\n if (data instanceof ArrayBuffer) {\r\n bufPromise = Promise.resolve(new Uint8Array(data));\r\n } else if (ArrayBuffer.isView(data)) {\r\n bufPromise = Promise.resolve(\r\n new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\r\n );\r\n } else if (typeof Blob !== 'undefined' && data instanceof Blob) {\r\n bufPromise = data.arrayBuffer().then((buffer) => new Uint8Array(buffer));\r\n } else {\r\n return;\r\n }\r\n\r\n // Queue the write operation\r\n const chunkSeq = pendingChunk?.seq ?? -1;\r\n pendingChunk = null;\r\n\r\n writeQueue = writeQueue\r\n .then(async () => {\r\n const buf = await bufPromise;\r\n\r\n // Call consumer's onData handler (stream-through, no buffering)\r\n if (onData) {\r\n await onData(buf);\r\n }\r\n\r\n received += buf.byteLength;\r\n currentFileReceived += buf.byteLength;\r\n const progressReceived = fileList ? (totalReceivedAllFiles + currentFileReceived) : received;\r\n const progressTotal = fileList ? fileList.totalSize : total;\r\n const percent = progressTotal ? Math.min(100, (progressReceived / progressTotal) * 100) : 0;\r\n if (!isStopped()) onProgress?.({ processedBytes: progressReceived, totalBytes: progressTotal, percent });\r\n\r\n // Send chunk acknowledgment\r\n if (chunkSeq >= 0) {\r\n sendChunkAck(conn, chunkSeq);\r\n }\r\n })\r\n .catch((err) => {\r\n try {\r\n conn.send({\r\n t: 'error',\r\n message: (err as Error)?.message || 'Receiver write failed.',\r\n });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n safeError(err as Error);\r\n });\r\n\r\n return;\r\n }\r\n\r\n // Handle control messages\r\n if (!isP2PMessage(data)) return;\r\n\r\n const msg = data;\r\n\r\n switch (msg.t) {\r\n case 'hello':\r\n currentSessionId = msg.sessionId || null;\r\n transitionTo('negotiating');\r\n onStatus?.({ phase: 'waiting', message: 'Waiting for file details...' });\r\n break;\r\n\r\n case 'file_list':\r\n // v3: Store file list for multi-file transfer\r\n fileList = msg as P2PFileListMessage;\r\n total = fileList.totalSize;\r\n break;\r\n\r\n case 'meta': {\r\n // For multi-file: meta comes for each file (first triggers ready, subsequent auto-transition)\r\n if (state !== 'negotiating' && !(state === 'transferring' && fileList)) {\r\n return;\r\n }\r\n\r\n // Session ID validation - reject if we're busy with a different session\r\n if (currentSessionId && msg.sessionId && msg.sessionId !== currentSessionId) {\r\n try {\r\n conn.send({ t: 'error', message: 'Busy with another session.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n return;\r\n }\r\n\r\n // Store the session ID for this transfer\r\n if (msg.sessionId) {\r\n currentSessionId = msg.sessionId;\r\n }\r\n\r\n const name = String(msg.name || 'file');\r\n const fileSize = Number(msg.size) || 0;\r\n const fi = msg.fileIndex;\r\n\r\n // For multi-file subsequent files, reset per-file tracking\r\n if (fileList && typeof fi === 'number' && fi > 0) {\r\n currentFileReceived = 0;\r\n // Don't reset writeQueue or received - they accumulate\r\n onFileStart?.({ fileIndex: fi, name, size: fileSize });\r\n break; // Already transferring, no need for ready signal\r\n }\r\n\r\n // First file (or single file transfer)\r\n received = 0;\r\n currentFileReceived = 0;\r\n totalReceivedAllFiles = 0;\r\n if (!fileList) {\r\n total = fileSize;\r\n }\r\n writeQueue = Promise.resolve();\r\n\r\n // Function to send ready signal\r\n const sendReady = (): void => {\r\n transitionTo('transferring');\r\n // Start watchdog once we're ready to receive data\r\n resetWatchdog();\r\n // Notify consumer about first file start (for multi-file ZIP assembly)\r\n if (fileList) {\r\n onFileStart?.({ fileIndex: 0, name, size: fileSize });\r\n }\r\n try {\r\n conn.send({ t: 'ready' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n };\r\n\r\n // Build metadata event\r\n const metaEvt: Parameters<NonNullable<typeof onMeta>>[0] = { name, total };\r\n if (fileList) {\r\n metaEvt.fileCount = fileList.fileCount;\r\n metaEvt.files = fileList.files.map(f => ({ name: f.name, size: f.size }));\r\n metaEvt.totalSize = fileList.totalSize;\r\n }\r\n\r\n if (autoReady) {\r\n if (!isStopped()) {\r\n onMeta?.(metaEvt);\r\n onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });\r\n }\r\n sendReady();\r\n } else {\r\n // Pass sendReady function to callback so consumer can trigger transfer start\r\n metaEvt.sendReady = sendReady;\r\n if (!isStopped()) {\r\n onMeta?.(metaEvt);\r\n onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });\r\n }\r\n }\r\n break;\r\n }\r\n\r\n case 'chunk':\r\n pendingChunk = msg as P2PChunkMessage;\r\n break;\r\n\r\n case 'ping':\r\n // Respond to heartbeat - keeps watchdog alive and confirms we're active\r\n try {\r\n conn.send({ t: 'pong', timestamp: Date.now() });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n break;\r\n\r\n case 'file_end': {\r\n // v3: Current file complete, ack it\r\n clearWatchdog();\r\n await writeQueue;\r\n\r\n const feIdx = msg.fileIndex;\r\n onFileEnd?.({ fileIndex: feIdx, receivedBytes: currentFileReceived });\r\n\r\n try {\r\n conn.send({ t: 'file_end_ack', fileIndex: feIdx, received: currentFileReceived, size: currentFileReceived });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n\r\n totalReceivedAllFiles += currentFileReceived;\r\n currentFileReceived = 0;\r\n\r\n // Restart watchdog for next file\r\n resetWatchdog();\r\n break;\r\n }\r\n\r\n case 'end':\r\n clearWatchdog();\r\n await writeQueue;\r\n\r\n // For multi-file, use totalReceivedAllFiles + any remaining\r\n const finalReceived = fileList ? (totalReceivedAllFiles + currentFileReceived) : received;\r\n const finalTotal = fileList ? fileList.totalSize : total;\r\n\r\n if (finalTotal && finalReceived < finalTotal) {\r\n const err = new DropgateNetworkError(\r\n 'Transfer ended before all data was received.'\r\n );\r\n try {\r\n conn.send({ t: 'error', message: err.message });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n throw err;\r\n }\r\n\r\n // Send end_ack immediately so sender can complete\r\n try {\r\n conn.send({ t: 'end_ack', received: finalReceived, total: finalTotal });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n\r\n // Mark as completed - protects against close handler race\r\n safeComplete({ received: finalReceived, total: finalTotal });\r\n\r\n // Send additional acks for reliability (fire-and-forget, best effort)\r\n (async () => {\r\n for (let i = 0; i < 2; i++) {\r\n await sleep(P2P_END_ACK_RETRY_DELAY_MS);\r\n try {\r\n conn.send({ t: 'end_ack', received: finalReceived, total: finalTotal });\r\n } catch {\r\n break; // Connection closed\r\n }\r\n }\r\n })().catch(() => { });\r\n break;\r\n\r\n case 'error':\r\n throw new DropgateNetworkError(msg.message || 'Sender reported an error.');\r\n\r\n case 'cancelled':\r\n if (state === 'cancelled' || state === 'closed' || state === 'completed') return;\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'sender', message: msg.reason });\r\n cleanup();\r\n break;\r\n }\r\n } catch (err) {\r\n safeError(err as Error);\r\n }\r\n });\r\n\r\n conn.on('close', () => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') {\r\n // Clean shutdown or already cancelled, ensure full cleanup\r\n cleanup();\r\n return;\r\n }\r\n\r\n // Sender disconnected or cancelled before transfer completed\r\n if (state === 'transferring') {\r\n // Connection closed during active transfer — the sender either cancelled\r\n // or disconnected. Treat as a sender-initiated cancellation so the UI\r\n // can show a clean message instead of a raw error.\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'sender' });\r\n cleanup();\r\n } else if (state === 'negotiating') {\r\n // We had metadata but transfer hadn't started\r\n transitionTo('closed');\r\n cleanup();\r\n onDisconnect?.();\r\n } else {\r\n // Disconnected before we even got file metadata\r\n safeError(new DropgateNetworkError('Sender disconnected before file details were received.'));\r\n }\r\n });\r\n });\r\n\r\n return {\r\n peer,\r\n stop,\r\n getStatus: () => state,\r\n getBytesReceived: () => received,\r\n getTotalBytes: () => total,\r\n getSessionId: () => currentSessionId,\r\n };\r\n}\r\n"],"mappings":"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","names":["crypto","ALLOWED_TRANSITIONS"]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/utils/network.ts","../../src/adapters/defaults.ts","../../src/p2p/utils.ts","../../src/p2p/helpers.ts","../../src/p2p/protocol.ts","../../src/p2p/send.ts","../../src/p2p/receive.ts"],"sourcesContent":["export interface DropgateErrorOptions {\r\n code?: string;\r\n details?: unknown;\r\n cause?: unknown;\r\n}\r\n\r\n/**\r\n * Base error class for all Dropgate errors\r\n */\r\nexport class DropgateError extends Error {\r\n readonly code: string;\r\n readonly details?: unknown;\r\n\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\r\n this.name = this.constructor.name;\r\n this.code = opts.code || 'DROPGATE_ERROR';\r\n this.details = opts.details;\r\n }\r\n}\r\n\r\n/**\r\n * Validation error for invalid inputs\r\n */\r\nexport class DropgateValidationError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'VALIDATION_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Network error for connection issues\r\n */\r\nexport class DropgateNetworkError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'NETWORK_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Protocol error for server communication issues\r\n */\r\nexport class DropgateProtocolError extends DropgateError {\r\n constructor(message: string, opts: DropgateErrorOptions = {}) {\r\n super(message, { ...opts, code: opts.code || 'PROTOCOL_ERROR' });\r\n }\r\n}\r\n\r\n/**\r\n * Abort error - replacement for DOMException with AbortError name\r\n * Used when operations are cancelled\r\n */\r\nexport class DropgateAbortError extends DropgateError {\r\n constructor(message = 'Operation aborted') {\r\n super(message, { code: 'ABORT_ERROR' });\r\n this.name = 'AbortError';\r\n }\r\n}\r\n\r\n/**\r\n * Timeout error - replacement for DOMException with TimeoutError name\r\n * Used when operations exceed their time limit\r\n */\r\nexport class DropgateTimeoutError extends DropgateError {\r\n constructor(message = 'Request timed out') {\r\n super(message, { code: 'TIMEOUT_ERROR' });\r\n this.name = 'TimeoutError';\r\n }\r\n}\r\n","import { DropgateAbortError, DropgateTimeoutError, DropgateValidationError } from '../errors.js';\r\nimport type { FetchFn, ServerTarget } from '../types.js';\r\n\r\n/**\r\n * Parse a server URL string into host, port, and secure components.\r\n * If no protocol is specified, defaults to HTTPS.\r\n */\r\nexport function parseServerUrl(urlStr: string): ServerTarget {\r\n let normalized = urlStr.trim();\r\n if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {\r\n normalized = 'https://' + normalized;\r\n }\r\n const url = new URL(normalized);\r\n return {\r\n host: url.hostname,\r\n port: url.port ? Number(url.port) : undefined,\r\n secure: url.protocol === 'https:',\r\n };\r\n}\r\n\r\n/**\r\n * Build a base URL from host, port, and secure options.\r\n */\r\nexport function buildBaseUrl(opts: ServerTarget): string {\r\n const { host, port, secure } = opts;\r\n\r\n if (!host || typeof host !== 'string') {\r\n throw new DropgateValidationError('Server host is required.');\r\n }\r\n\r\n const protocol = secure === false ? 'http' : 'https';\r\n const portSuffix = port ? `:${port}` : '';\r\n\r\n return `${protocol}://${host}${portSuffix}`;\r\n}\r\n\r\n/**\r\n * Sleep for a specified duration, with optional abort signal support.\r\n */\r\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (signal?.aborted) {\r\n return reject(signal.reason || new DropgateAbortError());\r\n }\r\n\r\n const t = setTimeout(resolve, ms);\r\n\r\n if (signal) {\r\n signal.addEventListener(\r\n 'abort',\r\n () => {\r\n clearTimeout(t);\r\n reject(signal.reason || new DropgateAbortError());\r\n },\r\n { once: true }\r\n );\r\n }\r\n });\r\n}\r\n\r\nexport interface AbortSignalWithCleanup {\r\n signal: AbortSignal;\r\n cleanup: () => void;\r\n}\r\n\r\n/**\r\n * Create an AbortSignal that combines a parent signal with a timeout.\r\n */\r\nexport function makeAbortSignal(\r\n parentSignal?: AbortSignal | null,\r\n timeoutMs?: number\r\n): AbortSignalWithCleanup {\r\n const controller = new AbortController();\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n\r\n const abort = (reason?: unknown): void => {\r\n if (!controller.signal.aborted) {\r\n controller.abort(reason);\r\n }\r\n };\r\n\r\n if (parentSignal) {\r\n if (parentSignal.aborted) {\r\n abort(parentSignal.reason);\r\n } else {\r\n parentSignal.addEventListener('abort', () => abort(parentSignal.reason), {\r\n once: true,\r\n });\r\n }\r\n }\r\n\r\n if (Number.isFinite(timeoutMs) && timeoutMs! > 0) {\r\n timeoutId = setTimeout(() => {\r\n abort(new DropgateTimeoutError());\r\n }, timeoutMs);\r\n }\r\n\r\n return {\r\n signal: controller.signal,\r\n cleanup: () => {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n },\r\n };\r\n}\r\n\r\nexport interface FetchJsonResult {\r\n res: Response;\r\n json: unknown;\r\n text: string;\r\n}\r\n\r\nexport interface FetchJsonOptions extends Omit<RequestInit, 'signal'> {\r\n timeoutMs?: number;\r\n signal?: AbortSignal;\r\n}\r\n\r\n/**\r\n * Fetch JSON from a URL with timeout and error handling.\r\n */\r\nexport async function fetchJson(\r\n fetchFn: FetchFn,\r\n url: string,\r\n opts: FetchJsonOptions = {}\r\n): Promise<FetchJsonResult> {\r\n const { timeoutMs, signal, ...rest } = opts;\r\n const { signal: s, cleanup } = makeAbortSignal(signal, timeoutMs);\r\n\r\n try {\r\n const res = await fetchFn(url, { ...rest, signal: s });\r\n const text = await res.text();\r\n\r\n let json: unknown = null;\r\n try {\r\n json = text ? JSON.parse(text) : null;\r\n } catch {\r\n // Ignore parse errors - json will remain null\r\n }\r\n\r\n return { res, json, text };\r\n } finally {\r\n cleanup();\r\n }\r\n}\r\n","import type { Base64Adapter, CryptoAdapter, FetchFn } from '../types.js';\r\n\r\n/**\r\n * Get the default Base64 adapter for the current environment.\r\n * Automatically detects Node.js Buffer vs browser btoa/atob.\r\n */\r\nexport function getDefaultBase64(): Base64Adapter {\r\n // Check for Node.js Buffer (works in Node.js and some bundlers)\r\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\r\n return {\r\n encode(bytes: Uint8Array): string {\r\n return Buffer.from(bytes).toString('base64');\r\n },\r\n decode(b64: string): Uint8Array {\r\n return new Uint8Array(Buffer.from(b64, 'base64'));\r\n },\r\n };\r\n }\r\n\r\n // Browser fallback using btoa/atob\r\n if (typeof btoa === 'function' && typeof atob === 'function') {\r\n return {\r\n encode(bytes: Uint8Array): string {\r\n let binary = '';\r\n for (let i = 0; i < bytes.length; i++) {\r\n binary += String.fromCharCode(bytes[i]);\r\n }\r\n return btoa(binary);\r\n },\r\n decode(b64: string): Uint8Array {\r\n const binary = atob(b64);\r\n const out = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i++) {\r\n out[i] = binary.charCodeAt(i);\r\n }\r\n return out;\r\n },\r\n };\r\n }\r\n\r\n throw new Error(\r\n 'No Base64 implementation available. Provide a Base64Adapter via options.'\r\n );\r\n}\r\n\r\n/**\r\n * Get the default crypto object for the current environment.\r\n * Returns globalThis.crypto if available.\r\n */\r\nexport function getDefaultCrypto(): CryptoAdapter | undefined {\r\n return globalThis.crypto as CryptoAdapter | undefined;\r\n}\r\n\r\n/**\r\n * Get the default fetch function for the current environment.\r\n * Returns globalThis.fetch if available.\r\n */\r\nexport function getDefaultFetch(): FetchFn | undefined {\r\n return globalThis.fetch?.bind(globalThis) as FetchFn | undefined;\r\n}\r\n","import type { CryptoAdapter } from '../types.js';\r\nimport { getDefaultCrypto } from '../adapters/defaults.js';\r\n\r\n/**\r\n * Check if a hostname is localhost\r\n */\r\nexport function isLocalhostHostname(hostname: string): boolean {\r\n const host = String(hostname || '').toLowerCase();\r\n return host === 'localhost' || host === '127.0.0.1' || host === '::1';\r\n}\r\n\r\n/**\r\n * Check if the current context allows P2P (HTTPS or localhost)\r\n */\r\nexport function isSecureContextForP2P(\r\n hostname?: string,\r\n isSecureContext?: boolean\r\n): boolean {\r\n return Boolean(isSecureContext) || isLocalhostHostname(hostname || '');\r\n}\r\n\r\n/**\r\n * Generate a P2P sharing code using cryptographically secure random.\r\n * Format: XXXX-0000 (4 letters + 4 digits)\r\n */\r\nexport function generateP2PCode(cryptoObj?: CryptoAdapter): string {\r\n const crypto = cryptoObj || getDefaultCrypto();\r\n const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Excluded I and O to avoid confusion\r\n\r\n if (crypto) {\r\n const randomBytes = new Uint8Array(8);\r\n crypto.getRandomValues(randomBytes);\r\n\r\n let letterPart = '';\r\n for (let i = 0; i < 4; i++) {\r\n letterPart += letters[randomBytes[i] % letters.length];\r\n }\r\n\r\n let numberPart = '';\r\n for (let i = 4; i < 8; i++) {\r\n numberPart += (randomBytes[i] % 10).toString();\r\n }\r\n\r\n return `${letterPart}-${numberPart}`;\r\n }\r\n\r\n // Fallback to Math.random (less secure, but works everywhere)\r\n let a = '';\r\n for (let i = 0; i < 4; i++) {\r\n a += letters[Math.floor(Math.random() * letters.length)];\r\n }\r\n let b = '';\r\n for (let i = 0; i < 4; i++) {\r\n b += Math.floor(Math.random() * 10);\r\n }\r\n return `${a}-${b}`;\r\n}\r\n\r\n/**\r\n * Check if a string looks like a P2P sharing code\r\n */\r\nexport function isP2PCodeLike(code: string): boolean {\r\n return /^[A-Z]{4}-\\d{4}$/.test(String(code || '').trim());\r\n}\r\n","import { DropgateNetworkError } from '../errors.js';\r\nimport type { P2PCapabilities } from '../types.js';\r\nimport type { PeerInstance, PeerOptions, P2PServerConfig } from './types.js';\r\n\r\n/**\r\n * Resolve P2P server configuration from user options and server capabilities.\r\n * User-provided values take precedence over server capabilities.\r\n */\r\nexport function resolvePeerConfig(\r\n userConfig: P2PServerConfig,\r\n serverCaps?: P2PCapabilities\r\n): { path: string; iceServers: RTCIceServer[] } {\r\n return {\r\n path: userConfig.peerjsPath ?? serverCaps?.peerjsPath ?? '/peerjs',\r\n iceServers: userConfig.iceServers ?? serverCaps?.iceServers ?? [],\r\n };\r\n}\r\n\r\n/**\r\n * Build PeerJS connection options from P2P server configuration.\r\n */\r\nexport function buildPeerOptions(config: P2PServerConfig = {}): PeerOptions {\r\n const { host, port, peerjsPath = '/peerjs', secure = false, iceServers = [] } = config;\r\n\r\n const peerOpts: PeerOptions = {\r\n host,\r\n path: peerjsPath,\r\n secure,\r\n config: { iceServers },\r\n debug: 0,\r\n };\r\n\r\n if (port) {\r\n peerOpts.port = port;\r\n }\r\n\r\n return peerOpts;\r\n}\r\n\r\nexport interface CreatePeerWithRetriesOptions {\r\n code?: string | null;\r\n codeGenerator: () => string;\r\n maxAttempts: number;\r\n buildPeer: (id: string) => PeerInstance;\r\n onCode?: (code: string, attempt: number) => void;\r\n}\r\n\r\n/**\r\n * Create a peer with retries if the code is already taken\r\n */\r\nexport async function createPeerWithRetries(\r\n opts: CreatePeerWithRetriesOptions\r\n): Promise<{ peer: PeerInstance; code: string }> {\r\n const { code, codeGenerator, maxAttempts, buildPeer, onCode } = opts;\r\n\r\n let nextCode = code || codeGenerator();\r\n let peer: PeerInstance | null = null;\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\r\n onCode?.(nextCode, attempt);\r\n\r\n try {\r\n peer = await new Promise<PeerInstance>((resolve, reject) => {\r\n const instance = buildPeer(nextCode);\r\n instance.on('open', () => resolve(instance));\r\n instance.on('error', (err: Error) => {\r\n try {\r\n instance.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n reject(err);\r\n });\r\n });\r\n\r\n return { peer, code: nextCode };\r\n } catch (err) {\r\n lastError = err as Error;\r\n nextCode = codeGenerator();\r\n }\r\n }\r\n\r\n throw lastError || new DropgateNetworkError('Could not establish PeerJS connection.');\r\n}\r\n","/**\r\n * P2P Protocol Definitions\r\n *\r\n * This file defines the application-level protocol for P2P file transfers.\r\n * Protocol version 2 introduces:\r\n * - Explicit handshake with version negotiation\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack retries for reliability\r\n * - Resume capability support\r\n *\r\n * Protocol version 3 introduces:\r\n * - Multi-file transfers via file_list, file_end, file_end_ack messages\r\n * - Sequential file-by-file transfer within a single session\r\n */\r\n\r\n// Protocol version for forward compatibility\r\nexport const P2P_PROTOCOL_VERSION = 3;\r\n\r\n/**\r\n * All possible P2P message types.\r\n */\r\nexport type P2PMessageType =\r\n | 'hello' // Initial handshake with protocol version\r\n | 'file_list' // v3: List of files in multi-file transfer\r\n | 'meta' // File metadata (name, size, mime)\r\n | 'ready' // Receiver is ready to receive\r\n | 'chunk' // Data chunk with sequence number\r\n | 'chunk_ack' // Chunk acknowledgment (for flow control)\r\n | 'file_end' // v3: Current file fully sent\r\n | 'file_end_ack' // v3: Current file receipt confirmed\r\n | 'end' // All chunks/files sent\r\n | 'end_ack' // Transfer verified complete\r\n | 'ping' // Heartbeat\r\n | 'pong' // Heartbeat response\r\n | 'error' // Error occurred\r\n | 'cancelled' // User cancelled\r\n | 'resume' // Request to resume from offset\r\n | 'resume_ack'; // Resume position confirmed\r\n\r\n/**\r\n * Base interface for all P2P messages.\r\n */\r\nexport interface P2PMessageBase {\r\n t: P2PMessageType;\r\n}\r\n\r\n/**\r\n * Initial handshake message exchanged between sender and receiver.\r\n */\r\nexport interface P2PHelloMessage extends P2PMessageBase {\r\n t: 'hello';\r\n protocolVersion: number;\r\n sessionId: string;\r\n}\r\n\r\n/**\r\n * v3: File list sent by sender after handshake for multi-file transfers.\r\n */\r\nexport interface P2PFileListMessage extends P2PMessageBase {\r\n t: 'file_list';\r\n fileCount: number;\r\n files: Array<{ name: string; size: number; mime: string }>;\r\n totalSize: number;\r\n}\r\n\r\n/**\r\n * File metadata sent by sender after handshake (single file)\r\n * or before each file's chunks in multi-file mode.\r\n */\r\nexport interface P2PMetaMessage extends P2PMessageBase {\r\n t: 'meta';\r\n sessionId: string;\r\n name: string;\r\n size: number;\r\n mime: string;\r\n /** v3: File index within the file list (0-based). Absent for single-file transfers. */\r\n fileIndex?: number;\r\n}\r\n\r\n/**\r\n * Receiver signals readiness to receive data.\r\n */\r\nexport interface P2PReadyMessage extends P2PMessageBase {\r\n t: 'ready';\r\n}\r\n\r\n/**\r\n * Chunk header sent before binary data.\r\n * The actual binary data follows immediately after this message.\r\n */\r\nexport interface P2PChunkMessage extends P2PMessageBase {\r\n t: 'chunk';\r\n seq: number; // Sequence number for ordering/ack\r\n offset: number; // Byte offset in file\r\n size: number; // Size of this chunk\r\n total: number; // Total file size\r\n}\r\n\r\n/**\r\n * Acknowledgment for a received chunk.\r\n */\r\nexport interface P2PChunkAckMessage extends P2PMessageBase {\r\n t: 'chunk_ack';\r\n seq: number; // Acknowledged sequence number\r\n received: number; // Total bytes received so far\r\n}\r\n\r\n/**\r\n * v3: Sender signals all chunks for the current file have been sent.\r\n */\r\nexport interface P2PFileEndMessage extends P2PMessageBase {\r\n t: 'file_end';\r\n fileIndex: number;\r\n attempt?: number;\r\n}\r\n\r\n/**\r\n * v3: Receiver confirms current file receipt.\r\n */\r\nexport interface P2PFileEndAckMessage extends P2PMessageBase {\r\n t: 'file_end_ack';\r\n fileIndex: number;\r\n received: number;\r\n size: number;\r\n}\r\n\r\n/**\r\n * Sender signals all chunks have been sent.\r\n */\r\nexport interface P2PEndMessage extends P2PMessageBase {\r\n t: 'end';\r\n attempt?: number; // Retry attempt number\r\n}\r\n\r\n/**\r\n * Receiver confirms transfer completion.\r\n */\r\nexport interface P2PEndAckMessage extends P2PMessageBase {\r\n t: 'end_ack';\r\n received: number;\r\n total: number;\r\n}\r\n\r\n/**\r\n * Heartbeat ping.\r\n */\r\nexport interface P2PPingMessage extends P2PMessageBase {\r\n t: 'ping';\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Heartbeat pong response.\r\n */\r\nexport interface P2PPongMessage extends P2PMessageBase {\r\n t: 'pong';\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Error message sent when something goes wrong.\r\n */\r\nexport interface P2PErrorMessage extends P2PMessageBase {\r\n t: 'error';\r\n message: string;\r\n code?: string;\r\n}\r\n\r\n/**\r\n * Cancellation message sent when user cancels transfer.\r\n */\r\nexport interface P2PCancelledMessage extends P2PMessageBase {\r\n t: 'cancelled';\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Resume request sent by receiver to continue interrupted transfer.\r\n */\r\nexport interface P2PResumeMessage extends P2PMessageBase {\r\n t: 'resume';\r\n sessionId: string;\r\n receivedBytes: number;\r\n}\r\n\r\n/**\r\n * Resume acknowledgment from sender.\r\n */\r\nexport interface P2PResumeAckMessage extends P2PMessageBase {\r\n t: 'resume_ack';\r\n resumeFromOffset: number;\r\n accepted: boolean;\r\n}\r\n\r\n/**\r\n * Union type of all possible P2P messages.\r\n */\r\nexport type P2PMessage =\r\n | P2PHelloMessage\r\n | P2PFileListMessage\r\n | P2PMetaMessage\r\n | P2PReadyMessage\r\n | P2PChunkMessage\r\n | P2PChunkAckMessage\r\n | P2PFileEndMessage\r\n | P2PFileEndAckMessage\r\n | P2PEndMessage\r\n | P2PEndAckMessage\r\n | P2PPingMessage\r\n | P2PPongMessage\r\n | P2PErrorMessage\r\n | P2PCancelledMessage\r\n | P2PResumeMessage\r\n | P2PResumeAckMessage;\r\n\r\n/**\r\n * Type guard to check if a value is a valid P2P message.\r\n */\r\nexport function isP2PMessage(value: unknown): value is P2PMessage {\r\n if (!value || typeof value !== 'object') return false;\r\n const msg = value as Record<string, unknown>;\r\n return typeof msg.t === 'string' && [\r\n 'hello', 'file_list', 'meta', 'ready', 'chunk', 'chunk_ack',\r\n 'file_end', 'file_end_ack', 'end', 'end_ack', 'ping', 'pong',\r\n 'error', 'cancelled', 'resume', 'resume_ack'\r\n ].includes(msg.t);\r\n}\r\n\r\n/**\r\n * Check if protocol versions are compatible.\r\n */\r\nexport function isProtocolCompatible(\r\n senderVersion: number,\r\n receiverVersion: number\r\n): boolean {\r\n return senderVersion === receiverVersion;\r\n}\r\n\r\n/**\r\n * Default chunk size for P2P transfers (64KB).\r\n * Smaller than standard upload to reduce latency and improve flow control.\r\n */\r\nexport const P2P_CHUNK_SIZE = 64 * 1024;\r\n\r\n/**\r\n * Default maximum unacknowledged chunks before sender pauses.\r\n * This creates backpressure when receiver is slow.\r\n */\r\nexport const P2P_MAX_UNACKED_CHUNKS = 32;\r\n\r\n/**\r\n * Default timeout for waiting on end acknowledgment (ms).\r\n */\r\nexport const P2P_END_ACK_TIMEOUT_MS = 15000;\r\n\r\n/**\r\n * Number of times to retry sending end message.\r\n */\r\nexport const P2P_END_ACK_RETRIES = 3;\r\n\r\n/**\r\n * Delay between multiple end_ack sends from receiver (ms).\r\n */\r\nexport const P2P_END_ACK_RETRY_DELAY_MS = 100;\r\n\r\n/**\r\n * Grace period after connection close before declaring failure (ms).\r\n * Allows for brief reconnection attempts.\r\n */\r\nexport const P2P_CLOSE_GRACE_PERIOD_MS = 2000;\r\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\r\nimport { sleep } from '../utils/network.js';\r\nimport type {\r\n P2PSendOptions,\r\n P2PSendSession,\r\n P2PSendState,\r\n DataConnection,\r\n P2PConnectionHealthEvent,\r\n} from './types.js';\r\nimport { generateP2PCode } from './utils.js';\r\nimport { buildPeerOptions, createPeerWithRetries, resolvePeerConfig } from './helpers.js';\r\nimport type { FileSource } from '../types.js';\r\nimport {\r\n P2P_PROTOCOL_VERSION,\r\n P2P_CHUNK_SIZE,\r\n P2P_MAX_UNACKED_CHUNKS,\r\n P2P_END_ACK_TIMEOUT_MS,\r\n P2P_END_ACK_RETRIES,\r\n P2P_CLOSE_GRACE_PERIOD_MS,\r\n isP2PMessage,\r\n type P2PChunkAckMessage,\r\n type P2PEndAckMessage,\r\n type P2PFileEndAckMessage,\r\n} from './protocol.js';\r\n\r\n// Timeout for detecting stalled receivers that stop sending acks\r\nconst P2P_UNACKED_CHUNK_TIMEOUT_MS = 30000;\r\n\r\n/**\r\n * Generate a unique session ID for transfer tracking.\r\n */\r\nfunction generateSessionId(): string {\r\n return crypto.randomUUID();\r\n}\r\n\r\n/**\r\n * Allowed state transitions to prevent invalid state changes.\r\n * This enforces a strict state machine where transitions only happen\r\n * in the expected order, preventing race conditions.\r\n */\r\nconst ALLOWED_TRANSITIONS: Record<P2PSendState, P2PSendState[]> = {\r\n initializing: ['listening', 'closed'],\r\n listening: ['handshaking', 'closed', 'cancelled'],\r\n handshaking: ['negotiating', 'closed', 'cancelled'],\r\n negotiating: ['transferring', 'closed', 'cancelled'],\r\n transferring: ['finishing', 'closed', 'cancelled'],\r\n finishing: ['awaiting_ack', 'closed', 'cancelled'],\r\n awaiting_ack: ['completed', 'closed', 'cancelled'],\r\n completed: ['closed'],\r\n cancelled: ['closed'],\r\n closed: [],\r\n};\r\n\r\n/**\r\n * Start a direct transfer (P2P) sender session.\r\n *\r\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor.\r\n * This removes DOM coupling (no script injection).\r\n *\r\n * Protocol v2 features:\r\n * - Explicit version handshake\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack retries for reliability\r\n * - Stream-through design for unlimited file sizes\r\n *\r\n * Example:\r\n * ```js\r\n * import Peer from 'peerjs';\r\n * import { startP2PSend } from '@dropgate/core/p2p';\r\n *\r\n * const session = await startP2PSend({\r\n * file: myFile,\r\n * Peer,\r\n * host: 'dropgate.link',\r\n * secure: true,\r\n * onCode: (code) => console.log('Share this code:', code),\r\n * onProgress: (evt) => console.log(`${evt.percent}% sent`),\r\n * onComplete: () => console.log('Done!'),\r\n * });\r\n * ```\r\n */\r\nexport async function startP2PSend(opts: P2PSendOptions): Promise<P2PSendSession> {\r\n const {\r\n file,\r\n Peer,\r\n serverInfo,\r\n host,\r\n port,\r\n peerjsPath,\r\n secure = false,\r\n iceServers,\r\n codeGenerator,\r\n cryptoObj,\r\n maxAttempts = 4,\r\n chunkSize = P2P_CHUNK_SIZE,\r\n endAckTimeoutMs = P2P_END_ACK_TIMEOUT_MS,\r\n bufferHighWaterMark = 8 * 1024 * 1024,\r\n bufferLowWaterMark = 2 * 1024 * 1024,\r\n heartbeatIntervalMs = 5000,\r\n chunkAcknowledgments = true,\r\n maxUnackedChunks = P2P_MAX_UNACKED_CHUNKS,\r\n onCode,\r\n onStatus,\r\n onProgress,\r\n onComplete,\r\n onError,\r\n onDisconnect,\r\n onCancel,\r\n onConnectionHealth,\r\n } = opts;\r\n\r\n // Normalize to files array\r\n const files: FileSource[] = Array.isArray(file) ? file : [file];\r\n const isMultiFile = files.length > 1;\r\n const totalSize = files.reduce((sum, f) => sum + f.size, 0);\r\n\r\n // Validate required options\r\n if (!files.length) {\r\n throw new DropgateValidationError('At least one file is required.');\r\n }\r\n\r\n if (!Peer) {\r\n throw new DropgateValidationError(\r\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\r\n );\r\n }\r\n\r\n // Check P2P capabilities if serverInfo is provided\r\n const p2pCaps = serverInfo?.capabilities?.p2p;\r\n if (serverInfo && !p2pCaps?.enabled) {\r\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\r\n }\r\n\r\n // Resolve config from user options and server capabilities\r\n const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(\r\n { peerjsPath, iceServers },\r\n p2pCaps\r\n );\r\n\r\n // Build peer options\r\n const peerOpts = buildPeerOptions({\r\n host,\r\n port,\r\n peerjsPath: finalPath,\r\n secure,\r\n iceServers: finalIceServers,\r\n });\r\n\r\n // Create the code generator\r\n const finalCodeGenerator = codeGenerator || (() => generateP2PCode(cryptoObj));\r\n\r\n // Create peer with retries\r\n const buildPeer = (id: string) => new Peer(id, peerOpts);\r\n const { peer, code } = await createPeerWithRetries({\r\n code: null,\r\n codeGenerator: finalCodeGenerator,\r\n maxAttempts,\r\n buildPeer,\r\n onCode,\r\n });\r\n\r\n // Generate unique session ID for this transfer\r\n const sessionId = generateSessionId();\r\n\r\n // State machine - replaces boolean flags to prevent race conditions\r\n let state: P2PSendState = 'listening';\r\n let activeConn: DataConnection | null = null;\r\n let sentBytes = 0;\r\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n let healthCheckTimer: ReturnType<typeof setInterval> | null = null;\r\n let lastActivityTime = Date.now();\r\n\r\n // Chunk acknowledgment tracking\r\n const unackedChunks = new Map<number, { offset: number; size: number; sentAt: number }>();\r\n let nextSeq = 0;\r\n let ackResolvers: Array<() => void> = [];\r\n\r\n // Track if transfer ever started to prevent connection replacement attacks\r\n let transferEverStarted = false;\r\n\r\n // Security: Connection rate limiting to prevent DoS attacks\r\n const connectionAttempts: number[] = []; // Timestamps of recent connection attempts\r\n const MAX_CONNECTION_ATTEMPTS = 10; // Max attempts allowed\r\n const CONNECTION_RATE_WINDOW_MS = 10000; // 10 second sliding window\r\n\r\n /**\r\n * Attempt a state transition. Returns true if transition was valid.\r\n * Logs a warning for invalid transitions but doesn't throw.\r\n */\r\n const transitionTo = (newState: P2PSendState): boolean => {\r\n if (!ALLOWED_TRANSITIONS[state].includes(newState)) {\r\n console.warn(`[P2P Send] Invalid state transition: ${state} -> ${newState}`);\r\n return false;\r\n }\r\n state = newState;\r\n return true;\r\n };\r\n\r\n const reportProgress = (data: { received: number; total: number }): void => {\r\n if (isStopped()) return;\r\n const safeTotal =\r\n Number.isFinite(data.total) && data.total > 0 ? data.total : totalSize;\r\n const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);\r\n const percent = safeTotal ? (safeReceived / safeTotal) * 100 : 0;\r\n onProgress?.({ processedBytes: safeReceived, totalBytes: safeTotal, percent });\r\n };\r\n\r\n // Safe error handler - prevents calling onError after completion or cancellation\r\n const safeError = (err: Error): void => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') return;\r\n transitionTo('closed');\r\n onError?.(err);\r\n cleanup();\r\n };\r\n\r\n // Safe complete handler - only fires from awaiting_ack state\r\n const safeComplete = (): void => {\r\n if (state !== 'awaiting_ack' && state !== 'finishing') return;\r\n transitionTo('completed');\r\n onComplete?.();\r\n cleanup();\r\n };\r\n\r\n // Cleanup all resources\r\n const cleanup = (): void => {\r\n // Clear heartbeat timer\r\n if (heartbeatTimer) {\r\n clearInterval(heartbeatTimer);\r\n heartbeatTimer = null;\r\n }\r\n\r\n // Clear health check timer\r\n if (healthCheckTimer) {\r\n clearInterval(healthCheckTimer);\r\n healthCheckTimer = null;\r\n }\r\n\r\n // Clear any pending ack resolvers\r\n ackResolvers.forEach((resolve) => resolve());\r\n ackResolvers = [];\r\n unackedChunks.clear();\r\n\r\n // Remove beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n try {\r\n activeConn?.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n try {\r\n peer.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n };\r\n\r\n // Handle browser tab close/refresh\r\n const handleUnload = (): void => {\r\n try {\r\n activeConn?.send({ t: 'error', message: 'Sender closed the connection.' });\r\n } catch {\r\n // Best effort\r\n }\r\n stop();\r\n };\r\n\r\n // Add beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n const stop = (): void => {\r\n if (state === 'closed' || state === 'cancelled') return;\r\n\r\n // If already completed, just cleanup without callbacks\r\n if (state === 'completed') {\r\n cleanup();\r\n return;\r\n }\r\n\r\n const wasActive = state === 'transferring' || state === 'finishing' || state === 'awaiting_ack';\r\n transitionTo('cancelled');\r\n\r\n // Notify peer before cleanup\r\n try {\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n if (activeConn && activeConn.open) {\r\n activeConn.send({ t: 'cancelled', message: 'Sender cancelled the transfer.' });\r\n }\r\n } catch {\r\n // Best effort\r\n }\r\n\r\n if (wasActive && onCancel) {\r\n onCancel({ cancelledBy: 'sender' });\r\n }\r\n\r\n cleanup();\r\n };\r\n\r\n // Helper to check if session is stopped - bypasses TypeScript narrowing\r\n // which doesn't understand state can change asynchronously\r\n const isStopped = (): boolean => state === 'closed' || state === 'cancelled';\r\n\r\n // Connection health monitoring\r\n const startHealthMonitoring = (conn: DataConnection): void => {\r\n if (!onConnectionHealth) return;\r\n\r\n healthCheckTimer = setInterval(() => {\r\n if (isStopped()) return;\r\n const dc = conn._dc;\r\n if (!dc) return;\r\n\r\n // Note: iceConnectionState is on RTCPeerConnection, not RTCDataChannel\r\n // We can only report bufferedAmount and readyState from the data channel\r\n const health: P2PConnectionHealthEvent = {\r\n iceConnectionState: (dc.readyState === 'open' ? 'connected' : 'disconnected') as P2PConnectionHealthEvent['iceConnectionState'],\r\n bufferedAmount: dc.bufferedAmount,\r\n lastActivityMs: Date.now() - lastActivityTime,\r\n };\r\n\r\n onConnectionHealth(health);\r\n }, 2000);\r\n };\r\n\r\n // Handle chunk acknowledgment\r\n const handleChunkAck = (msg: P2PChunkAckMessage): void => {\r\n lastActivityTime = Date.now();\r\n unackedChunks.delete(msg.seq);\r\n reportProgress({ received: msg.received, total: totalSize });\r\n\r\n // Resolve any pending waitForAck promises\r\n const resolver = ackResolvers.shift();\r\n if (resolver) resolver();\r\n };\r\n\r\n // Wait for chunk acknowledgment when too many unacked\r\n const waitForAck = (): Promise<void> => {\r\n return new Promise((resolve) => {\r\n ackResolvers.push(resolve);\r\n });\r\n };\r\n\r\n // Send chunk with sequence tracking\r\n const sendChunk = async (conn: DataConnection, data: ArrayBuffer, offset: number, fileTotal?: number): Promise<void> => {\r\n // Wait if too many unacknowledged chunks (flow control)\r\n if (chunkAcknowledgments) {\r\n while (unackedChunks.size >= maxUnackedChunks) {\r\n // Security: Check for stale unacked chunks (receiver stopped responding)\r\n const now = Date.now();\r\n for (const [_seq, chunk] of unackedChunks) {\r\n if (now - chunk.sentAt > P2P_UNACKED_CHUNK_TIMEOUT_MS) {\r\n throw new DropgateNetworkError('Receiver stopped acknowledging chunks');\r\n }\r\n }\r\n\r\n await Promise.race([\r\n waitForAck(),\r\n sleep(1000), // Timeout to prevent deadlock\r\n ]);\r\n if (isStopped()) return;\r\n }\r\n }\r\n\r\n const seq = nextSeq++;\r\n if (chunkAcknowledgments) {\r\n unackedChunks.set(seq, { offset, size: data.byteLength, sentAt: Date.now() });\r\n }\r\n\r\n // Send chunk header then binary data\r\n conn.send({ t: 'chunk', seq, offset, size: data.byteLength, total: fileTotal ?? totalSize });\r\n conn.send(data);\r\n sentBytes += data.byteLength;\r\n\r\n // Buffer-based flow control using data channel thresholds\r\n const dc = conn._dc;\r\n if (dc && bufferHighWaterMark > 0) {\r\n while (dc.bufferedAmount > bufferHighWaterMark) {\r\n await new Promise<void>((resolve) => {\r\n const fallback = setTimeout(resolve, 60);\r\n try {\r\n dc.addEventListener(\r\n 'bufferedamountlow',\r\n () => {\r\n clearTimeout(fallback);\r\n resolve();\r\n },\r\n { once: true }\r\n );\r\n } catch {\r\n // Fallback only\r\n }\r\n });\r\n if (isStopped()) return;\r\n }\r\n }\r\n };\r\n\r\n // Robust end-ack with retries\r\n const waitForEndAck = async (\r\n conn: DataConnection,\r\n ackPromise: Promise<P2PEndAckMessage>\r\n ): Promise<P2PEndAckMessage> => {\r\n const baseTimeout = endAckTimeoutMs;\r\n\r\n for (let attempt = 0; attempt < P2P_END_ACK_RETRIES; attempt++) {\r\n conn.send({ t: 'end', attempt });\r\n\r\n const timeout = baseTimeout * Math.pow(1.5, attempt);\r\n const result = await Promise.race([\r\n ackPromise,\r\n sleep(timeout).then(() => null as P2PEndAckMessage | null),\r\n ]);\r\n\r\n if (result && result.t === 'end_ack') {\r\n return result;\r\n }\r\n\r\n // Check if connection is still alive\r\n if (isStopped()) {\r\n throw new DropgateNetworkError('Connection closed during completion.');\r\n }\r\n }\r\n\r\n throw new DropgateNetworkError('Receiver did not confirm completion after retries.');\r\n };\r\n\r\n peer.on('connection', (conn: DataConnection) => {\r\n if (isStopped()) return;\r\n\r\n // Security: Connection rate limiting\r\n const now = Date.now();\r\n // Remove old attempts outside the sliding window\r\n while (connectionAttempts.length > 0 && connectionAttempts[0] < now - CONNECTION_RATE_WINDOW_MS) {\r\n connectionAttempts.shift();\r\n }\r\n // Check if we've exceeded the rate limit\r\n if (connectionAttempts.length >= MAX_CONNECTION_ATTEMPTS) {\r\n console.warn('[P2P Send] Connection rate limit exceeded, rejecting connection');\r\n try {\r\n conn.send({ t: 'error', message: 'Too many connection attempts. Please wait.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n }\r\n connectionAttempts.push(now);\r\n\r\n // Connection replacement logic - allow new connections if old one is dead\r\n if (activeConn) {\r\n // Check if existing connection is actually still open\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n const isOldConnOpen = activeConn.open !== false;\r\n\r\n if (isOldConnOpen && state === 'transferring') {\r\n // Actively transferring, reject new connection\r\n try {\r\n conn.send({ t: 'error', message: 'Transfer already in progress.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n } else if (!isOldConnOpen) {\r\n // Old connection is dead, clean it up\r\n try {\r\n activeConn.close();\r\n } catch {\r\n // Ignore\r\n }\r\n activeConn = null;\r\n\r\n // Security: Never allow reconnection if transfer ever started\r\n // This prevents race condition attacks where receiver disconnects briefly\r\n // and reconnects to restart transfer and corrupt data\r\n if (transferEverStarted) {\r\n try {\r\n conn.send({ t: 'error', message: 'Transfer already started with another receiver. Cannot reconnect.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n }\r\n\r\n // Reset state to allow new transfer (only if never started transferring)\r\n state = 'listening';\r\n sentBytes = 0;\r\n nextSeq = 0;\r\n unackedChunks.clear();\r\n } else {\r\n // Connection exists but not transferring (maybe in negotiating state)\r\n // Reject to avoid confusion\r\n try {\r\n conn.send({ t: 'error', message: 'Another receiver is already connected.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n try {\r\n conn.close();\r\n } catch {\r\n // Ignore close errors\r\n }\r\n return;\r\n }\r\n }\r\n\r\n activeConn = conn;\r\n transitionTo('handshaking');\r\n if (!isStopped()) onStatus?.({ phase: 'connected', message: 'Receiver connected.' });\r\n lastActivityTime = Date.now();\r\n\r\n let helloResolve: ((version: number) => void) | null = null;\r\n let readyResolve: (() => void) | null = null;\r\n let endAckResolve: ((msg: P2PEndAckMessage) => void) | null = null;\r\n let fileEndAckResolve: ((msg: P2PFileEndAckMessage) => void) | null = null;\r\n\r\n const helloPromise = new Promise<number>((resolve) => {\r\n helloResolve = resolve;\r\n });\r\n\r\n const readyPromise = new Promise<void>((resolve) => {\r\n readyResolve = resolve;\r\n });\r\n\r\n const endAckPromise = new Promise<P2PEndAckMessage>((resolve) => {\r\n endAckResolve = resolve;\r\n });\r\n\r\n conn.on('data', (data: unknown) => {\r\n lastActivityTime = Date.now();\r\n\r\n // Handle binary data (we don't expect binary from receiver)\r\n if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\r\n return;\r\n }\r\n\r\n if (!isP2PMessage(data)) return;\r\n\r\n const msg = data;\r\n\r\n switch (msg.t) {\r\n case 'hello':\r\n helloResolve?.(msg.protocolVersion);\r\n break;\r\n\r\n case 'ready':\r\n if (!isStopped()) onStatus?.({ phase: 'transferring', message: 'Receiver accepted. Starting transfer...' });\r\n readyResolve?.();\r\n break;\r\n\r\n case 'chunk_ack':\r\n handleChunkAck(msg as P2PChunkAckMessage);\r\n break;\r\n\r\n case 'file_end_ack':\r\n fileEndAckResolve?.(msg as P2PFileEndAckMessage);\r\n break;\r\n\r\n case 'end_ack':\r\n endAckResolve?.(msg as P2PEndAckMessage);\r\n break;\r\n\r\n case 'pong':\r\n // Heartbeat response received, connection is alive\r\n break;\r\n\r\n case 'error':\r\n safeError(new DropgateNetworkError(msg.message || 'Receiver reported an error.'));\r\n break;\r\n\r\n case 'cancelled':\r\n if (state === 'cancelled' || state === 'closed' || state === 'completed') return;\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'receiver', message: msg.reason });\r\n cleanup();\r\n break;\r\n }\r\n });\r\n\r\n conn.on('open', async () => {\r\n try {\r\n if (isStopped()) return;\r\n\r\n // Start health monitoring\r\n startHealthMonitoring(conn);\r\n\r\n // Protocol v2: Send hello first\r\n conn.send({\r\n t: 'hello',\r\n protocolVersion: P2P_PROTOCOL_VERSION,\r\n sessionId,\r\n });\r\n\r\n // Wait for receiver's hello (with timeout)\r\n const receiverVersion = await Promise.race([\r\n helloPromise,\r\n sleep(10000).then(() => null as number | null),\r\n ]);\r\n\r\n if (isStopped()) return;\r\n\r\n if (receiverVersion === null) {\r\n throw new DropgateNetworkError('Receiver did not respond to handshake.');\r\n } else if (receiverVersion !== P2P_PROTOCOL_VERSION) {\r\n throw new DropgateNetworkError(\r\n `Protocol version mismatch: sender v${P2P_PROTOCOL_VERSION}, receiver v${receiverVersion}`\r\n );\r\n }\r\n\r\n transitionTo('negotiating');\r\n if (!isStopped()) onStatus?.({ phase: 'waiting', message: 'Connected. Waiting for receiver to accept...' });\r\n\r\n // v3: Send file_list for multi-file transfers\r\n if (isMultiFile) {\r\n conn.send({\r\n t: 'file_list',\r\n fileCount: files.length,\r\n files: files.map(f => ({ name: f.name, size: f.size, mime: f.type || 'application/octet-stream' })),\r\n totalSize,\r\n });\r\n }\r\n\r\n // Send metadata for the first file (or the only file)\r\n conn.send({\r\n t: 'meta',\r\n sessionId,\r\n name: files[0].name,\r\n size: files[0].size,\r\n mime: files[0].type || 'application/octet-stream',\r\n ...(isMultiFile ? { fileIndex: 0 } : {}),\r\n });\r\n\r\n const dc = conn._dc;\r\n\r\n if (dc && Number.isFinite(bufferLowWaterMark)) {\r\n try {\r\n dc.bufferedAmountLowThreshold = bufferLowWaterMark;\r\n } catch {\r\n // Ignore threshold setting errors\r\n }\r\n }\r\n\r\n // Wait for ready signal\r\n await readyPromise;\r\n if (isStopped()) return;\r\n\r\n // Start heartbeat for long transfers\r\n if (heartbeatIntervalMs > 0) {\r\n heartbeatTimer = setInterval(() => {\r\n if (state === 'transferring' || state === 'finishing' || state === 'awaiting_ack') {\r\n try {\r\n conn.send({ t: 'ping', timestamp: Date.now() });\r\n } catch {\r\n // Ignore ping errors\r\n }\r\n }\r\n }, heartbeatIntervalMs);\r\n }\r\n\r\n transitionTo('transferring');\r\n transferEverStarted = true; // Security: Mark that transfer has started\r\n\r\n let overallSentBytes = 0;\r\n\r\n // Send file(s) in chunks\r\n for (let fi = 0; fi < files.length; fi++) {\r\n const currentFile = files[fi];\r\n\r\n // For multi-file (after first file), send meta for subsequent files\r\n if (isMultiFile && fi > 0) {\r\n conn.send({\r\n t: 'meta',\r\n sessionId,\r\n name: currentFile.name,\r\n size: currentFile.size,\r\n mime: currentFile.type || 'application/octet-stream',\r\n fileIndex: fi,\r\n });\r\n }\r\n\r\n // Send this file's chunks\r\n for (let offset = 0; offset < currentFile.size; offset += chunkSize) {\r\n if (isStopped()) return;\r\n\r\n const slice = currentFile.slice(offset, offset + chunkSize);\r\n const buf = await slice.arrayBuffer();\r\n if (isStopped()) return;\r\n\r\n await sendChunk(conn, buf, offset, currentFile.size);\r\n overallSentBytes += buf.byteLength;\r\n reportProgress({ received: overallSentBytes, total: totalSize });\r\n }\r\n\r\n if (isStopped()) return;\r\n\r\n // For multi-file: send file_end and wait for file_end_ack\r\n if (isMultiFile) {\r\n const fileEndAckPromise = new Promise<P2PFileEndAckMessage>((resolve) => {\r\n fileEndAckResolve = resolve;\r\n });\r\n\r\n conn.send({ t: 'file_end', fileIndex: fi });\r\n\r\n const feAck = await Promise.race([\r\n fileEndAckPromise,\r\n sleep(endAckTimeoutMs).then(() => null as P2PFileEndAckMessage | null),\r\n ]);\r\n\r\n if (isStopped()) return;\r\n\r\n if (!feAck) {\r\n throw new DropgateNetworkError(`Receiver did not confirm receipt of file ${fi + 1}/${files.length}.`);\r\n }\r\n }\r\n }\r\n\r\n if (isStopped()) return;\r\n\r\n transitionTo('finishing');\r\n transitionTo('awaiting_ack');\r\n\r\n // Wait for end acknowledgment with retries\r\n const ackResult = await waitForEndAck(conn, endAckPromise);\r\n\r\n if (isStopped()) return;\r\n\r\n const ackTotal = Number(ackResult.total) || totalSize;\r\n const ackReceived = Number(ackResult.received) || 0;\r\n\r\n if (ackTotal && ackReceived < ackTotal) {\r\n throw new DropgateNetworkError('Receiver reported an incomplete transfer.');\r\n }\r\n\r\n reportProgress({ received: ackReceived || ackTotal, total: ackTotal });\r\n safeComplete();\r\n } catch (err) {\r\n safeError(err as Error);\r\n }\r\n });\r\n\r\n conn.on('error', (err: Error) => {\r\n safeError(err);\r\n });\r\n\r\n conn.on('close', () => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') {\r\n // Clean shutdown or already cancelled, ensure full cleanup\r\n cleanup();\r\n return;\r\n }\r\n\r\n // Special handling for awaiting_ack state - give grace period\r\n if (state === 'awaiting_ack') {\r\n // Connection closed while waiting for end_ack\r\n // Give a grace period for the ack to have been processed\r\n setTimeout(() => {\r\n if (state === 'awaiting_ack') {\r\n // Still waiting, treat as failure\r\n safeError(new DropgateNetworkError('Connection closed while awaiting confirmation.'));\r\n }\r\n }, P2P_CLOSE_GRACE_PERIOD_MS);\r\n return;\r\n }\r\n\r\n if (state === 'transferring' || state === 'finishing') {\r\n // Connection closed during active transfer — the receiver either cancelled\r\n // or disconnected. Treat as a receiver-initiated cancellation so the UI\r\n // can reset cleanly instead of showing a raw error.\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'receiver' });\r\n cleanup();\r\n } else {\r\n // Disconnected before transfer started (during waiting/negotiating phase)\r\n // Reset state to allow reconnection\r\n activeConn = null;\r\n state = 'listening';\r\n sentBytes = 0;\r\n nextSeq = 0;\r\n unackedChunks.clear();\r\n onDisconnect?.();\r\n }\r\n });\r\n });\r\n\r\n return {\r\n peer,\r\n code,\r\n sessionId,\r\n stop,\r\n getStatus: () => state,\r\n getBytesSent: () => sentBytes,\r\n getConnectedPeerId: () => {\r\n if (!activeConn) return null;\r\n // @ts-expect-error - peer property exists on PeerJS DataConnection\r\n return activeConn.peer || null;\r\n },\r\n };\r\n}\r\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\r\nimport { sleep } from '../utils/network.js';\r\nimport type { P2PReceiveOptions, P2PReceiveSession, P2PReceiveState, DataConnection } from './types.js';\r\nimport { isP2PCodeLike } from './utils.js';\r\nimport { buildPeerOptions, resolvePeerConfig } from './helpers.js';\r\nimport {\r\n P2P_PROTOCOL_VERSION,\r\n P2P_END_ACK_RETRY_DELAY_MS,\r\n isP2PMessage,\r\n type P2PChunkMessage,\r\n type P2PFileListMessage,\r\n} from './protocol.js';\r\n\r\n/**\r\n * Allowed state transitions to prevent invalid state changes.\r\n */\r\nconst ALLOWED_TRANSITIONS: Record<P2PReceiveState, P2PReceiveState[]> = {\r\n initializing: ['connecting', 'closed'],\r\n connecting: ['handshaking', 'closed', 'cancelled'],\r\n handshaking: ['negotiating', 'closed', 'cancelled'],\r\n negotiating: ['transferring', 'closed', 'cancelled'],\r\n transferring: ['completed', 'closed', 'cancelled'],\r\n completed: ['closed'],\r\n cancelled: ['closed'],\r\n closed: [],\r\n};\r\n\r\n/**\r\n * Start a direct transfer (P2P) receiver session.\r\n *\r\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor and handle file writing.\r\n * This removes DOM coupling (no streamSaver).\r\n *\r\n * Protocol v2 features:\r\n * - Explicit version handshake\r\n * - Chunk-level acknowledgments for flow control\r\n * - Multiple end-ack sends for reliability\r\n * - Stream-through design for unlimited file sizes\r\n *\r\n * Example:\r\n * ```js\r\n * import Peer from 'peerjs';\r\n * import { startP2PReceive } from '@dropgate/core/p2p';\r\n *\r\n * let writer;\r\n * const session = await startP2PReceive({\r\n * code: 'ABCD-1234',\r\n * Peer,\r\n * host: 'dropgate.link',\r\n * secure: true,\r\n * onMeta: ({ name, total }) => {\r\n * // Consumer creates file writer\r\n * writer = createWriteStream(name);\r\n * },\r\n * onData: async (chunk) => {\r\n * // Consumer writes data\r\n * await writer.write(chunk);\r\n * },\r\n * onComplete: () => {\r\n * writer.close();\r\n * console.log('Done!');\r\n * },\r\n * });\r\n * ```\r\n */\r\nexport async function startP2PReceive(opts: P2PReceiveOptions): Promise<P2PReceiveSession> {\r\n const {\r\n code,\r\n Peer,\r\n serverInfo,\r\n host,\r\n port,\r\n peerjsPath,\r\n secure = false,\r\n iceServers,\r\n autoReady = true,\r\n watchdogTimeoutMs = 15000,\r\n onStatus,\r\n onMeta,\r\n onData,\r\n onProgress,\r\n onFileStart,\r\n onFileEnd,\r\n onComplete,\r\n onError,\r\n onDisconnect,\r\n onCancel,\r\n } = opts;\r\n\r\n // Validate required options\r\n if (!code) {\r\n throw new DropgateValidationError('No sharing code was provided.');\r\n }\r\n\r\n if (!Peer) {\r\n throw new DropgateValidationError(\r\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\r\n );\r\n }\r\n\r\n // Check P2P capabilities if serverInfo is provided\r\n const p2pCaps = serverInfo?.capabilities?.p2p;\r\n if (serverInfo && !p2pCaps?.enabled) {\r\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\r\n }\r\n\r\n // Validate and normalize code\r\n const normalizedCode = String(code).trim().replace(/\\s+/g, '').toUpperCase();\r\n if (!isP2PCodeLike(normalizedCode)) {\r\n throw new DropgateValidationError('Invalid direct transfer code.');\r\n }\r\n\r\n // Resolve config from user options and server capabilities\r\n const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(\r\n { peerjsPath, iceServers },\r\n p2pCaps\r\n );\r\n\r\n // Build peer options\r\n const peerOpts = buildPeerOptions({\r\n host,\r\n port,\r\n peerjsPath: finalPath,\r\n secure,\r\n iceServers: finalIceServers,\r\n });\r\n\r\n // Create peer (receiver doesn't need a specific ID)\r\n const peer = new Peer(undefined, peerOpts);\r\n\r\n // State machine - replaces boolean flags to prevent race conditions\r\n let state: P2PReceiveState = 'initializing';\r\n let total = 0;\r\n let received = 0;\r\n let currentSessionId: string | null = null;\r\n let writeQueue = Promise.resolve();\r\n let watchdogTimer: ReturnType<typeof setTimeout> | null = null;\r\n let activeConn: DataConnection | null = null;\r\n\r\n let pendingChunk: P2PChunkMessage | null = null;\r\n\r\n // Multi-file tracking (v3)\r\n let fileList: P2PFileListMessage | null = null;\r\n let currentFileReceived = 0;\r\n let totalReceivedAllFiles = 0;\r\n\r\n // Security: Chunk sequence validation\r\n let expectedChunkSeq = 0;\r\n\r\n // Security: Write queue depth limiting to prevent memory exhaustion\r\n let writeQueueDepth = 0;\r\n const MAX_WRITE_QUEUE_DEPTH = 100;\r\n\r\n // Security: Maximum file count for multi-file transfers\r\n const MAX_FILE_COUNT = 10000;\r\n\r\n /**\r\n * Attempt a state transition. Returns true if transition was valid.\r\n */\r\n const transitionTo = (newState: P2PReceiveState): boolean => {\r\n if (!ALLOWED_TRANSITIONS[state].includes(newState)) {\r\n console.warn(`[P2P Receive] Invalid state transition: ${state} -> ${newState}`);\r\n return false;\r\n }\r\n state = newState;\r\n return true;\r\n };\r\n\r\n // Helper to check if session is stopped\r\n const isStopped = (): boolean => state === 'closed' || state === 'cancelled';\r\n\r\n // Watchdog - detects dead connections during transfer\r\n const resetWatchdog = (): void => {\r\n if (watchdogTimeoutMs <= 0) return;\r\n\r\n if (watchdogTimer) {\r\n clearTimeout(watchdogTimer);\r\n }\r\n\r\n watchdogTimer = setTimeout(() => {\r\n if (state === 'transferring') {\r\n safeError(new DropgateNetworkError('Connection timed out (no data received).'));\r\n }\r\n }, watchdogTimeoutMs);\r\n };\r\n\r\n const clearWatchdog = (): void => {\r\n if (watchdogTimer) {\r\n clearTimeout(watchdogTimer);\r\n watchdogTimer = null;\r\n }\r\n };\r\n\r\n // Safe error handler - prevents calling onError after completion or cancellation\r\n const safeError = (err: Error): void => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') return;\r\n transitionTo('closed');\r\n onError?.(err);\r\n cleanup();\r\n };\r\n\r\n // Safe complete handler - only fires from transferring state\r\n const safeComplete = (completeData: { received: number; total: number }): void => {\r\n if (state !== 'transferring') return;\r\n transitionTo('completed');\r\n onComplete?.(completeData);\r\n // Don't immediately cleanup - let acks be sent first\r\n // The sender will close the connection after receiving ack\r\n // Our close handler will call cleanup when that happens\r\n };\r\n\r\n // Cleanup all resources\r\n const cleanup = (): void => {\r\n clearWatchdog();\r\n\r\n // Remove beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n try {\r\n peer.destroy();\r\n } catch {\r\n // Ignore destroy errors\r\n }\r\n };\r\n\r\n // Handle browser tab close/refresh\r\n const handleUnload = (): void => {\r\n try {\r\n activeConn?.send({ t: 'error', message: 'Receiver closed the connection.' });\r\n } catch {\r\n // Best effort\r\n }\r\n stop();\r\n };\r\n\r\n // Add beforeunload listener if in browser\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('beforeunload', handleUnload);\r\n }\r\n\r\n const stop = (): void => {\r\n if (state === 'closed' || state === 'cancelled') return;\r\n\r\n // If already completed, just cleanup without callbacks\r\n if (state === 'completed') {\r\n cleanup();\r\n return;\r\n }\r\n\r\n const wasActive = state === 'transferring';\r\n transitionTo('cancelled');\r\n\r\n // Notify peer before cleanup\r\n try {\r\n // @ts-expect-error - open property may exist on PeerJS connections\r\n if (activeConn && activeConn.open) {\r\n activeConn.send({ t: 'cancelled', reason: 'Receiver cancelled the transfer.' });\r\n }\r\n } catch {\r\n // Best effort\r\n }\r\n\r\n if (wasActive && onCancel) {\r\n onCancel({ cancelledBy: 'receiver' });\r\n }\r\n\r\n cleanup();\r\n };\r\n\r\n // Send chunk acknowledgment\r\n const sendChunkAck = (conn: DataConnection, seq: number): void => {\r\n try {\r\n conn.send({ t: 'chunk_ack', seq, received });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n };\r\n\r\n peer.on('error', (err: Error) => {\r\n safeError(err);\r\n });\r\n\r\n peer.on('open', () => {\r\n transitionTo('connecting');\r\n const conn = peer.connect(normalizedCode, { reliable: true });\r\n activeConn = conn;\r\n\r\n conn.on('open', () => {\r\n transitionTo('handshaking');\r\n onStatus?.({ phase: 'connected', message: 'Connected.' });\r\n\r\n // Send our hello immediately\r\n conn.send({\r\n t: 'hello',\r\n protocolVersion: P2P_PROTOCOL_VERSION,\r\n sessionId: '',\r\n });\r\n });\r\n\r\n conn.on('data', async (data: unknown) => {\r\n try {\r\n // Note: Watchdog is reset only on actual binary data, not control messages\r\n // This prevents attackers from keeping connections alive with just pings\r\n\r\n // Handle binary data - this is file content\r\n if (data instanceof ArrayBuffer || ArrayBuffer.isView(data) ||\r\n (typeof Blob !== 'undefined' && data instanceof Blob)) {\r\n\r\n // CRITICAL SECURITY: Only accept binary data if we're in 'transferring' state\r\n // This ensures the receiver has:\r\n // 1. Received file metadata (meta message)\r\n // 2. Consented to receive (sent 'ready' signal)\r\n // Without this check, a malicious sender could force data onto the receiver\r\n if (state !== 'transferring') {\r\n throw new DropgateValidationError(\r\n 'Received binary data before transfer was accepted. Possible malicious sender.'\r\n );\r\n }\r\n\r\n // Security: Only reset watchdog on actual binary data (prevents keep-alive attacks)\r\n resetWatchdog();\r\n\r\n // Security: Check write queue depth to prevent memory exhaustion\r\n if (writeQueueDepth >= MAX_WRITE_QUEUE_DEPTH) {\r\n throw new DropgateNetworkError('Write queue overflow - receiver cannot keep up');\r\n }\r\n\r\n // Process the binary chunk\r\n let bufPromise: Promise<Uint8Array>;\r\n\r\n if (data instanceof ArrayBuffer) {\r\n bufPromise = Promise.resolve(new Uint8Array(data));\r\n } else if (ArrayBuffer.isView(data)) {\r\n bufPromise = Promise.resolve(\r\n new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\r\n );\r\n } else if (typeof Blob !== 'undefined' && data instanceof Blob) {\r\n bufPromise = data.arrayBuffer().then((buffer) => new Uint8Array(buffer));\r\n } else {\r\n return;\r\n }\r\n\r\n // Queue the write operation\r\n const chunkSeq = pendingChunk?.seq ?? -1;\r\n const expectedSize = pendingChunk?.size;\r\n pendingChunk = null;\r\n\r\n writeQueueDepth++;\r\n writeQueue = writeQueue\r\n .then(async () => {\r\n const buf = await bufPromise;\r\n\r\n // Security: Validate chunk size matches declared size\r\n if (expectedSize !== undefined && buf.byteLength !== expectedSize) {\r\n throw new DropgateValidationError(\r\n `Chunk size mismatch: expected ${expectedSize}, got ${buf.byteLength}`\r\n );\r\n }\r\n\r\n // Security: Validate we don't receive more than declared total\r\n const newReceived = received + buf.byteLength;\r\n if (total > 0 && newReceived > total) {\r\n throw new DropgateValidationError(\r\n `Received more data than expected: ${newReceived} > ${total}`\r\n );\r\n }\r\n\r\n // Call consumer's onData handler (stream-through, no buffering)\r\n if (onData) {\r\n await onData(buf);\r\n }\r\n\r\n received += buf.byteLength;\r\n currentFileReceived += buf.byteLength;\r\n const progressReceived = fileList ? (totalReceivedAllFiles + currentFileReceived) : received;\r\n const progressTotal = fileList ? fileList.totalSize : total;\r\n const percent = progressTotal ? Math.min(100, (progressReceived / progressTotal) * 100) : 0;\r\n if (!isStopped()) onProgress?.({ processedBytes: progressReceived, totalBytes: progressTotal, percent });\r\n\r\n // Send chunk acknowledgment\r\n if (chunkSeq >= 0) {\r\n sendChunkAck(conn, chunkSeq);\r\n }\r\n })\r\n .catch((err) => {\r\n try {\r\n conn.send({\r\n t: 'error',\r\n message: (err as Error)?.message || 'Receiver write failed.',\r\n });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n safeError(err as Error);\r\n })\r\n .finally(() => {\r\n writeQueueDepth--;\r\n });\r\n\r\n return;\r\n }\r\n\r\n // Handle control messages\r\n if (!isP2PMessage(data)) return;\r\n\r\n const msg = data;\r\n\r\n switch (msg.t) {\r\n case 'hello':\r\n currentSessionId = msg.sessionId || null;\r\n transitionTo('negotiating');\r\n onStatus?.({ phase: 'waiting', message: 'Waiting for file details...' });\r\n break;\r\n\r\n case 'file_list': {\r\n // v3: Store file list for multi-file transfer\r\n const fileListMsg = msg as P2PFileListMessage;\r\n\r\n // Security: Validate file count\r\n if (fileListMsg.fileCount > MAX_FILE_COUNT) {\r\n throw new DropgateValidationError(`Too many files: ${fileListMsg.fileCount}`);\r\n }\r\n\r\n // Security: Validate total size matches sum of file sizes\r\n const sumSize = fileListMsg.files.reduce((sum, f) => sum + f.size, 0);\r\n if (sumSize !== fileListMsg.totalSize) {\r\n throw new DropgateValidationError(\r\n `File list size mismatch: declared ${fileListMsg.totalSize}, actual sum ${sumSize}`\r\n );\r\n }\r\n\r\n fileList = fileListMsg;\r\n total = fileListMsg.totalSize;\r\n break;\r\n }\r\n\r\n case 'meta': {\r\n // For multi-file: meta comes for each file (first triggers ready, subsequent auto-transition)\r\n if (state !== 'negotiating' && !(state === 'transferring' && fileList)) {\r\n return;\r\n }\r\n\r\n // Session ID validation - reject if we're busy with a different session\r\n if (currentSessionId && msg.sessionId && msg.sessionId !== currentSessionId) {\r\n try {\r\n conn.send({ t: 'error', message: 'Busy with another session.' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n return;\r\n }\r\n\r\n // Store the session ID for this transfer\r\n if (msg.sessionId) {\r\n currentSessionId = msg.sessionId;\r\n }\r\n\r\n const name = String(msg.name || 'file');\r\n const fileSize = Number(msg.size) || 0;\r\n const fi = msg.fileIndex;\r\n\r\n // For multi-file subsequent files, reset per-file tracking\r\n if (fileList && typeof fi === 'number' && fi > 0) {\r\n currentFileReceived = 0;\r\n // Don't reset writeQueue or received - they accumulate\r\n onFileStart?.({ fileIndex: fi, name, size: fileSize });\r\n break; // Already transferring, no need for ready signal\r\n }\r\n\r\n // First file (or single file transfer)\r\n received = 0;\r\n currentFileReceived = 0;\r\n totalReceivedAllFiles = 0;\r\n if (!fileList) {\r\n total = fileSize;\r\n }\r\n writeQueue = Promise.resolve();\r\n\r\n // Function to send ready signal\r\n const sendReady = (): void => {\r\n transitionTo('transferring');\r\n // Start watchdog once we're ready to receive data\r\n resetWatchdog();\r\n // Notify consumer about first file start (for multi-file ZIP assembly)\r\n if (fileList) {\r\n onFileStart?.({ fileIndex: 0, name, size: fileSize });\r\n }\r\n try {\r\n conn.send({ t: 'ready' });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n };\r\n\r\n // Build metadata event\r\n const metaEvt: Parameters<NonNullable<typeof onMeta>>[0] = { name, total };\r\n if (fileList) {\r\n metaEvt.fileCount = fileList.fileCount;\r\n metaEvt.files = fileList.files.map(f => ({ name: f.name, size: f.size }));\r\n metaEvt.totalSize = fileList.totalSize;\r\n }\r\n\r\n if (autoReady) {\r\n if (!isStopped()) {\r\n onMeta?.(metaEvt);\r\n onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });\r\n }\r\n sendReady();\r\n } else {\r\n // Pass sendReady function to callback so consumer can trigger transfer start\r\n metaEvt.sendReady = sendReady;\r\n if (!isStopped()) {\r\n onMeta?.(metaEvt);\r\n onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });\r\n }\r\n }\r\n break;\r\n }\r\n\r\n case 'chunk': {\r\n const chunkMsg = msg as P2PChunkMessage;\r\n\r\n // Security: Only accept chunk messages if we're in 'transferring' state\r\n if (state !== 'transferring') {\r\n throw new DropgateValidationError(\r\n 'Received chunk message before transfer was accepted.'\r\n );\r\n }\r\n\r\n // Security: Validate chunk sequence (must be in order)\r\n if (chunkMsg.seq !== expectedChunkSeq) {\r\n throw new DropgateValidationError(\r\n `Chunk sequence error: expected ${expectedChunkSeq}, got ${chunkMsg.seq}`\r\n );\r\n }\r\n expectedChunkSeq++;\r\n\r\n pendingChunk = chunkMsg;\r\n break;\r\n }\r\n\r\n case 'ping':\r\n // Respond to heartbeat - keeps watchdog alive and confirms we're active\r\n try {\r\n conn.send({ t: 'pong', timestamp: Date.now() });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n break;\r\n\r\n case 'file_end': {\r\n // v3: Current file complete, ack it\r\n clearWatchdog();\r\n await writeQueue;\r\n\r\n const feIdx = msg.fileIndex;\r\n onFileEnd?.({ fileIndex: feIdx, receivedBytes: currentFileReceived });\r\n\r\n try {\r\n conn.send({ t: 'file_end_ack', fileIndex: feIdx, received: currentFileReceived, size: currentFileReceived });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n\r\n totalReceivedAllFiles += currentFileReceived;\r\n currentFileReceived = 0;\r\n\r\n // Restart watchdog for next file\r\n resetWatchdog();\r\n break;\r\n }\r\n\r\n case 'end':\r\n clearWatchdog();\r\n await writeQueue;\r\n\r\n // For multi-file, use totalReceivedAllFiles + any remaining\r\n const finalReceived = fileList ? (totalReceivedAllFiles + currentFileReceived) : received;\r\n const finalTotal = fileList ? fileList.totalSize : total;\r\n\r\n if (finalTotal && finalReceived < finalTotal) {\r\n const err = new DropgateNetworkError(\r\n 'Transfer ended before all data was received.'\r\n );\r\n try {\r\n conn.send({ t: 'error', message: err.message });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n throw err;\r\n }\r\n\r\n // Send end_ack immediately so sender can complete\r\n try {\r\n conn.send({ t: 'end_ack', received: finalReceived, total: finalTotal });\r\n } catch {\r\n // Ignore send errors\r\n }\r\n\r\n // Mark as completed - protects against close handler race\r\n safeComplete({ received: finalReceived, total: finalTotal });\r\n\r\n // Send additional acks for reliability (fire-and-forget, best effort)\r\n (async () => {\r\n for (let i = 0; i < 2; i++) {\r\n await sleep(P2P_END_ACK_RETRY_DELAY_MS);\r\n try {\r\n conn.send({ t: 'end_ack', received: finalReceived, total: finalTotal });\r\n } catch {\r\n break; // Connection closed\r\n }\r\n }\r\n })().catch(() => { });\r\n break;\r\n\r\n case 'error':\r\n throw new DropgateNetworkError(msg.message || 'Sender reported an error.');\r\n\r\n case 'cancelled':\r\n if (state === 'cancelled' || state === 'closed' || state === 'completed') return;\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'sender', message: msg.reason });\r\n cleanup();\r\n break;\r\n }\r\n } catch (err) {\r\n safeError(err as Error);\r\n }\r\n });\r\n\r\n conn.on('close', () => {\r\n if (state === 'closed' || state === 'completed' || state === 'cancelled') {\r\n // Clean shutdown or already cancelled, ensure full cleanup\r\n cleanup();\r\n return;\r\n }\r\n\r\n // Sender disconnected or cancelled before transfer completed\r\n if (state === 'transferring') {\r\n // Connection closed during active transfer — the sender either cancelled\r\n // or disconnected. Treat as a sender-initiated cancellation so the UI\r\n // can show a clean message instead of a raw error.\r\n transitionTo('cancelled');\r\n onCancel?.({ cancelledBy: 'sender' });\r\n cleanup();\r\n } else if (state === 'negotiating') {\r\n // We had metadata but transfer hadn't started\r\n transitionTo('closed');\r\n cleanup();\r\n onDisconnect?.();\r\n } else {\r\n // Disconnected before we even got file metadata\r\n safeError(new DropgateNetworkError('Sender disconnected before file details were received.'));\r\n }\r\n });\r\n });\r\n\r\n return {\r\n peer,\r\n stop,\r\n getStatus: () => state,\r\n getBytesReceived: () => received,\r\n getTotalBytes: () => total,\r\n getSessionId: () => currentSessionId,\r\n };\r\n}\r\n"],"mappings":";;;;;AASO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAIvC,YAAY,SAAiB,OAA6B,CAAC,GAAG;AAC5D,UAAM,SAAS,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,MAAS;AAJ7E,wBAAS;AACT,wBAAS;AAIP,SAAK,OAAO,KAAK,YAAY;AAC7B,SAAK,OAAO,KAAK,QAAQ;AACzB,SAAK,UAAU,KAAK;AAAA,EACtB;AACF;AAKO,IAAM,0BAAN,cAAsC,cAAc;AAAA,EACzD,YAAY,SAAiB,OAA6B,CAAC,GAAG;AAC5D,UAAM,SAAS,EAAE,GAAG,MAAM,MAAM,KAAK,QAAQ,mBAAmB,CAAC;AAAA,EACnE;AACF;AAKO,IAAM,uBAAN,cAAmC,cAAc;AAAA,EACtD,YAAY,SAAiB,OAA6B,CAAC,GAAG;AAC5D,UAAM,SAAS,EAAE,GAAG,MAAM,MAAM,KAAK,QAAQ,gBAAgB,CAAC;AAAA,EAChE;AACF;AAeO,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,YAAY,UAAU,qBAAqB;AACzC,UAAM,SAAS,EAAE,MAAM,cAAc,CAAC;AACtC,SAAK,OAAO;AAAA,EACd;AACF;;;AClBO,SAAS,MAAM,IAAY,QAAqC;AACrE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO,OAAO,UAAU,IAAI,mBAAmB,CAAC;AAAA,IACzD;AAEA,UAAM,IAAI,WAAW,SAAS,EAAE;AAEhC,QAAI,QAAQ;AACV,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AACJ,uBAAa,CAAC;AACd,iBAAO,OAAO,UAAU,IAAI,mBAAmB,CAAC;AAAA,QAClD;AAAA,QACA,EAAE,MAAM,KAAK;AAAA,MACf;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;ACTO,SAAS,mBAA8C;AAC5D,SAAO,WAAW;AACpB;;;AC7CO,SAAS,oBAAoB,UAA2B;AAC7D,QAAM,OAAO,OAAO,YAAY,EAAE,EAAE,YAAY;AAChD,SAAO,SAAS,eAAe,SAAS,eAAe,SAAS;AAClE;AAKO,SAAS,sBACd,UACA,iBACS;AACT,SAAO,QAAQ,eAAe,KAAK,oBAAoB,YAAY,EAAE;AACvE;AAMO,SAAS,gBAAgB,WAAmC;AACjE,QAAMA,UAAS,aAAa,iBAAiB;AAC7C,QAAM,UAAU;AAEhB,MAAIA,SAAQ;AACV,UAAM,cAAc,IAAI,WAAW,CAAC;AACpC,IAAAA,QAAO,gBAAgB,WAAW;AAElC,QAAI,aAAa;AACjB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,oBAAc,QAAQ,YAAY,CAAC,IAAI,QAAQ,MAAM;AAAA,IACvD;AAEA,QAAI,aAAa;AACjB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,qBAAe,YAAY,CAAC,IAAI,IAAI,SAAS;AAAA,IAC/C;AAEA,WAAO,GAAG,UAAU,IAAI,UAAU;AAAA,EACpC;AAGA,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,SAAK,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC;AAAA,EACzD;AACA,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,SAAK,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAAA,EACpC;AACA,SAAO,GAAG,CAAC,IAAI,CAAC;AAClB;AAKO,SAAS,cAAc,MAAuB;AACnD,SAAO,mBAAmB,KAAK,OAAO,QAAQ,EAAE,EAAE,KAAK,CAAC;AAC1D;;;ACvDO,SAAS,kBACd,YACA,YAC8C;AAC9C,SAAO;AAAA,IACL,MAAM,WAAW,cAAc,YAAY,cAAc;AAAA,IACzD,YAAY,WAAW,cAAc,YAAY,cAAc,CAAC;AAAA,EAClE;AACF;AAKO,SAAS,iBAAiB,SAA0B,CAAC,GAAgB;AAC1E,QAAM,EAAE,MAAM,MAAM,aAAa,WAAW,SAAS,OAAO,aAAa,CAAC,EAAE,IAAI;AAEhF,QAAM,WAAwB;AAAA,IAC5B;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,QAAQ,EAAE,WAAW;AAAA,IACrB,OAAO;AAAA,EACT;AAEA,MAAI,MAAM;AACR,aAAS,OAAO;AAAA,EAClB;AAEA,SAAO;AACT;AAaA,eAAsB,sBACpB,MAC+C;AAC/C,QAAM,EAAE,MAAM,eAAe,aAAa,WAAW,OAAO,IAAI;AAEhE,MAAI,WAAW,QAAQ,cAAc;AACrC,MAAI,OAA4B;AAChC,MAAI,YAA0B;AAE9B,WAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,aAAS,UAAU,OAAO;AAE1B,QAAI;AACF,aAAO,MAAM,IAAI,QAAsB,CAAC,SAAS,WAAW;AAC1D,cAAM,WAAW,UAAU,QAAQ;AACnC,iBAAS,GAAG,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAC3C,iBAAS,GAAG,SAAS,CAAC,QAAe;AACnC,cAAI;AACF,qBAAS,QAAQ;AAAA,UACnB,QAAQ;AAAA,UAER;AACA,iBAAO,GAAG;AAAA,QACZ,CAAC;AAAA,MACH,CAAC;AAED,aAAO,EAAE,MAAM,MAAM,SAAS;AAAA,IAChC,SAAS,KAAK;AACZ,kBAAY;AACZ,iBAAW,cAAc;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,IAAI,qBAAqB,wCAAwC;AACtF;;;ACpEO,IAAM,uBAAuB;AA0M7B,SAAS,aAAa,OAAqC;AAC9D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAM;AACZ,SAAO,OAAO,IAAI,MAAM,YAAY;AAAA,IAChC;AAAA,IAAS;AAAA,IAAa;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAS;AAAA,IAChD;AAAA,IAAY;AAAA,IAAgB;AAAA,IAAO;AAAA,IAAW;AAAA,IAAQ;AAAA,IACtD;AAAA,IAAS;AAAA,IAAa;AAAA,IAAU;AAAA,EACpC,EAAE,SAAS,IAAI,CAAC;AACpB;AAKO,SAAS,qBACZ,eACA,iBACO;AACP,SAAO,kBAAkB;AAC7B;AAMO,IAAM,iBAAiB,KAAK;AAM5B,IAAM,yBAAyB;AAK/B,IAAM,yBAAyB;AAK/B,IAAM,sBAAsB;AAK5B,IAAM,6BAA6B;AAMnC,IAAM,4BAA4B;;;ACnPzC,IAAM,+BAA+B;AAKrC,SAAS,oBAA4B;AACnC,SAAO,OAAO,WAAW;AAC3B;AAOA,IAAM,sBAA4D;AAAA,EAChE,cAAc,CAAC,aAAa,QAAQ;AAAA,EACpC,WAAW,CAAC,eAAe,UAAU,WAAW;AAAA,EAChD,aAAa,CAAC,eAAe,UAAU,WAAW;AAAA,EAClD,aAAa,CAAC,gBAAgB,UAAU,WAAW;AAAA,EACnD,cAAc,CAAC,aAAa,UAAU,WAAW;AAAA,EACjD,WAAW,CAAC,gBAAgB,UAAU,WAAW;AAAA,EACjD,cAAc,CAAC,aAAa,UAAU,WAAW;AAAA,EACjD,WAAW,CAAC,QAAQ;AAAA,EACpB,WAAW,CAAC,QAAQ;AAAA,EACpB,QAAQ,CAAC;AACX;AA8BA,eAAsB,aAAa,MAA+C;AAChF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,sBAAsB,IAAI,OAAO;AAAA,IACjC,qBAAqB,IAAI,OAAO;AAAA,IAChC,sBAAsB;AAAA,IACtB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,QAAsB,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAC9D,QAAM,cAAc,MAAM,SAAS;AACnC,QAAM,YAAY,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAG1D,MAAI,CAAC,MAAM,QAAQ;AACjB,UAAM,IAAI,wBAAwB,gCAAgC;AAAA,EACpE;AAEA,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,YAAY,cAAc;AAC1C,MAAI,cAAc,CAAC,SAAS,SAAS;AACnC,UAAM,IAAI,wBAAwB,6CAA6C;AAAA,EACjF;AAGA,QAAM,EAAE,MAAM,WAAW,YAAY,gBAAgB,IAAI;AAAA,IACvD,EAAE,YAAY,WAAW;AAAA,IACzB;AAAA,EACF;AAGA,QAAM,WAAW,iBAAiB;AAAA,IAChC;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,EACd,CAAC;AAGD,QAAM,qBAAqB,kBAAkB,MAAM,gBAAgB,SAAS;AAG5E,QAAM,YAAY,CAAC,OAAe,IAAI,KAAK,IAAI,QAAQ;AACvD,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,sBAAsB;AAAA,IACjD,MAAM;AAAA,IACN,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,YAAY,kBAAkB;AAGpC,MAAI,QAAsB;AAC1B,MAAI,aAAoC;AACxC,MAAI,YAAY;AAChB,MAAI,iBAAwD;AAC5D,MAAI,mBAA0D;AAC9D,MAAI,mBAAmB,KAAK,IAAI;AAGhC,QAAM,gBAAgB,oBAAI,IAA8D;AACxF,MAAI,UAAU;AACd,MAAI,eAAkC,CAAC;AAGvC,MAAI,sBAAsB;AAG1B,QAAM,qBAA+B,CAAC;AACtC,QAAM,0BAA0B;AAChC,QAAM,4BAA4B;AAMlC,QAAM,eAAe,CAAC,aAAoC;AACxD,QAAI,CAAC,oBAAoB,KAAK,EAAE,SAAS,QAAQ,GAAG;AAClD,cAAQ,KAAK,wCAAwC,KAAK,OAAO,QAAQ,EAAE;AAC3E,aAAO;AAAA,IACT;AACA,YAAQ;AACR,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC,SAAoD;AAC1E,QAAI,UAAU,EAAG;AACjB,UAAM,YACJ,OAAO,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,IAAI,KAAK,QAAQ;AAC/D,UAAM,eAAe,KAAK,IAAI,OAAO,KAAK,QAAQ,KAAK,GAAG,aAAa,CAAC;AACxE,UAAM,UAAU,YAAa,eAAe,YAAa,MAAM;AAC/D,iBAAa,EAAE,gBAAgB,cAAc,YAAY,WAAW,QAAQ,CAAC;AAAA,EAC/E;AAGA,QAAM,YAAY,CAAC,QAAqB;AACtC,QAAI,UAAU,YAAY,UAAU,eAAe,UAAU,YAAa;AAC1E,iBAAa,QAAQ;AACrB,cAAU,GAAG;AACb,YAAQ;AAAA,EACV;AAGA,QAAM,eAAe,MAAY;AAC/B,QAAI,UAAU,kBAAkB,UAAU,YAAa;AACvD,iBAAa,WAAW;AACxB,iBAAa;AACb,YAAQ;AAAA,EACV;AAGA,QAAM,UAAU,MAAY;AAE1B,QAAI,gBAAgB;AAClB,oBAAc,cAAc;AAC5B,uBAAiB;AAAA,IACnB;AAGA,QAAI,kBAAkB;AACpB,oBAAc,gBAAgB;AAC9B,yBAAmB;AAAA,IACrB;AAGA,iBAAa,QAAQ,CAAC,YAAY,QAAQ,CAAC;AAC3C,mBAAe,CAAC;AAChB,kBAAc,MAAM;AAGpB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,oBAAoB,gBAAgB,YAAY;AAAA,IACzD;AAEA,QAAI;AACF,kBAAY,MAAM;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,eAAe,MAAY;AAC/B,QAAI;AACF,kBAAY,KAAK,EAAE,GAAG,SAAS,SAAS,gCAAgC,CAAC;AAAA,IAC3E,QAAQ;AAAA,IAER;AACA,SAAK;AAAA,EACP;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,iBAAiB,gBAAgB,YAAY;AAAA,EACtD;AAEA,QAAM,OAAO,MAAY;AACvB,QAAI,UAAU,YAAY,UAAU,YAAa;AAGjD,QAAI,UAAU,aAAa;AACzB,cAAQ;AACR;AAAA,IACF;AAEA,UAAM,YAAY,UAAU,kBAAkB,UAAU,eAAe,UAAU;AACjF,iBAAa,WAAW;AAGxB,QAAI;AAEF,UAAI,cAAc,WAAW,MAAM;AACjC,mBAAW,KAAK,EAAE,GAAG,aAAa,SAAS,iCAAiC,CAAC;AAAA,MAC/E;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,aAAa,UAAU;AACzB,eAAS,EAAE,aAAa,SAAS,CAAC;AAAA,IACpC;AAEA,YAAQ;AAAA,EACV;AAIA,QAAM,YAAY,MAAe,UAAU,YAAY,UAAU;AAGjE,QAAM,wBAAwB,CAAC,SAA+B;AAC5D,QAAI,CAAC,mBAAoB;AAEzB,uBAAmB,YAAY,MAAM;AACnC,UAAI,UAAU,EAAG;AACjB,YAAM,KAAK,KAAK;AAChB,UAAI,CAAC,GAAI;AAIT,YAAM,SAAmC;AAAA,QACvC,oBAAqB,GAAG,eAAe,SAAS,cAAc;AAAA,QAC9D,gBAAgB,GAAG;AAAA,QACnB,gBAAgB,KAAK,IAAI,IAAI;AAAA,MAC/B;AAEA,yBAAmB,MAAM;AAAA,IAC3B,GAAG,GAAI;AAAA,EACT;AAGA,QAAM,iBAAiB,CAAC,QAAkC;AACxD,uBAAmB,KAAK,IAAI;AAC5B,kBAAc,OAAO,IAAI,GAAG;AAC5B,mBAAe,EAAE,UAAU,IAAI,UAAU,OAAO,UAAU,CAAC;AAG3D,UAAM,WAAW,aAAa,MAAM;AACpC,QAAI,SAAU,UAAS;AAAA,EACzB;AAGA,QAAM,aAAa,MAAqB;AACtC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,mBAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAGA,QAAM,YAAY,OAAO,MAAsB,MAAmB,QAAgB,cAAsC;AAEtH,QAAI,sBAAsB;AACxB,aAAO,cAAc,QAAQ,kBAAkB;AAE7C,cAAM,MAAM,KAAK,IAAI;AACrB,mBAAW,CAAC,MAAM,KAAK,KAAK,eAAe;AACzC,cAAI,MAAM,MAAM,SAAS,8BAA8B;AACrD,kBAAM,IAAI,qBAAqB,uCAAuC;AAAA,UACxE;AAAA,QACF;AAEA,cAAM,QAAQ,KAAK;AAAA,UACjB,WAAW;AAAA,UACX,MAAM,GAAI;AAAA;AAAA,QACZ,CAAC;AACD,YAAI,UAAU,EAAG;AAAA,MACnB;AAAA,IACF;AAEA,UAAM,MAAM;AACZ,QAAI,sBAAsB;AACxB,oBAAc,IAAI,KAAK,EAAE,QAAQ,MAAM,KAAK,YAAY,QAAQ,KAAK,IAAI,EAAE,CAAC;AAAA,IAC9E;AAGA,SAAK,KAAK,EAAE,GAAG,SAAS,KAAK,QAAQ,MAAM,KAAK,YAAY,OAAO,aAAa,UAAU,CAAC;AAC3F,SAAK,KAAK,IAAI;AACd,iBAAa,KAAK;AAGlB,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,sBAAsB,GAAG;AACjC,aAAO,GAAG,iBAAiB,qBAAqB;AAC9C,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,gBAAM,WAAW,WAAW,SAAS,EAAE;AACvC,cAAI;AACF,eAAG;AAAA,cACD;AAAA,cACA,MAAM;AACJ,6BAAa,QAAQ;AACrB,wBAAQ;AAAA,cACV;AAAA,cACA,EAAE,MAAM,KAAK;AAAA,YACf;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF,CAAC;AACD,YAAI,UAAU,EAAG;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,OACpB,MACA,eAC8B;AAC9B,UAAM,cAAc;AAEpB,aAAS,UAAU,GAAG,UAAU,qBAAqB,WAAW;AAC9D,WAAK,KAAK,EAAE,GAAG,OAAO,QAAQ,CAAC;AAE/B,YAAM,UAAU,cAAc,KAAK,IAAI,KAAK,OAAO;AACnD,YAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,QAChC;AAAA,QACA,MAAM,OAAO,EAAE,KAAK,MAAM,IAA+B;AAAA,MAC3D,CAAC;AAED,UAAI,UAAU,OAAO,MAAM,WAAW;AACpC,eAAO;AAAA,MACT;AAGA,UAAI,UAAU,GAAG;AACf,cAAM,IAAI,qBAAqB,sCAAsC;AAAA,MACvE;AAAA,IACF;AAEA,UAAM,IAAI,qBAAqB,oDAAoD;AAAA,EACrF;AAEA,OAAK,GAAG,cAAc,CAAC,SAAyB;AAC9C,QAAI,UAAU,EAAG;AAGjB,UAAM,MAAM,KAAK,IAAI;AAErB,WAAO,mBAAmB,SAAS,KAAK,mBAAmB,CAAC,IAAI,MAAM,2BAA2B;AAC/F,yBAAmB,MAAM;AAAA,IAC3B;AAEA,QAAI,mBAAmB,UAAU,yBAAyB;AACxD,cAAQ,KAAK,iEAAiE;AAC9E,UAAI;AACF,aAAK,KAAK,EAAE,GAAG,SAAS,SAAS,6CAA6C,CAAC;AAAA,MACjF,QAAQ;AAAA,MAER;AACA,UAAI;AACF,aAAK,MAAM;AAAA,MACb,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AACA,uBAAmB,KAAK,GAAG;AAG3B,QAAI,YAAY;AAGd,YAAM,gBAAgB,WAAW,SAAS;AAE1C,UAAI,iBAAiB,UAAU,gBAAgB;AAE7C,YAAI;AACF,eAAK,KAAK,EAAE,GAAG,SAAS,SAAS,gCAAgC,CAAC;AAAA,QACpE,QAAQ;AAAA,QAER;AACA,YAAI;AACF,eAAK,MAAM;AAAA,QACb,QAAQ;AAAA,QAER;AACA;AAAA,MACF,WAAW,CAAC,eAAe;AAEzB,YAAI;AACF,qBAAW,MAAM;AAAA,QACnB,QAAQ;AAAA,QAER;AACA,qBAAa;AAKb,YAAI,qBAAqB;AACvB,cAAI;AACF,iBAAK,KAAK,EAAE,GAAG,SAAS,SAAS,oEAAoE,CAAC;AAAA,UACxG,QAAQ;AAAA,UAER;AACA,cAAI;AACF,iBAAK,MAAM;AAAA,UACb,QAAQ;AAAA,UAER;AACA;AAAA,QACF;AAGA,gBAAQ;AACR,oBAAY;AACZ,kBAAU;AACV,sBAAc,MAAM;AAAA,MACtB,OAAO;AAGL,YAAI;AACF,eAAK,KAAK,EAAE,GAAG,SAAS,SAAS,yCAAyC,CAAC;AAAA,QAC7E,QAAQ;AAAA,QAER;AACA,YAAI;AACF,eAAK,MAAM;AAAA,QACb,QAAQ;AAAA,QAER;AACA;AAAA,MACF;AAAA,IACF;AAEA,iBAAa;AACb,iBAAa,aAAa;AAC1B,QAAI,CAAC,UAAU,EAAG,YAAW,EAAE,OAAO,aAAa,SAAS,sBAAsB,CAAC;AACnF,uBAAmB,KAAK,IAAI;AAE5B,QAAI,eAAmD;AACvD,QAAI,eAAoC;AACxC,QAAI,gBAA0D;AAC9D,QAAI,oBAAkE;AAEtE,UAAM,eAAe,IAAI,QAAgB,CAAC,YAAY;AACpD,qBAAe;AAAA,IACjB,CAAC;AAED,UAAM,eAAe,IAAI,QAAc,CAAC,YAAY;AAClD,qBAAe;AAAA,IACjB,CAAC;AAED,UAAM,gBAAgB,IAAI,QAA0B,CAAC,YAAY;AAC/D,sBAAgB;AAAA,IAClB,CAAC;AAED,SAAK,GAAG,QAAQ,CAAC,SAAkB;AACjC,yBAAmB,KAAK,IAAI;AAG5B,UAAI,gBAAgB,eAAe,YAAY,OAAO,IAAI,GAAG;AAC3D;AAAA,MACF;AAEA,UAAI,CAAC,aAAa,IAAI,EAAG;AAEzB,YAAM,MAAM;AAEZ,cAAQ,IAAI,GAAG;AAAA,QACb,KAAK;AACH,yBAAe,IAAI,eAAe;AAClC;AAAA,QAEF,KAAK;AACH,cAAI,CAAC,UAAU,EAAG,YAAW,EAAE,OAAO,gBAAgB,SAAS,0CAA0C,CAAC;AAC1G,yBAAe;AACf;AAAA,QAEF,KAAK;AACH,yBAAe,GAAyB;AACxC;AAAA,QAEF,KAAK;AACH,8BAAoB,GAA2B;AAC/C;AAAA,QAEF,KAAK;AACH,0BAAgB,GAAuB;AACvC;AAAA,QAEF,KAAK;AAEH;AAAA,QAEF,KAAK;AACH,oBAAU,IAAI,qBAAqB,IAAI,WAAW,6BAA6B,CAAC;AAChF;AAAA,QAEF,KAAK;AACH,cAAI,UAAU,eAAe,UAAU,YAAY,UAAU,YAAa;AAC1E,uBAAa,WAAW;AACxB,qBAAW,EAAE,aAAa,YAAY,SAAS,IAAI,OAAO,CAAC;AAC3D,kBAAQ;AACR;AAAA,MACJ;AAAA,IACF,CAAC;AAED,SAAK,GAAG,QAAQ,YAAY;AAC1B,UAAI;AACF,YAAI,UAAU,EAAG;AAGjB,8BAAsB,IAAI;AAG1B,aAAK,KAAK;AAAA,UACR,GAAG;AAAA,UACH,iBAAiB;AAAA,UACjB;AAAA,QACF,CAAC;AAGD,cAAM,kBAAkB,MAAM,QAAQ,KAAK;AAAA,UACzC;AAAA,UACA,MAAM,GAAK,EAAE,KAAK,MAAM,IAAqB;AAAA,QAC/C,CAAC;AAED,YAAI,UAAU,EAAG;AAEjB,YAAI,oBAAoB,MAAM;AAC5B,gBAAM,IAAI,qBAAqB,wCAAwC;AAAA,QACzE,WAAW,oBAAoB,sBAAsB;AACnD,gBAAM,IAAI;AAAA,YACR,sCAAsC,oBAAoB,eAAe,eAAe;AAAA,UAC1F;AAAA,QACF;AAEA,qBAAa,aAAa;AAC1B,YAAI,CAAC,UAAU,EAAG,YAAW,EAAE,OAAO,WAAW,SAAS,+CAA+C,CAAC;AAG1G,YAAI,aAAa;AACf,eAAK,KAAK;AAAA,YACR,GAAG;AAAA,YACH,WAAW,MAAM;AAAA,YACjB,OAAO,MAAM,IAAI,QAAM,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,QAAQ,2BAA2B,EAAE;AAAA,YAClG;AAAA,UACF,CAAC;AAAA,QACH;AAGA,aAAK,KAAK;AAAA,UACR,GAAG;AAAA,UACH;AAAA,UACA,MAAM,MAAM,CAAC,EAAE;AAAA,UACf,MAAM,MAAM,CAAC,EAAE;AAAA,UACf,MAAM,MAAM,CAAC,EAAE,QAAQ;AAAA,UACvB,GAAI,cAAc,EAAE,WAAW,EAAE,IAAI,CAAC;AAAA,QACxC,CAAC;AAED,cAAM,KAAK,KAAK;AAEhB,YAAI,MAAM,OAAO,SAAS,kBAAkB,GAAG;AAC7C,cAAI;AACF,eAAG,6BAA6B;AAAA,UAClC,QAAQ;AAAA,UAER;AAAA,QACF;AAGA,cAAM;AACN,YAAI,UAAU,EAAG;AAGjB,YAAI,sBAAsB,GAAG;AAC3B,2BAAiB,YAAY,MAAM;AACjC,gBAAI,UAAU,kBAAkB,UAAU,eAAe,UAAU,gBAAgB;AACjF,kBAAI;AACF,qBAAK,KAAK,EAAE,GAAG,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,cAChD,QAAQ;AAAA,cAER;AAAA,YACF;AAAA,UACF,GAAG,mBAAmB;AAAA,QACxB;AAEA,qBAAa,cAAc;AAC3B,8BAAsB;AAEtB,YAAI,mBAAmB;AAGvB,iBAAS,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,gBAAM,cAAc,MAAM,EAAE;AAG5B,cAAI,eAAe,KAAK,GAAG;AACzB,iBAAK,KAAK;AAAA,cACR,GAAG;AAAA,cACH;AAAA,cACA,MAAM,YAAY;AAAA,cAClB,MAAM,YAAY;AAAA,cAClB,MAAM,YAAY,QAAQ;AAAA,cAC1B,WAAW;AAAA,YACb,CAAC;AAAA,UACH;AAGA,mBAAS,SAAS,GAAG,SAAS,YAAY,MAAM,UAAU,WAAW;AACnE,gBAAI,UAAU,EAAG;AAEjB,kBAAM,QAAQ,YAAY,MAAM,QAAQ,SAAS,SAAS;AAC1D,kBAAM,MAAM,MAAM,MAAM,YAAY;AACpC,gBAAI,UAAU,EAAG;AAEjB,kBAAM,UAAU,MAAM,KAAK,QAAQ,YAAY,IAAI;AACnD,gCAAoB,IAAI;AACxB,2BAAe,EAAE,UAAU,kBAAkB,OAAO,UAAU,CAAC;AAAA,UACjE;AAEA,cAAI,UAAU,EAAG;AAGjB,cAAI,aAAa;AACf,kBAAM,oBAAoB,IAAI,QAA8B,CAAC,YAAY;AACvE,kCAAoB;AAAA,YACtB,CAAC;AAED,iBAAK,KAAK,EAAE,GAAG,YAAY,WAAW,GAAG,CAAC;AAE1C,kBAAM,QAAQ,MAAM,QAAQ,KAAK;AAAA,cAC/B;AAAA,cACA,MAAM,eAAe,EAAE,KAAK,MAAM,IAAmC;AAAA,YACvE,CAAC;AAED,gBAAI,UAAU,EAAG;AAEjB,gBAAI,CAAC,OAAO;AACV,oBAAM,IAAI,qBAAqB,4CAA4C,KAAK,CAAC,IAAI,MAAM,MAAM,GAAG;AAAA,YACtG;AAAA,UACF;AAAA,QACF;AAEA,YAAI,UAAU,EAAG;AAEjB,qBAAa,WAAW;AACxB,qBAAa,cAAc;AAG3B,cAAM,YAAY,MAAM,cAAc,MAAM,aAAa;AAEzD,YAAI,UAAU,EAAG;AAEjB,cAAM,WAAW,OAAO,UAAU,KAAK,KAAK;AAC5C,cAAM,cAAc,OAAO,UAAU,QAAQ,KAAK;AAElD,YAAI,YAAY,cAAc,UAAU;AACtC,gBAAM,IAAI,qBAAqB,2CAA2C;AAAA,QAC5E;AAEA,uBAAe,EAAE,UAAU,eAAe,UAAU,OAAO,SAAS,CAAC;AACrE,qBAAa;AAAA,MACf,SAAS,KAAK;AACZ,kBAAU,GAAY;AAAA,MACxB;AAAA,IACF,CAAC;AAED,SAAK,GAAG,SAAS,CAAC,QAAe;AAC/B,gBAAU,GAAG;AAAA,IACf,CAAC;AAED,SAAK,GAAG,SAAS,MAAM;AACrB,UAAI,UAAU,YAAY,UAAU,eAAe,UAAU,aAAa;AAExE,gBAAQ;AACR;AAAA,MACF;AAGA,UAAI,UAAU,gBAAgB;AAG5B,mBAAW,MAAM;AACf,cAAI,UAAU,gBAAgB;AAE5B,sBAAU,IAAI,qBAAqB,gDAAgD,CAAC;AAAA,UACtF;AAAA,QACF,GAAG,yBAAyB;AAC5B;AAAA,MACF;AAEA,UAAI,UAAU,kBAAkB,UAAU,aAAa;AAIrD,qBAAa,WAAW;AACxB,mBAAW,EAAE,aAAa,WAAW,CAAC;AACtC,gBAAQ;AAAA,MACV,OAAO;AAGL,qBAAa;AACb,gBAAQ;AACR,oBAAY;AACZ,kBAAU;AACV,sBAAc,MAAM;AACpB,uBAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,cAAc,MAAM;AAAA,IACpB,oBAAoB,MAAM;AACxB,UAAI,CAAC,WAAY,QAAO;AAExB,aAAO,WAAW,QAAQ;AAAA,IAC5B;AAAA,EACF;AACF;;;AC9xBA,IAAMC,uBAAkE;AAAA,EACtE,cAAc,CAAC,cAAc,QAAQ;AAAA,EACrC,YAAY,CAAC,eAAe,UAAU,WAAW;AAAA,EACjD,aAAa,CAAC,eAAe,UAAU,WAAW;AAAA,EAClD,aAAa,CAAC,gBAAgB,UAAU,WAAW;AAAA,EACnD,cAAc,CAAC,aAAa,UAAU,WAAW;AAAA,EACjD,WAAW,CAAC,QAAQ;AAAA,EACpB,WAAW,CAAC,QAAQ;AAAA,EACpB,QAAQ,CAAC;AACX;AAwCA,eAAsB,gBAAgB,MAAqD;AACzF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,YAAY;AAAA,IACZ,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,wBAAwB,+BAA+B;AAAA,EACnE;AAEA,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,YAAY,cAAc;AAC1C,MAAI,cAAc,CAAC,SAAS,SAAS;AACnC,UAAM,IAAI,wBAAwB,6CAA6C;AAAA,EACjF;AAGA,QAAM,iBAAiB,OAAO,IAAI,EAAE,KAAK,EAAE,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAC3E,MAAI,CAAC,cAAc,cAAc,GAAG;AAClC,UAAM,IAAI,wBAAwB,+BAA+B;AAAA,EACnE;AAGA,QAAM,EAAE,MAAM,WAAW,YAAY,gBAAgB,IAAI;AAAA,IACvD,EAAE,YAAY,WAAW;AAAA,IACzB;AAAA,EACF;AAGA,QAAM,WAAW,iBAAiB;AAAA,IAChC;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,EACd,CAAC;AAGD,QAAM,OAAO,IAAI,KAAK,QAAW,QAAQ;AAGzC,MAAI,QAAyB;AAC7B,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,MAAI,mBAAkC;AACtC,MAAI,aAAa,QAAQ,QAAQ;AACjC,MAAI,gBAAsD;AAC1D,MAAI,aAAoC;AAExC,MAAI,eAAuC;AAG3C,MAAI,WAAsC;AAC1C,MAAI,sBAAsB;AAC1B,MAAI,wBAAwB;AAG5B,MAAI,mBAAmB;AAGvB,MAAI,kBAAkB;AACtB,QAAM,wBAAwB;AAG9B,QAAM,iBAAiB;AAKvB,QAAM,eAAe,CAAC,aAAuC;AAC3D,QAAI,CAACA,qBAAoB,KAAK,EAAE,SAAS,QAAQ,GAAG;AAClD,cAAQ,KAAK,2CAA2C,KAAK,OAAO,QAAQ,EAAE;AAC9E,aAAO;AAAA,IACT;AACA,YAAQ;AACR,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAe,UAAU,YAAY,UAAU;AAGjE,QAAM,gBAAgB,MAAY;AAChC,QAAI,qBAAqB,EAAG;AAE5B,QAAI,eAAe;AACjB,mBAAa,aAAa;AAAA,IAC5B;AAEA,oBAAgB,WAAW,MAAM;AAC/B,UAAI,UAAU,gBAAgB;AAC5B,kBAAU,IAAI,qBAAqB,0CAA0C,CAAC;AAAA,MAChF;AAAA,IACF,GAAG,iBAAiB;AAAA,EACtB;AAEA,QAAM,gBAAgB,MAAY;AAChC,QAAI,eAAe;AACjB,mBAAa,aAAa;AAC1B,sBAAgB;AAAA,IAClB;AAAA,EACF;AAGA,QAAM,YAAY,CAAC,QAAqB;AACtC,QAAI,UAAU,YAAY,UAAU,eAAe,UAAU,YAAa;AAC1E,iBAAa,QAAQ;AACrB,cAAU,GAAG;AACb,YAAQ;AAAA,EACV;AAGA,QAAM,eAAe,CAAC,iBAA4D;AAChF,QAAI,UAAU,eAAgB;AAC9B,iBAAa,WAAW;AACxB,iBAAa,YAAY;AAAA,EAI3B;AAGA,QAAM,UAAU,MAAY;AAC1B,kBAAc;AAGd,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,oBAAoB,gBAAgB,YAAY;AAAA,IACzD;AAEA,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,eAAe,MAAY;AAC/B,QAAI;AACF,kBAAY,KAAK,EAAE,GAAG,SAAS,SAAS,kCAAkC,CAAC;AAAA,IAC7E,QAAQ;AAAA,IAER;AACA,SAAK;AAAA,EACP;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,iBAAiB,gBAAgB,YAAY;AAAA,EACtD;AAEA,QAAM,OAAO,MAAY;AACvB,QAAI,UAAU,YAAY,UAAU,YAAa;AAGjD,QAAI,UAAU,aAAa;AACzB,cAAQ;AACR;AAAA,IACF;AAEA,UAAM,YAAY,UAAU;AAC5B,iBAAa,WAAW;AAGxB,QAAI;AAEF,UAAI,cAAc,WAAW,MAAM;AACjC,mBAAW,KAAK,EAAE,GAAG,aAAa,QAAQ,mCAAmC,CAAC;AAAA,MAChF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,aAAa,UAAU;AACzB,eAAS,EAAE,aAAa,WAAW,CAAC;AAAA,IACtC;AAEA,YAAQ;AAAA,EACV;AAGA,QAAM,eAAe,CAAC,MAAsB,QAAsB;AAChE,QAAI;AACF,WAAK,KAAK,EAAE,GAAG,aAAa,KAAK,SAAS,CAAC;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,OAAK,GAAG,SAAS,CAAC,QAAe;AAC/B,cAAU,GAAG;AAAA,EACf,CAAC;AAED,OAAK,GAAG,QAAQ,MAAM;AACpB,iBAAa,YAAY;AACzB,UAAM,OAAO,KAAK,QAAQ,gBAAgB,EAAE,UAAU,KAAK,CAAC;AAC5D,iBAAa;AAEb,SAAK,GAAG,QAAQ,MAAM;AACpB,mBAAa,aAAa;AAC1B,iBAAW,EAAE,OAAO,aAAa,SAAS,aAAa,CAAC;AAGxD,WAAK,KAAK;AAAA,QACR,GAAG;AAAA,QACH,iBAAiB;AAAA,QACjB,WAAW;AAAA,MACb,CAAC;AAAA,IACH,CAAC;AAED,SAAK,GAAG,QAAQ,OAAO,SAAkB;AACvC,UAAI;AAKF,YAAI,gBAAgB,eAAe,YAAY,OAAO,IAAI,KACvD,OAAO,SAAS,eAAe,gBAAgB,MAAO;AAOvD,cAAI,UAAU,gBAAgB;AAC5B,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAGA,wBAAc;AAGd,cAAI,mBAAmB,uBAAuB;AAC5C,kBAAM,IAAI,qBAAqB,gDAAgD;AAAA,UACjF;AAGA,cAAI;AAEJ,cAAI,gBAAgB,aAAa;AAC/B,yBAAa,QAAQ,QAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,UACnD,WAAW,YAAY,OAAO,IAAI,GAAG;AACnC,yBAAa,QAAQ;AAAA,cACnB,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AAAA,YAC9D;AAAA,UACF,WAAW,OAAO,SAAS,eAAe,gBAAgB,MAAM;AAC9D,yBAAa,KAAK,YAAY,EAAE,KAAK,CAAC,WAAW,IAAI,WAAW,MAAM,CAAC;AAAA,UACzE,OAAO;AACL;AAAA,UACF;AAGA,gBAAM,WAAW,cAAc,OAAO;AACtC,gBAAM,eAAe,cAAc;AACnC,yBAAe;AAEf;AACA,uBAAa,WACV,KAAK,YAAY;AAChB,kBAAM,MAAM,MAAM;AAGlB,gBAAI,iBAAiB,UAAa,IAAI,eAAe,cAAc;AACjE,oBAAM,IAAI;AAAA,gBACR,iCAAiC,YAAY,SAAS,IAAI,UAAU;AAAA,cACtE;AAAA,YACF;AAGA,kBAAM,cAAc,WAAW,IAAI;AACnC,gBAAI,QAAQ,KAAK,cAAc,OAAO;AACpC,oBAAM,IAAI;AAAA,gBACR,qCAAqC,WAAW,MAAM,KAAK;AAAA,cAC7D;AAAA,YACF;AAGA,gBAAI,QAAQ;AACV,oBAAM,OAAO,GAAG;AAAA,YAClB;AAEA,wBAAY,IAAI;AAChB,mCAAuB,IAAI;AAC3B,kBAAM,mBAAmB,WAAY,wBAAwB,sBAAuB;AACpF,kBAAM,gBAAgB,WAAW,SAAS,YAAY;AACtD,kBAAM,UAAU,gBAAgB,KAAK,IAAI,KAAM,mBAAmB,gBAAiB,GAAG,IAAI;AAC1F,gBAAI,CAAC,UAAU,EAAG,cAAa,EAAE,gBAAgB,kBAAkB,YAAY,eAAe,QAAQ,CAAC;AAGvG,gBAAI,YAAY,GAAG;AACjB,2BAAa,MAAM,QAAQ;AAAA,YAC7B;AAAA,UACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,gBAAI;AACF,mBAAK,KAAK;AAAA,gBACR,GAAG;AAAA,gBACH,SAAU,KAAe,WAAW;AAAA,cACtC,CAAC;AAAA,YACH,QAAQ;AAAA,YAER;AACA,sBAAU,GAAY;AAAA,UACxB,CAAC,EACA,QAAQ,MAAM;AACb;AAAA,UACF,CAAC;AAEH;AAAA,QACF;AAGA,YAAI,CAAC,aAAa,IAAI,EAAG;AAEzB,cAAM,MAAM;AAEZ,gBAAQ,IAAI,GAAG;AAAA,UACb,KAAK;AACH,+BAAmB,IAAI,aAAa;AACpC,yBAAa,aAAa;AAC1B,uBAAW,EAAE,OAAO,WAAW,SAAS,8BAA8B,CAAC;AACvE;AAAA,UAEF,KAAK,aAAa;AAEhB,kBAAM,cAAc;AAGpB,gBAAI,YAAY,YAAY,gBAAgB;AAC1C,oBAAM,IAAI,wBAAwB,mBAAmB,YAAY,SAAS,EAAE;AAAA,YAC9E;AAGA,kBAAM,UAAU,YAAY,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AACpE,gBAAI,YAAY,YAAY,WAAW;AACrC,oBAAM,IAAI;AAAA,gBACR,qCAAqC,YAAY,SAAS,gBAAgB,OAAO;AAAA,cACnF;AAAA,YACF;AAEA,uBAAW;AACX,oBAAQ,YAAY;AACpB;AAAA,UACF;AAAA,UAEA,KAAK,QAAQ;AAEX,gBAAI,UAAU,iBAAiB,EAAE,UAAU,kBAAkB,WAAW;AACtE;AAAA,YACF;AAGA,gBAAI,oBAAoB,IAAI,aAAa,IAAI,cAAc,kBAAkB;AAC3E,kBAAI;AACF,qBAAK,KAAK,EAAE,GAAG,SAAS,SAAS,6BAA6B,CAAC;AAAA,cACjE,QAAQ;AAAA,cAER;AACA;AAAA,YACF;AAGA,gBAAI,IAAI,WAAW;AACjB,iCAAmB,IAAI;AAAA,YACzB;AAEA,kBAAM,OAAO,OAAO,IAAI,QAAQ,MAAM;AACtC,kBAAM,WAAW,OAAO,IAAI,IAAI,KAAK;AACrC,kBAAM,KAAK,IAAI;AAGf,gBAAI,YAAY,OAAO,OAAO,YAAY,KAAK,GAAG;AAChD,oCAAsB;AAEtB,4BAAc,EAAE,WAAW,IAAI,MAAM,MAAM,SAAS,CAAC;AACrD;AAAA,YACF;AAGA,uBAAW;AACX,kCAAsB;AACtB,oCAAwB;AACxB,gBAAI,CAAC,UAAU;AACb,sBAAQ;AAAA,YACV;AACA,yBAAa,QAAQ,QAAQ;AAG7B,kBAAM,YAAY,MAAY;AAC5B,2BAAa,cAAc;AAE3B,4BAAc;AAEd,kBAAI,UAAU;AACZ,8BAAc,EAAE,WAAW,GAAG,MAAM,MAAM,SAAS,CAAC;AAAA,cACtD;AACA,kBAAI;AACF,qBAAK,KAAK,EAAE,GAAG,QAAQ,CAAC;AAAA,cAC1B,QAAQ;AAAA,cAER;AAAA,YACF;AAGA,kBAAM,UAAqD,EAAE,MAAM,MAAM;AACzE,gBAAI,UAAU;AACZ,sBAAQ,YAAY,SAAS;AAC7B,sBAAQ,QAAQ,SAAS,MAAM,IAAI,QAAM,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AACxE,sBAAQ,YAAY,SAAS;AAAA,YAC/B;AAEA,gBAAI,WAAW;AACb,kBAAI,CAAC,UAAU,GAAG;AAChB,yBAAS,OAAO;AAChB,6BAAa,EAAE,gBAAgB,UAAU,YAAY,OAAO,SAAS,EAAE,CAAC;AAAA,cAC1E;AACA,wBAAU;AAAA,YACZ,OAAO;AAEL,sBAAQ,YAAY;AACpB,kBAAI,CAAC,UAAU,GAAG;AAChB,yBAAS,OAAO;AAChB,6BAAa,EAAE,gBAAgB,UAAU,YAAY,OAAO,SAAS,EAAE,CAAC;AAAA,cAC1E;AAAA,YACF;AACA;AAAA,UACF;AAAA,UAEA,KAAK,SAAS;AACZ,kBAAM,WAAW;AAGjB,gBAAI,UAAU,gBAAgB;AAC5B,oBAAM,IAAI;AAAA,gBACR;AAAA,cACF;AAAA,YACF;AAGA,gBAAI,SAAS,QAAQ,kBAAkB;AACrC,oBAAM,IAAI;AAAA,gBACR,kCAAkC,gBAAgB,SAAS,SAAS,GAAG;AAAA,cACzE;AAAA,YACF;AACA;AAEA,2BAAe;AACf;AAAA,UACF;AAAA,UAEA,KAAK;AAEH,gBAAI;AACF,mBAAK,KAAK,EAAE,GAAG,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,YAChD,QAAQ;AAAA,YAER;AACA;AAAA,UAEF,KAAK,YAAY;AAEf,0BAAc;AACd,kBAAM;AAEN,kBAAM,QAAQ,IAAI;AAClB,wBAAY,EAAE,WAAW,OAAO,eAAe,oBAAoB,CAAC;AAEpE,gBAAI;AACF,mBAAK,KAAK,EAAE,GAAG,gBAAgB,WAAW,OAAO,UAAU,qBAAqB,MAAM,oBAAoB,CAAC;AAAA,YAC7G,QAAQ;AAAA,YAER;AAEA,qCAAyB;AACzB,kCAAsB;AAGtB,0BAAc;AACd;AAAA,UACF;AAAA,UAEA,KAAK;AACH,0BAAc;AACd,kBAAM;AAGN,kBAAM,gBAAgB,WAAY,wBAAwB,sBAAuB;AACjF,kBAAM,aAAa,WAAW,SAAS,YAAY;AAEnD,gBAAI,cAAc,gBAAgB,YAAY;AAC5C,oBAAM,MAAM,IAAI;AAAA,gBACd;AAAA,cACF;AACA,kBAAI;AACF,qBAAK,KAAK,EAAE,GAAG,SAAS,SAAS,IAAI,QAAQ,CAAC;AAAA,cAChD,QAAQ;AAAA,cAER;AACA,oBAAM;AAAA,YACR;AAGA,gBAAI;AACF,mBAAK,KAAK,EAAE,GAAG,WAAW,UAAU,eAAe,OAAO,WAAW,CAAC;AAAA,YACxE,QAAQ;AAAA,YAER;AAGA,yBAAa,EAAE,UAAU,eAAe,OAAO,WAAW,CAAC;AAG3D,aAAC,YAAY;AACX,uBAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,sBAAM,MAAM,0BAA0B;AACtC,oBAAI;AACF,uBAAK,KAAK,EAAE,GAAG,WAAW,UAAU,eAAe,OAAO,WAAW,CAAC;AAAA,gBACxE,QAAQ;AACN;AAAA,gBACF;AAAA,cACF;AAAA,YACF,GAAG,EAAE,MAAM,MAAM;AAAA,YAAE,CAAC;AACpB;AAAA,UAEF,KAAK;AACH,kBAAM,IAAI,qBAAqB,IAAI,WAAW,2BAA2B;AAAA,UAE3E,KAAK;AACH,gBAAI,UAAU,eAAe,UAAU,YAAY,UAAU,YAAa;AAC1E,yBAAa,WAAW;AACxB,uBAAW,EAAE,aAAa,UAAU,SAAS,IAAI,OAAO,CAAC;AACzD,oBAAQ;AACR;AAAA,QACJ;AAAA,MACF,SAAS,KAAK;AACZ,kBAAU,GAAY;AAAA,MACxB;AAAA,IACF,CAAC;AAED,SAAK,GAAG,SAAS,MAAM;AACrB,UAAI,UAAU,YAAY,UAAU,eAAe,UAAU,aAAa;AAExE,gBAAQ;AACR;AAAA,MACF;AAGA,UAAI,UAAU,gBAAgB;AAI5B,qBAAa,WAAW;AACxB,mBAAW,EAAE,aAAa,SAAS,CAAC;AACpC,gBAAQ;AAAA,MACV,WAAW,UAAU,eAAe;AAElC,qBAAa,QAAQ;AACrB,gBAAQ;AACR,uBAAe;AAAA,MACjB,OAAO;AAEL,kBAAU,IAAI,qBAAqB,wDAAwD,CAAC;AAAA,MAC9F;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,kBAAkB,MAAM;AAAA,IACxB,eAAe,MAAM;AAAA,IACrB,cAAc,MAAM;AAAA,EACtB;AACF;","names":["crypto","ALLOWED_TRANSITIONS"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dropgate/core",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Headless Dropgate client library for file uploads and P2P transfers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -88,4 +88,4 @@
88
88
  "dependencies": {
89
89
  "fflate": "^0.8.2"
90
90
  }
91
- }
91
+ }