vh-dev-client 1.0.0 → 1.0.1

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.
@@ -300,6 +300,9 @@ function createVH(options) {
300
300
  requestId: input.requestId,
301
301
  method: input.method,
302
302
  url: input.url,
303
+ ...input.params === void 0 ? {} : { params: input.params },
304
+ ...input.headers === void 0 ? {} : { headers: input.headers },
305
+ ...input.body === void 0 ? {} : { body: input.body },
303
306
  timestamp: Date.now()
304
307
  });
305
308
  },
@@ -309,7 +312,12 @@ function createVH(options) {
309
312
  eventId: createId(),
310
313
  appName,
311
314
  requestId: input.requestId,
315
+ ...input.method === void 0 ? {} : { method: input.method },
316
+ ...input.url === void 0 ? {} : { url: input.url },
312
317
  ...input.status === void 0 ? {} : { status: input.status },
318
+ ...input.statusText === void 0 ? {} : { statusText: input.statusText },
319
+ ...input.headers === void 0 ? {} : { headers: input.headers },
320
+ ...input.body === void 0 ? {} : { body: input.body },
313
321
  ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs },
314
322
  ...input.error === void 0 ? {} : { error: input.error },
315
323
  timestamp: Date.now()
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n ...(input.params === undefined ? {} : { params: input.params }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.method === undefined ? {} : { method: input.method }),\n ...(input.url === undefined ? {} : { url: input.url }),\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.statusText === undefined\n ? {}\n : { statusText: input.statusText }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":";AAIO,SAAS,WAAmB;AACjC,MAAI,OAAO,WAAW,QAAQ,eAAe,YAAY;AACvD,WAAO,WAAW,OAAO,WAAW;AAAA,EACtC;AAEA,SAAO;AAAA,IACL,KAAK,IAAI,EAAE,SAAS,EAAE;AAAA,IACtB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,IAClC,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,EACpC,EAAE,KAAK,GAAG;AACZ;;;ACLO,IAAM,eAAN,MAAmB;AAAA,EAChB,QAAmB,CAAC;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET;AAAA,EACA,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EAEpB,YAAY,SAAyB;AACnC,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,UAAU,QAAQ;AAEvB,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,eAAe;AAAA,EACzB;AAAA,EAEA,QAAQ,OAAyB;AAC/B,QAAI,KAAK,WAAW;AAClB,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,MAAM,UAAU,KAAK,cAAc;AAE1C,WAAK,MAAM,MAAM;AAAA,IACnB;AAEA,SAAK,MAAM,KAAK,KAAK;AAErB,QAAI,KAAK,MAAM,UAAU,KAAK,cAAc;AAC1C,WAAK,KAAK,MAAM;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAa,KAAK,MAAM,WAAW,GAAG;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU;AACjB,WAAK,iBAAiB;AACtB;AAAA,IACF;AAEA,SAAK,WAAW;AAEhB,QAAI;AACF,SAAG;AACD,aAAK,iBAAiB;AAEtB,YAAI,KAAK,MAAM,WAAW,GAAG;AAC3B;AAAA,QACF;AAEA,cAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,YAAY;AAEpD,YAAI;AACF,gBAAM,KAAK,QAAQ,KAAK;AAAA,QAC1B,QAAQ;AAEN,eAAK,MAAM,QAAQ,GAAG,KAAK;AAE3B,cAAI,KAAK,MAAM,SAAS,KAAK,cAAc;AACzC,iBAAK,MAAM,SAAS,KAAK;AAAA,UAC3B;AAEA;AAAA,QACF;AAAA,MACF,SAAS,KAAK,kBAAkB,KAAK,MAAM,UAAU,KAAK;AAAA,IAC5D,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AAEA,UAAM,KAAK,MAAM;AACjB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;ACxGA,SAAS,cAAc;AACvB,SAAS,YAAY,mBAAmB;AAGxC,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,IAAI,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,CAAC;AAAA,EACjD;AAEA,QAAM,SAAS;AACf,QAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAEtC,SAAO,IAAI,KACR,IAAI,CAAC,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,gBAAgB,OAAO,GAAG,CAAC,CAAC,EAAE,EACrE,KAAK,GAAG,CAAC;AACd;AAEA,eAAsB,kBACpB,OACA,SACA,MACiB;AACjB,QAAM,QAAQ,gBAAgB;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,MAAM,QAAQ;AAAA,EAChB,CAAC;AAED,SAAO,WAAW,OAAO,YAAY,KAAK,CAAC,CAAC;AAC9C;;;ACjCA,SAAS,UAAuB;AAczB,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EAEjB,YAAY,SAA2B;AACrC,SAAK,YAAY,QAAQ,aAAa;AAEtC,SAAK,SAAS,GAAG,QAAQ,WAAW;AAAA,MAClC,aAAa;AAAA,MACb,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB,mBAAmB;AAAA,MACnB,sBAAsB;AAAA,MACtB,SAAS,KAAK;AAAA,IAChB,CAAC;AAED,SAAK,OAAO,GAAG,WAAW,MAAM;AAC9B,cAAQ,IAAI,0BAA0B,KAAK,OAAO,EAAE;AAAA,IACtD,CAAC;AAED,SAAK,OAAO,GAAG,iBAAiB,CAAC,UAAU;AACzC,cAAQ,MAAM,iCAAiC,MAAM,OAAO;AAAA,IAC9D,CAAC;AAED,SAAK,OAAO,GAAG,cAAc,CAAC,WAAW;AACvC,cAAQ,IAAI,6BAA6B,MAAM;AAAA,IACjD,CAAC;AAAA,EACH;AAAA,EAEQ,oBAAmC;AACzC,QAAI,KAAK,OAAO,WAAW;AACzB,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,aAAK,OAAO,IAAI,WAAW,SAAS;AACpC,aAAK,OAAO,IAAI,iBAAiB,cAAc;AAAA,MACjD;AAEA,YAAM,YAAY,MAAM;AACtB,gBAAQ;AACR,gBAAQ;AAAA,MACV;AAEA,YAAM,iBAAiB,CAAC,UAAiB;AAGvC,gBAAQ;AACR,eAAO,IAAI,MAAM,mCAAmC,MAAM,OAAO,EAAE,CAAC;AAAA,MACtE;AAEA,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR;AAAA,UACE,IAAI,MAAM,wCAAwC,KAAK,SAAS,IAAI;AAAA,QACtE;AAAA,MACF,GAAG,KAAK,SAAS;AAEjB,WAAK,OAAO,KAAK,WAAW,SAAS;AACrC,WAAK,OAAO,KAAK,iBAAiB,cAAc;AAIhD,UAAI,KAAK,OAAO,WAAW;AACzB,kBAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,QAAkC;AAChD,QAAI,OAAO,WAAW,GAAG;AACvB;AAAA,IACF;AAEA,UAAM,KAAK,kBAAkB;AAE7B,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,UAAU;AAEd,YAAM,UAAU,WAAW,MAAM;AAC/B;AAAA,UACE,IAAI;AAAA,YACF,6CAA6C,KAAK,SAAS;AAAA,UAC7D;AAAA,QACF;AAAA,MACF,GAAG,KAAK,SAAS;AAEjB,YAAM,SAAS,CAAC,UAAkB;AAChC,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,OAAO;AAEpB,YAAI,OAAO;AACT,iBAAO,KAAK;AAAA,QACd,OAAO;AACL,kBAAQ;AAAA,QACV;AAAA,MACF;AAEA,WAAK,OAAO,KAAK,aAAa,QAAQ,CAAC,QAAmB;AACxD,YAAI,CAAC,KAAK,IAAI;AACZ,iBAAO,IAAI,MAAM,KAAK,SAAS,8BAA8B,CAAC;AAC9D;AAAA,QACF;AAEA,gBAAQ,IAAI,mCAAmC,OAAO,MAAM,SAAS;AAErE,eAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;;;AClGO,SAAS,SAAS,SAA8B;AACrD,QAAM,UAAU,QAAQ,QAAQ,KAAK;AAErC,QAAM,aAAa;AAAA,IACjB,GAAG,IAAI;AAAA,OACJ,MAAM,QAAQ,QAAQ,SAAS,IAC5B,QAAQ,YACR,CAAC,QAAQ,SAAS,GACpB,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MACE,WAAW,WAAW,KACtB,WAAW,KAAK,CAAC,QAAQ;AACvB,QAAI;AACF,YAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,aAAO,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,OAAO,QAAQ;AAAA,IACtD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,GACD;AACA,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,QAAM,aAAa,WAAW;AAAA,IAC5B,CAAC,cAAc,IAAI,YAAY,EAAE,UAAU,CAAC;AAAA,EAC9C;AAEA,QAAM,UAAU,IAAI,aAAa;AAAA,IAC/B,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,cAAc;AAAA,IAEd,MAAM,QAAQ,QAAQ;AACpB,YAAM,QAAQ;AAAA,QACZ,WAAW,IAAI,CAAC,cAAc,UAAU,UAAU,MAAM,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,YAAY;AAEhB,QAAM,cAAc,oBAAI,IAAmB;AAE3C,iBAAe,qBAAoC;AACjD,WAAO,YAAY,OAAO,GAAG;AAC3B,YAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,WAAS,QAAQ,OAAsB;AACrC,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,YAAQ,QAAQ,KAAK;AAAA,EACvB;AAEA,WAAS,SACP,OACA,SACA,MACM;AACN,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,QAAI,kBAAkB;AACpB,cAAQ,KAAK,EAAE,IAAI,OAAO,KAAK,OAAO,IAAI,QAAQ,EAAE;AAAA,IACtD;AAEA,UAAM,UAAU,kBAAkB,OAAO,SAAS,IAAI,EACnD,KAAK,CAAC,gBAAgB;AACrB,UAAI,WAAW;AACb;AAAA,MACF;AAEA,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,SAAS,SAAS;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK;AAAA,QACrC,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AAAA,IACH,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,cAAQ,MAAM,8CAA8C,KAAK;AAAA,IACnE,CAAC,EACA,KAAK,MAAM;AACV,kBAAY,OAAO,OAAO;AAAA,IAC5B,CAAC;AAEH,gBAAY,IAAI,OAAO;AAAA,EACzB;AAEA,QAAM,MAAuB;AAAA,IAC3B,OAAO,CAAC,SAAS,SAAS,SAAS,SAAS,SAAS,IAAI;AAAA,IACzD,MAAM,CAAC,SAAS,SAAS,SAAS,QAAQ,SAAS,IAAI;AAAA,IACvD,MAAM,CAAC,SAAS,SAAS,SAAS,QAAQ,SAAS,IAAI;AAAA,IACvD,OAAO,CAAC,SAAS,SAAS,SAAS,SAAS,SAAS,IAAI;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,SAAS;AAAA,MACP,IAAI,OAAO;AACT,gBAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS,SAAS;AAAA,UAClB;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,QAAQ,MAAM;AAAA,UACd,KAAK,MAAM;AAAA,UACX,GAAI,MAAM,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;AAAA,UAC7D,GAAI,MAAM,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;AAAA,UAChE,GAAI,MAAM,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,MAAM,KAAK;AAAA,UACvD,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,MAEA,IAAI,OAAO;AACT,gBAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS,SAAS;AAAA,UAClB;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,GAAI,MAAM,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;AAAA,UAC7D,GAAI,MAAM,QAAQ,SAAY,CAAC,IAAI,EAAE,KAAK,MAAM,IAAI;AAAA,UACpD,GAAI,MAAM,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;AAAA,UAC7D,GAAI,MAAM,eAAe,SACrB,CAAC,IACD,EAAE,YAAY,MAAM,WAAW;AAAA,UACnC,GAAI,MAAM,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;AAAA,UAChE,GAAI,MAAM,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,MAAM,KAAK;AAAA,UACvD,GAAI,MAAM,eAAe,SACrB,CAAC,IACD,EAAE,YAAY,MAAM,WAAW;AAAA,UACnC,GAAI,MAAM,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,UAC1D,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ;AACZ,YAAM,mBAAmB;AACzB,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,IAEA,MAAM,UAAU;AACd,UAAI,WAAW;AACb;AAAA,MACF;AAEA,YAAM,mBAAmB;AACzB,YAAM,QAAQ,QAAQ;AAEtB,kBAAY;AAEZ,YAAM,QAAQ,IAAI,WAAW,IAAI,CAAC,cAAc,UAAU,QAAQ,CAAC,CAAC;AAAA,IACtE;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -11,16 +11,28 @@ interface VHLogEvent extends VHBaseEvent {
11
11
  data?: Record<string, unknown>;
12
12
  fingerprint: string;
13
13
  }
