fix(realtime): redact browser event details

This commit is contained in:
2026-08-13 00:19:24 +00:00
parent de804a6c2a
commit 08ea4d96bb
8 changed files with 570 additions and 327 deletions
+28 -50
View File
@@ -4,8 +4,8 @@ import { MESSAGE_MAILBOX_NATIONAL_BASE, MESSAGE_MAILBOX_PUBLIC, type MessageType
import {
applyReadModelDelta,
cloneReadModelJson,
type RealtimeEvent,
type RealtimeReadModelChanges,
type PublicRealtimeEvent,
type RealtimeReadModelInvalidation,
} from '@sammo-ts/common';
import { trpc } from '../utils/trpc';
import { useMapViewerStore } from './mapViewer';
@@ -13,7 +13,7 @@ import { useSessionStore } from './session';
import { createLatestRefreshQueue } from '../utils/latestRefreshQueue';
import { createRateLimitedRefreshQueue } from '../utils/rateLimitedRefreshQueue';
import { structurallyShare } from '../utils/structuralShare';
import { createMergedReadModelRefreshQueue, resolveDashboardRefreshPlan } from '../utils/dashboardReadModel';
import { createMergedReadModelRefreshQueue } from '../utils/dashboardReadModel';
import { createBroadcastTabCoordinator, type BroadcastTabCoordinator } from '../utils/broadcastTabCoordinator';
import { resolveWithReadModelSnapshotFallback } from '../utils/readModelDeltaRecovery';
@@ -612,16 +612,11 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
}
);
const refreshChangedReadModels = async (changes: RealtimeReadModelChanges) => {
const refreshChangedReadModels = async (plan: RealtimeReadModelInvalidation) => {
const id = generalId.value;
if (!id) {
return;
}
const plan = resolveDashboardRefreshPlan(changes, {
generalId: id,
cityId: city.value?.id ?? null,
nationId: nation.value?.id ?? null,
});
if (!Object.values(plan).some(Boolean)) {
return;
}
@@ -944,12 +939,12 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
return url.toString();
};
const parseRealtimePayload = (raw: MessageEvent): RealtimeEvent | null => {
const parseRealtimePayload = (raw: MessageEvent): PublicRealtimeEvent | null => {
if (!raw.data || typeof raw.data !== 'string') {
return null;
}
try {
const parsed = JSON.parse(raw.data) as RealtimeEvent;
const parsed = JSON.parse(raw.data) as PublicRealtimeEvent;
if (!parsed || typeof parsed !== 'object') {
return null;
}
@@ -962,21 +957,6 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
}
};
const isMailboxRelevant = (mailbox: number): boolean => {
if (mailbox === MESSAGE_MAILBOX_PUBLIC) {
return true;
}
const currentGeneralId = generalId.value;
if (currentGeneralId && mailbox === currentGeneralId) {
return true;
}
const currentNationId = nationId.value;
if (currentNationId && mailbox === MESSAGE_MAILBOX_NATIONAL_BASE + currentNationId) {
return true;
}
return false;
};
const closeRealtimeSource = () => {
if (!realtimeSource) {
return;
@@ -1095,36 +1075,34 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
realtimeStatus.value = realtimeEnabled.value ? 'idle' : 'paused';
realtimeCoordinator?.postFromLeader({ kind: 'status', status: 'idle' });
});
source.addEventListener('turnCompleted', (event) => {
source.addEventListener('readModelInvalidated', (event) => {
if (realtimeCoordinator !== null && !realtimeCoordinator.isLeader()) return;
const payload = parseRealtimePayload(event);
if (!payload || payload.type !== 'turnCompleted') {
if (!payload || payload.type !== 'readModelInvalidated') {
return;
}
if (!payload.changes) {
// Rolling deployment fallback for an older daemon.
readModelRefreshQueue.request(payload.invalidation);
});
source.addEventListener('messagesInvalidated', (event) => {
if (realtimeCoordinator !== null && !realtimeCoordinator.isLeader()) return;
const payload = parseRealtimePayload(event);
if (!payload || payload.type !== 'messagesInvalidated') {
return;
}
void refreshMessages();
});
// Rolling deployment fallback: an older API may still expose internal
// events. Do not inspect their payload; use the bounded full refresh.
for (const legacyEventType of ['turnCompleted', 'readModelChanged'] as const) {
source.addEventListener(legacyEventType, () => {
if (realtimeCoordinator !== null && !realtimeCoordinator.isLeader()) return;
realtimeRefreshQueue.request();
return;
}
readModelRefreshQueue.request(payload.changes);
});
source.addEventListener('readModelChanged', (event) => {
});
}
source.addEventListener('messageCreated', () => {
if (realtimeCoordinator !== null && !realtimeCoordinator.isLeader()) return;
const payload = parseRealtimePayload(event);
if (!payload || payload.type !== 'readModelChanged') {
return;
}
readModelRefreshQueue.request(payload.changes);
});
source.addEventListener('messageCreated', (event) => {
if (realtimeCoordinator !== null && !realtimeCoordinator.isLeader()) return;
const payload = parseRealtimePayload(event);
if (!payload || payload.type !== 'messageCreated') {
return;
}
if (isMailboxRelevant(payload.mailbox)) {
void refreshMessages();
}
void refreshMessages();
});
source.addEventListener('ping', () => {
if (realtimeEnabled.value) {