Files
bare-operating-system/packages/bare-os-booter/lib/p2p/swarm-disk.js
T
2026-08-18 18:11:28 -04:00

920 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import b4a from 'b4a'
import c from 'compact-encoding'
import {
PROTOCOL_NAME,
PROTOCOL_APP_CHANNEL_NAME,
PROTOCOL_CAP_CHANNEL_NAME,
PROTOCOL_CHAT_CHANNEL_NAME,
PROTOCOL_MESHDROP_CHANNEL_NAME
} from 'bare-os-protocol/constants.js'
import { bareOsChatMuxEnabled } from './bare-os-chat-service.js'
import { bareOsMeshdropMuxEnabled } from './bare-os-meshdrop-service.js'
import {
bareOsHostBooterInfo,
bareOsHostBooterWarn
} from './bare-os-host-booter-log.js'
import { getKernelCapabilityWords } from 'bare-os-protocol'
/**
* Host-side booter log for swarm disk (stderr JSON when BARE_OS_BOOT_TRACE=json|ndjson; else stderr or console.warn).
* @param {'info'|'warn'} level
* @param {string} message
* @param {Record<string, unknown> | null} [detail]
*/
function emitSwarmDiskHostLog(level, message, detail = null) {
const env = globalThis.process?.env
const trace =
env &&
(env.BARE_OS_BOOT_TRACE === 'json' || env.BARE_OS_BOOT_TRACE === 'ndjson')
const err = globalThis.process?.stderr
if (trace && err && typeof err.write === 'function') {
err.write(
`${JSON.stringify({
type: 'booterHost',
bootTraceSchemaVersion: 2,
component: 'swarm_disk',
level,
message,
detail,
ts: Date.now()
})}\n`
)
}
if (level === 'warn') {
bareOsHostBooterWarn(
'swarm_disk',
message,
detail ? JSON.stringify(detail) : ''
)
return
}
bareOsHostBooterInfo(
'swarm_disk',
message,
detail ? JSON.stringify(detail) : ''
)
}
/** Default max wait for block 0 (MBR) before failing boot (override **`BARE_OS_MBR_READ_TIMEOUT_MS`**). */
const MBR_READ_TIMEOUT_MS_DEFAULT = 60_000
/** Cap-channel (`bare-os-cap-v1`) message size bound when **`BARE_OS_PROTOMUX_CAP_CHANNEL`** is enabled. */
export const BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES = 65536
/**
* Max wait (ms) for block 0 / MBR replication before boot failure.
* Exported for microbench / unit introspection (same rules as booter).
* @param {SwarmDisk} disk
*/
export function mbrReadTimeoutMsForDisk(disk) {
const env = globalThis.process?.env
const raw = String(env?.BARE_OS_MBR_READ_TIMEOUT_MS ?? '').trim()
if (raw) {
const n = Math.floor(Number(raw))
if (Number.isFinite(n) && n >= 3000) return Math.min(n, 600_000)
}
const adapt =
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === '1' ||
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === 'true'
if (adapt && disk && disk.peers && typeof disk.peers.size === 'number') {
const n = disk.peers.size
if (n <= 1) return Math.min(120_000, MBR_READ_TIMEOUT_MS_DEFAULT + 30_000)
if (n >= 4) return Math.max(45_000, MBR_READ_TIMEOUT_MS_DEFAULT - 15_000)
}
return MBR_READ_TIMEOUT_MS_DEFAULT
}
/**
* Interval (ms) to re-send pending block read requests to all peers while waiting for MBR / block data.
* **`0`** disables periodic rebroadcast (initial + per-connect sends only).
* Override **`BARE_OS_MBR_READ_REBROADCAST_MS`** (defaults **5000**, clamp **2000****60000**).
* @param {Record<string, string | undefined> | null | undefined} env
*/
export function mbrReadRebroadcastMsFromEnv(env) {
const raw = String(env?.BARE_OS_MBR_READ_REBROADCAST_MS ?? '').trim()
if (raw === '0' || raw.toLowerCase() === 'false') return 0
const n = Math.floor(Number(raw))
if (Number.isFinite(n) && n >= 0) {
if (n === 0) return 0
return Math.min(60_000, Math.max(2_000, n))
}
return 5_000
}
export class SwarmDisk {
constructor() {
this.localRAM = new Map()
this.peers = new Set()
this.pendingReads = new Map()
this.pendingSearches = new Map()
this.pendingRpc = new Map()
this.searchIdCounter = 0
this.rpcIdCounter = 0
this.drive = null
this.personalDrive = null
this.os = null
/** @type {import('hyperdrive').default[]} */
this.auxiliaryDrives = []
/** @type {Record<string, unknown> | null} Last `bare_os.capabilities` RPC result (or error object). */
this.seedCapabilityInfo = null
/** @type {Record<string, unknown> | null} Last `bare_os.replication_status` RPC result when available. */
this.seedReplicationStatus = null
/** @type {Record<string, unknown> | null} */
this.seedManifestHints = null
/** @type {Record<string, unknown> | null} */
this.seedPeerHealth = null
/** @type {Record<string, unknown> | null} */
this.seedStagingSlot = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationQueue = null
/** @type {Record<string, unknown> | null} */
this.seedCapabilityAttestation = null
/** @type {Record<string, unknown> | null} */
this.seedMbrLayout = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotHints = null
/** @type {Record<string, unknown> | null} */
this.seedPeerFirewallStats = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationPlan = null
/** @type {Record<string, unknown> | null} */
this.seedDhtBootstrapHint = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotChain = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorCompactionHint = null
/** @type {Record<string, unknown> | null} */
this.seedUpdaterState = null
/** @type {Record<string, unknown> | null} */
this.seedBlindPeerTopologyV2 = null
/** @type {Record<string, unknown> | null} */
this.seedCompactPing = null
/** @type {Record<string, unknown> | null} */
this.seedCorestoreStats = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotManifestSlice = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorDriveHintV2 = null
/** @type {Record<string, unknown> | null} */
this.seedHrpcRegistrySummary = null
/** @type {Record<string, unknown> | null} */
this.seedProtomuxCapabilityAd = null
/** Cumulative count of buffers received on optional Protomux app-channel pairs (operator observability). */
this.protomuxAppChannelRxTotal = 0
/** Cumulative count of buffers received on optional Protomux cap-channel pairs (`bare-os-cap-v1`). */
this.protomuxCapChannelRxTotal = 0
/** Cumulative chat `event` messages received on `bare-os-chat-v1` (when enabled). */
this.protomuxChatChannelRxTotal = 0
/** Cumulative meshdrop envelopes received on `bare-os-meshdrop-v1` (when enabled). */
this.protomuxMeshdropChannelRxTotal = 0
/** @type {ReturnType<import('./bare-os-chat-service.js').createBareOsChatService> | null} */
this.bareOsChatService = null
/** @type {ReturnType<import('./bare-os-meshdrop-service.js').createBareOsMeshdropService> | null} */
this.bareOsMeshdropService = null
/** @type {Record<string, unknown> | null} Host-requested Hyperswarm caps (from env); surfaced on disk.os RPC. */
this.swarmConnectionBudget = null
/** @type {Record<string, unknown> | null} */
this.seedDhtAddressBook = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationThrottleHint = null
/** @type {Record<string, unknown> | null} */
this.seedBundlebeeStage = null
/** @type {Record<string, unknown> | null} */
this.seedHttpDhtProxyHint = null
/** @type {Record<string, unknown> | null} */
this.seedProtomuxRpcPoolHint = null
/** @type {Record<string, unknown> | null} */
this.seedHyperblobStoreHint = null
/** @type {Record<string, unknown> | null} */
this.seedSigningRequestQueueHint = null
/** @type {Record<string, unknown> | null} */
this.seedCoreStorageLayoutHint = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorDriveCompactionV3 = null
/** @type {Record<string, unknown> | null} */
this.seedBundlebeeCliStage = null
/** @type {Record<string, unknown> | null} */
this.seedReadyGuardV2 = null
/** @type {Record<string, unknown> | null} */
this.seedBlindRelayCircuitHint = null
/** @type {Record<string, unknown> | null} */
this.seedHttpDhtProxyRoutes = null
/** @type {string[]} MBR-derived drive key hex list (primary + failovers). */
this.mbrKeysHex = []
/**
* 512-byte MBR block used at boot (copy); enables peer block-0 service when mirrored to {@link SwarmDisk#localRAM}.
* @type {Uint8Array | null}
*/
this.bootMbr512 = null
/** True after peer system seed eligibility passed and MBR was published to `localRAM`. */
this.peerSystemSeedActive = false
/**
* Local Noise wire static key (32 bytes), last seen from `Hyperswarm` socket `publicKey`.
* Peers validate chat `senderPk` against `socket.remotePublicKey`, which matches **this** — not `swarm.keyPair.publicKey`.
*/
this.localNoiseWirePk = /** @type {Uint8Array | null} */ (null)
}
/**
* One JSON-RPC-style round-trip to a specific peer.
* @param {unknown} peer
* @param {string} module
* @param {string} method
* @param {string[]} args
* @param {number} timeoutMs
* @returns {Promise<unknown>}
*/
_rpcOne(peer, module, method, args, timeoutMs) {
const id = this.rpcIdCounter++
return new Promise((resolve, reject) => {
const to = setTimeout(() => {
this.pendingRpc.delete(id)
reject(new Error('swarm-disk rpc: timeout'))
}, timeoutMs)
this.pendingRpc.set(id, (m) => {
clearTimeout(to)
if (m.success) {
const r = m.result || ''
try {
resolve(r ? JSON.parse(r) : null)
} catch {
resolve(r)
}
} else {
reject(new Error(m.error || 'swarm-disk rpc failed'))
}
})
const chan =
/** @type {{ chan: { messages: { send: (m: unknown) => void }[] } }} */ (
peer
).chan
void chan.fullyOpened().then((opened) => {
if (!opened) return
if (!this.pendingRpc.has(id)) return
try {
chan.messages[5].send({ id, module, method, args })
} catch (err) {
this.pendingRpc.delete(id)
clearTimeout(to)
reject(
err instanceof Error
? err
: new Error((err && err.message) || String(err))
)
}
})
})
}
/**
* JSON-RPC-style call to swarm peers. For **`bare_os`**, tries each peer in turn when
* **`BARE_OS_SWARM_RPC_TRY_PEERS`** is not **`0`/`false`** (default: try all).
* **`capabilities`** responses must include **`kernelCapabilityWords`** (wire v2).
* @param {string} module
* @param {string} method
* @param {string[]} [args]
* @param {number} [timeoutMs]
* @returns {Promise<unknown>}
*/
async rpc(module, method, args = [], timeoutMs = 8000) {
if (!this.peers.size) throw new Error('swarm-disk rpc: no peers')
const peers = [...this.peers]
const mod = String(module || '')
const meth = String(method || '')
const env = globalThis.process?.env
const tryAll =
String(env?.BARE_OS_SWARM_RPC_TRY_PEERS ?? '1').toLowerCase() !== '0' &&
String(env?.BARE_OS_SWARM_RPC_TRY_PEERS ?? '1').toLowerCase() !==
'false' &&
mod === 'bare_os' &&
peers.length > 1
const perPeerMs = tryAll
? Math.min(
2500,
Math.max(800, Math.floor(timeoutMs / Math.min(peers.length, 4)))
)
: timeoutMs
if (tryAll) {
/** @type {Error | null} */
let lastErr = null
for (const peer of peers) {
try {
const r = await this._rpcOne(peer, mod, meth, args, perPeerMs)
if (meth === 'capabilities') {
const words = getKernelCapabilityWords(r)
if (!words) {
lastErr = new Error(
'swarm-disk rpc: capabilities missing kernelCapabilityWords'
)
continue
}
}
return r
} catch (e) {
lastErr =
e instanceof Error ? e : new Error((e && e.message) || String(e))
}
}
throw lastErr || new Error('swarm-disk rpc: all peers failed')
}
return this._rpcOne(peers[0], mod, meth, args, timeoutMs)
}
/**
* @param {import('corestore').default} store
* @param {import('hyperswarm').default} swarm
* @param {import('hyperdrive').default} Hyperdrive
*/
async initPersonalDrive(store, swarm, Hyperdrive) {
const localStore = store.namespace('bare-os-personal-v1', {
writable: true
})
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
if (!this.personalDrive.writable) {
try {
await this.personalDrive.close()
} catch (_) {}
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
}
if (!this.personalDrive.writable) {
emitSwarmDiskHostLog(
'warn',
'[bare-os-booter] Personal Hyperdrive is not writable — identity and $HOME writes will fail. Check Corestore path permissions and that no other process holds the store read-only.',
{
writable: false,
idPrefix: b4a.toString(this.personalDrive.id, 'hex').slice(0, 16)
}
)
}
emitSwarmDiskHostLog(
'info',
'Personal Hyperdrive mounted; joining swarm discovery',
{
idPrefix: b4a.toString(this.personalDrive.id, 'hex').slice(0, 16),
writable: this.personalDrive.writable
}
)
swarm.join(this.personalDrive.discoveryKey)
}
addPeer(mux, socket) {
const disk = this
/** @type {any} */
let chan
const context = {
onread(index) {
const data = disk.localRAM.get(index)
if (data) chan.messages[1].send({ index, data })
},
ondata(m) {
const cb = disk.pendingReads.get(m.index)
if (cb) {
disk.pendingReads.delete(m.index)
cb(m.data)
}
},
ongossip() {},
async onsearchreq(m) {
const matches = disk.os ? await disk.os.searchLocal?.(m.query) : []
chan.messages[4].send({ id: m.id, matches: matches || [] })
},
onsearchres(m) {
const st = disk.pendingSearches.get(m.id)
if (!st || st.settled) return
st.results.push(m.matches || [])
st.remaining--
if (st.remaining <= 0) st.finish()
},
async onrpcreq(m) {
if (!disk.os) {
chan.messages[6].send({
id: m.id,
success: false,
result: '',
error: 'OS not initialized'
})
return
}
try {
const result = await disk.os.execRpc?.(m.module, m.method, m.args)
chan.messages[6].send({
id: m.id,
success: true,
result: String(result ?? ''),
error: ''
})
} catch (err) {
chan.messages[6].send({
id: m.id,
success: false,
result: '',
error: err.message
})
}
},
onrpcres(m) {
const cb = disk.pendingRpc.get(m.id)
if (cb) {
disk.pendingRpc.delete(m.id)
cb(m)
}
}
}
chan = mux.createChannel({ protocol: PROTOCOL_NAME, userData: context })
chan.addMessage({
encoding: c.uint32,
onmessage: (index, ch) => ch.userData.onread(index)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.index)
c.buffer.preencode(state, m.data)
},
encode(state, m) {
c.uint32.encode(state, m.index)
c.buffer.encode(state, m.data)
},
decode(state) {
return { index: c.uint32.decode(state), data: c.buffer.decode(state) }
}
},
onmessage: (m, ch) => ch.userData.ondata(m)
})
chan.addMessage({
encoding: c.buffer,
onmessage: (bitfield, ch) => ch.userData.ongossip(bitfield)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.string.preencode(state, m.query)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.string.encode(state, m.query)
},
decode(state) {
return { id: c.uint32.decode(state), query: c.string.decode(state) }
}
},
onmessage: (m, ch) => ch.userData.onsearchreq(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.array(c.string).preencode(state, m.matches)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.array(c.string).encode(state, m.matches)
},
decode(state) {
return {
id: c.uint32.decode(state),
matches: c.array(c.string).decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onsearchres(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.string.preencode(state, m.module)
c.string.preencode(state, m.method)
c.array(c.string).preencode(state, m.args)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.string.encode(state, m.module)
c.string.encode(state, m.method)
c.array(c.string).encode(state, m.args)
},
decode(state) {
return {
id: c.uint32.decode(state),
module: c.string.decode(state),
method: c.string.decode(state),
args: c.array(c.string).decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onrpcreq(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.bool.preencode(state, m.success)
c.string.preencode(state, m.result)
c.string.preencode(state, m.error)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.bool.encode(state, m.success)
c.string.encode(state, m.result || '')
c.string.encode(state, m.error || '')
},
decode(state) {
return {
id: c.uint32.decode(state),
success: c.bool.decode(state),
result: c.string.decode(state),
error: c.string.decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onrpcres(m)
})
chan.open()
const appChOn =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_PROTOMUX_APP_CHANNEL === '1' ||
globalThis.process.env.BARE_OS_PROTOMUX_APP_CHANNEL === 'true')
if (appChOn) {
mux.pair({ protocol: PROTOCOL_APP_CHANNEL_NAME }, (achan) => {
achan.addMessage({
encoding: c.buffer,
onmessage: () => {
try {
this.protomuxAppChannelRxTotal++
} catch {
/* ignore */
}
}
})
achan.open()
})
}
const capChOn =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_PROTOMUX_CAP_CHANNEL === '1' ||
globalThis.process.env.BARE_OS_PROTOMUX_CAP_CHANNEL === 'true')
if (capChOn) {
mux.pair({ protocol: PROTOCOL_CAP_CHANNEL_NAME }, (cchan) => {
cchan.addMessage({
encoding: c.buffer,
onmessage: (buf) => {
try {
const n = buf ? buf.byteLength : 0
if (n > BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES) {
emitSwarmDiskHostLog('warn', 'protomux_cap_payload_oversized', {
byteLength: n,
maxBytes: BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES
})
return
}
this.protomuxCapChannelRxTotal++
} catch {
/* ignore */
}
}
})
cchan.open()
})
}
const peer = {
chan,
mux,
socket,
id: null,
chatChan: null,
meshdropChan: null
}
if (
bareOsChatMuxEnabled(globalThis.process?.env) &&
this.bareOsChatService &&
mux.stream &&
!mux.stream.destroyed
) {
try {
this.bareOsChatService.pairOnMux(this, mux, socket, peer)
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_chat_pair_on_connect_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
if (
bareOsMeshdropMuxEnabled(globalThis.process?.env) &&
this.bareOsMeshdropService &&
mux.stream &&
!mux.stream.destroyed
) {
try {
this.bareOsMeshdropService.pairOnMux(this, mux, socket, peer)
} catch (e) {
emitSwarmDiskHostLog(
'warn',
'bare_os_meshdrop_pair_on_connect_failed',
{
message:
(e && /** @type {{ message?: string }} */ (e).message) ||
String(e)
}
)
}
}
/** Sync local Noise static key once the secret stream exposes `publicKey` (may follow handshake). */
const cacheLocalNoiseWirePk = () => {
if (socket.publicKey && socket.publicKey.byteLength === 32) {
disk.localNoiseWirePk = b4a.from(socket.publicKey)
}
}
cacheLocalNoiseWirePk()
this.peers.add(peer)
const pendingIndices = [...this.pendingReads.keys()]
for (const idx of pendingIndices) {
this._sendReadIndexToPeer(peer, idx)
}
const collabNd =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_COLLAB_SESSION_NDJSON === '1' ||
globalThis.process.env.BARE_OS_COLLAB_SESSION_NDJSON === 'true')
if (collabNd) {
emitSwarmDiskHostLog('info', 'collab_session_peer', {
peerCount: this.peers.size,
capChannelEnabled: capChOn,
appChannelEnabled: appChOn,
note: 'Host NDJSON preview; mirror to personal-drive tooling off-guest if policy allows.'
})
}
const setPeerId = () => {
if (socket.remotePublicKey && !peer.id) {
peer.id = b4a.toString(socket.remotePublicKey, 'hex')
return true
}
if (socket.handshakeHash && !peer.id) {
peer.id = b4a.toString(socket.handshakeHash, 'hex')
return true
}
return false
}
socket.on('handshake', () => {
setPeerId()
cacheLocalNoiseWirePk()
})
if (!setPeerId()) {
let attempts = 0
const tryAgain = () => {
attempts++
if (attempts > 10) {
if (!peer.id) peer.id = 'peer-' + Date.now().toString(36)
return
}
if (!setPeerId()) setTimeout(tryAgain, attempts * 50)
}
setTimeout(tryAgain, 50)
}
mux.stream.on('close', () => {
this.peers.delete(peer)
})
if (this.drive)
this.drive.replicate(mux.stream, { live: true, download: true })
if (this.personalDrive) this.personalDrive.replicate(mux.stream)
for (const d of this.auxiliaryDrives || []) {
try {
d.replicate(mux.stream, { live: true, download: true })
} catch (_) {}
}
}
/**
* Protomux message 0: request block index (MBR). Only send after channel pairing.
* @param {unknown} peer
* @param {number} index
*/
_sendReadIndexToPeer(peer, index) {
const chan =
/** @type {{ chan: { messages: { send: (idx: number) => void }[] } }} */ (
peer
).chan
void chan.fullyOpened().then((opened) => {
if (!opened) return
if (!this.pendingReads.has(index)) return
try {
chan.messages[0].send(index)
} catch {
/* ignore — channel closed or not ready */
}
})
}
/**
* Re-broadcast a pending block read to every connected peer (lossy links, slow pairing).
* @param {number} index
*/
_broadcastPendingReadIndex(index) {
for (const p of this.peers) {
this._sendReadIndexToPeer(p, index)
}
}
async read(index) {
if (this.localRAM.has(index)) return this.localRAM.get(index)
return new Promise((resolve, reject) => {
const timeoutMs = mbrReadTimeoutMsForDisk(this)
/** @type {ReturnType<typeof setInterval> | null} */
let rebroadcastIv = null
const timeout = setTimeout(() => {
if (rebroadcastIv) clearInterval(rebroadcastIv)
this.pendingReads.delete(index)
reject(new Error('MBR read timeout'))
}, timeoutMs)
this.pendingReads.set(index, (data) => {
if (rebroadcastIv) clearInterval(rebroadcastIv)
clearTimeout(timeout)
resolve(data)
})
const rbMs = mbrReadRebroadcastMsFromEnv(globalThis.process?.env)
this._broadcastPendingReadIndex(index)
if (rbMs > 0) {
rebroadcastIv = setInterval(() => {
if (!this.pendingReads.has(index)) {
if (rebroadcastIv) clearInterval(rebroadcastIv)
return
}
this._broadcastPendingReadIndex(index)
}, rbMs)
}
})
}
async search(query) {
const id = this.searchIdCounter++
const peerList = [...this.peers]
const n = peerList.length
if (!n) return []
const self = this
return new Promise((resolve) => {
/** @type {unknown[][]} */
const results = []
const state = {
remaining: n,
results,
settled: false,
timeout: /** @type {ReturnType<typeof setTimeout> | null} */ (null),
finish() {
if (state.settled) return
state.settled = true
if (state.timeout) clearTimeout(state.timeout)
self.pendingSearches.delete(id)
resolve(results.flat())
}
}
state.timeout = setTimeout(() => state.finish(), 3000)
self.pendingSearches.set(id, state)
for (const peer of peerList) {
void peer.chan.fullyOpened().then((opened) => {
if (!opened) return
if (state.settled) return
try {
peer.chan.messages[3].send({ id, query })
} catch {
/* ignore */
}
})
}
})
}
/**
* (Re)pair `bare-os-chat-v1` on every live mux — used after `ensureDiskBareOsChatTransport` replaces
* the service (guest → user, logout → guest) so `peer.chatChan` and handlers match the active instance.
*/
async pairBareOsChatExistingPeers() {
if (!bareOsChatMuxEnabled(globalThis.process?.env)) return
const svc = this.bareOsChatService
if (!svc || typeof svc.pairOnMux !== 'function') return
for (const peer of [...this.peers]) {
const st = peer.mux && peer.mux.stream
if (!st || st.destroyed) continue
try {
const mux = peer.mux
const wired = peer.chatChan
/** @type {Promise<unknown>[]} */
const closing = []
if (wired && typeof wired.close === 'function') {
try {
wired.close()
if (typeof wired.fullyClosed === 'function')
closing.push(wired.fullyClosed())
} catch {
/* ignore */
}
}
peer.chatChan = null
const prevCh =
mux &&
typeof mux.getLastChannel === 'function' &&
mux.getLastChannel({ protocol: PROTOCOL_CHAT_CHANNEL_NAME })
if (prevCh && typeof prevCh.close === 'function' && prevCh !== wired) {
try {
prevCh.close()
if (typeof prevCh.fullyClosed === 'function')
closing.push(prevCh.fullyClosed())
} catch {
/* ignore */
}
}
if (closing.length) await Promise.all(closing)
} catch {
/* ignore */
}
try {
if (typeof peer.mux.unpair === 'function') {
try {
peer.mux.unpair({ protocol: PROTOCOL_CHAT_CHANNEL_NAME })
} catch {
/* ignore */
}
}
svc.pairOnMux(this, peer.mux, peer.socket, peer)
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_chat_late_pair_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
}
/**
* (Re)pair `bare-os-meshdrop-v1` on every live mux — used after meshdrop service env/user transitions.
*/
pairBareOsMeshdropExistingPeers() {
if (!bareOsMeshdropMuxEnabled(globalThis.process?.env)) return
const svc = this.bareOsMeshdropService
if (!svc || typeof svc.pairOnMux !== 'function') return
for (const peer of [...this.peers]) {
const st = peer.mux && peer.mux.stream
if (!st || st.destroyed) continue
try {
const mux = peer.mux
const wired = peer.meshdropChan
if (wired && typeof wired.close === 'function') {
try {
wired.close()
} catch {
/* ignore */
}
}
peer.meshdropChan = null
const prevCh =
mux &&
typeof mux.getLastChannel === 'function' &&
mux.getLastChannel({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
if (prevCh && typeof prevCh.close === 'function' && prevCh !== wired) {
try {
prevCh.close()
} catch {
/* ignore */
}
}
} catch {
/* ignore */
}
try {
svc.pairOnMux(this, peer.mux, peer.socket, peer)
if (typeof peer.mux.unpair === 'function') {
try {
peer.mux.unpair({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
} catch {
/* ignore */
}
}
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_meshdrop_late_pair_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
}
}