Skip to content

⬅️ Back to Table of Contents

📄 useWebSocket

📊 Analysis Summary

Metric Count
🔧 Functions 9
📦 Imports 15
📊 Variables & Constants 1
🟢 Vue Composition API 1
📐 Interfaces 2
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/core/useWebSocket/index.ts

📦 Imports

Name Source
AnyFn @vueuse/shared
Fn @vueuse/shared
TimerHandle @vueuse/shared
MaybeRefOrGetter vue
ShallowRef vue
ConfigurableScheduler ../_configurable
isClient @vueuse/shared
isWorker @vueuse/shared
toRef @vueuse/shared
tryOnScopeDispose @vueuse/shared
useIntervalFn @vueuse/shared
shallowRef vue
toValue vue
watch vue
useEventListener ../useEventListener

Variables & Constants

Name Type Kind Value Exported
DEFAULT_PING_MESSAGE "ping" const 'ping'

Vue Composition API

Name Type Reactive Variables Composables
watch watch none none

Functions

useWebSocket(url: MaybeRefOrGetter<string | URL | undefin…, options: UseWebSocketOptions): UseWebSocketReturn<Data>

Reactive WebSocket client.

Parameters:

  • url any: No description

See: https://vueuse.org/useWebSocket

Raw JSDoc
/**
 * Reactive WebSocket client.
 *
 * @see https://vueuse.org/useWebSocket
 * @param url
 */

Calls:

  • shallowRef (from vue)
  • toRef (from @vueuse/shared)
  • wsRef.value.send
  • clearTimeout
  • resetRetry
  • resetHeartbeat
  • heartbeatPause
  • wsRef.value.close
  • bufferedData.push
  • _sendBuffer
  • onConnected
  • heartbeatResume
  • onDisconnected
  • resolveNestedOptions
  • checkRetries
  • delay
  • setTimeout
  • onFailed
  • onError
  • toValue (from vue)
  • onMessage
  • useIntervalFn (from @vueuse/shared)
  • scheduler
  • send
  • close
  • useEventListener (from ../useEventListener)
  • tryOnScopeDispose (from @vueuse/shared)
  • _init
  • open
  • watch (from vue)

Internal Comments:

// Status code 1000 -> Normal Closure https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code (x2)
// auto-reconnect will be trigger with ws.onclose() (x3)

