{"version":3,"file":"utils-CwkqVmBx.mjs","names":[],"sources":["../src/channels.ts","../src/utils.ts"],"sourcesContent":["import type { StreamChannelRef } from './iii-types'\n\n/**\n * Direction of a streaming channel endpoint. Mirrors the Rust SDK's\n * `ChannelDirection` enum and matches the literal values used by\n * {@link StreamChannelRef.direction}.\n */\nexport const ChannelDirection = {\n  Read: 'read',\n  Write: 'write',\n} as const\nexport type ChannelDirection = (typeof ChannelDirection)[keyof typeof ChannelDirection]\n\n/**\n * Discriminated runtime tag for an item observed on a streaming channel.\n * Mirrors the Rust SDK's `ChannelItem` enum (`Text` / `Binary`). Carrier for\n * factory + type-guard helpers so callers can construct and discriminate\n * channel items without depending on Rust-specific shape.\n */\nexport type ChannelItem =\n  | { type: 'text'; value: string }\n  | { type: 'binary'; value: Uint8Array }\n\nexport const ChannelItem = {\n  /** Construct a text channel item. */\n  Text(value: string): ChannelItem {\n    return { type: 'text', value }\n  },\n  /** Construct a binary channel item. */\n  Binary(value: Uint8Array): ChannelItem {\n    return { type: 'binary', value }\n  },\n} as const\n\n/**\n * Write end of a streaming channel. Uses native browser WebSocket.\n *\n * @example\n * ```typescript\n * import { createChannel } from 'iii-browser-sdk/helpers'\n * const channel = await createChannel(iii)\n *\n * channel.writer.sendMessage(JSON.stringify({ type: 'event', data: 'test' }))\n * channel.writer.sendBinary(new Uint8Array([1, 2, 3]))\n * channel.writer.close()\n * ```\n */\nexport class ChannelWriter {\n  private static readonly FRAME_SIZE = 64 * 1024\n  private ws: WebSocket | null = null\n  private wsReady = false\n  private readonly pendingMessages: {\n    data: ArrayBuffer | string\n    resolve: () => void\n    reject: (err: Error) => void\n  }[] = []\n  private readonly url: string\n\n  constructor(engineWsBase: string, ref: StreamChannelRef) {\n    this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, 'write')\n  }\n\n  private ensureConnected(): void {\n    if (this.ws) return\n\n    this.ws = new WebSocket(this.url)\n    this.ws.binaryType = 'arraybuffer'\n\n    this.ws.addEventListener('open', () => {\n      this.wsReady = true\n      for (const { data, resolve, reject } of this.pendingMessages) {\n        try {\n          this.ws?.send(data)\n          resolve()\n        } catch (err) {\n          reject(err instanceof Error ? err : new Error(String(err)))\n        }\n      }\n      this.pendingMessages.length = 0\n    })\n\n    this.ws.addEventListener('error', () => {\n      for (const { reject } of this.pendingMessages) {\n        reject(new Error('WebSocket error'))\n      }\n      this.pendingMessages.length = 0\n    })\n  }\n\n  /** Send a text message through the channel. */\n  sendMessage(msg: string): void {\n    this.ensureConnected()\n    this.sendRaw(msg)\n  }\n\n  /** Send binary data through the channel. */\n  sendBinary(data: Uint8Array): void {\n    this.ensureConnected()\n\n    let offset = 0\n    while (offset < data.length) {\n      const end = Math.min(offset + ChannelWriter.FRAME_SIZE, data.length)\n      const chunk = data.subarray(offset, end)\n      const buffer = chunk.buffer instanceof ArrayBuffer ? chunk.buffer : new ArrayBuffer(chunk.byteLength)\n      if (!(chunk.buffer instanceof ArrayBuffer)) {\n        new Uint8Array(buffer).set(chunk)\n      }\n      this.sendRaw(buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength))\n      offset = end\n    }\n  }\n\n  /** Close the channel writer. */\n  close(): void {\n    if (!this.ws) return\n\n    const doClose = () => {\n      if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n        this.ws.close(1000, 'channel_close')\n      }\n    }\n\n    if (this.wsReady) {\n      doClose()\n    } else {\n      this.ws.addEventListener('open', () => doClose())\n    }\n  }\n\n  private sendRaw(data: ArrayBuffer | string): void {\n    if (this.wsReady && this.ws && this.ws.readyState === WebSocket.OPEN) {\n      this.ws.send(data)\n    } else {\n      this.ensureConnected()\n      this.pendingMessages.push({\n        data,\n        resolve: () => {\n          //\n        },\n        reject: () => {\n          console.error('Failed to send message')\n        },\n      })\n    }\n  }\n}\n\n/**\n * Read end of a streaming channel. Uses native browser WebSocket.\n *\n * @example\n * ```typescript\n * import { createChannel } from 'iii-browser-sdk/helpers'\n * const channel = await createChannel(iii)\n *\n * channel.reader.onMessage((msg) => console.log('Got:', msg))\n * channel.reader.onBinary((data) => console.log('Binary:', data.byteLength))\n * ```\n */\nexport class ChannelReader {\n  private ws: WebSocket | null = null\n  private connected = false\n  private readonly messageCallbacks: Array<(msg: string) => void> = []\n  private readonly binaryCallbacks: Array<(data: Uint8Array) => void> = []\n  private readonly url: string\n\n  constructor(engineWsBase: string, ref: StreamChannelRef) {\n    this.url = buildChannelUrl(engineWsBase, ref.channel_id, ref.access_key, 'read')\n  }\n\n  private ensureConnected(): void {\n    if (this.connected) return\n    this.connected = true\n\n    this.ws = new WebSocket(this.url)\n    this.ws.binaryType = 'arraybuffer'\n\n    this.ws.addEventListener('message', (event: MessageEvent) => {\n      if (event.data instanceof ArrayBuffer) {\n        const data = new Uint8Array(event.data)\n        for (const cb of this.binaryCallbacks) {\n          cb(data)\n        }\n      } else if (typeof event.data === 'string') {\n        for (const cb of this.messageCallbacks) {\n          cb(event.data)\n        }\n      }\n    })\n\n    this.ws.addEventListener('close', () => {\n      this.ws = null\n    })\n\n    this.ws.addEventListener('error', () => {\n      this.ws = null\n    })\n  }\n\n  /** Register a callback to receive text messages from the channel. */\n  onMessage(callback: (msg: string) => void): void {\n    this.messageCallbacks.push(callback)\n    this.ensureConnected()\n  }\n\n  /** Register a callback to receive binary data from the channel. */\n  onBinary(callback: (data: Uint8Array) => void): void {\n    this.binaryCallbacks.push(callback)\n    this.ensureConnected()\n  }\n\n  /** Read all binary data from the channel until it closes. */\n  async readAll(): Promise<Uint8Array> {\n    this.ensureConnected()\n    const chunks: Uint8Array[] = []\n\n    return new Promise<Uint8Array>((resolve) => {\n      const onData = (data: Uint8Array) => {\n        chunks.push(data)\n      }\n      this.binaryCallbacks.push(onData)\n\n      const originalWs = this.ws\n      if (originalWs) {\n        originalWs.addEventListener('close', () => {\n          const totalLength = chunks.reduce((sum, c) => sum + c.length, 0)\n          const result = new Uint8Array(totalLength)\n          let offset = 0\n          for (const chunk of chunks) {\n            result.set(chunk, offset)\n            offset += chunk.length\n          }\n          resolve(result)\n        })\n      }\n    })\n  }\n\n  /** Close the channel reader. */\n  close(): void {\n    if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {\n      this.ws.close(1000, 'channel_close')\n    }\n  }\n}\n\nfunction buildChannelUrl(\n  engineWsBase: string,\n  channelId: string,\n  accessKey: string,\n  direction: 'read' | 'write',\n): string {\n  const base = engineWsBase.replace(/\\/$/, '')\n  return `${base}/ws/channels/${channelId}?key=${encodeURIComponent(accessKey)}&dir=${direction}`\n}\n","import type { StreamChannelRef } from './iii-types'\n\n/**\n * Safely stringify a value, handling circular references, BigInt, and other edge cases.\n * Returns \"[unserializable]\" if serialization fails for any reason.\n */\nexport function safeStringify(value: unknown): string {\n  const seen = new WeakSet<object>()\n\n  try {\n    return JSON.stringify(value, (_key, val) => {\n      if (typeof val === 'bigint') {\n        return val.toString()\n      }\n\n      if (val !== null && typeof val === 'object') {\n        if (seen.has(val)) {\n          return '[Circular]'\n        }\n        seen.add(val)\n      }\n\n      return val\n    })\n  } catch {\n    return '[unserializable]'\n  }\n}\n\n/**\n * Type guard that checks if a value is a {@link StreamChannelRef}.\n *\n * @param value - Value to check.\n * @returns `true` if the value is a valid `StreamChannelRef`.\n */\nexport const isChannelRef = (value: unknown): value is StreamChannelRef => {\n  if (typeof value !== 'object' || value === null) return false\n  const maybe = value as Partial<StreamChannelRef>\n  return (\n    typeof maybe.channel_id === 'string' &&\n    typeof maybe.access_key === 'string' &&\n    (maybe.direction === 'read' || maybe.direction === 'write')\n  )\n}\n\n/**\n * Recursively extract all {@link StreamChannelRef} values from a JSON-like\n * input, returning each match paired with its dotted/bracketed path. Mirrors\n * the Rust SDK's `extract_channel_refs`.\n *\n * @param data - Arbitrary JSON-like value.\n * @returns Array of `[path, ref]` tuples. Empty when no refs are found.\n */\nexport const extractChannelRefs = (data: unknown): Array<[string, StreamChannelRef]> => {\n  const refs: Array<[string, StreamChannelRef]> = []\n  extractRefsRecursive(data, '', refs)\n  return refs\n}\n\nconst extractRefsRecursive = (\n  data: unknown,\n  prefix: string,\n  refs: Array<[string, StreamChannelRef]>,\n): void => {\n  if (isChannelRef(data)) {\n    refs.push([prefix, data])\n    return\n  }\n  if (Array.isArray(data)) {\n    for (let i = 0; i < data.length; i++) {\n      const path = prefix === '' ? `[${i}]` : `${prefix}[${i}]`\n      extractRefsRecursive(data[i], path, refs)\n    }\n    return\n  }\n  if (typeof data !== 'object' || data === null) return\n\n  for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n    const path = prefix === '' ? key : `${prefix}.${key}`\n    extractRefsRecursive(value, path, refs)\n  }\n}\n"],"mappings":"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"}