OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / backend / p2p-first-fetch.js

54 lines · javascript ref:
// P2P-first hybrid fetch (privacy hardening — Privacy-routing PRIV0). The old
// path raced relay HTTP and P2P in parallel, so the relay saw EVERY fetch even
// when P2P could serve it. This tries P2P first and only contacts the relay if
// P2P misses or is slower than a short grace window — so the relay stops seeing
// fetches it never needed to serve, at near-zero latency cost (a fast P2P hit
// returns immediately; a slow/missing one still falls back to the relay).
//
// Pure + injectable so it's Node-testable: the caller passes fetchP2P/fetchRelay
// thunks that resolve to a content object (or null on miss). `relayFirst`
// restores the old parallel race (kill-switch). Returns
// { result, source, relayContacted } — relayContacted lets the caller verify the
// privacy property in tests/telemetry.

async function p2pFirstFetch ({ fetchP2P, fetchRelay, graceMs = 500, relayFirst = false } = {}) {
  // start P2P once; both the grace race and any fallback reuse this one promise
  const p2p = Promise.resolve().then(() => fetchP2P()).catch(() => null)

  // no relay available, or a P2P-only deployment
  if (typeof fetchRelay !== 'function') {
    const r = await p2p
    return { result: r ? { ...r, source: 'p2p' } : null, source: r ? 'p2p' : null, relayContacted: false }
  }

  // kill-switch: old behavior (race both from the start)
  if (relayFirst) return raceBoth(p2p, fetchRelay)

  // P2P-FIRST: race P2P against a short grace timer. If P2P answers first, the
  // relay is never contacted.
  const GRACE = Symbol('grace')
  const winner = await Promise.race([
    p2p.then((r) => (r ? { ...r, source: 'p2p' } : GRACE)),
    new Promise((res) => setTimeout(() => res(GRACE), Math.max(0, graceMs))),
  ])
  if (winner && winner !== GRACE) {
    return { result: winner, source: 'p2p', relayContacted: false }
  }

  // P2P missed or was too slow → contact the relay now, still letting a late P2P
  // result win the fallback race (reliability preserved).
  return raceBoth(p2p, fetchRelay)
}

// Fire both and take the first success; relay IS contacted here.
async function raceBoth (p2p, fetchRelay) {
  const relay = Promise.resolve().then(() => fetchRelay()).catch(() => null)
  const result = await Promise.any([
    p2p.then((r) => (r ? { ...r, source: 'p2p' } : Promise.reject(new Error('p2p miss')))),
    relay.then((r) => (r ? { ...r, source: 'relay' } : Promise.reject(new Error('relay miss')))),
  ]).catch(() => null)
  return { result, source: result ? result.source : null, relayContacted: true }
}

module.exports = { p2pFirstFetch }