Code
export function useWebSocket<Data = any>(
  url: MaybeRefOrGetter<string | URL | undefined>,
  options: UseWebSocketOptions = {},
): UseWebSocketReturn<Data> {
  const {
    onConnected,
    onDisconnected,
    onError,
    onMessage,
    immediate = true,
    autoConnect = true,
    autoClose = true,
    protocols = [],
  } = options

  const data = shallowRef<Data | null>(null)
  const status = shallowRef<WebSocketStatus>('CLOSED')
  const wsRef = shallowRef<WebSocket | undefined>()
  const urlRef = toRef(url)

  let heartbeatPause: Fn | undefined
  let heartbeatResume: Fn | undefined

  let explicitlyClosed = false
  let retried = 0

  let bufferedData: (string | ArrayBuffer | Blob)[] = []

  let retryTimeout: TimerHandle
  let pongTimeoutWait: TimerHandle

  const _sendBuffer = () => {
    if (bufferedData.length && wsRef.value && status.value === 'OPEN') {
      for (const buffer of bufferedData)
        wsRef.value.send(buffer)
      bufferedData = []
    }
  }

  const resetRetry = () => {
    if (retryTimeout != null) {
      clearTimeout(retryTimeout)
      retryTimeout = undefined
    }
  }

  const resetHeartbeat = () => {
    clearTimeout(pongTimeoutWait)
    pongTimeoutWait = undefined
  }

  // Status code 1000 -> Normal Closure https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
  const close: WebSocket['close'] = (code = 1000, reason) => {
    resetRetry()
    if ((!isClient && !isWorker) || !wsRef.value)
      return
    explicitlyClosed = true
    resetHeartbeat()
    heartbeatPause?.()
    wsRef.value.close(code, reason)
    wsRef.value = undefined
    status.value = 'CLOSED'
  }

  const send = (data: string | ArrayBuffer | Blob, useBuffer = true) => {
    if (!wsRef.value || status.value !== 'OPEN') {
      if (useBuffer)
        bufferedData.push(data)
      return false
    }
    _sendBuffer()
    wsRef.value.send(data)
    return true
  }

  const _init = () => {
    if (explicitlyClosed || typeof urlRef.value === 'undefined')
      return

    const ws = new WebSocket(urlRef.value, protocols)
    wsRef.value = ws
    status.value = 'CONNECTING'

    ws.onopen = () => {
      if (wsRef.value !== ws)
        return

      status.value = 'OPEN'
      retried = 0
      onConnected?.(ws!)
      heartbeatResume?.()
      _sendBuffer()
    }

    ws.onclose = (ev) => {
      if (wsRef.value === ws)
        status.value = 'CLOSED'

      resetHeartbeat()
      heartbeatPause?.()
      onDisconnected?.(ws, ev)

      if (!explicitlyClosed && options.autoReconnect && (wsRef.value == null || ws === wsRef.value)) {
        const {
          retries = -1,
          delay = 1000,
          onFailed,
        } = resolveNestedOptions(options.autoReconnect)

        const checkRetries = typeof retries === 'function'
          ? retries
          : () => typeof retries === 'number' && (retries < 0 || retried < retries)

        if (checkRetries(retried)) {
          retried += 1
          const delayTime = typeof delay === 'function' ? delay(retried) : delay
          retryTimeout = setTimeout(_init, delayTime)
        }
        else {
          onFailed?.()
        }
      }
    }

    ws.onerror = (e) => {
      onError?.(ws!, e)
    }

    ws.onmessage = (e: MessageEvent) => {
      if (wsRef.value !== ws)
        return

      if (options.heartbeat) {
        resetHeartbeat()
        const {
          message = DEFAULT_PING_MESSAGE,
          responseMessage = message,
        } = resolveNestedOptions(options.heartbeat)
        if (e.data === toValue(responseMessage))
          return
      }

      data.value = e.data
      onMessage?.(ws!, e)
    }
  }

  if (options.heartbeat) {
    const {
      message = DEFAULT_PING_MESSAGE,
      scheduler = (cb: AnyFn) => useIntervalFn(cb, 1000, { immediate: false }),
      pongTimeout = 1000,
    } = resolveNestedOptions(options.heartbeat)

    const { pause, resume } = scheduler(() => {
      send(toValue(message), false)
      if (pongTimeoutWait != null)
        return
      pongTimeoutWait = setTimeout(() => {
        // auto-reconnect will be trigger with ws.onclose()
        close()
        explicitlyClosed = false
      }, pongTimeout)
    })

    heartbeatPause = pause
    heartbeatResume = resume
  }

  if (autoClose) {
    if (isClient)
      useEventListener('beforeunload', () => close(), { passive: true })
    tryOnScopeDispose(close)
  }

  const open = () => {
    if (!isClient && !isWorker)
      return

    close()
    explicitlyClosed = false
    retried = 0
    _init()
  }

  if (immediate)
    open()

  if (autoConnect)
    watch(urlRef, open)

  return {
    data,
    status,
    close,
    send,
    open,
    ws: wsRef,
  }
}

resolveNestedOptions(options: T | true): T

Parameters:

  • options T | true

Returns: T

Code
function resolveNestedOptions<T>(options: T | true): T {
  if (options === true)
    return {} as T
  return options
}

Internal helpers

Declared inside another function in this file.

_sendBuffer(): void

Returns: void

Calls:

  • wsRef.value.send
Code
() => {
    if (bufferedData.length && wsRef.value && status.value === 'OPEN') {
      for (const buffer of bufferedData)
        wsRef.value.send(buffer)
      bufferedData = []
    }
  }

resetRetry(): void

Returns: void

Calls:

  • clearTimeout
