OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / auth / ssh-resync-trigger.js

44 lines · javascript ref:
/**
 * #74 Phase 1 — fire the SSH authorized_keys resync on IdentityRegistry
 * replication updates, not only on local enroll/remove/boot.
 *
 * The add/remove handlers (lib/handlers/ssh-keys.js) resync only the daemon
 * that took the write. So a key enrolled on backend A landed in the
 * *replicated* IdentityRegistry view but A's peers stayed blind to it in their
 * authorized_keys until their next boot — the #73 "enroll-on-stortron,
 * push-to-tabertron is denied" propagation gap.
 *
 * Autobase 'update' fires on BOTH local append AND remote replication, so
 * watching it materializes a peer-enrolled key into every backend within one
 * replication round, no reboot. Debounced (default 1500ms, matching
 * scheduleMirrorReannounce in pear-git.js) to coalesce an update burst into a
 * single rebuild.
 *
 * Returns detach() — removes the listener and cancels any pending timer.
 * A no-op (returns an empty detach) when base/resync are unusable, so the
 * caller can wire it unconditionally.
 */
export function attachSshResyncTrigger ({ base, resync, debounceMs = 1500, onSynced, onError }) {
  if (!base || typeof base.on !== 'function') return () => {}
  if (typeof resync !== 'function') return () => {}

  let timer = null
  function schedule () {
    if (timer) return
    timer = setTimeout(() => {
      timer = null
      Promise.resolve()
        .then(resync)
        .then((r) => { if (typeof onSynced === 'function') onSynced(r) })
        .catch((err) => { if (typeof onError === 'function') onError(err) })
    }, debounceMs)
  }

  base.on('update', schedule)

  return function detach () {
    try { base.off('update', schedule) } catch {}
    if (timer) { clearTimeout(timer); timer = null }
  }
}