OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / scripts / verify-app-full.js

119 lines · javascript ref:
/**
 * Thorough reachability check for a drive — stronger than verify-pin.js.
 *
 * verify-pin.js samples ONE top-level blob; this one fetches a SPREAD of
 * blobs across the whole file tree (first, last, deepest, and evenly-spaced
 * picks) from a brand-new peer with no local cache. Run this with every
 * local `pear seed` daemon for the key STOPPED — then a success proves the
 * HiveRelay fleet (not your laptop) is serving the full content.
 *
 * Emits a single JSON result line prefixed with RESULT: for easy parsing.
 *
 * Usage:
 *   node scripts/verify-app-full.js --key <64-hex> [--name x] [--samples 12] [--timeout 90]
 */

import Hyperswarm from 'hyperswarm'
import Corestore from 'corestore'
import Hyperdrive from 'hyperdrive'
import b4a from 'b4a'
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'

function parseArgs (argv) {
  const a = { key: null, name: 'app', samples: 12, timeout: 90 }
  for (let i = 0; i < argv.length; i++) {
    if (argv[i] === '--key') a.key = argv[++i]
    else if (argv[i] === '--name') a.name = argv[++i]
    else if (argv[i] === '--samples') a.samples = parseInt(argv[++i], 10)
    else if (argv[i] === '--timeout') a.timeout = parseInt(argv[++i], 10)
  }
  return a
}

const args = parseArgs(process.argv.slice(2))
if (!/^[0-9a-f]{64}$/i.test(args.key || '')) {
  console.error('error: --key must be 64-char hex')
  process.exit(2)
}

const PEER_TIMEOUT_MS = 30_000
const result = {
  name: args.name,
  key: args.key,
  peers: 0,
  metaLength: 0,
  entries: 0,
  sampled: 0,
  blobsPresent: 0,
  blobsMissing: 0,
  bytes: 0,
  ok: false,
  error: null
}

const storage = mkdtempSync(join(tmpdir(), 'verify-full-'))
const store = new Corestore(storage)
const swarm = new Hyperswarm()
const drive = new Hyperdrive(store, b4a.from(args.key, 'hex'))
swarm.on('connection', (conn) => { result.peers++; store.replicate(conn) })

function emit (code) {
  console.log('RESULT: ' + JSON.stringify(result))
  swarm.destroy().catch(() => {})
  store.close().catch(() => {})
  try { rmSync(storage, { recursive: true, force: true }) } catch {}
  process.exit(code)
}

async function withTimeout (p, ms, label) {
  return Promise.race([
    p,
    new Promise((_, rej) => setTimeout(() => rej(new Error(label + ' timed out')), ms))
  ])
}

async function main () {
  await drive.ready()
  swarm.join(drive.discoveryKey, { server: false, client: true })

  await withTimeout(
    new Promise((res) => swarm.once('connection', res)),
    PEER_TIMEOUT_MS, 'peer discovery'
  )
  await withTimeout(drive.core.update({ wait: true }), 20_000, 'metadata update')
  result.metaLength = drive.core.length

  // Collect all current entries (recursive).
  const entries = []
  for await (const e of drive.list('/', { recursive: true })) {
    if (e?.value?.blob?.byteLength > 0) entries.push(e)
  }
  result.entries = entries.length
  if (entries.length === 0) throw new Error('drive has no file entries')

  // Spread sample: first, last, and evenly-spaced picks across the listing,
  // so we exercise blobs from all over the tree — not just the top.
  const n = Math.min(args.samples, entries.length)
  const picks = new Set()
  for (let i = 0; i < n; i++) picks.add(Math.floor((i * (entries.length - 1)) / Math.max(1, n - 1)))
  const sample = [...picks].map((i) => entries[i])
  result.sampled = sample.length

  for (const e of sample) {
    try {
      const buf = await withTimeout(drive.get(e.key), 20_000, 'blob ' + e.key)
      if (buf && buf.length > 0) { result.blobsPresent++; result.bytes += buf.length }
      else result.blobsMissing++
    } catch (_) {
      result.blobsMissing++
    }
  }

  result.ok = result.peers > 0 && result.entries > 0 && result.blobsMissing === 0 && result.blobsPresent > 0
  emit(result.ok ? 0 : 1)
}

main().catch((err) => { result.error = err.message; emit(1) })