OverviewFilesCommitsIssuesDocsReleasesPeers

hyperident-shell / lib / names.js

74 lines · javascript ref:
// Name resolution for hyperident-shell.
//
// Two layers:
//   1. a local names store (~/.hyperident-shell/names) — `name <z-base32-pubkey>`
//      per line, SSH known_hosts style. Immediate, offline, per-machine.
//   2. (future) the hyperident HandleRegistry — a P2P `name -> identity` registry
//      (plan Phase 2, to be extracted into hyper-identity). resolve() has the hook
//      where a registry lookup slots in; the local store then acts as a cache /
//      override. The call site (`hyperident-shell <name-or-pubkey>`) is unchanged
//      when that lands.
import fs from '#node:fs'
import os from '#node:os'
import path from '#node:path'
import process from '#node:process'
import HypercoreId from 'hypercore-id-encoding'

function homedir () {
  return (os.homedir && os.homedir()) || process.env.HOME || process.env.USERPROFILE || '.'
}

function namesFile () {
  return path.join(homedir(), '.hyperident-shell', 'names')
}

export function loadNames (file = namesFile()) {
  const map = new Map()
  let txt
  try {
    txt = fs.readFileSync(file, 'utf8')
  } catch (e) {
    if (e.code === 'ENOENT') return map
    throw e
  }
  for (const line of txt.split('\n')) {
    const s = line.trim()
    if (!s || s.startsWith('#')) continue
    const sp = s.indexOf(' ')
    const name = sp === -1 ? s : s.slice(0, sp).trim()
    const key = sp === -1 ? '' : s.slice(sp + 1).trim()
    if (name && key) map.set(name, key)
  }
  return map
}

export function addName (name, pubkey, file = namesFile()) {
  HypercoreId.decode(pubkey) // validate (throws if not a valid key)
  fs.mkdirSync(path.dirname(file), { recursive: true })
  const map = loadNames(file)
  map.set(name, pubkey)
  const out = [...map.entries()].map(([n, k]) => n + ' ' + k).join('\n') + '\n'
  fs.writeFileSync(file, out, { mode: 0o600 })
}

/**
 * Resolve a name-or-pubkey to a 32-byte public key Buffer.
 *   1. local names store (name -> pubkey)
 *   2. treat the input as a raw z-base32/hex pubkey
 *   3. TODO(Phase 2): query the hyperident HandleRegistry (name -> identityRoot
 *      -> device pubkey) and cache the result in the local store.
 */
export function resolve (nameOrKey, file = namesFile()) {
  const map = loadNames(file)
  if (map.has(nameOrKey)) return HypercoreId.decode(map.get(nameOrKey))

  try {
    return HypercoreId.decode(nameOrKey)
  } catch {}

  throw new Error(
    'cannot resolve "' + nameOrKey + '" — not a known name or a valid pubkey.\n' +
    '  add it with:  hyperident-shell add-name ' + nameOrKey + ' <pubkey>'
  )
}