// ap-inbox.js — read what the fediverse sent us, from ap-bridge's control socket.
//
// ap-bridge 0.0.6 keeps inbound replies, mentions, likes and boosts (before that it verified them,
// returned 202, and dropped them). They are NOT served over its public HTTP surface, deliberately:
// an AP inbox holds other people's messages to us, and publishing it would republish their content
// under our host. So this reads the control socket, which is local and privileged.
//
// ⛔ LOCAL ONLY, AND THAT IS THE HONEST LIMIT. ap-bridge is single-instance by construction — its RSA
// keypair, follower list and delivered-map are per-node state, and ap.7rnx.net is a static vhost to
// one backend. A favela-web on any other host cannot read this and must SAY so rather than render an
// empty list.
//
// ⛔ null MEANS "COULD NOT ASK". [] means "asked, nothing there". The existing AP reach reader draws
// exactly this distinction ("so 'not projected' and 'could not ask' never render alike") and the
// notifications page depends on it: a user with no replies and a user whose node cannot see the
// fediverse must not be shown the same page.
const DEFAULT_SOCK = '/.config/ap-bridge/svc.sock'
export function createApInbox ({ sockPath = null, connect = null, home = null, exists = null } = {}) {
const path = sockPath || ((home || '') + DEFAULT_SOCK)
return {
get path () { return path },
// Is there an ap-bridge on this machine at all? Checked before connecting so the common case
// (no AP side here) costs nothing and logs nothing.
available () {
if (!exists) return true // no probe injected: let the connect attempt decide
try { return !!exists(path) } catch { return false }
},
/**
* @returns {Promise<Array|null>} items, or null when we could not ask.
*/
async list ({ limit = 50 } = {}) {
if (!this.available()) return null
if (typeof connect !== 'function') return null
let client = null
try {
client = await connect(path)
const res = await client.call('notifications', { limit })
const items = res && res.items
return Array.isArray(items) ? items : null
} catch {
// Unreachable, wrong version (no `notifications` verb), or mid-restart. All the same answer:
// we could not ask. Never throw — the notifications page must render its atproto half
// regardless of whether the AP side is up.
return null
} finally {
try { await client?.close?.() } catch {}
}
}
}
}