Skip to content

Commit afd097a

Browse files
committed
fix(delete): accept only known recordings disposals from the renderer
Any value but "trash" took the permanent-delete path, so a malformed one removed recordings for good. Also fixes a grammar slip in the media-library docs.
1 parent c91f0f0 commit afd097a

3 files changed

Lines changed: 80 additions & 6 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Deleting a project takes the recordings disposal from the renderer, and every value but
2+
// "trash" would remove files for good, so the bridge accepts only the known ones.
3+
4+
import { beforeEach, describe, expect, it, vi } from "vitest";
5+
import type { NativeBridgeResponse } from "../../src/native/contracts";
6+
import { type NativeBridgeContext, registerNativeBridgeHandlers } from "./nativeBridge";
7+
8+
const electron = vi.hoisted(() => ({ handle: vi.fn() }));
9+
vi.mock("electron", () => ({
10+
app: { getAppPath: () => "", isPackaged: false },
11+
ipcMain: { handle: electron.handle, removeHandler: vi.fn() },
12+
shell: {},
13+
}));
14+
vi.mock("../native-bridge/services/compositorViewService", () => ({
15+
CompositorViewService: class {},
16+
}));
17+
18+
const service = vi.hoisted(() => ({
19+
deleteProject: vi.fn(async () => ({ success: true })),
20+
}));
21+
vi.mock("../native-bridge/services/aiEditionService", () => ({
22+
AiEditionService: class {
23+
deleteProject = service.deleteProject;
24+
},
25+
}));
26+
27+
let invoke: (payload: unknown) => Promise<NativeBridgeResponse>;
28+
29+
beforeEach(() => {
30+
electron.handle.mockClear();
31+
service.deleteProject.mockClear();
32+
registerNativeBridgeHandlers({
33+
getPlatform: () => "linux",
34+
getAiEditionDocuments: () => ({}),
35+
getAiEditionLlmConfig: () => ({}),
36+
} as unknown as NativeBridgeContext);
37+
const handler = electron.handle.mock.calls[0]?.[1] as (
38+
event: unknown,
39+
request: unknown,
40+
) => Promise<NativeBridgeResponse>;
41+
invoke = (payload) =>
42+
handler(
43+
{ sender: {} },
44+
{ domain: "aiEdition", action: "document.delete", payload, requestId: "r" },
45+
);
46+
});
47+
48+
describe("native bridge project delete", () => {
49+
it.each(["Trash", "delete", null, 1])("rejects recordings=%s as a bad request", async (value) => {
50+
const response = await invoke({ projectId: "p", recordings: value });
51+
52+
expect(response.ok ? undefined : response.error?.code).toBe("INVALID_REQUEST");
53+
expect(service.deleteProject).not.toHaveBeenCalled();
54+
});
55+
56+
it.each([undefined, "keep", "trash", "permanent"])("passes recordings=%s on", async (value) => {
57+
const response = await invoke({ projectId: "p", recordings: value });
58+
59+
expect(response.ok).toBe(true);
60+
expect(service.deleteProject).toHaveBeenCalledWith("p", value);
61+
});
62+
});

‎electron/ipc/nativeBridge.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -573,14 +573,26 @@ export function registerNativeBridgeHandlers(context: NativeBridgeContext) {
573573
requestId,
574574
await aiEditionService.save(request.payload.document),
575575
);
576-
case "document.delete":
576+
// Renderer input: anything but "trash" would take the permanent path.
577+
case "document.delete": {
578+
const disposal: unknown = request.payload.recordings;
579+
if (
580+
disposal !== undefined &&
581+
disposal !== "keep" &&
582+
disposal !== "trash" &&
583+
disposal !== "permanent"
584+
) {
585+
return createErrorResponse(
586+
requestId,
587+
"INVALID_REQUEST",
588+
"Invalid recordings disposal.",
589+
);
590+
}
577591
return createSuccessResponse(
578592
requestId,
579-
await aiEditionService.deleteProject(
580-
request.payload.projectId,
581-
request.payload.recordings,
582-
),
593+
await aiEditionService.deleteProject(request.payload.projectId, disposal),
583594
);
595+
}
584596
case "document.recordings":
585597
return createSuccessResponse(
586598
requestId,

‎website/docs/media-library.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ The **Format** control in the **Composition** facet sets the shape of the frame;
5252

5353
**Open project** lists your recent `.openscreen` files with a search box, keyboard navigation, and a **Browse files…** escape hatch. You can also drop a `.openscreen` file onto the empty editor.
5454

55-
The trash icon on a project's row deletes the project after a confirm. Its recordings are kept unless you switch on **Also move its recordings to the Trash** (**Recycle Bin** on Windows): that sends the takes in OpenScreen's recordings folder to the system Trash, except any another project still uses, which the row names. If the Trash refuses, as it can on some Linux setups and drives, OpenScreen asks before deleting them permanently.
55+
The trash icon on a project's row deletes the project after a confirm. Its recordings are kept unless you switch on **Also move its recordings to the Trash** (**Recycle Bin** on Windows): that sends the takes in OpenScreen's recordings folder to the system Trash, except any that another project still uses, which the row names. If the Trash refuses, as it can on some Linux setups and drives, OpenScreen asks before deleting them permanently.

0 commit comments

Comments
 (0)