OverviewFilesCommitsIssuesDocsReleasesPeers

keet-daemon / test / probe-vault-setup.js

117 lines · javascript ref:
// probe-vault-setup.js — break down what `vault.setup` does into individual
// per-step calls with their own timeouts, so we can see WHICH call hangs
// instead of just "vault.setup timed out at 60s".
//
// `_vaultSetup` (router.js:293) does roughly:
//   1. await backend.api.core.confirmRecoveryPhrase()
//   2. const raw = await backend.api.core.getRecoveryPhrase()
//   3. if (phrase) { await backend.api.core.storeRecoveryPhrase(phrase) }
//      else        { phrase = generateMnemonic();
//                    await backend.api.core.restoreIdentity(phrase) }
//   4. await this._vault.encrypt(...)
//
// All four of those backend.api.core calls await `onlineBoot.ready()`
// internally. If onlineBoot never resolves, step 1 hangs forever.
//
// This probe wraps each call in a 15s timeout and prints which one fires
// first (or which one succeeds). On stortron — where the user reported
// vault.setup timeout — this should show whether step 1 hangs immediately
// (onlineBoot deadlock) or some later step is the culprit.
//
// SAFETY: uses real ~/.config/keet-cli storage, but DOES NOT create a new
// identity. Operates on whatever the active identity is. Reads only.
// If a recovery phrase already exists it won't be overwritten. The vault
// encrypt step is skipped (no actual mnemonic stored) so this is non-
// destructive.
//
// Run:  bare test/probe-vault-setup.js

import path from 'bare-path'
import os from 'bare-os'
import { startDaemon } from '../index.js'

const STORAGE = path.join(os.homedir(), '.config', 'keet-cli')
const SOCKET = '/tmp/keet-daemon-vault-probe.sock'
const PER_STEP_TIMEOUT_MS = 15000

function withTimeout (promise, ms, label) {
  return new Promise((resolve, reject) => {
    const t = setTimeout(() => reject(new Error(label + ' timed out after ' + ms + 'ms')), ms)
    promise.then(v => { clearTimeout(t); resolve(v) },
                 e => { clearTimeout(t); reject(e) })
  })
}

async function timed (label, fn) {
  const t0 = Date.now()
  console.log('[probe] ' + label.padEnd(36) + ' starting...')
  try {
    const result = await withTimeout(fn(), PER_STEP_TIMEOUT_MS, label)
    console.log('[probe]   → OK in ' + (Date.now() - t0) + 'ms')
    return { ok: true, result }
  } catch (err) {
    console.log('[probe]   → FAIL after ' + (Date.now() - t0) + 'ms')
    console.log('  ' + (err && err.message))
    if (err && err.stack && !err.message.includes('timed out')) {
      for (const line of err.stack.split('\n').slice(0, 6)) console.log('  ' + line)
    }
    return { ok: false, err }
  }
}

async function main () {
  console.log('[probe] storage:', STORAGE)
  console.log('[probe] starting daemon (gives onlineBoot 30s to settle)...')

  const daemon = await startDaemon({ storage: STORAGE, socketPath: SOCKET })
  console.log('[probe] daemon up. backend.ready resolved (offlineBoot only).')

  const backend = daemon.router.backend
  const core = backend.api.core

  // Wait a few seconds to give onlineBoot a chance to settle on its own
  // before we start probing. If onlineBoot.ready() resolves naturally,
  // step 1 below will return immediately.
  console.log('[probe] sleeping 5s to let onlineBoot settle...')
  await new Promise(r => setTimeout(r, 5000))

  console.log('')
  console.log('== probing each vault.setup sub-call independently ==')

  await timed('confirmRecoveryPhrase()', () => core.confirmRecoveryPhrase())
  const phraseRes = await timed('getRecoveryPhrase()', () => core.getRecoveryPhrase())
  let phrase = null
  if (phraseRes.ok) {
    const raw = phraseRes.result
    phrase = typeof raw === 'string' && raw.length > 0 ? raw : (raw && raw.phrase) || null
    console.log('[probe] getRecoveryPhrase returned: ' + (phrase ? 'phrase ' + phrase.split(' ').length + ' words' : 'null/empty'))
  }

  // Also probe onlineBoot state directly via getIdentity (a different
  // onlineBoot-dependent call) so we know if it's confirmRecoveryPhrase
  // specifically or onlineBoot universally.
  await timed('getIdentity()', () => core.getIdentity())

  // Try restoreIdentity with a NEW mnemonic — only if getRecoveryPhrase
  // returned empty (meaning fresh-storage scenario).
  if (!phrase) {
    console.log('[probe] no existing phrase, would generate + restoreIdentity in real flow')
    console.log('[probe] SKIPPING restoreIdentity to keep this probe non-destructive')
  } else {
    console.log('[probe] phrase already present; storeRecoveryPhrase is the next step (skipped)')
  }

  console.log('')
  console.log('== shutdown ==')
  try { await daemon.uds.stop() } catch {}
  try { await daemon.router.core.close() } catch {}
  try { await daemon.core.close() } catch {}
  Bare.exit(0)
}

main().catch(err => {
  console.error('[probe] uncaught:', err && err.message)
  if (err && err.stack) console.error(err.stack)
  Bare.exit(1)
})