Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/app-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { createTrayHealth } from "./tray-health.js";

const PROVIDER_LABELS = Object.freeze({ jellyfin: "Jellyfin", emby: "Emby", plex: "Plex", navidrome: "Navidrome" });
// Own keys only, so an id such as "constructor" is not read off Object.prototype.
function providerLabel(id) { return typeof id === "string" && Object.hasOwn(PROVIDER_LABELS, id) ? PROVIDER_LABELS[id] : undefined; }
const STATE_BY_FAILURE = Object.freeze({ unauthorized: "authentication_failed", unreachable: "unreachable", timeout: "unreachable", error: "error" });

// Tracks what the local status page shows. It only keeps the latest poll
Expand Down Expand Up @@ -69,7 +71,7 @@
try { rows = servers(); } catch { rows = []; }
if (!Array.isArray(rows)) return Object.freeze([]);
return Object.freeze(rows.map((row, index) => Object.freeze({
type: PROVIDER_LABELS[row?.provider] ?? word(row?.provider),
type: providerLabel(row?.provider) ?? word(row?.provider),
address: serverOrigin(config.servers?.[index]?.serverUrl),
user: text(row?.displayName),
state: word(row?.state),
Expand Down Expand Up @@ -108,7 +110,7 @@
build: buildLabel(build),
uptimeMs: Math.max(0, elapsedNow() - startedElapsed),
server: Object.freeze({
type: PROVIDER_LABELS[config.provider] ?? config.provider,
type: providerLabel(config.provider) ?? config.provider,
address: serverOrigin(config.serverUrl),
user: text(config.identity?.displayName),
state: safeMode ? "safe_mode" : failure ? STATE_BY_FAILURE[failure] : lastOkAt === null ? "starting" : "connected",
Expand All @@ -126,29 +128,29 @@
// Support bundle for bug reports (#115): allow-listed labels only. The
// address, user name and current track are passed as sensitive values so
// they are scrubbed even if they turn up inside an error string.
function diagnostics() {
const s = snapshot();
const problem = serverProblem(s.servers);
const outputs = ["card", ...(s.discord.enabled ? ["discord"] : []), ...(s.hosted.enabled ? ["hosted"] : [])];
const serverHealthy = s.server.state === "connected" && !problem;
const discordHealthy = !s.discord.enabled || s.discord.state === "ready";
const discordFailed = s.discord.enabled && ["degraded", "failed", "closed"].includes(s.discord.state);
const hosting = hostedOutput(s.hosted);
const health = serverHealthy && discordHealthy && ["healthy", "disabled"].includes(hosting) ? "healthy"
: (s.server.state === "starting" && hosting !== "failed" && !problem && !discordFailed) || (serverHealthy && discordHealthy && hosting === "starting") ? "starting" : "degraded";
const sensitiveValues = [config.serverUrl, s.server.address, s.server.user, playing?.title, playing?.subtitle].filter((value) => typeof value === "string");
return createDiagnosticRecord({
version: s.version ?? undefined,
platform,
packageType: packageType ?? undefined,
enabledOutputs: outputs,
provider: { type: config.provider, status: s.server.state },
health,
errors: [failure, problem?.reason, s.discord.error, s.hosted.enabled && hosting === "failed" ? `hosted_${s.hosted.error ?? "upload_failed"}` : null].filter((value) => typeof value === "string"),
sensitiveValues,
build: build ?? undefined,
});
}

Check notice on line 153 in src/app-status.js

View check run for this annotation

codefactor.io / CodeFactor

src/app-status.js#L131-L153

Complex Method

// Short, privacy-safe line for the tray tooltip and menu (#121): no track,
// user name or address. Windows caps tooltips at 63 characters.
Expand Down
5 changes: 5 additions & 0 deletions test/app-status.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,8 @@ test("the card cover status reports a short allow-listed reason and nothing else
status.setCardArtwork(() => ({ state: "failed", reason: "http://secret.example/?token=abc" }));
assert.equal(status.snapshot().cardArtwork.reason, "unknown");
});

test("a provider id that names an Object.prototype property is shown as its own text, not a function", () => {
const status = createAppStatus({ config: { ...config, provider: "constructor" }, version: "0.1.1-dev" });
assert.equal(status.snapshot().server.type, "constructor");
});
Loading