Code
() => {
    if (retryTimeout != null) {
      clearTimeout(retryTimeout)
      retryTimeout = undefined
    }
  }

resetHeartbeat(): void

Returns: void

Calls:

  • clearTimeout
Code
() => {
    clearTimeout(pongTimeoutWait)
    pongTimeoutWait = undefined
  }

close(code: number, reason: string): void

Parameters:

  • code number
  • reason string

Returns: void

Calls:

  • resetRetry
  • resetHeartbeat
  • heartbeatPause
  • wsRef.value.close
Code
(code = 1000, reason) => {
    resetRetry()
    if ((!isClient && !isWorker) || !wsRef.value)
      return
    explicitlyClosed = true
    resetHeartbeat()
    heartbeatPause?.()
    wsRef.value.close(code, reason)
    wsRef.value = undefined
    status.value = 'CLOSED'
  }

send(data: string | ArrayBuffer | Blob, useBuffer: boolean): boolean

Parameters:

  • data string | ArrayBuffer | Blob
  • useBuffer boolean

Returns: boolean

Calls:

  • bufferedData.push
  • _sendBuffer
  • wsRef.value.send
Code
(data: string | ArrayBuffer | Blob, useBuffer = true) => {
    if (!wsRef.value || status.value !== 'OPEN') {
      if (useBuffer)
        bufferedData.push(data)
      return false
    }
    _sendBuffer()
    wsRef.value.send(data)
    return true
  }

_init(): void

Returns: void

Calls:

  • onConnected
  • heartbeatResume
  • _sendBuffer
  • resetHeartbeat
  • heartbeatPause
  • onDisconnected
  • resolveNestedOptions
  • checkRetries
  • delay
  • setTimeout
  • onFailed
  • onError
  • toValue (from vue)
  • onMessage
Code
() => {
    if (explicitlyClosed || typeof urlRef.value === 'undefined')
      return

    const ws = new WebSocket(urlRef.value, protocols)
    wsRef.value = ws
    status.value = 'CONNECTING'

    ws.onopen = () => {
      if (wsRef.value !== ws)
        return

      status.value = 'OPEN'
      retried = 0
      onConnected?.(ws!)
      heartbeatResume?.()
      _sendBuffer()
    }

    ws.onclose = (ev) => {
      if (wsRef.value === ws)
        status.value = 'CLOSED'

      resetHeartbeat()
      heartbeatPause?.()
      onDisconnected?.(ws, ev)

      if (!explicitlyClosed && options.autoReconnect && (wsRef.value == null || ws === wsRef.value)) {
        const {
          retries = -1,
          delay = 1000,
          onFailed,
        } = resolveNestedOptions(options.autoReconnect)

        const checkRetries = typeof retries === 'function'
          ? retries
          : () => typeof retries === 'number' && (retries < 0 || retried < retries)

        if (checkRetries(retried)) {
          retried += 1
          const delayTime = typeof delay === 'function' ? delay(retried) : delay
          retryTimeout = setTimeout(_init, delayTime)
        }
        else {
          onFailed?.()
        }
      }
    }

    ws.onerror = (e) => {
      onError?.(ws!, e)
    }

    ws.onmessage = (e: MessageEvent) => {
      if (wsRef.value !== ws)
        return

      if (options.heartbeat) {
        resetHeartbeat()
        const {
          message = DEFAULT_PING_MESSAGE,
          responseMessage = message,
        } = resolveNestedOptions(options.heartbeat)
        if (e.data === toValue(responseMessage))
          return
      }

      data.value = e.data
      onMessage?.(ws!, e)
    }
  }

open(): void

Returns: void

Calls:

  • close
  • _init
Code
() => {
    if (!isClient && !isWorker)
      return

    close()
    explicitlyClosed = false
    retried = 0
    _init()
  }

Interfaces

UseWebSocketOptions

Interface Code
export interface UseWebSocketOptions {
  onConnected?: (ws: WebSocket) => void
  onDisconnected?: (ws: WebSocket, event: CloseEvent) => void
  onError?: (ws: WebSocket, event: Event) => void
  onMessage?: (ws: WebSocket, event: MessageEvent) => void

