{"version":3,"file":"webchat-BUYjhPs9.mjs","names":["Base","options: any","params: Record<string, string | boolean | number | undefined>","config: IWebchatConfiguration","evt: Websocket.MessageEvent","stop?: boolean","sampleRate?: number","e: MessageEvent","constraints: MediaStreamConstraints","f32: Float32Array","u8: Uint8Array"],"sources":["../src/client/webchat/webchat.ts"],"sourcesContent":["import {\n  IWebchatConfiguration,\n  IWebchatState,\n  IWebchatWSSettings,\n} from \"../../types/webchat\";\nimport Base from \"../base\";\nimport Websocket from \"isomorphic-ws\";\n\ninterface IWebchatPublic {\n  state: IWebchatState;\n  start: (config: IWebchatConfiguration) => Promise<Websocket>;\n  stop: () => void;\n}\n\nclass Webchat extends Base implements IWebchatPublic {\n  state: IWebchatState;\n  private connectionUrl: string | null = null;\n  private websocket: Websocket | null = null;\n  private wsSettings: IWebchatWSSettings;\n  private lastActivityAt: number | null = null;\n  private keepAliveInterval: ReturnType<typeof setInterval> | null = null;\n\n  private audioContext: AudioContext | null = null;\n  private micStream: MediaStream | null = null;\n  private sourceNode: MediaStreamAudioSourceNode | null = null;\n  private micNode: AudioWorkletNode | null = null;\n  private sinkGain: GainNode | null = null;\n\n  constructor(options: any) {\n    super(options);\n    this.endpoint =\n      options?.endpoint ||\n      \"wss://stream-v2.aws.dc8.bland.ai/ws/connect/blandshared\";\n    this.connectionUrl = null;\n    this.wsSettings = { interval: 15000, timeout: 10000 };\n    this.state = \"closed\";\n  }\n\n  private params(\n    params: Record<string, string | boolean | number | undefined>\n  ): string {\n    const q = new URLSearchParams();\n    for (const [k, v] of Object.entries(params)) {\n      if (v !== undefined && v !== null) q.append(k, String(v));\n    }\n    return q.toString();\n  }\n\n  public async start(config: IWebchatConfiguration): Promise<Websocket> {\n    if (this.state !== \"closed\") throw new Error(\"Webchat already started\");\n    this.state = \"connecting\";\n\n    if (!config.sessionId) {\n      this.state = \"closed\";\n      throw new Error(\n        \"A session token is required to start the webchat. Please use the admin client server-side to generate a session token.\"\n      );\n    }\n\n    if (!config.agentId) {\n      this.state = \"closed\";\n      throw new Error(\n        \"An agent ID is required to start the webchat. Please create an agent in the dashboard, and use the agent ID here.\"\n      );\n    }\n\n    const wssUrl = `${this.endpoint}?${this.params({\n      agent: config.agentId,\n      token: config.sessionId,\n    })}`;\n\n    this.connectionUrl = wssUrl;\n    this.websocket = this.create();\n    this.websocket.binaryType = \"arraybuffer\";\n\n    this.websocket.onopen = async (): Promise<void> => {\n      try {\n        await this.initAudioWorklet(config.sampleRate);\n        this.state = \"open\";\n        this.keepAlive();\n      } catch (e) {\n        this.state = \"closed\";\n        this.stop();\n        throw e;\n      }\n    };\n\n    this.websocket.onmessage = (evt: Websocket.MessageEvent): void => {\n      this.setActivity();\n      if (evt.data instanceof ArrayBuffer) {\n        const u8 = new Uint8Array(evt.data as ArrayBuffer);\n        this.playPcm(u8);\n      }\n    };\n\n    this.websocket.onclose = (): void => {\n      this.keepAlive(true);\n      this.teardownAudio();\n      this.state = \"closed\";\n    };\n\n    this.websocket.onerror = (): void => {\n      this.keepAlive(true);\n      this.teardownAudio();\n      this.state = \"closed\";\n    };\n\n    return this.websocket as Websocket;\n  }\n\n  private create(): Websocket {\n    if (!this.connectionUrl) {\n      this.state = \"closed\";\n      throw new Error(\n        \"Connection URL is not set. Please call start() before creating the websocket connection.\"\n      );\n    }\n\n    this.websocket = new Websocket(this.connectionUrl || \"\");\n    return this.websocket;\n  }\n\n  public stop(): void {\n    if (\n      this.websocket &&\n      (this.websocket.readyState === this.websocket.OPEN ||\n        this.websocket.readyState === this.websocket.CONNECTING)\n    ) {\n      this.state = \"closing\";\n      this.websocket.close();\n    }\n    this.keepAlive(true);\n    this.teardownAudio();\n  }\n\n  private keepAlive(stop?: boolean): void {\n    if (stop) {\n      if (this.keepAliveInterval) clearInterval(this.keepAliveInterval);\n      this.keepAliveInterval = null;\n      this.lastActivityAt = null;\n    } else {\n      this.lastActivityAt = Date.now();\n      if (this.keepAliveInterval) clearInterval(this.keepAliveInterval);\n      this.keepAliveInterval = setInterval((): void => {\n        if (\n          this.lastActivityAt &&\n          Date.now() - this.lastActivityAt > this.wsSettings.timeout\n        ) {\n          this.stop();\n          this.state = \"closed\";\n        }\n      }, this.wsSettings.interval);\n    }\n  }\n\n  private setActivity(): void {\n    this.lastActivityAt = Date.now();\n  }\n\n  private async initAudioWorklet(sampleRate?: number): Promise<void> {\n    this.audioContext = new AudioContext(sampleRate ? { sampleRate } : {});\n    const code = `\n            class MicCaptureProcessor extends AudioWorkletProcessor {\n                process(inputs) {\n                    const ch = inputs[0] && inputs[0][0]\n                    if (ch) {\n                        const copy = new Float32Array(ch.length)\n                        copy.set(ch)\n                        this.port.postMessage(copy.buffer, [copy.buffer])\n                    }\n                    return true\n                }\n            }\n            registerProcessor('mic-capture', MicCaptureProcessor)\n        `;\n    const blob = new Blob([code], { type: \"application/javascript\" });\n    const url = URL.createObjectURL(blob);\n    await this.audioContext.audioWorklet.addModule(url);\n    URL.revokeObjectURL(url);\n\n    this.micNode = new AudioWorkletNode(this.audioContext, \"mic-capture\");\n    this.micNode.port.onmessage = (e: MessageEvent): void => {\n      if (!this.websocket || this.websocket.readyState !== this.websocket.OPEN)\n        return;\n      const f32 = new Float32Array(e.data as ArrayBuffer);\n      const pcm = this.float32ToPCM16(f32);\n      this.websocket.send(pcm);\n    };\n\n    const constraints: MediaStreamConstraints = {\n      audio: {\n        sampleRate: sampleRate,\n        echoCancellation: true,\n        noiseSuppression: true,\n        channelCount: 1,\n      },\n    };\n    this.micStream = await navigator.mediaDevices.getUserMedia(constraints);\n    this.sourceNode = this.audioContext.createMediaStreamSource(this.micStream);\n    this.sinkGain = this.audioContext.createGain();\n    this.sinkGain.gain.value = 0;\n\n    this.sourceNode.connect(this.micNode);\n    this.micNode.connect(this.sinkGain);\n    this.sinkGain.connect(this.audioContext.destination);\n    await this.audioContext.resume();\n  }\n\n  private teardownAudio(): void {\n    if (this.micNode) {\n      try {\n        this.micNode.disconnect();\n      } catch {}\n      this.micNode.port.onmessage = null as any;\n      this.micNode = null;\n    }\n    if (this.sourceNode) {\n      try {\n        this.sourceNode.disconnect();\n      } catch {}\n      this.sourceNode = null;\n    }\n    if (this.sinkGain) {\n      try {\n        this.sinkGain.disconnect();\n      } catch {}\n      this.sinkGain = null;\n    }\n    if (this.micStream) {\n      this.micStream.getTracks().forEach((t) => t.stop());\n      this.micStream = null;\n    }\n    if (this.audioContext) {\n      this.audioContext.close();\n      this.audioContext = null;\n    }\n  }\n\n  private float32ToPCM16(f32: Float32Array): Uint8Array {\n    const out = new Int16Array(f32.length);\n    for (let i = 0; i < f32.length; i++) {\n      const s = Math.max(-1, Math.min(1, f32[i]));\n      out[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n    }\n    return new Uint8Array(out.buffer);\n  }\n\n  private pcm16ToFloat32(u8: Uint8Array): Float32Array {\n    const dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);\n    const len = u8.byteLength / 2;\n    const f32 = new Float32Array(len);\n    for (let i = 0; i < len; i++) {\n      const s = dv.getInt16(i * 2, true);\n      f32[i] = s / 0x8000;\n    }\n    return f32;\n  }\n\n  private playPcm(u8: Uint8Array): void {\n    if (!this.audioContext) return;\n    const f32 = this.pcm16ToFloat32(u8);\n    const buffer = this.audioContext.createBuffer(\n      1,\n      f32.length,\n      this.audioContext.sampleRate\n    );\n    buffer.copyToChannel(new Float32Array(f32), 0, 0);\n    const src = this.audioContext.createBufferSource();\n    src.buffer = buffer;\n    src.connect(this.audioContext.destination);\n    src.start();\n  }\n}\n\nexport type { IWebchatPublic };\nexport default Webchat;\n"],"mappings":"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"}