OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / pear-link / index.js

152 lines · javascript ref:
'use strict'
const path = require('path')
const { ERR_INVALID_LINK } = require('pear-errors')
const hid = require('hypercore-id-encoding')
const FILE = 'file:'
const PEAR = 'pear:'
const DOUB = '//'

class PearLink {
  normalize(link) {
    // if link has link format, separator is always '/' even in Windows
    if (link.startsWith(FILE + DOUB)) {
      return link.endsWith('/') ? link.slice(0, -1) : link
    } else return link.endsWith(path.sep) ? link.slice(0, -1) : link
  }

  serialize(o) {
    o = hid.isValid(o) ? { drive: { key: o } } : o
    let { protocol, pathname = '', search = '', hash = '', drive } = o
    if (protocol === FILE) return `${protocol}//${pathname}${search}${hash}`
    if (!protocol && drive) protocol = PEAR
    if (protocol === PEAR) {
      const key = hid.normalize(drive.key)
      const base = [
        drive.fork,
        drive.length,
        key,
        drive.hash && hid.encode(drive.hash)
      ]
        .filter((p) => (p ?? '') + '')
        .join('.')
      return `${protocol}//${base}${pathname}${search}${hash}`
    }
    throw ERR_INVALID_LINK('Unsupported protocol', {
      protocol,
      pathname,
      search,
      hash,
      drive
    })
  }

  parse(link) {
    if (!link) throw ERR_INVALID_LINK('No link specified', { link })
    const isPath =
      link.startsWith(PEAR + DOUB) === false &&
      link.startsWith(FILE + DOUB) === false
    const isRelativePath = isPath && link[0] !== '/' && link[1] !== ':'
    const { protocol, pathname, hostname, search, hash } = isRelativePath
      ? new URL(link, FILE + DOUB + path.resolve('.') + '/')
      : new URL(isPath ? FILE + DOUB + link : link)
    const info = { link, protocol, hostname, pathname, search, hash }
    if (protocol === FILE) {
      // file:///some/path/to/a/file.js
      const startsWithRoot = hostname === ''
      if (!pathname) throw ERR_INVALID_LINK('Path is missing', info)
      if (!startsWithRoot) {
        throw ERR_INVALID_LINK('Path needs to start from the root, "/"', info)
      }
      return {
        protocol,
        pathname,
        search,
        hash,
        origin: this.normalize(`${protocol}//${hostname}${pathname}`),
        drive: {
          key: null,
          length: null,
          fork: null,
          hash: null
        }
      }
    } else if (protocol === PEAR) {
      const [fork, length, key, apphash] = hostname.split('.')
      const parts = hostname.split('.').length

      if (parts === 1) {
        // pear://key[/some/path]
        const key = hostname
        const origin = `${protocol}//${hostname}`
        return {
          protocol,
          pathname,
          search,
          hash,
          origin,
          drive: {
            key: hid.decode(key),
            length: null,
            fork: null,
            hash: null
          }
        }
      }

      if (parts === 2) {
        // pear://fork.length[/some/path]
        throw ERR_INVALID_LINK('Incorrect hostname', info)
      }

      const origin = `${protocol}//${key}`

      if (parts === 3) {
        // pear://fork.length.key[/some/path]
        if (!Number.isInteger(+fork) || !Number.isInteger(+length)) {
          throw ERR_INVALID_LINK('Incorrect hostname', info)
        }
        return {
          protocol,
          pathname,
          search,
          hash,
          origin,
          drive: {
            key: hid.decode(key),
            length: Number(length),
            fork: Number(fork),
            hash: null
          }
        }
      }

      if (parts === 4) {
        // pear://fork.length.key.dhash[/some/path]
        if (!Number.isInteger(+fork) || !Number.isInteger(+length)) {
          throw ERR_INVALID_LINK('Incorrect hostname', info)
        }

        return {
          protocol,
          pathname,
          search,
          hash,
          origin,
          drive: {
            key: hid.decode(key),
            length: Number(length),
            fork: Number(fork),
            hash: hid.decode(apphash)
          }
        }
      }

      throw ERR_INVALID_LINK('Incorrect hostname', info)
    }

    throw ERR_INVALID_LINK('Protocol is not supported', info)
  }
}

module.exports = new PearLink()