refactor(ui): consolidate presence payload parsing (#129029)

* refactor(ui): consolidate presence payload parsing

* chore(ui): tighten presence assertion safety baselines
This commit is contained in:
Peter Steinberger
2026-08-24 23:51:27 -07:00
committed by GitHub
parent 2d0ac795b6
commit fbf8460da3
6 changed files with 12 additions and 41 deletions
+2 -3
View File
@@ -4062,7 +4062,6 @@ ui/src/components/terminal/terminal-panel-upload.ts 2
ui/src/components/terminal/terminal-panel.ts 2
ui/src/components/terminal/terminal-pending-actions.ts 1
ui/src/components/theme-mode-toggle.ts 1
ui/src/components/viewer-facepile.ts 2
ui/src/components/web-awesome.ts 2
ui/src/components/wizard-step-controls.ts 4
ui/src/i18n/lib/registry.ts 2
@@ -4228,7 +4227,7 @@ ui/src/pages/custodian/custodian-session-store.ts 1
ui/src/pages/custodian/custodian-surface.ts 2
ui/src/pages/custodian/session-lifecycle.ts 1
ui/src/pages/debug/view.ts 3
ui/src/pages/devices/devices-page.ts 3
ui/src/pages/devices/devices-page.ts 1
ui/src/pages/devices/view-exec-approvals.ts 7
ui/src/pages/devices/view.ts 5
ui/src/pages/labs/labs-registry.ts 6
@@ -4243,7 +4242,7 @@ ui/src/pages/new-session/composer.ts 1
ui/src/pages/new-session/detail-chip.ts 2
ui/src/pages/new-session/discovery.ts 3
ui/src/pages/new-session/draft-place-browser.ts 4
ui/src/pages/new-session/new-session-runtime.ts 3
ui/src/pages/new-session/new-session-runtime.ts 1
ui/src/pages/new-session/place-browser.ts 1
ui/src/pages/new-session/place-facts.ts 1
ui/src/pages/new-session/preferences.ts 1
+2 -10
View File
@@ -1,6 +1,6 @@
import { html, nothing } from "lit";
import { property } from "lit/decorators.js";
import type { PresenceEntry } from "../api/types.ts";
import { readPresenceEntries } from "../app/user-profile.ts";
import {
presenceViewerLabel,
projectPresenceEntries,
@@ -20,14 +20,6 @@ import {
} from "./person-activity-link.ts";
import "./tooltip.ts";
function readPresenceEntries(value: unknown): PresenceEntry[] {
if (!value || typeof value !== "object") {
return [];
}
const presence = (value as { presence?: unknown }).presence;
return Array.isArray(presence) ? (presence as PresenceEntry[]) : [];
}
function normalized(value: string | null | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
@@ -92,7 +84,7 @@ class ViewerFacepile extends OpenClawLightDomContentsElement {
override render() {
const projection = projectPresenceEntries(
readPresenceEntries(this.presencePayload),
readPresenceEntries(this.presencePayload) ?? [],
this.selfUserId,
this.selfInstanceId,
);
+2 -11
View File
@@ -1,4 +1,5 @@
import type { PresenceEntry } from "../api/types.ts";
import { readPresenceEntries } from "../app/user-profile.ts";
export type PresenceViewer = {
id: string;
@@ -24,16 +25,6 @@ function firstSorted(values: Iterable<string | null | undefined>): string | unde
.toSorted()[0];
}
function readPresenceEntries(value: unknown): PresenceEntry[] {
if (!value || typeof value !== "object") {
return [];
}
// SAFETY: Gateway snapshots are protocol-validated before reaching UI projections.
const presence = (value as { presence?: unknown }).presence;
// SAFETY: The validated presence array carries PresenceEntry protocol records.
return Array.isArray(presence) ? (presence as PresenceEntry[]) : [];
}
function presenceEntrySortKey(entry: PresenceEntry): string {
return [
normalized(entry.host) ?? "",
@@ -119,7 +110,7 @@ export function projectPresencePayload(
cachedAuthenticatedSelfUserId = authenticatedSelfUserId;
cachedSelfInstanceId = selfInstanceId;
cachedPresenceProjection = projectPresenceViewers(
readPresenceEntries(value),
readPresenceEntries(value) ?? [],
authenticatedSelfUserId,
selfInstanceId,
);
+5 -10
View File
@@ -11,6 +11,7 @@ import {
type ApplicationGatewaySnapshot,
} from "../../app/context.ts";
import { hasOperatorAdminAccess, hasOperatorPairingAccess } from "../../app/operator-access.ts";
import { readPresenceEntries } from "../../app/user-profile.ts";
import { showConfirmDialog, type ConfirmDialogOptions } from "../../components/confirm-dialog.ts";
import { showSecretRevealDialog } from "../../components/secret-reveal-dialog.ts";
import { renderDocsLink } from "../../components/settings-ui.ts";
@@ -62,12 +63,6 @@ type InventoryRemovalPrompt =
| { kind: "entry"; entry: InventoryRemovalRequest }
| { kind: "stale"; entries: InventoryRemovalRequest[] };
function readPresence(value: unknown): PresenceEntry[] | null {
const presence =
value && typeof value === "object" ? (value as { presence?: unknown }).presence : null;
return Array.isArray(presence) ? (presence as PresenceEntry[]) : null;
}
function presenceConnectivitySignature(entries: PresenceEntry[]): string {
const states = new Map<string, "connected" | "offline">();
for (const entry of entries) {
@@ -154,7 +149,7 @@ class DevicesPage extends OpenClawLightDomElement {
if (this.gateway.gateway !== gateway || this.context.gateway !== gateway) {
return;
}
const presence = event.event === "presence" ? readPresence(event.payload) : null;
const presence = event.event === "presence" ? readPresenceEntries(event.payload) : null;
if (presence) {
const connectivityChanged =
presenceConnectivitySignature(presence) !==
@@ -226,7 +221,7 @@ class DevicesPage extends OpenClawLightDomElement {
snapshot.client &&
(change.identityChanged || change.connectionChanged)
) {
const initialPresence = readPresence(snapshot.hello?.snapshot);
const initialPresence = readPresenceEntries(snapshot.hello?.snapshot);
this.presence = initialPresence ?? [];
void this.loadPresence();
}
@@ -250,7 +245,7 @@ class DevicesPage extends OpenClawLightDomElement {
const snapshot = this.context.gateway.snapshot;
if (!this.gateway.isRouteDataCurrent(data)) {
this.resetServerState(snapshot);
this.presence = readPresence(snapshot.hello?.snapshot) ?? [];
this.presence = readPresenceEntries(snapshot.hello?.snapshot) ?? [];
void this.loadPresence();
this.ensureInitialData();
return;
@@ -261,7 +256,7 @@ class DevicesPage extends OpenClawLightDomElement {
connected: snapshot.phase === "connected",
requestGeneration: this.gateway.epoch,
};
const initialPresence = readPresence(snapshot.hello?.snapshot);
const initialPresence = readPresenceEntries(snapshot.hello?.snapshot);
if (initialPresence) {
this.presence = initialPresence;
}
+1 -1
View File
@@ -5,6 +5,7 @@ import { selectApplicationSession } from "../../app/agent-selection.ts";
import { applicationContext, type ApplicationContext } from "../../app/context.ts";
import { beginNativeWindowDragFromTopInset } from "../../app/native-window-drag.ts";
import { loadSettings } from "../../app/settings.ts";
import { readPresenceEntries } from "../../app/user-profile.ts";
import type { ImageLightboxItem } from "../../components/image-lightbox.ts";
import { t } from "../../i18n/index.ts";
import "../../components/web-awesome-popover.ts";
@@ -39,7 +40,6 @@ import {
handleSessionPickerEvent,
isPlaceTopologyEvent,
presenceStateSignature,
readPresenceEntries,
} from "./new-session-runtime.ts";
import { renderProjectChip, resolveProjectChip } from "./project-chip.ts";
import type { SubmissionOutcomeReason } from "./session-placement-recovery-state.ts";
@@ -16,12 +16,6 @@ export function isPlaceTopologyEvent(event: string): boolean {
return PLACE_TOPOLOGY_EVENTS.has(event);
}
export function readPresenceEntries(value: unknown): PresenceEntry[] | null {
const presence =
value && typeof value === "object" ? (value as { presence?: unknown }).presence : null;
return Array.isArray(presence) ? (presence as PresenceEntry[]) : null;
}
export function presenceStateSignature(entries: PresenceEntry[]): string {
const states = new Map<string, "connected" | "offline">();
for (const entry of entries) {