<commit_before>/* global CustomEvent */
import WebSocket from 'ws'
import Path from 'function-tree/lib/Path'
const VERSION = 'v1'

class Devtools {
  constructor (options = {
    remoteDebugger: null
  }) {
    this.VERSION = VERSION
    this.remoteDebugger = options.remoteDebugger || null
    this.backlog = []
    this.latestExecutionId = null
    this.isConnected = false
    this.ws = null
    this.isResettingDebugger = false
    this.isBrowserEnv = typeof window !== 'undefined' && Boolean(window.addEventListener)

    this.sendInitial = this.sendInitial.bind(this)

    this.init()
  }

  addListeners () {
    if (this.remoteDebugger) {
      this.ws = new WebSocket(`ws://${this.remoteDebugger}`)
      this.ws.onmessage = (event) => {
        const message = JSON.parse(event.data)
        switch (message.type) {
          case 'pong':
            this.sendInitial()
            break
          case 'ping':
            this.sendInitial()
            break
        }
      }
    } else if (this.isBrowserEnv) {
      window.addEventListener('funtion-tree.debugger.pong', this.sendInitial)
      window.addEventListener('funtion-tree.debugger.ping', this.sendInitial)
    }
  }
  init () {
    this.addListeners()

    if (this.remoteDebugger) {
      this.ws.onopen = () => {
        this.ws.send(JSON.stringify({type: 'ping'}))
      }
      this.ws.onclose = () => {
        console.warn('Could not connect to Function Tree Debugger application, make sure it is running on the correct port')
        if (this.isBrowserEnv) {
          this.reInit()
        }
      }
      this.ws.onerror = () => {
        if (this.isBrowserEnv) {
          this.reInit()
        }
      }
    } else if (this.isBrowserEnv) {
      const event = new CustomEvent('function-tree.client.message', {
        detail: JSON.stringify({type: 'ping'})
      })
      window.dispatchEvent(event)
    }
  }
  safeStringify (object) {
    const refs = []

    return JSON.stringify(object, (key, value) => {
      const isObject = (
        typeof value === 'object' &&
        value !== null &&
        !Array.isArray(value)
      )

      if (isObject && refs.indexOf(value) > -1) {
        return '[CIRCULAR]'
      } else if (isObject) {
        refs.push(value)
      }

      return value
    })
  }
  reInit () {
    this.remoteDebugger = null
    this.addListeners()

    const event = new CustomEvent('function-tree.client.message', {
      detail: JSON.stringify({type: 'ping'})
    })
    window.dispatchEvent(event)
  }
  sendMessage (stringifiedMessage) {
    if (this.remoteDebugger) {
      this.ws.send(stringifiedMessage)
    } else if (this.isBrowserEnv) {
      const event = new CustomEvent('function-tree.client.message', {detail: stringifiedMessage})
      window.dispatchEvent(event)
    }
  }
  watchExecution (tree) {
    tree.on('start', (execution) => {
      const message = JSON.stringify({
        type: 'executionStart',
        data: {
          execution: {
            executionId: execution.id,
            name: execution.name,
            staticTree: execution.staticTree,
            datetime: execution.datetime
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('end', (execution) => {
      const message = JSON.stringify({
        type: 'executionEnd',
        data: {
          execution: {
            executionId: execution.id
          }
        }
      })
      this.latestExecutionId = execution.id

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('pathStart', (path, execution, funcDetails) => {
      const message = JSON.stringify({
        type: 'executionPathStart',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            path
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('functionStart', (execution, funcDetails, payload) => {
      const message = this.safeStringify({
        type: 'execution',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            payload,
            data: null
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('functionEnd', (execution, funcDetails, payload, result) => {
      if (!result || (result instanceof Path && !result.payload)) {
        return
      }

      const message = JSON.stringify({
        type: 'executionFunctionEnd',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            output: result instanceof Path ? result.payload : result
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })<commit_msg>fix(function-tree): safe stringify on function end (#575)<commit_after>/* global CustomEvent */
import WebSocket from 'ws'
import Path from 'function-tree/lib/Path'
const VERSION = 'v1'

class Devtools {
  constructor (options = {
    remoteDebugger: null
  }) {
    this.VERSION = VERSION
    this.remoteDebugger = options.remoteDebugger || null
    this.backlog = []
    this.latestExecutionId = null
    this.isConnected = false
    this.ws = null
    this.isResettingDebugger = false
    this.isBrowserEnv = typeof window !== 'undefined' && Boolean(window.addEventListener)

    this.sendInitial = this.sendInitial.bind(this)

    this.init()
  }

  addListeners () {
    if (this.remoteDebugger) {
      this.ws = new WebSocket(`ws://${this.remoteDebugger}`)
      this.ws.onmessage = (event) => {
        const message = JSON.parse(event.data)
        switch (message.type) {
          case 'pong':
            this.sendInitial()
            break
          case 'ping':
            this.sendInitial()
            break
        }
      }
    } else if (this.isBrowserEnv) {
      window.addEventListener('funtion-tree.debugger.pong', this.sendInitial)
      window.addEventListener('funtion-tree.debugger.ping', this.sendInitial)
    }
  }
  init () {
    this.addListeners()

    if (this.remoteDebugger) {
      this.ws.onopen = () => {
        this.ws.send(JSON.stringify({type: 'ping'}))
      }
      this.ws.onclose = () => {
        console.warn('Could not connect to Function Tree Debugger application, make sure it is running on the correct port')
        if (this.isBrowserEnv) {
          this.reInit()
        }
      }
      this.ws.onerror = () => {
        if (this.isBrowserEnv) {
          this.reInit()
        }
      }
    } else if (this.isBrowserEnv) {
      const event = new CustomEvent('function-tree.client.message', {
        detail: JSON.stringify({type: 'ping'})
      })
      window.dispatchEvent(event)
    }
  }
  safeStringify (object) {
    const refs = []

    return JSON.stringify(object, (key, value) => {
      const isObject = (
        typeof value === 'object' &&
        value !== null &&
        !Array.isArray(value)
      )

      if (isObject && refs.indexOf(value) > -1) {
        return '[CIRCULAR]'
      } else if (isObject) {
        refs.push(value)
      }

      return value
    })
  }
  reInit () {
    this.remoteDebugger = null
    this.addListeners()

    const event = new CustomEvent('function-tree.client.message', {
      detail: JSON.stringify({type: 'ping'})
    })
    window.dispatchEvent(event)
  }
  sendMessage (stringifiedMessage) {
    if (this.remoteDebugger) {
      this.ws.send(stringifiedMessage)
    } else if (this.isBrowserEnv) {
      const event = new CustomEvent('function-tree.client.message', {detail: stringifiedMessage})
      window.dispatchEvent(event)
    }
  }
  watchExecution (tree) {
    tree.on('start', (execution) => {
      const message = JSON.stringify({
        type: 'executionStart',
        data: {
          execution: {
            executionId: execution.id,
            name: execution.name,
            staticTree: execution.staticTree,
            datetime: execution.datetime
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('end', (execution) => {
      const message = JSON.stringify({
        type: 'executionEnd',
        data: {
          execution: {
            executionId: execution.id
          }
        }
      })
      this.latestExecutionId = execution.id

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('pathStart', (path, execution, funcDetails) => {
      const message = JSON.stringify({
        type: 'executionPathStart',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            path
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('functionStart', (execution, funcDetails, payload) => {
      const message = this.safeStringify({
        type: 'execution',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            payload,
            data: null
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
    tree.on('functionEnd', (execution, funcDetails, payload, result) => {
      if (!result || (result instanceof Path && !result.payload)) {
        return
      }

      const message = this.safeStringify({
        type: 'executionFunctionEnd',
        data: {
          execution: {
            executionId: execution.id,
            functionIndex: funcDetails.functionIndex,
            output: result instanceof Path ? result.payload : result
          }
        }
      })

      if (this.isConnected) {
        this.sendMessage(message)
      } else {
        this.backlog.push(message)
      }
    })
{'fraction_non_alphanumeric': 0.1230226796264532, 'fraction_numerical': 0.0006670478368591576, 'mean_word_length': 1.9168982768204559, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 25, 'https://': 0, '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': '5097975', 'n_tokens_mistral': 3050, 'n_tokens_neox': 3025, 'n_words': 582}