<commit_before>/* global WebSocket */

// Custom object wrapping logic to websocket and exposing a subscription
// interface
let cozySocket

// Important, must match the spec,
// see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const WEBSOCKET_STATE = {
  OPEN: 1
}

const KEEPALIVE = {
  INTERVAL: 30000,
  METHOD_NAME: 'ping'
}

const NUM_RETRIES = 3

// Send a subscribe message for the given doctype trough the given websocket, but
// only if it is in a ready state. If not, retry a few milliseconds later.
function subscribeWhenReady(doctype, socket) {
  if (socket.readyState === WEBSOCKET_STATE.OPEN) {
    try {
      socket.send(
        JSON.stringify({
          method: 'SUBSCRIBE',
          payload: {
            type: doctype
          }
        })
      )
    } catch (error) {
      console.warn &&
        console.warn(`Cannot subscribe to doctype ${doctype}: ${error.message}`)
      throw error
    }
  } else {
    setTimeout(() => {
      subscribeWhenReady(doctype, socket)
    }, 10)
  }
}

function getWebsocketProtocol() {
  return window.location.protocol === 'http:' ? 'ws' : 'wss'
}

function keepAlive(socket, interval, message) {
  const keepAliveInterval = setInterval(() => {
    if (socket.readyState === WEBSOCKET_STATE.OPEN) {
      socket.send(message)
    } else {
      clearInterval(keepAliveInterval)
    }
  }, interval)

  return socket
}

async function connectWebSocket(cozy, onmessage, onclose, numRetries) {
  return new Promise((resolve, reject) => {
    const protocol = getWebsocketProtocol()
    const socket = new WebSocket(
      `${protocol}:${cozy._url}/realtime/`,
      'io.cozy.websocket'
    )

    socket.onopen = event => {
      try {
        socket.send(
          JSON.stringify({
            method: 'AUTH',
            payload: cozy._token.token
          })
        )
      } catch (error) {
        return reject(error)
      }

      const windowUnloadHandler = () => socket.close()
      window.addEventListener('beforeunload', windowUnloadHandler)

      socket.onmessage = onmessage
      socket.onclose = event => {
        window.removeEventListener('beforeunload', windowUnloadHandler)
        if (typeof onclose === 'function') onclose(event, numRetries)
      }
      socket.onerror = error =>
        console.error && console.error(`WebSocket error: ${error.message}`)

      resolve(
        keepAlive(
          socket,
          KEEPALIVE.INTERVAL,
          `{"method":"${KEEPALIVE.METHOD_NAME}"}`
        )
      )
    }
  })
}