14
+ type VHHeaders = Record<string, string>;
15
+ type VHQueryParams = Record<string, string | number | boolean | null | undefined>;
14
16
  interface VHNetworkRequestEvent extends VHBaseEvent {
15
17
  type: "network:req";
16
18
  requestId: string;
19
+ /** HTTP request details */
17
20
  method: string;
18
21
  url: string;
22
+ params?: VHQueryParams;
23
+ headers?: VHHeaders;
24
+ body?: unknown;
19
25
  }
20
26
  interface VHNetworkResponseEvent extends VHBaseEvent {
21
27
  type: "network:res";
22
28
  requestId: string;
29
+ /** HTTP response details */
30
+ method?: string;
31
+ url?: string;
23
32
  status?: number;
33
+ statusText?: string;
34
+ headers?: VHHeaders;
35
+ body?: unknown;
24
36
  durationMs?: number;
25
37
  error?: string;
26
38
  }
@@ -34,10 +46,18 @@ interface VHNetworkRequestInput {
34
46
  requestId: string;
35
47
  method: string;
36
48
  url: string;
49
+ params?: VHQueryParams;
50
+ headers?: VHHeaders;
51
+ body?: unknown;
37
52
  }
38
53
  interface VHNetworkResponseInput {
39
54
  requestId: string;
55
+ method?: string;
56
+ url?: string;
40
57
  status?: number;
58
+ statusText?: string;
59
+ headers?: VHHeaders;
60
+ body?: unknown;
41
61
  durationMs?: number;
42
62
  error?: string;
43
63
  }
