OverviewFilesCommitsIssuesDocsReleasesPeers

hypermosh-shell / lib / registry.mjs

98 lines · javascript ref:
// P2P handle-registry integration for hypermosh — resolve a name to a mosh
// server key via the SHARED hyperident HandleRegistry (the same registry
// hyperident-shell uses). This is the identity-layer name registry; hypermosh
// is a READ consumer.
//
// The shared registry maps `name -> { identity:{identityRoot,devicePubkey}, metadata }`.
// hyperident-shell claims a name pointing devicePubkey at the shell address; a
// hypermosh server runs on its OWN key (≠ the device key), so we read the mosh
// address from `metadata.moshKey` (falling back to devicePubkey if a handle only
// carries a single address). One handle (e.g. `celltron`) can thus serve both
// `hyperident-shell celltron` and `hypermosh celltron` off the same entry.
//
// Dual-runtime (node + bare): corestore/hyperswarm/autobase all run on both.
import Corestore from 'corestore'
import Hyperswarm from 'hyperswarm'
import fs from '#node:fs'
import os from '#node:os'
import path from '#node:path'
import process from '#node:process'
import { HandleRegistry } from 'hyper-identity/handle-registry'
import { mkLog } from './log.mjs'

const log = mkLog('registry')

// The shared/global hyperident HandleRegistry key (what hyperident-shell uses).
// Override per-machine via HYPERMOSH_REGISTRY or ~/.hypermosh/config.json.
const DEFAULT_REGISTRY_KEY = 'ba3197256a5928a802c79cc824eaa26b7d5745212820e67f4f5a1d989d86d580'

function homedir () { return (os.homedir && os.homedir()) || process.env.HOME || process.env.USERPROFILE || '.' }
function configFile () { return path.join(homedir(), '.hypermosh', 'config.json') }
function storeDir () { return path.join(homedir(), '.hypermosh', 'registry-store') }

export function isRegistryKey (s) { return typeof s === 'string' && /^[0-9a-fA-F]{64}$/.test(s) }

// env → config.json → shared default.
export function loadRegistryKey () {
  if (isRegistryKey(process.env.HYPERMOSH_REGISTRY)) return process.env.HYPERMOSH_REGISTRY
  try {
    const k = JSON.parse(fs.readFileSync(configFile(), 'utf8')).registryKey
    if (isRegistryKey(k)) return k
  } catch {}
  return DEFAULT_REGISTRY_KEY
}

// Open a read replica of the shared registry and replicate it over Hyperswarm.
async function openRegistry (registryKey) {
  const store = new Corestore(storeDir())
  const reg = new HandleRegistry(store, { bootstrap: registryKey })
  await reg.ready()
  const swarm = new Hyperswarm()
  swarm.on('connection', (conn) => reg.replicate(conn))
  swarm.join(reg.discoveryKey, { server: false, client: true })
  await swarm.flush()
  return {
    reg, swarm, store,
    async close () {
      try { await swarm.destroy() } catch {}
      try { await reg.close() } catch {}
      try { await store.close() } catch {}
    }
  }
}

// Resolve a name to its full registry entry (or null). Joins the swarm, syncs,
// and polls until the name resolves or the timeout elapses.
export async function resolveEntry (name, { registryKey = loadRegistryKey(), timeoutMs = 8000 } = {}) {
  if (!isRegistryKey(registryKey)) return null
  log('resolving', name, 'via registry', registryKey.slice(0, 8))
  const r = await openRegistry(registryKey)
  try {
    const deadline = Date.now() + timeoutMs
    let entry = null
    while (!entry && Date.now() < deadline) {
      await r.reg.update()
      entry = await r.reg.resolve(name)
      if (!entry) await new Promise((res) => setTimeout(res, 200))
    }
    return entry
  } finally {
    await r.close()
  }
}

// Extract the mosh server key from a registry entry: prefer metadata.moshKey,
// fall back to the entry's devicePubkey (a handle that carries only one address).
export function moshKeyFromEntry (entry) {
  if (!entry) return null
  return (entry.metadata && entry.metadata.moshKey) || (entry.identity && entry.identity.devicePubkey) || null
}

// High-level: resolve a name to the mosh SERVER key (z-base32/hex string), or null.
export async function resolveMoshKey (name, opts = {}) {
  const entry = await resolveEntry(name, opts)
  const key = moshKeyFromEntry(entry)
  if (key) log('resolved', name, '->', String(key).slice(0, 8), entry.metadata && entry.metadata.moshKey ? '(moshKey)' : '(devicePubkey fallback)')
  return key
}