  /**
   * Send heartbeat for every x milliseconds passed
   *
   * @default false
   */
  heartbeat?: boolean | ConfigurableScheduler & {
    /**
     * Message for the heartbeat
     *
     * @default 'ping'
     */
    message?: MaybeRefOrGetter<WebSocketHeartbeatMessage>

    /**
     * Response message for the heartbeat, if undefined the message will be used
     */
    responseMessage?: MaybeRefOrGetter<WebSocketHeartbeatMessage>

    /**
     * Heartbeat response timeout, in milliseconds
     *
     * @default 1000
     */
    pongTimeout?: number
  }

  /**
   * Enabled auto reconnect
   *
   * @default false
   */
  autoReconnect?: boolean | {
    /**
     * Maximum retry times.
     *
     * Or you can pass a predicate function (which returns true if you want to retry).
     *
     * @default -1
     */
    retries?: number | ((retried: number) => boolean)

    /**
     * Delay for reconnect, in milliseconds
     *
     * Or you can pass a function to calculate the delay based on the number of retries.
     *
     * @default 1000
     */
    delay?: number | ((retries: number) => number)

    /**
     * On maximum retry times reached.
     */
    onFailed?: Fn
  }

  /**
   * Immediately open the connection when calling this composable
   *
   * @default true
   */
  immediate?: boolean

  /**
   * Automatically connect to the websocket when URL changes
   *
   * @default true
   */
  autoConnect?: boolean

  /**
   * Automatically close a connection
   *
   * @default true
   */
  autoClose?: boolean

  /**
   * List of one or more sub-protocol strings
   *
   * @default []
   */
  protocols?: string[]
}

Properties

Name Type Optional Description
onConnected (ws: WebSocket) => void not shown
onDisconnected (ws: WebSocket, event: CloseEvent) => void not shown
onError (ws: WebSocket, event: Event) => void not shown
onMessage (ws: WebSocket, event: MessageEvent) => void not shown
heartbeat boolean \| ConfigurableScheduler & { /** * Message for the heartbeat * * @def... not shown
autoReconnect boolean \| { /** * Maximum retry times. * * Or you can pass a predicate funct... not shown
immediate boolean not shown
autoConnect boolean not shown
autoClose boolean not shown
protocols string[] not shown

UseWebSocketReturn<T>

Interface Code
export interface UseWebSocketReturn<T> {
  /**
   * Reference to the latest data received via the websocket,
   * can be watched to respond to incoming messages
   */
  data: ShallowRef<T | null>

  /**
   * The current websocket status, can be only one of:
   * 'OPEN', 'CONNECTING', 'CLOSED'
   */
  status: ShallowRef<WebSocketStatus>

  /**
   * Closes the websocket connection gracefully.
   */
  close: WebSocket['close']

  /**
   * Reopen the websocket connection.
   * If there the current one is active, will close it before opening a new one.
   */
  open: Fn

  /**
   * Sends data through the websocket connection.
   *
   * @param data
   * @param useBuffer when the socket is not yet open, store the data into the buffer and sent them one connected. Default to true.
   */
  send: (data: string | ArrayBuffer | Blob, useBuffer?: boolean) => boolean

  /**
   * Reference to the WebSocket instance.
   */
  ws: ShallowRef<WebSocket | undefined>
}

Properties

Name Type Optional Description
data ShallowRef<T \| null> not shown
status ShallowRef<WebSocketStatus> not shown
close WebSocket['close'] not shown
open Fn not shown
send (data: string \| ArrayBuffer \| Blob, useBuffer?: boolean) => boolean not shown
ws ShallowRef<WebSocket \| undefined> not shown

Type Aliases

WebSocketStatus

type WebSocketStatus = 'OPEN' | 'CONNECTING' | 'CLOSED';

WebSocketHeartbeatMessage

type WebSocketHeartbeatMessage = string | ArrayBuffer | Blob;

Generated by Syntax Scribe