package/dist/index.js CHANGED
@@ -300,6 +300,9 @@ function createVH(options) {
300
300
  requestId: input.requestId,
301
301
  method: input.method,
302
302
  url: input.url,
303
+ ...input.params === void 0 ? {} : { params: input.params },
304
+ ...input.headers === void 0 ? {} : { headers: input.headers },
305
+ ...input.body === void 0 ? {} : { body: input.body },
303
306
  timestamp: Date.now()
304
307
  });
305
308
  },
@@ -309,7 +312,12 @@ function createVH(options) {
309
312
  eventId: createId(),
310
313
  appName,
311
314
  requestId: input.requestId,
315
+ ...input.method === void 0 ? {} : { method: input.method },
316
+ ...input.url === void 0 ? {} : { url: input.url },
312
317
  ...input.status === void 0 ? {} : { status: input.status },
318
+ ...input.statusText === void 0 ? {} : { statusText: input.statusText },
319
+ ...input.headers === void 0 ? {} : { headers: input.headers },
320
+ ...input.body === void 0 ? {} : { body: input.body },
313
321
  ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs },
314
322
  ...input.error === void 0 ? {} : { error: input.error },
315
323
  timestamp: Date.now()
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/id.ts","../src/core/batcher.ts","../src/core/fingerprint.ts","../src/core/transport.ts","../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/core/id.ts","../src/core/batcher.ts","../src/core/fingerprint.ts","../src/core/transport.ts","../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n ...(input.params === undefined ? {} : { params: input.params }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.method === undefined ? {} : { method: input.method }),\n ...(input.url === undefined ? {} : { url: input.url }),\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.statusText === undefined\n ? {}\n : { statusText: input.statusText }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -300,6 +300,9 @@ function createVH(options) {
300
300
  requestId: input.requestId,
301
301
  method: input.method,
302
302
  url: input.url,
303
+ ...input.params === void 0 ? {} : { params: input.params },
304
+ ...input.headers === void 0 ? {} : { headers: input.headers },
305
+ ...input.body === void 0 ? {} : { body: input.body },
303
306
  timestamp: Date.now()
304
307
  });
