// 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
}