pear-api
Version:
Pear API Base & Integration Module
140 lines (132 loc) • 5.85 kB
JavaScript
'use strict'
const { isWindows, isBare } = require('which-runtime')
const os = require('os')
const fsp = require('fs/promises')
const path = require('path')
const hypercoreid = require('hypercore-id-encoding')
const crypto = require('hypercore-crypto')
const { PLATFORM_DIR, SWAP, RUNTIME } = require('./constants')
const CWD = isBare ? os.cwd() : process.cwd()
const ENV = isBare ? require('bare-env') : process.env
const plink = require('./link')
const { ERR_INVALID_APP_STORAGE } = require('./errors')
module.exports = class State {
env = null
channel = null
args = null
checkpoint = null
#onupdate = null
runtime = RUNTIME
reloadingSince = 0
type = null
entrypoints = null
entrypoint = null
applink = null
dht = null
route = null
routes = null
unrouted = null
assets = {}
static async localPkg (state) {
let pkg
try {
pkg = JSON.parse(await fsp.readFile(path.join(state.dir, 'package.json')))
} catch (err) {
if (err.code !== 'ENOENT' && err.code !== 'EISDIR' && err.code !== 'ENOTDIR') throw err
const parent = path.dirname(state.dir)
if (parent === state.dir || path.resolve(state.dir) === path.resolve(parent)) return null
state.dir = parent
return this.localPkg(state)
}
return pkg
}
static appname (pkg) {
return pkg?.pear?.name ?? pkg?.name ?? null
}
static route (pathname, routes, unrouted) {
if (!routes) return pathname
if (unrouted.some((unroute) => pathname.startsWith(unroute))) return pathname
let route = typeof routes === 'string' ? routes : (routes[pathname] ?? pathname)
if (route[0] === '.') route = route.length === 1 ? '/' : route.slice(1)
return route
}
static storageFromLink (link) {
const parsed = typeof link === 'string' ? plink.parse(link) : link
const appStorage = path.join(PLATFORM_DIR, 'app-storage')
return parsed.protocol !== 'pear:'
? path.join(appStorage, 'by-random', crypto.randomBytes(16).toString('hex'))
: path.join(appStorage, 'by-dkey', crypto.discoveryKey(hypercoreid.decode(parsed.drive.key)).toString('hex'))
}
static configFrom (state) {
const { id, startId, key, links, alias, env, gui, assets, options, checkpoint, checkout, flags, dev, stage, storage, name, main, args, channel, release, applink, query, fragment, link, linkData, entrypoint, route, routes, dir, dht, prerunning } = state
const pearDir = PLATFORM_DIR
const swapDir = SWAP
return { id, startId, key, links, alias, env, gui, assets, options, checkpoint, checkout, flags, dev, stage, storage, name, main, args, channel, release, applink, query, fragment, link, linkData, entrypoint, route, routes, dir, dht, prerunning, pearDir, swapDir }
}
update (state) {
Object.assign(this, state)
this.#onupdate()
}
constructor (params = {}) {
const { dht, link = '.', startId = null, id = null, args = null, env = ENV, cwd = CWD, dir = cwd, cmdArgs, onupdate = () => {}, flags, run, storage = null, pid } = params
const {
appling, channel, devtools, checkout, stage, updates, updatesDiff,
links = '', prerunning = false, dev = false, parent = null,
followSymlinks, unsafeClearAppStorage, chromeWebrtcInternals
} = flags
const parsedLink = plink.parse(link)
const { drive: { alias = null, key = null } = {}, pathname: route = '', protocol, origin, hash, search } = parsedLink
let pathname = protocol === 'file:' && isWindows ? route.slice(1) : route
// for on disk route support, this relies on passed in dir being the actual project dir:
if (protocol === 'file:') pathname = pathname.slice(dir.length)
const store = flags.tmpStore ? path.join(os.tmpdir(), crypto.randomBytes(16).toString('hex')) : flags.store
this.#onupdate = onupdate
this.startId = startId
this.dht = dht
this.store = store
this.args = args
this.appling = appling
this.channel = channel || null
this.checkout = checkout
this.cwd = cwd
this.dir = dir
this.run = run
this.storage = storage
this.flags = flags
this.dev = dev
this.devtools = this.dev || devtools
this.updatesDiff = this.dev || updatesDiff
this.updates = updates
this.stage = stage
this.fragment = hash ? hash.slice(1) : ''
this.query = search ? search.slice(1) : ''
this.route = pathname
this.linkData = this.route?.startsWith('/') ? this.route.slice(1) : this.route
this.key = key
this.link = link ? (link.startsWith(protocol) ? link : plink.normalize(plink.serialize(parsedLink))) : null
this.applink = key ? origin : plink.normalize(plink.serialize(plink.parse(this.dir)))
this.alias = alias
this.cmdArgs = cmdArgs
this.id = id
this.followSymlinks = followSymlinks
this.rti = flags.rti ? JSON.parse(flags.rti) : null // important to know if this throws, so no try/catch
this.prerunning = prerunning
this.parent = parent
this.pid = pid
this.clearAppStorage = unsafeClearAppStorage
this.chromeWebrtcInternals = chromeWebrtcInternals
this.env = { ...env }
if (this.stage || (this.run && this.dev === false)) {
this.env.NODE_ENV = this.env.NODE_ENV || 'production'
}
this.links = links.split(',').reduce((links, kv) => {
const [key, value] = kv.split('=')
links[key] = value
return links
}, {})
this.storage = this.store ? (path.isAbsolute(this.store) ? this.store : path.resolve(this.cwd, this.store)) : this.storage
const invalidStorage = this.key === null && this.storage !== null &&
this.storage.startsWith(this.dir) && this.storage.includes(path.sep + 'pear' + path.sep + 'pear' + path.sep) === false
if (invalidStorage) throw ERR_INVALID_APP_STORAGE('Application Storage may not be inside the project directory. --store "' + this.storage + '" is invalid')
}
}