305
308
  },
@@ -309,7 +312,12 @@ function createVH(options) {
309
312
  eventId: createId(),
310
313
  appName,
311
314
  requestId: input.requestId,
315
+ ...input.method === void 0 ? {} : { method: input.method },
316
+ ...input.url === void 0 ? {} : { url: input.url },
312
317
  ...input.status === void 0 ? {} : { status: input.status },
318
+ ...input.statusText === void 0 ? {} : { statusText: input.statusText },
319
+ ...input.headers === void 0 ? {} : { headers: input.headers },
320
+ ...input.body === void 0 ? {} : { body: input.body },
313
321
  ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs },
314
322
  ...input.error === void 0 ? {} : { error: input.error },
315
323
  timestamp: Date.now()
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":";AAIO,SAAS,WAAmB;AACjC,MAAI,OAAO,WAAW,QAAQ,eAAe,YAAY;AACvD,WAAO,WAAW,OAAO,WAAW;AAAA,EACtC;AAEA,SAAO;AAAA,IACL,KAAK,IAAI,EAAE,SAAS,EAAE;AAAA,IACtB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,IAClC,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,EACpC,EAAE,KAAK,GAAG;AACZ;;;ACLO,IAAM,eAAN,MAAmB;AAAA,EAChB,QAAmB,CAAC;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET;AAAA,EACA,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EAEpB,YAAY,SAAyB;AACnC,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,UAAU,QAAQ;AAEvB,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,eAAe;AAAA,EACzB;AAAA,EAEA,QAAQ,OAAyB;AAC/B,QAAI,KAAK,WAAW;AAClB,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,MAAM,UAAU,KAAK,cAAc;AAE1C,WAAK,MAAM,MAAM;AAAA,IACnB;AAEA,SAAK,MAAM,KAAK,KAAK;AAErB,QAAI,KAAK,MAAM,UAAU,KAAK,cAAc;AAC1C,WAAK,KAAK,MAAM;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAa,KAAK,MAAM,WAAW,GAAG;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU;AACjB,WAAK,iBAAiB;AACtB;AAAA,IACF;AAEA,SAAK,WAAW;AAEhB,QAAI;AACF,SAAG;AACD,aAAK,iBAAiB;AAEtB,YAAI,KAAK,MAAM,WAAW,GAAG;AAC3B;AAAA,QACF;AAEA,cAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,YAAY;AAEpD,YAAI;AACF,gBAAM,KAAK,QAAQ,KAAK;AAAA,QAC1B,QAAQ;AAEN,eAAK,MAAM,QAAQ,GAAG,KAAK;AAE3B,cAAI,KAAK,MAAM,SAAS,KAAK,cAAc;AACzC,iBAAK,MAAM,SAAS,KAAK;AAAA,UAC3B;AAEA;AAAA,QACF;AAAA,MACF,SAAS,KAAK,kBAAkB,KAAK,MAAM,UAAU,KAAK;AAAA,IAC5D,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AAEA,UAAM,KAAK,MAAM;AACjB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;ACxGA,SAAS,cAAc;AACvB,SAAS,YAAY,mBAAmB;AAGxC,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,IAAI,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,CAAC;AAAA,EACjD;AAEA,QAAM,SAAS;AACf,QAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAEtC,SAAO,IAAI,KACR,IAAI,CAAC,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,gBAAgB,OAAO,GAAG,CAAC,CAAC,EAAE,EACrE,KAAK,GAAG,CAAC;AACd;AAEA,eAAsB,kBACpB,OACA,SACA,MACiB;AACjB,QAAM,QAAQ,gBAAgB;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,MAAM,QAAQ;AAAA,EAChB,CAAC;AAED,SAAO,WAAW,OAAO,YAAY,KAAK,CAAC,CAAC;AAC9C;;;ACjCA,SAAS,UAAuB;AAczB,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EAEjB,YAAY,SAA2B;AACrC,SAAK,YAAY,QAAQ,aAAa;AAEtC,SAAK,SAAS,GAAG,QAAQ,WAAW;AAAA,MAClC,aAAa;AAAA,MACb,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB,mBAAmB;AAAA,MACnB,sBAAsB;AAAA,MACtB,SAAS,KAAK;AAAA,IAChB,CAAC;AAED,SAAK,OAAO,GAAG,WAAW,MAAM;AAC9B,cAAQ,IAAI,0BAA0B,KAAK,OAAO,EAAE;AAAA,IACtD,CAAC;AAED,SAAK,OAAO,GAAG,iBAAiB,CAAC,UAAU;AACzC,cAAQ,MAAM,iCAAiC,MAAM,OAAO;AAAA,IAC9D,CAAC;AAED,SAAK,OAAO,GAAG,cAAc,CAAC,WAAW;AACvC,cAAQ,IAAI,6BAA6B,MAAM;AAAA,IACjD,CAAC;AAAA,EACH;AAAA,EAEQ,oBAAmC;AACzC,QAAI,KAAK,OAAO,WAAW;AACzB,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,aAAK,OAAO,IAAI,WAAW,SAAS;AACpC,aAAK,OAAO,IAAI,iBAAiB,cAAc;AAAA,MACjD;AAEA,YAAM,YAAY,MAAM;AACtB,gBAAQ;AACR,gBAAQ;AAAA,MACV;AAEA,YAAM,iBAAiB,CAAC,UAAiB;AAGvC,gBAAQ;AACR,eAAO,IAAI,MAAM,mCAAmC,MAAM,OAAO,EAAE,CAAC;AAAA,MACtE;AAEA,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR;AAAA,UACE,IAAI,MAAM,wCAAwC,KAAK,SAAS,IAAI;AAAA,QACtE;AAAA,MACF,GAAG,KAAK,SAAS;AAEjB,WAAK,OAAO,KAAK,WAAW,SAAS;AACrC,WAAK,OAAO,KAAK,iBAAiB,cAAc;AAIhD,UAAI,KAAK,OAAO,WAAW;AACzB,kBAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,QAAkC;AAChD,QAAI,OAAO,WAAW,GAAG;AACvB;AAAA,IACF;AAEA,UAAM,KAAK,kBAAkB;AAE7B,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,UAAU;AAEd,YAAM,UAAU,WAAW,MAAM;AAC/B;AAAA,UACE,IAAI;AAAA,YACF,6CAA6C,KAAK,SAAS;AAAA,UAC7D;AAAA,QACF;AAAA,MACF,GAAG,KAAK,SAAS;AAEjB,YAAM,SAAS,CAAC,UAAkB;AAChC,YAAI,QAAS;AACb,kBAAU;AACV,qBAAa,OAAO;AAEpB,YAAI,OAAO;AACT,iBAAO,KAAK;AAAA,QACd,OAAO;AACL,kBAAQ;AAAA,QACV;AAAA,MACF;AAEA,WAAK,OAAO,KAAK,aAAa,QAAQ,CAAC,QAAmB;AACxD,YAAI,CAAC,KAAK,IAAI;AACZ,iBAAO,IAAI,MAAM,KAAK,SAAS,8BAA8B,CAAC;AAC9D;AAAA,QACF;AAEA,gBAAQ,IAAI,mCAAmC,OAAO,MAAM,SAAS;AAErE,eAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;;;AClGO,SAAS,SAAS,SAA8B;AACrD,QAAM,UAAU,QAAQ,QAAQ,KAAK;AAErC,QAAM,aAAa;AAAA,IACjB,GAAG,IAAI;AAAA,OACJ,MAAM,QAAQ,QAAQ,SAAS,IAC5B,QAAQ,YACR,CAAC,QAAQ,SAAS,GACpB,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MACE,WAAW,WAAW,KACtB,WAAW,KAAK,CAAC,QAAQ;AACvB,QAAI;AACF,YAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,aAAO,CAAC,CAAC,SAAS,QAAQ,EAAE,SAAS,OAAO,QAAQ;AAAA,IACtD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,GACD;AACA,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,QAAM,aAAa,WAAW;AAAA,IAC5B,CAAC,cAAc,IAAI,YAAY,EAAE,UAAU,CAAC;AAAA,EAC9C;AAEA,QAAM,UAAU,IAAI,aAAa;AAAA,IAC/B,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,cAAc;AAAA,IAEd,MAAM,QAAQ,QAAQ;AACpB,YAAM,QAAQ;AAAA,QACZ,WAAW,IAAI,CAAC,cAAc,UAAU,UAAU,MAAM,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,YAAY;AAEhB,QAAM,cAAc,oBAAI,IAAmB;AAE3C,iBAAe,qBAAoC;AACjD,WAAO,YAAY,OAAO,GAAG;AAC3B,YAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,WAAS,QAAQ,OAAsB;AACrC,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,YAAQ,QAAQ,KAAK;AAAA,EACvB;AAEA,WAAS,SACP,OACA,SACA,MACM;AACN,QAAI,WAAW;AACb,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAEA,QAAI,kBAAkB;AACpB,cAAQ,KAAK,EAAE,IAAI,OAAO,KAAK,OAAO,IAAI,QAAQ,EAAE;AAAA,IACtD;AAEA,UAAM,UAAU,kBAAkB,OAAO,SAAS,IAAI,EACnD,KAAK,CAAC,gBAAgB;AACrB,UAAI,WAAW;AACb;AAAA,MACF;AAEA,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,SAAS,SAAS;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK;AAAA,QACrC,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC;AAAA,IACH,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,cAAQ,MAAM,8CAA8C,KAAK;AAAA,IACnE,CAAC,EACA,KAAK,MAAM;AACV,kBAAY,OAAO,OAAO;AAAA,IAC5B,CAAC;AAEH,gBAAY,IAAI,OAAO;AAAA,EACzB;AAEA,QAAM,MAAuB;AAAA,IAC3B,OAAO,CAAC,SAAS,SAAS,SAAS,SAAS,SAAS,IAAI;AAAA,IACzD,MAAM,CAAC,SAAS,SAAS,SAAS,QAAQ,SAAS,IAAI;AAAA,IACvD,MAAM,CAAC,SAAS,SAAS,SAAS,QAAQ,SAAS,IAAI;AAAA,IACvD,OAAO,CAAC,SAAS,SAAS,SAAS,SAAS,SAAS,IAAI;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,SAAS;AAAA,MACP,IAAI,OAAO;AACT,gBAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS,SAAS;AAAA,UAClB;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,QAAQ,MAAM;AAAA,UACd,KAAK,MAAM;AAAA,UACX,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,MAEA,IAAI,OAAO;AACT,gBAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS,SAAS;AAAA,UAClB;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,GAAI,MAAM,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;AAAA,UAC7D,GAAI,MAAM,eAAe,SACrB,CAAC,IACD,EAAE,YAAY,MAAM,WAAW;AAAA,UACnC,GAAI,MAAM,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,UAC1D,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ;AACZ,YAAM,mBAAmB;AACzB,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,IAEA,MAAM,UAAU;AACd,UAAI,WAAW;AACb;AAAA,MACF;AAEA,YAAM,mBAAmB;AACzB,YAAM,QAAQ,QAAQ;AAEtB,kBAAY;AAEZ,YAAM,QAAQ,IAAI,WAAW,IAAI,CAAC,cAAc,UAAU,QAAQ,CAAC,CAAC;AAAA,IACtE;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n ...(input.params === undefined ? {} : { params: input.params }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.method === undefined ? {} : { method: input.method }),\n ...(input.url === undefined ? {} : { url: input.url }),\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.statusText === undefined\n ? {}\n : { statusText: input.statusText }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -300,6 +300,9 @@ function createVH(options) {
300
300
  requestId: input.requestId,
301
301
  method: input.method,
302
302
  url: input.url,
303
+ ...input.params === void 0 ? {} : { params: input.params },
304
+ ...input.headers === void 0 ? {} : { headers: input.headers },
305
+ ...input.body === void 0 ? {} : { body: input.body },
303
306
  timestamp: Date.now()
304
307
  });
