Publish all active provider surfaces
This commit is contained in:
parent
6dd6fdcf0e
commit
49f75f85df
3 changed files with 83 additions and 16 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue