mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
fix(google-meet): contain node host stream failures (#102105)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 624dfa6cf6)
This commit is contained in:
committed by
Dallin Romney
parent
75f0470c6c
commit
1918ea2563
@@ -9,7 +9,7 @@ type MockChild = EventEmitter & {
|
||||
kill: ReturnType<typeof vi.fn>;
|
||||
stdout?: EventEmitter;
|
||||
stderr?: EventEmitter;
|
||||
stdin?: { write: ReturnType<typeof vi.fn> };
|
||||
stdin?: EventEmitter & { write: ReturnType<typeof vi.fn> };
|
||||
};
|
||||
|
||||
const children: MockChild[] = [];
|
||||
@@ -34,7 +34,7 @@ vi.mock("node:child_process", async (importOriginal) => {
|
||||
}),
|
||||
stdout: new EventEmitter(),
|
||||
stderr: new EventEmitter(),
|
||||
stdin: { write: vi.fn() },
|
||||
stdin: Object.assign(new EventEmitter(), { write: vi.fn() }),
|
||||
}) as MockChild;
|
||||
children.push(child);
|
||||
return child;
|
||||
@@ -166,6 +166,48 @@ describe("google-meet node host bridge sessions", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("closes once when command-pair streams fail together", async () => {
|
||||
const originalPlatform = process.platform;
|
||||
children.length = 0;
|
||||
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: "darwin" });
|
||||
try {
|
||||
const start = JSON.parse(
|
||||
await handleGoogleMeetNodeHostCommand(
|
||||
JSON.stringify({
|
||||
action: "start",
|
||||
url: "https://meet.google.com/xyz-abcd-uvw",
|
||||
mode: "realtime",
|
||||
launch: false,
|
||||
audioInputCommand: ["mock-rec"],
|
||||
audioOutputCommand: ["mock-play"],
|
||||
}),
|
||||
),
|
||||
);
|
||||
const [outputProcess, inputProcess] = children;
|
||||
if (!outputProcess || !inputProcess) {
|
||||
throw new Error("expected Google Meet node host command-pair processes");
|
||||
}
|
||||
|
||||
outputProcess.stderr?.emit("error", new Error("output stderr failed"));
|
||||
inputProcess.stdout?.emit("error", new Error("input stdout failed"));
|
||||
inputProcess.stderr?.emit("error", new Error("input stderr failed"));
|
||||
|
||||
const status = JSON.parse(
|
||||
await handleGoogleMeetNodeHostCommand(
|
||||
JSON.stringify({ action: "status", bridgeId: start.bridgeId }),
|
||||
),
|
||||
);
|
||||
expect(status.bridge.closed).toBe(true);
|
||||
expect(outputProcess.kill).toHaveBeenCalledTimes(1);
|
||||
expect(inputProcess.kill).toHaveBeenCalledTimes(1);
|
||||
expect(outputProcess.kill).toHaveBeenCalledWith("SIGTERM");
|
||||
expect(inputProcess.kill).toHaveBeenCalledWith("SIGTERM");
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform });
|
||||
}
|
||||
});
|
||||
|
||||
it("lists active bridge sessions and hides closed sessions", async () => {
|
||||
const originalPlatform = process.platform;
|
||||
children.length = 0;
|
||||
|
||||
@@ -107,32 +107,28 @@ function wake(session: NodeBridgeSession) {
|
||||
}
|
||||
|
||||
function stopSession(session: NodeBridgeSession) {
|
||||
const wasClosed = session.closed;
|
||||
// Process and stream errors can arrive together during teardown. Close once
|
||||
// so the same children do not get duplicate termination timers.
|
||||
if (session.closed) {
|
||||
return;
|
||||
}
|
||||
session.closed = true;
|
||||
session.closedAt ??= new Date().toISOString();
|
||||
session.closedAt = new Date().toISOString();
|
||||
terminateChild(session.input);
|
||||
terminateChild(session.output);
|
||||
if (!wasClosed) {
|
||||
wake(session);
|
||||
}
|
||||
wake(session);
|
||||
}
|
||||
|
||||
function attachOutputProcessHandlers(session: NodeBridgeSession, outputProcess: ChildProcess) {
|
||||
outputProcess.on("exit", () => {
|
||||
const stopIfCurrent = () => {
|
||||
if (session.output === outputProcess) {
|
||||
stopSession(session);
|
||||
}
|
||||
});
|
||||
outputProcess.on("error", () => {
|
||||
if (session.output === outputProcess) {
|
||||
stopSession(session);
|
||||
}
|
||||
});
|
||||
outputProcess.stdin?.on?.("error", () => {
|
||||
if (session.output === outputProcess) {
|
||||
stopSession(session);
|
||||
}
|
||||
});
|
||||
};
|
||||
outputProcess.on("exit", stopIfCurrent);
|
||||
outputProcess.on("error", stopIfCurrent);
|
||||
outputProcess.stdin?.on("error", stopIfCurrent);
|
||||
outputProcess.stderr?.on("error", stopIfCurrent);
|
||||
}
|
||||
|
||||
function startOutputProcess(command: { command: string; args: string[] }) {
|
||||
@@ -178,9 +174,12 @@ function startCommandPair(params: {
|
||||
}
|
||||
wake(session);
|
||||
});
|
||||
inputProcess.on("exit", () => stopSession(session));
|
||||
const stop = () => stopSession(session);
|
||||
inputProcess.on("exit", stop);
|
||||
inputProcess.on("error", stop);
|
||||
inputProcess.stdout?.on("error", stop);
|
||||
inputProcess.stderr?.on("error", stop);
|
||||
attachOutputProcessHandlers(session, outputProcess);
|
||||
inputProcess.on("error", () => stopSession(session));
|
||||
sessions.set(session.id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user