// Peeking must ANSWER before it has finished.
//
// ⛔ `hold()` opens an autobee on a key we have never seen and joins the swarm for it, and
// `SwarmTransport.join` awaits `discovery.flushed()` — a DHT announce round trip whose result is
// never used. Doing that inside the request meant a socket call that sat there until the DHT
// answered, which from a browser is indistinguishable from a hung daemon. `pairOffer` already
// learned this: "a socket request that blocked for five minutes would look like a hung daemon".
//
// So peek registers the intent, answers, and opens in the background. The three states a page needs
// are then: opening / held-as-peek / followed.
import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import { FavelaNode } from '../lib/node.js'
import { saveCredential } from '../lib/device.js'
import { identity } from './helpers.js'
const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'favela-lazy-'))
const KEY = 'ab'.repeat(32)
/**
* A transport that can be made to stop answering — a DHT that is simply not there.
*
* ⚠ It starts OPEN. Blocking from the first call would hang the node's own boot, which opens its
* own feed and its namespace through this same seam — an earlier version of this file did exactly
* that and the failure looked like the code under test rather than the stub.
*/
function stuckTransport () {
let release = () => {}
const gate = new Promise((r) => { release = r })
return {
blocked: false,
joins: 0,
release,
async join () {
if (!this.blocked) return
this.joins++
return gate
},
async leave () {},
async destroy () {}
}
}
async function node (transport) {
const storage = tmp()
const id = await identity()
saveCredential(storage, await id.device())
const n = new FavelaNode(storage, { transport })
await n.ready()
// Boot is done; from here the swarm stops answering.
if (transport) transport.blocked = true
return n
}
test('★ peek answers immediately even when the swarm never does', async (t) => {
const transport = stuckTransport()
const n = await node(transport)
t.teardown(async () => { transport.release(); await n.close() })
const started = Date.now()
const r = await n.peek(KEY)
const took = Date.now() - started
// ⛔ The assertion is about ANSWERING, not about speed of light: the gate below is still open, so
// a blocking implementation could not have returned at all.
t.ok(took < 2000, 'answered while the join is still outstanding (' + took + 'ms)')
t.is(r.feed, KEY)
t.is(r.status, 'opening', 'and it says so rather than implying the feed is ready')
})
test('the state a page needs: opening, then held', async (t) => {
const transport = stuckTransport()
const n = await node(transport)
t.teardown(async () => { transport.release(); await n.close() })
await n.peek(KEY)
t.alike(n.holding(KEY), { held: false, peeked: false, following: false, opening: true, blocked: false },
'not held YET — a page must not render "nothing from them" while it is still arriving')
transport.release()
await n.opening(KEY)
const after = n.holding(KEY)
t.is(after.opening, false)
t.is(after.held, true, 'and now their page has something to render')
t.is(after.peeked, true, 'held to read, still not followed')
})
test('⛔ peeking twice does not open twice', async (t) => {
const transport = stuckTransport()
const n = await node(transport)
t.teardown(async () => { transport.release(); await n.close() })
await n.peek(KEY)
await n.peek(KEY)
await n.peek(KEY)
transport.release()
await n.opening(KEY)
// A page that polls, or a reader who presses twice, must not stack opens of the same feed.
t.is(transport.joins, 1, 'one open, however many times it is asked for')
})
test('⚠ a swarm that is down does not stop you holding the feed', async (t) => {
let boot = true
const n = await node({
async join () { if (boot) return; throw new Error('swarm is down') },
async leave () {}, async destroy () {},
set blocked (v) { boot = !v }
})
t.teardown(() => n.close())
await n.peek(KEY)
await n.opening(KEY)
// ⛔ A feed with no swarm join is still a usable feed — it does not replicate, which is degraded
// and not a failure to open. The join used to be awaited unguarded, so it threw after the feed
// was already open and left it orphaned: draining, untracked, never closed.
t.is(n.holding(KEY).held, true, 'held anyway, it just will not replicate')
// And either way the attempt is OVER. A peek stuck on "opening" would leave the page saying
// "fetching…" for ever with no way to retry.
t.is(n.holding(KEY).opening, false)
})