Publish all active provider surfaces

This commit is contained in:
Meta Vens 2026-06-06 20:20:04 +01:00
commit 49f75f85df
3 changed files with 83 additions and 16 deletions

View file

@ -211,6 +211,40 @@ function localIpv4Prefixes() {
}
async function fetchEveProvider(url, providerId, manifest = null) {
const direct = await fetchEveProviderDirect(providerDeckUrl(url, providerId), providerId, manifest);
if (direct.state === "active") {
return direct;
}
const broker = await fetchEveProviderViaBroker(url, providerId, manifest);
if (broker.state === "active") {
return broker;
}
return broker.detail ? broker : direct;
}
async function fetchEveProviderDirect(url, providerId, manifest = null) {
try {
const socket = await openWebSocket(url);
try {
for (let index = 0; index < 3; index += 1) {
const message = await readServerTextFrame(socket, 1000);
const state = JSON.parse(message);
if (state?.providerId === providerId) {
return activeProviderInterface(url, providerId, state, manifest);
}
}
return dashboardUnavailable(providerId, url, "direct provider stream did not publish matching state");
} finally {
socket.destroy();
}
} catch (error) {
return dashboardUnavailable(providerId, url, error.message);
}
}
async function fetchEveProviderViaBroker(url, providerId, manifest = null) {
try {
const socket = await openWebSocket(url);
try {
@ -219,18 +253,7 @@ async function fetchEveProvider(url, providerId, manifest = null) {
const message = await readServerTextFrame(socket, 2500);
const state = JSON.parse(message);
if (state?.providerId === providerId) {
const surface = state.surface?.root ? state.surface : legacySurfaceFromNodes(state, manifest);
return {
providerId,
title: state.title || manifest?.title || providerId,
state: "active",
detail: `${surface?.root?.kind || "surface"} ${countSurfaceNodes(surface, state)} nodes`,
version: state.version,
updatedAt: state.updatedAt,
source: url,
manifest,
surface,
};
return activeProviderInterface(url, providerId, state, manifest);
}
}
return dashboardUnavailable(providerId, url, "provider did not publish matching state");
@ -242,6 +265,28 @@ async function fetchEveProvider(url, providerId, manifest = null) {
}
}
function activeProviderInterface(source, providerId, state, manifest = null) {
const surface = state.surface?.root ? state.surface : legacySurfaceFromNodes(state, manifest);
return {
providerId,
title: state.title || manifest?.title || providerId,
state: "active",
detail: `${surface?.root?.kind || "surface"} ${countSurfaceNodes(surface, state)} nodes`,
version: state.version,
updatedAt: state.updatedAt,
source,
manifest,
surface,
};
}
function providerDeckUrl(url, providerId) {
const deck = new URL(url);
deck.pathname = `/eve/deck/${encodeURIComponent(providerId)}`;
deck.search = "";
return deck.toString();
}
function dashboardUnavailable(providerId, source, detail) {
return {
providerId,

View file

@ -5,7 +5,7 @@ const { observationPane } = require("./observations.cjs");
const { stableId } = require("./utils.cjs");
function buildSurface({ observedAt, docker, adb, hosts, yggdrasilServices, verses, interfaces, observations, layout }) {
const activeInterfaces = interfaces.filter(hasOverviewSignal);
const activeInterfaces = interfaces.filter((entry) => entry?.surface?.root);
const activeObservationStreams = observations.streams.filter((entry) => entry.state === "active");
return {
schema: "gamecult.eve.surface.v1",
@ -45,6 +45,7 @@ function buildSurface({ observedAt, docker, adb, hosts, yggdrasilServices, verse
source: entry.source,
status: entry.state,
detail: entry.detail,
overviewSignal: hasOverviewSignal(entry),
version: entry.version,
updatedAt: entry.updatedAt,
layout: mergeLayoutIntent(layout.tiles?.[entry.providerId], entry, interfaces),
@ -94,7 +95,7 @@ function stonksHasOverviewSignal(root, text) {
function marketMarqueeText(interfaces) {
const stonks = interfaces.find((entry) => String(entry.providerId || "").toLowerCase() === "stonks.market");
const root = stonks?.surface?.root;
if (!root) return "";
if (!root) return generalMarqueeText(interfaces);
const explicit = String(root.props?.marqueeText || "").trim();
if (explicit) return explicit;
return surfaceText(root)
@ -105,6 +106,19 @@ function marketMarqueeText(interfaces) {
.join(" / ");
}
function generalMarqueeText(interfaces) {
return interfaces
.filter((entry) => entry?.surface?.root && entry.state === "active")
.flatMap((entry) => surfaceText(entry.surface.root)
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.map((line) => `${entry.title || entry.providerId}: ${line}`))
.filter((line) => !/\b(unavailable|not discovered|playing:\s*none|queue:\s*empty)\b/i.test(line))
.slice(0, 24)
.join(" / ");
}
function spotiverseHasOverviewSignal(root, text) {
const status = String(root.props?.status || "").toLowerCase();
if (status === "warn" && /\brate limit\b/i.test(text)) return false;

View file

@ -343,7 +343,7 @@ function openWebSocket(url) {
return;
}
socket.off("data", onHandshake);
socket.unshift(buffer.subarray(marker + 4));
socket.odinPendingData = buffer.subarray(marker + 4);
resolve(socket);
});
socket.on("timeout", () => {
@ -373,7 +373,8 @@ function sendClientFrame(socket, textValue) {
function readServerTextFrame(socket, timeoutMs) {
return new Promise((resolve, reject) => {
let buffer = Buffer.alloc(0);
let buffer = socket.odinPendingData || Buffer.alloc(0);
socket.odinPendingData = null;
const timer = setTimeout(() => cleanup(new Error("timed out waiting for dashboard frame")), timeoutMs);
function cleanup(error, value) {
clearTimeout(timer);
@ -395,6 +396,13 @@ function readServerTextFrame(socket, timeoutMs) {
}
socket.on("data", onData);
socket.on("error", onError);
const frame = tryReadFrame(buffer);
if (frame) {
buffer = buffer.subarray(frame.consumed);
socket.odinPendingData = buffer.length ? buffer : null;
if (frame.opcode === 0x1) cleanup(null, frame.payload.toString("utf8"));
if (frame.opcode === 0x8) cleanup(new Error("dashboard websocket closed"));
}
});
}