fix(webui): snapshot mutation replay frames

This commit is contained in:
chengyongru
2026-08-16 21:40:14 +08:00
committed by chengyongru
parent e51ffc8978
commit 2b6a0443c0
2 changed files with 24 additions and 10 deletions
+20 -6
View File
@@ -109,10 +109,8 @@ interface PendingRequest<T> {
timer: ReturnType<typeof setTimeout>;
}
type WebUIRequestFrame = Extract<Outbound, { type: "webui_request" }>;
interface PendingWebUIRequest extends PendingRequest<unknown> {
frame: WebUIRequestFrame;
serializedFrame: string;
}
export class WebUIMutationError extends Error {
@@ -868,6 +866,13 @@ export class NanobotClient {
}
return new Promise<T>((resolve, reject) => {
let serializedFrame: string;
try {
serializedFrame = JSON.stringify(frame);
} catch {
reject(new WebUIMutationError(503, "Could not encode WebUI request"));
return;
}
const timer = setTimeout(() => {
this.pendingWebUIRequests.delete(requestId);
reject(
@@ -881,10 +886,10 @@ export class NanobotClient {
resolve: (value) => resolve(value as T),
reject,
timer,
frame,
serializedFrame,
});
try {
socket.send(JSON.stringify(frame));
socket.send(serializedFrame);
} catch {
clearTimeout(timer);
this.pendingWebUIRequests.delete(requestId);
@@ -1028,7 +1033,7 @@ export class NanobotClient {
this.rawSend({ type: "attach", chat_id: chatId });
}
for (const pending of this.pendingWebUIRequests.values()) {
this.rawSend(pending.frame);
this.rawSendSerialized(pending.serializedFrame);
}
// Flush anything queued during reconnect.
const queued = this.sendQueue.splice(0);
@@ -1476,4 +1481,13 @@ export class NanobotClient {
this.sendQueue.push(frame);
}
}
private rawSendSerialized(serializedFrame: string): void {
if (!this.socket) return;
try {
this.socket.send(serializedFrame);
} catch {
// The pending request remains available for the next successful reconnect.
}
}
}
+4 -4
View File
@@ -183,12 +183,12 @@ describe("NanobotClient", () => {
const firstSocket = lastSocket();
firstSocket.fakeOpen();
const pending = client.requestMutation<{ ran: boolean }>(
"automation.run",
{ id: "daily-summary" },
);
const payload = { id: "daily-summary", options: { force: false } };
const pending = client.requestMutation<{ ran: boolean }>("automation.run", payload);
const frame = firstSocket.sent.at(-1) as string;
const requestId = JSON.parse(frame).request_id;
payload.id = "weekly-summary";
payload.options.force = true;
const settled = expect(pending).resolves.toEqual({ ran: true });
firstSocket.fakeCloseWithCode(1006);