305
308
  },
@@ -309,7 +312,12 @@ function createVH(options) {
309
312
  eventId: createId(),
310
313
  appName,
311
314
  requestId: input.requestId,
315
+ ...input.method === void 0 ? {} : { method: input.method },
316
+ ...input.url === void 0 ? {} : { url: input.url },
312
317
  ...input.status === void 0 ? {} : { status: input.status },
318
+ ...input.statusText === void 0 ? {} : { statusText: input.statusText },
319
+ ...input.headers === void 0 ? {} : { headers: input.headers },
320
+ ...input.body === void 0 ? {} : { body: input.body },
313
321
  ...input.durationMs === void 0 ? {} : { durationMs: input.durationMs },
314
322
  ...input.error === void 0 ? {} : { error: input.error },
315
323
  timestamp: Date.now()
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/core/id.ts","../../src/core/batcher.ts","../../src/core/fingerprint.ts","../../src/core/transport.ts","../../src/index.ts"],"sourcesContent":["/**\n * Generates an event ID across browser, Node.js, and React Native.\n * Uses Web Crypto when available, with a non-cryptographic fallback.\n */\nexport function createId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n\n return [\n Date.now().toString(36),\n Math.random().toString(36).slice(2),\n Math.random().toString(36).slice(2),\n ].join(\"-\");\n}\n","import type { VHEvent } from \"./types.js\";\n\nexport interface BatcherOptions {\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n onFlush: (events: VHEvent[]) => Promise<void>;\n}\n\nexport class EventBatcher {\n private queue: VHEvent[] = [];\n private readonly maxBatchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: BatcherOptions[\"onFlush\"];\n\n private timer: ReturnType<typeof setInterval> | undefined;\n private flushing = false;\n private flushRequested = false;\n private destroyed = false;\n\n constructor(options: BatcherOptions) {\n this.maxBatchSize = options.maxBatchSize ?? 50;\n this.flushIntervalMs = options.flushIntervalMs ?? 5000;\n this.maxQueueSize = options.maxQueueSize ?? 5000;\n this.onFlush = options.onFlush;\n\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n enqueue(event: VHEvent): boolean {\n if (this.destroyed) {\n return false;\n }\n\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest queued event to keep memory bounded.\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n\n return true;\n }\n\n async flush(): Promise<void> {\n if (this.destroyed && this.queue.length === 0) {\n return;\n }\n\n if (this.flushing) {\n this.flushRequested = true;\n return;\n }\n\n this.flushing = true;\n\n try {\n do {\n this.flushRequested = false;\n\n if (this.queue.length === 0) {\n break;\n }\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n\n try {\n await this.onFlush(batch);\n } catch {\n // Restore failed events to the front of the queue.\n this.queue.unshift(...batch);\n\n if (this.queue.length > this.maxQueueSize) {\n this.queue.length = this.maxQueueSize;\n }\n\n break;\n }\n } while (this.flushRequested || this.queue.length >= this.maxBatchSize);\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n await this.flush();\n this.destroyed = true;\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bytesToHex, utf8ToBytes } from \"@noble/hashes/utils.js\";\nimport type { VHLogLevel } from \"./types.js\";\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"undefined\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record).sort();\n\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)\n .join(\",\")}}`;\n}\n\nexport async function createFingerprint(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n const input = stableStringify({\n level,\n message,\n data: data ?? null,\n });\n\n return bytesToHex(sha256(utf8ToBytes(input)));\n}\n","import { io, type Socket } from \"socket.io-client\";\nimport type { VHEvent } from \"./types.js\";\n\nexport interface TransportOptions {\n serverUrl: string;\n timeoutMs?: number;\n}\n\ninterface BatchAck {\n ok: boolean;\n eventIds?: string[];\n error?: string;\n}\n\nexport class VHTransport {\n private readonly socket: Socket;\n private readonly timeoutMs: number;\n\n constructor(options: TransportOptions) {\n this.timeoutMs = options.timeoutMs ?? 5000;\n\n this.socket = io(options.serverUrl, {\n autoConnect: true,\n reconnection: true,\n reconnectionAttempts: Infinity,\n reconnectionDelay: 500,\n reconnectionDelayMax: 5000,\n timeout: this.timeoutMs,\n });\n\n this.socket.on(\"connect\", () => {\n console.log(\"[VH Client] Connected:\", this.socket.id);\n });\n\n this.socket.on(\"connect_error\", (error) => {\n console.error(\"[VH Client] Connection error:\", error.message);\n });\n\n this.socket.on(\"disconnect\", (reason) => {\n console.log(\"[VH Client] Disconnected:\", reason);\n });\n }\n\n private waitForConnection(): Promise<void> {\n if (this.socket.connected) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onConnectError);\n };\n\n const onConnect = () => {\n cleanup();\n resolve();\n };\n\n const onConnectError = (error: Error) => {\n // Socket.IO may reconnect after this; this send attempt fails\n // rather than waiting indefinitely.\n cleanup();\n reject(new Error(`Unable to connect to VH server: ${error.message}`));\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(\n new Error(`VH server connection timed out after ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onConnectError);\n\n // Recheck after registering listeners to avoid missing a connection\n // that happens between the initial check and listener registration.\n if (this.socket.connected) {\n onConnect();\n }\n });\n }\n\n async sendBatch(events: VHEvent[]): Promise<void> {\n if (events.length === 0) {\n return;\n }\n\n await this.waitForConnection();\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n finish(\n new Error(\n `VH server acknowledgement timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n this.socket.emit(\"vh:events\", events, (ack?: BatchAck) => {\n if (!ack?.ok) {\n finish(new Error(ack?.error ?? \"VH server rejected the batch\"));\n return;\n }\n\n console.log(`[VH Client] Server acknowledged ${events.length} events`);\n\n finish();\n });\n });\n }\n\n async destroy(): Promise<void> {\n this.socket.disconnect();\n }\n\n get connected(): boolean {\n return this.socket.connected;\n }\n}\n","import { createId } from \"./core/id.js\";\nimport { EventBatcher } from \"./core/batcher.js\";\nimport { createFingerprint } from \"./core/fingerprint.js\";\nimport { VHTransport } from \"./core/transport.js\";\n\nimport type {\n VHEvent,\n VHLogLevel,\n VHNetworkRequestInput,\n VHNetworkResponseInput,\n VHOptions,\n} from \"./core/types.js\";\n\nexport type {\n VHEvent,\n VHLogLevel,\n VHLogEvent,\n VHNetworkRequestEvent,\n VHNetworkResponseEvent,\n VHOptions,\n} from \"./core/types.js\";\n\nexport interface VHClient {\n log: Record<\n VHLogLevel,\n (message: string, data?: Record<string, unknown>) => void\n >;\n\n network: {\n req(input: VHNetworkRequestInput): void;\n res(input: VHNetworkResponseInput): void;\n };\n\n flush(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport function createVH(options: VHOptions): VHClient {\n const appName = options.appName.trim();\n\n const serverUrls = [\n ...new Set(\n (Array.isArray(options.serverUrl)\n ? options.serverUrl\n : [options.serverUrl]\n ).map((url) => url.trim()),\n ),\n ];\n\n if (!appName) {\n throw new Error(\"appName is required\");\n }\n\n if (\n serverUrls.length === 0 ||\n serverUrls.some((url) => {\n try {\n const parsed = new URL(url);\n\n return ![\"http:\", \"https:\"].includes(parsed.protocol);\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"serverUrl must contain valid http:// or https:// URLs\");\n }\n\n const showConsoleOnLog = options.showConsoleOnLog ?? true;\n\n const transports = serverUrls.map(\n (serverUrl) => new VHTransport({ serverUrl }),\n );\n\n const batcher = new EventBatcher({\n maxBatchSize: 50,\n flushIntervalMs: 5000,\n maxQueueSize: 5000,\n\n async onFlush(events) {\n await Promise.all(\n transports.map((transport) => transport.sendBatch(events)),\n );\n },\n });\n\n let destroyed = false;\n\n const pendingLogs = new Set<Promise<void>>();\n\n async function waitForPendingLogs(): Promise<void> {\n while (pendingLogs.size > 0) {\n await Promise.all([...pendingLogs]);\n }\n }\n\n function enqueue(event: VHEvent): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n batcher.enqueue(event);\n }\n\n function writeLog(\n level: VHLogLevel,\n message: string,\n data?: Record<string, unknown>,\n ): void {\n if (destroyed) {\n throw new Error(\"VH client has been destroyed\");\n }\n\n if (showConsoleOnLog) {\n console[level](`[${appName}] ${message}`, data ?? \"\");\n }\n\n const pending = createFingerprint(level, message, data)\n .then((fingerprint) => {\n if (destroyed) {\n return;\n }\n\n enqueue({\n type: \"log\",\n eventId: createId(),\n fingerprint,\n appName,\n level,\n message,\n ...(data === undefined ? {} : { data }),\n timestamp: Date.now(),\n });\n })\n .catch((error: unknown) => {\n console.error(\"[vh-dev-client] Failed to create log event\", error);\n })\n .then(() => {\n pendingLogs.delete(pending);\n });\n\n pendingLogs.add(pending);\n }\n\n const log: VHClient[\"log\"] = {\n debug: (message, data) => writeLog(\"debug\", message, data),\n info: (message, data) => writeLog(\"info\", message, data),\n warn: (message, data) => writeLog(\"warn\", message, data),\n error: (message, data) => writeLog(\"error\", message, data),\n };\n\n return {\n log,\n\n network: {\n req(input) {\n enqueue({\n type: \"network:req\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n method: input.method,\n url: input.url,\n ...(input.params === undefined ? {} : { params: input.params }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n timestamp: Date.now(),\n });\n },\n\n res(input) {\n enqueue({\n type: \"network:res\",\n eventId: createId(),\n appName,\n requestId: input.requestId,\n ...(input.method === undefined ? {} : { method: input.method }),\n ...(input.url === undefined ? {} : { url: input.url }),\n ...(input.status === undefined ? {} : { status: input.status }),\n ...(input.statusText === undefined\n ? {}\n : { statusText: input.statusText }),\n ...(input.headers === undefined ? {} : { headers: input.headers }),\n ...(input.body === undefined ? {} : { body: input.body }),\n ...(input.durationMs === undefined\n ? {}\n : { durationMs: input.durationMs }),\n ...(input.error === undefined ? {} : { error: input.error }),\n timestamp: Date.now(),\n });\n },\n },\n\n async flush() {\n await waitForPendingLogs();\n await batcher.flush();\n },\n\n async destroy() {\n if (destroyed) {\n return;\n }\n\n await waitForPendingLogs();\n await batcher.destroy();\n\n destroyed = true;\n\n await Promise.all(transports.map((transport) => transport.destroy()));\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vh-dev-client",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Development logging and network monitoring SDK for VH Dev Helper",
5
5
  "license": "MIT",
6
6
  "type": "module",