function getCozySocket(cozy) {
  return new Promise(async (resolve, reject) => {
    if (cozySocket) return resolve(cozySocket)

    const listeners = {}

    let socket

    const onSocketMessage = event => {
      const data = JSON.parse(event.data)
      const eventType = data.event.toLowerCase()
      const payload = data.payload

      if (eventType === 'error') {
        const isPingError =
          data.payload &&
          data.payload.source &&
          data.payload.source.method === KEEPALIVE.METHOD_NAME
        if (isPingError) return

        const realtimeError = new Error(payload.title)
        const errorFields = ['status', 'code', 'source']
        errorFields.forEach(property => {
          realtimeError[property] = payload[property]
        })

        throw realtimeError
      }

      if (listeners[payload.type] && listeners[payload.type][eventType]) {
        listeners[payload.type][eventType].forEach(listener => {
          listener(payload.doc)
        })
      }
    }

    const onSocketClose = async (event, numRetries) => {
      if (!event.wasClean) {
        console.warn &&
          console.warn(
            `WebSocket closed unexpectedly with code ${event.code} and ${event.reason
              ? `reason: '${event.reason}'`
              : 'no reason'}.`
          )

        if (numRetries) {
          console.warn &&
            console.warn(`Reconnecting ... ${numRetries} tries left.`)
          try {
            socket = await connectWebSocket(
              cozy,
              onSocketMessage,
              onSocketClose,
              --numRetries
            )
          } catch (error) {
            console.error &&
              console.error(
                `Unable to reconnect to realtime. Error: ${error.message}`
              )
          }
        }
      }
    }

    try {
      socket = await connectWebSocket(
        cozy,
        onSocketMessage,
        onSocketClose,
        NUM_RETRIES
      )
    } catch (error) {
      reject(error)
    }

    cozySocket = {
      subscribe: (doctype, event, listener) => {<commit_msg>feat: increase realtime retry delay at every retry ✨<commit_after>/* global WebSocket */

// Custom object wrapping logic to websocket and exposing a subscription
// interface
let cozySocket

// Important, must match the spec,
// see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const WEBSOCKET_STATE = {
  OPEN: 1
}

const KEEPALIVE = {
  INTERVAL: 30000,
  METHOD_NAME: 'ping'
}

const NUM_RETRIES = 3
const RETRY_BASE_DELAY = 1000

// Send a subscribe message for the given doctype trough the given websocket, but
// only if it is in a ready state. If not, retry a few milliseconds later.
function subscribeWhenReady(doctype, socket) {
  if (socket.readyState === WEBSOCKET_STATE.OPEN) {
    try {
      socket.send(
        JSON.stringify({
          method: 'SUBSCRIBE',
          payload: {
            type: doctype
          }
        })
      )
    } catch (error) {
      console.warn &&
        console.warn(`Cannot subscribe to doctype ${doctype}: ${error.message}`)
      throw error
    }
  } else {
    setTimeout(() => {
      subscribeWhenReady(doctype, socket)
    }, 10)
  }
}

function getWebsocketProtocol() {
  return window.location.protocol === 'http:' ? 'ws' : 'wss'
}

function keepAlive(socket, interval, message) {
  const keepAliveInterval = setInterval(() => {
    if (socket.readyState === WEBSOCKET_STATE.OPEN) {
      socket.send(message)
    } else {
      clearInterval(keepAliveInterval)
    }
  }, interval)

  return socket
}

async function connectWebSocket(
  cozy,
  onmessage,
  onclose,
  numRetries,
  retryDelay
) {
  return new Promise((resolve, reject) => {
    const protocol = getWebsocketProtocol()
    const socket = new WebSocket(
      `${protocol}:${cozy._url}/realtime/`,
      'io.cozy.websocket'
    )

    socket.onopen = event => {
      try {
        socket.send(
          JSON.stringify({
            method: 'AUTH',
            payload: cozy._token.token
          })
        )
      } catch (error) {
        return reject(error)
      }

      const windowUnloadHandler = () => socket.close()
      window.addEventListener('beforeunload', windowUnloadHandler)

      socket.onmessage = onmessage
      socket.onclose = event => {
        window.removeEventListener('beforeunload', windowUnloadHandler)
        if (typeof onclose === 'function')
          onclose(event, numRetries, retryDelay)
      }
      socket.onerror = error =>
        console.error && console.error(`WebSocket error: ${error.message}`)

      resolve(
        keepAlive(
          socket,
          KEEPALIVE.INTERVAL,
          `{"method":"${KEEPALIVE.METHOD_NAME}"}`
        )
      )
    }
  })
}

function getCozySocket(cozy) {
  return new Promise(async (resolve, reject) => {
    if (cozySocket) return resolve(cozySocket)

    const listeners = {}

    let socket

    const onSocketMessage = event => {
      const data = JSON.parse(event.data)
      const eventType = data.event.toLowerCase()
      const payload = data.payload

      if (eventType === 'error') {
        const isPingError =
          data.payload &&
          data.payload.source &&
          data.payload.source.method === KEEPALIVE.METHOD_NAME
        if (isPingError) return

        const realtimeError = new Error(payload.title)
        const errorFields = ['status', 'code', 'source']
        errorFields.forEach(property => {
          realtimeError[property] = payload[property]
        })

        throw realtimeError
      }

      if (listeners[payload.type] && listeners[payload.type][eventType]) {
        listeners[payload.type][eventType].forEach(listener => {
          listener(payload.doc)
        })
      }
    }

    const onSocketClose = async (event, numRetries, retryDelay) => {
      if (!event.wasClean) {
        console.warn &&
          console.warn(
            `WebSocket closed unexpectedly with code ${event.code} and ${event.reason
              ? `reason: '${event.reason}'`
              : 'no reason'}.`
          )

        if (numRetries) {
          console.warn &&
            console.warn(`Reconnecting ... ${numRetries} tries left.`)
          setTimeout(async () => {
            try {
              socket = await connectWebSocket(
                cozy,
                onSocketMessage,
                onSocketClose,
                --numRetries,
                retryDelay + 1000
              )
            } catch (error) {
              console.error &&
                console.error(
                  `Unable to reconnect to realtime. Error: ${error.message}`
                )
            }
          }, retryDelay)
        }
      }
    }

    try {
      socket = await connectWebSocket(
        cozy,
        onSocketMessage,
        onSocketClose,
        NUM_RETRIES,
        RETRY_BASE_DELAY
      )
    } catch (error) {
      reject(error)
    }

    cozySocket = {
      subscribe: (doctype, event, listener) => {
{'fraction_non_alphanumeric': 0.11493288590604027, 'fraction_numerical': 0.002726510067114094, 'mean_word_length': 2.0333969465648853, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 30, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4697254', 'n_tokens_mistral': 2821, 'n_tokens_neox': 2713, 'n_words': 616}