function setup () {
  var DEFAULTS = {
    favicon: {type: 'image/png', data: NOT_FOUND, headers: {'Content-Type': 'image/jpeg', 'Cache-Control': 'no-cache'}},
    thumb: {type: 'image/jpeg', data: NOT_FOUND, headers: {'Content-Type': 'image/jpeg', 'Cache-Control': 'no-cache'}},
    cover: {type: 'image/jpeg', data: NOT_FOUND, headers: {'Content-Type': 'image/jpeg', 'Cache-Control': 'no-cache'}},
    screenshot: {type: 'image/png', data: NOT_FOUND, headers: {'Content-Type': 'image/jpeg', 'Cache-Control': 'no-cache'}}
  }

  // load defaults
  fs.readFile(path.join(__dirname, './assets/img/favicons/default.png'), (err, buf) => {
    if (err) { console.error('Failed to load default favicon', path.join(__dirname, './assets/img/default-favicon.png'), err) }
    if (buf) { DEFAULTS.favicon.data = buf }
  })
  fs.readFile(path.join(__dirname, './assets/img/default-thumb.jpg'), (err, buf) => {
    if (err) { console.error('Failed to load default thumb', path.join(__dirname, './assets/img/default-thumb.jpg'), err) }
    if (buf) {
      DEFAULTS.thumb.data = buf
      DEFAULTS.screenshot.data = buf
    }
  })
  fs.readFile(path.join(__dirname, './assets/img/default-cover.jpg'), (err, buf) => {
    if (err) { console.error('Failed to load default cover', path.join(__dirname, './assets/img/default-cover.jpg'), err) }
    if (buf) { DEFAULTS.cover.data = buf }
  })

  // detect if is retina
  let display = screen.getPrimaryDisplay()
  const isRetina = display.scaleFactor >= 2

  // register favicon protocol
  handler = async (request, cb) => {
    // parse the URL
    let {asset, url, size} = parseAssetUrl(request.url)
    if (isRetina) {
      size *= 2
    }

    // validate
    if (asset !== 'favicon' && asset !== 'thumb' && asset !== 'cover' && asset !== 'screenshot') {
      return cb({data: NOT_FOUND})
    }

    // hardcoded assets
    if (asset !== 'screenshot' && url.startsWith('beaker://')) {
      let name = /beaker:\/\/([^\/]+)/.exec(url)[1]
      return servePng(path.join(__dirname, `./assets/img/favicons/${name}.png`), DEFAULTS[asset], cb)
    }

    try {
      // look up in db
      let data
      if (asset === 'screenshot') {
        data = await sitedata.get(url, 'screenshot', {dontExtractOrigin: true, normalizeUrl: true})

        // DISABLED- seems to generate some pretty bad error behaviors on win7
        // see https://github.com/beakerbrowser/beaker/issues/1872#issuecomment-739463243
        // -prf
        // if (!data && !url.startsWith('dat:')) {
        //   // try to fetch the screenshot
        //   let p = activeCaptures[url]
        //   if (!p) {
        //     p = activeCaptures[url] = capturePage(url)
        //   }
        //   let nativeImg = await p
        //   delete activeCaptures[url]
        //   if (nativeImg) {
        //     data = nativeImg.toDataURL()
        //     await sitedata.set(url, 'screenshot', data, {dontExtractOrigin: true, normalizeUrl: true})
        //   } else {
        //     return serveJpg(path.join(__dirname, `./assets/img/default-screenshot.jpg`), DEFAULTS[asset], cb)
        //   }
        // }
        if (!data) {
          return serveJpg(path.join(__dirname, `./assets/img/default-screenshot.jpg`), DEFAULTS[asset], cb)
        }
      } else {
        data = await sitedata.get(url, asset)
        if (!data && asset === 'thumb') {
          if (url.startsWith('hyper://private')) {
            return serveJpg(path.join(__dirname, `./assets/img/default-private-screenshot.jpg`), DEFAULTS[asset], cb)
          }
          // try fallback to screenshot
          data = await sitedata.get(url, 'screenshot', {dontExtractOrigin: true, normalizeUrl: true})
          if (!data) {
            // try fallback to favicon
            data = await sitedata.get(url, 'favicon')
          }
        }
        if (!data && asset === 'favicon') {
          // try fallback to thumb
          data = await sitedata.get(url, 'thumb')
        }
      }
      if (data) {
        if (size) {
          let img = nativeImage.createFromDataURL(data)
          data = img.resize({width: size}).toDataURL()
        }
        
        // `data` is a data url ('data:image/png;base64,...')
        // so, skip the beginning and pull out the data
        let parts = data.split(',')
        if (parts[1]) {
          let mimeType = /data:([^;]+);base64/.exec(parts[0])[1]
          data = parts[1]
          if (data) {
            return cb({mimeType, data: Buffer.from(data, 'base64'), headers: {'Cache-Control': 'no-cache'}})
          }
        }
      }
    } catch (e) {
      // ignore
      console.log(e)
    }

    cb(DEFAULTS[asset])
  }
}
asset:{type}{-dimension?}:{url}

Helper protocol to serve site favicons and avatars from the cache.
Examples:

 - asset:favicon:hyper://beakerbrowser.com
 - asset:favicon-32:hyper://beakerbrowser.com
 - asset:thumb:hyper://beakerbrowser.com
 - asset:cover:hyper://beakerbrowser.com