{"version":3,"file":"wsClient.cjs","names":["WS_STATES","ws","sleep","ws: IsoWS","newWebSocket"],"sources":["../../src/network/wsClient.ts"],"sourcesContent":["import { sleep } from '../helpers.js'\nimport type { WebSocketRetryOptions } from '../types.js'\nimport { newWebSocket, WS_STATES, type IsoWS } from './iso-ws.js'\n\nexport { WS_STATES } from './iso-ws.js'\n\nexport type WebSocketClientOptions = {\n  baseUrl: string | URL\n  retry: Required<WebSocketRetryOptions>\n  timeout: number\n}\n\nfunction matchesCloseCode(code: number, list: (number | [number, number])[]): boolean {\n  for (const item of list) {\n    if (typeof item === 'number') {\n      if (code === item) return true\n    } else {\n      const [start, end] = item\n      if (code >= start && code <= end) return true\n    }\n  }\n  return false\n}\n\nfunction removeWsListeners(ws?: IsoWS | null): void {\n  if (!ws) {\n    return\n  }\n\n  ws.onopen = null\n  ws.onerror = null\n  ws.onmessage = null\n  ws.onclose = null\n}\n\nexport class WebSocketClient {\n  private readonly baseUrl: string | URL\n  private readonly retry: Required<WebSocketRetryOptions>\n  private readonly timeout: number\n\n  constructor(options: WebSocketClientOptions) {\n    this.baseUrl = options.baseUrl\n    this.retry = options.retry\n    this.timeout = options.timeout\n\n    // Ensure maxAttemptsPerConnection, maxDelay, maxConnections and timeout are non-negative integers\n    this.retry.maxAttemptsPerConnection = Math.max(\n      0,\n      Math.floor(this.retry.maxAttemptsPerConnection)\n    )\n    this.retry.maxConnections = Math.max(0, Math.floor(this.retry.maxConnections))\n    this.timeout = Math.max(0, Math.floor(this.timeout))\n  }\n\n  createSession(url: string): WebSocketSession {\n    return new WebSocketSession({\n      retry: this.retry,\n      timeout: this.timeout,\n      url: new URL(url, this.baseUrl),\n    })\n  }\n}\n\nclass WebSocketSession implements Omit<IsoWS, 'onopen'> {\n  onconnecting: ((event: { connection: number; attempt: number }) => void) | null = null\n  onopen: ((event: { connection: number; attempt: number }) => void) | null = null\n  onerror: IsoWS['onerror'] = null\n  onclose: IsoWS['onclose'] = null\n  onmessage: IsoWS['onmessage'] = null\n\n  private _readyState: IsoWS['readyState'] = WS_STATES.CONNECTING\n  private _url: IsoWS['url']\n  private readonly retry: Required<WebSocketRetryOptions>\n  private readonly timeout: number\n\n  private ws: IsoWS | null = null\n  private connectionCount = 0\n  private connectionAttempt = 0\n  private connectionTimeoutId: ReturnType<typeof setTimeout> | undefined\n\n  constructor({\n    retry,\n    timeout,\n    url,\n  }: {\n    retry: Required<WebSocketRetryOptions>\n    timeout: number\n    url: string | URL\n  }) {\n    this._url = url.toString()\n\n    this.retry = retry\n    this.timeout = timeout\n\n    this.connect().catch(() => {\n      // errors are emitted via event handlers; no throw\n    })\n  }\n\n  get readyState(): IsoWS['readyState'] {\n    return this._readyState\n  }\n\n  get url(): IsoWS['url'] {\n    return this._url\n  }\n\n  send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n    if (this.readyState === WS_STATES.OPEN) {\n      if (!this.ws) {\n        throw new Error('readyState is open but ws is not initialized')\n      }\n      this.ws.send(data)\n    } else {\n      throw new Error('WebSocket is not open')\n    }\n  }\n\n  close(code = 1000, reason = ''): void {\n    if (this.readyState === WS_STATES.CLOSING || this.readyState === WS_STATES.CLOSED) {\n      return\n    }\n\n    this.clearConnectionTimeout()\n    this._readyState = WS_STATES.CLOSING\n\n    if (this.ws?.readyState === WS_STATES.OPEN) {\n      this.ws.close(code)\n    } /* if (this.readyState === WS_STATES.CONNECTING) */ else {\n      this.onWsClose(code, reason)\n    }\n  }\n\n  private onWsClose(code = 1000, reason = ''): void {\n    this.clearConnectionTimeout()\n\n    if (this.readyState !== WS_STATES.CLOSED) {\n      this._readyState = WS_STATES.CLOSED\n      this.onclose?.({ code, reason })\n    }\n\n    this.onconnecting = null\n    this.onopen = null\n    this.onclose = null\n    this.onerror = null\n    this.onmessage = null\n\n    removeWsListeners(this.ws)\n    this.ws = null\n  }\n\n  private async connect(isRetry = false): Promise<void> {\n    this.clearConnectionTimeout()\n\n    if (!isRetry) {\n      this.connectionCount += 1\n      this.connectionAttempt = 0\n    }\n    this.connectionAttempt += 1\n    this._readyState = WS_STATES.CONNECTING\n    this.onconnecting?.({ connection: this.connectionCount, attempt: this.connectionAttempt })\n\n    if (this.timeout > 0) {\n      this.connectionTimeoutId = setTimeout(() => {\n        this.close(3008, 'WebSocket connection timeout')\n      }, this.timeout)\n    }\n\n    const onError = async (ws: IsoWS | null, err?: Error) => {\n      this.clearConnectionTimeout()\n      removeWsListeners(ws)\n\n      if (this.readyState !== WS_STATES.CONNECTING) {\n        return\n      }\n\n      const noRetry =\n        this.retry.maxAttemptsPerConnection > 0 &&\n        this.connectionAttempt >= this.retry.maxAttemptsPerConnection\n      if (noRetry) {\n        this.onerror?.(new Error('WebSocket connection error', { cause: err }))\n        this.close(1006, 'WebSocket connection error')\n        return\n      }\n\n      await sleep(this.retry.delay(this.connectionAttempt))\n      if (this.readyState === WS_STATES.CONNECTING) {\n        this.connect(true)\n      }\n    }\n\n    let ws: IsoWS\n    try {\n      ws = await newWebSocket(this.url)\n    } catch (err) {\n      onError(null, err instanceof Error ? err : new Error(String(err)))\n      return\n    }\n\n    if (this.readyState !== WS_STATES.CONNECTING) {\n      ws.close(1001)\n      return\n    }\n\n    ws.onerror = () => onError(ws)\n    ws.onopen = () => {\n      this.clearConnectionTimeout()\n      removeWsListeners(ws)\n\n      if (this.readyState !== WS_STATES.CONNECTING) {\n        // User closed the connection during the connection attempt\n        ws.close(1001)\n        return\n      }\n\n      ws.onmessage = (event) => {\n        this.onmessage?.(event)\n      }\n      ws.onclose = (event) => {\n        removeWsListeners(ws)\n\n        if (this.ws !== ws) {\n          // Should not be possible ?\n          return\n        }\n\n        this.ws = null\n\n        if (this.readyState === WS_STATES.CLOSING) {\n          this.onWsClose(event.code, event.reason || '')\n          return\n        }\n\n        if (\n          (this.retry.maxConnections > 0 && this.connectionCount >= this.retry.maxConnections) ||\n          !matchesCloseCode(event.code, this.retry.closeCodes)\n        ) {\n          this.close(event.code, event.reason || '')\n          return\n        }\n\n        this.connect()\n      }\n\n      this.ws = ws\n      this._readyState = WS_STATES.OPEN\n      this.onopen?.({ connection: this.connectionCount, attempt: this.connectionAttempt })\n    }\n  }\n\n  private clearConnectionTimeout(): void {\n    if (this.connectionTimeoutId) {\n      clearTimeout(this.connectionTimeoutId)\n      this.connectionTimeoutId = undefined\n    }\n  }\n}\n\nexport type { WebSocketSession }\n"],"mappings":"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"}