bugsnag/bugsnag-js

View on GitHub
packages/plugin-electron-app-breadcrumbs/app-breadcrumbs.js

Summary

Maintainability
A
3 hrs
Test Coverage
const debounce = require('lodash.debounce')
const debounceOptions = { leading: true }

const BREADCRUMB_STATE = 'state'

module.exports = (app, BrowserWindow) => ({
  load (client) {
    if (!client._isBreadcrumbTypeEnabled(BREADCRUMB_STATE)) {
      return
    }

    app.on('ready', () => {
      client.leaveBreadcrumb('App became ready', undefined, BREADCRUMB_STATE)
    })

    app.on('will-quit', () => {
      client.leaveBreadcrumb('App is quitting', undefined, BREADCRUMB_STATE)
    })

    app.on('child-process-gone', (_event, details) => {
      const message = details.name
        ? `${details.name} (${details.type}) child process unexpectedly disappeared`
        : `${details.type} child process unexpectedly disappeared`

      client.leaveBreadcrumb(
        message,
        { reason: details.reason, exitCode: details.exitCode },
        BREADCRUMB_STATE
      )
    })

    app.on('render-process-gone', (_event, webContents, details) => {
      client.leaveBreadcrumb(
        'Renderer process unexpectedly disappeared',
        { webContentsId: webContents.id, reason: details.reason, exitCode: details.exitCode },
        BREADCRUMB_STATE
      )
    })

    const leaveBrowserWindowBreadcrumb = (action, browserWindow, extras = {}) => {
      client.leaveBreadcrumb(
        `Browser window ${browserWindow.id} ${action}`,
        { id: browserWindow.id, title: browserWindow.title, ...extras },
        BREADCRUMB_STATE
      )
    }

    app.on('browser-window-blur', (_event, browserWindow) => {
      leaveBrowserWindowBreadcrumb('lost focus', browserWindow)
    })

    app.on('browser-window-focus', (_event, browserWindow) => {
      leaveBrowserWindowBreadcrumb('gained focus', browserWindow)
    })

    app.on('browser-window-created', (_event, browserWindow) => {
      client.leaveBreadcrumb(
        `Browser window ${browserWindow.id} created`,
        { id: browserWindow.id, title: browserWindow.title },
        BREADCRUMB_STATE
      )

      // attach listeners to the new window
      attachBrowserWindowListeners(leaveBrowserWindowBreadcrumb, browserWindow)
    })

    // attach listeners to any windows that are already open
    BrowserWindow.getAllWindows().forEach(browserWindow => {
      attachBrowserWindowListeners(leaveBrowserWindowBreadcrumb, browserWindow)
    })
  }
})

function attachBrowserWindowListeners (leaveBreadcrumb, browserWindow) {
  // the moved event fires too frequently to add a breadcrumb each time
  const onMoved = debounce(() => {
    // it's possible for the window to be destroyed at this point because we
    // debounce this callback. If we try to use 'getPosition' when the window is
    // destroyed then we'll will raise a TypeError, so we bail out instead
    if (browserWindow.isDestroyed()) {
      return
    }

    const [left, top] = browserWindow.getPosition()

    leaveBreadcrumb('was moved', browserWindow, { left, top })
  }, 250, debounceOptions)

  // when the 'closed' event fires we aren't allowed to read from the browserWindow,
  // so we cache the values we care about in the 'close' event instead
  // we don't use the close event for the breadcrumb as it can be cancelled
  const lastKnownState = { id: browserWindow.id, title: browserWindow.title }

  browserWindow.on('close', () => {
    lastKnownState.id = browserWindow.id
    lastKnownState.title = browserWindow.title
  })

  browserWindow.on('closed', () => {
    // cancel the onMoved callback in case it's still scheduled to run
    onMoved.cancel()

    leaveBreadcrumb('closed', lastKnownState)
  })

  browserWindow.on('unresponsive', () => {
    leaveBreadcrumb('became unresponsive', browserWindow)
  })

  browserWindow.on('responsive', () => {
    leaveBreadcrumb('became responsive', browserWindow)
  })

  browserWindow.on('show', () => {
    leaveBreadcrumb('was shown', browserWindow)
  })

  browserWindow.on('hide', () => {
    leaveBreadcrumb('was hidden', browserWindow)
  })

  browserWindow.on('maximize', () => {
    leaveBreadcrumb('was maximized', browserWindow)
  })

  browserWindow.on('minimize', () => {
    leaveBreadcrumb('was minimized', browserWindow)
  })

  browserWindow.on('resized', () => {
    const [width, height] = browserWindow.getSize()

    leaveBreadcrumb('was resized', browserWindow, { width, height })
  })

  browserWindow.on('moved', onMoved)

  browserWindow.on('enter-full-screen', () => {
    leaveBreadcrumb('went full-screen', browserWindow)
  })

  browserWindow.on('leave-full-screen', () => {
    leaveBreadcrumb('left full-screen', browserWindow)
  })
}