Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions apps/desktop/src/preview/BrowserSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { fromPartition, sessions } = vi.hoisted(() => ({
readonly clearCache: ReturnType<typeof vi.fn>;
readonly clearStorageData: ReturnType<typeof vi.fn>;
readonly getUserAgent: ReturnType<typeof vi.fn>;
readonly setPermissionCheckHandler: ReturnType<typeof vi.fn>;
readonly setPermissionRequestHandler: ReturnType<typeof vi.fn>;
readonly setUserAgent: ReturnType<typeof vi.fn>;
}
Expand All @@ -28,6 +29,13 @@ vi.mock("electron", () => ({

import * as BrowserSession from "./BrowserSession.ts";

type PermissionRequestHandler = (
webContents: unknown,
permission: string,
callback: (granted: boolean) => void,
) => void;
type PermissionCheckHandler = (webContents: unknown, permission: string) => boolean;

const layer = BrowserSession.layer.pipe(Layer.provide(NodeServices.layer));

describe("BrowserSession", () => {
Expand All @@ -39,6 +47,7 @@ describe("BrowserSession", () => {
clearCache: vi.fn(() => Promise.resolve()),
clearStorageData: vi.fn(() => Promise.resolve()),
getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/0.0.27"),
setPermissionCheckHandler: vi.fn(),
setPermissionRequestHandler: vi.fn(),
setUserAgent: vi.fn(),
};
Expand All @@ -61,6 +70,35 @@ describe("BrowserSession", () => {
}).pipe(Effect.provide(layer)),
);

it.effect("grants clipboard permissions for previewed pages", () =>
Effect.gen(function* () {
const browserSessions = yield* BrowserSession.BrowserSession;

const partition = yield* browserSessions.getPartition("scope-a");
yield* browserSessions.getSession("scope-a");
const mockSession = sessions.get(partition);
assert.isDefined(mockSession);

assert.strictEqual(mockSession.setPermissionRequestHandler.mock.calls.length, 1);
const requestHandler = mockSession.setPermissionRequestHandler.mock
.calls[0]?.[0] as PermissionRequestHandler;
const requested = new Map<string, boolean>();
for (const permission of ["clipboard-read", "clipboard-sanitized-write", "midi"]) {
requestHandler(undefined, permission, (granted) => requested.set(permission, granted));
}
assert.strictEqual(requested.get("clipboard-read"), true);
assert.strictEqual(requested.get("clipboard-sanitized-write"), true);
assert.strictEqual(requested.get("midi"), false);

assert.strictEqual(mockSession.setPermissionCheckHandler.mock.calls.length, 1);
const checkHandler = mockSession.setPermissionCheckHandler.mock
.calls[0]?.[0] as PermissionCheckHandler;
assert.strictEqual(checkHandler(undefined, "clipboard-read"), true);
assert.strictEqual(checkHandler(undefined, "clipboard-sanitized-write"), true);
assert.strictEqual(checkHandler(undefined, "midi"), false);
}).pipe(Effect.provide(layer)),
);

it.effect("preserves partition scope and the platform failure chain", () => {
const nativeCause = new Error("native digest failed");
const platformCause = PlatformError.systemError({
Expand Down
19 changes: 17 additions & 2 deletions apps/desktop/src/preview/BrowserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ import * as SynchronizedRef from "effect/SynchronizedRef";

const PREVIEW_PARTITION_PREFIX = "persist:t3code-preview-";

const ALLOWED_PERMISSIONS = [
"clipboard-read",
"clipboard-sanitized-write",
"notifications",
"geolocation",
] as const;
const PERMISSION_CHECK_ALLOWLIST: ReadonlySet<string> = new Set(ALLOWED_PERMISSIONS);
// The request handler can also receive the legacy "clipboard-write" permission name.
const PERMISSION_REQUEST_ALLOWLIST: ReadonlySet<string> = new Set([
...ALLOWED_PERMISSIONS,
"clipboard-write",
]);

export class BrowserSessionPartitionDerivationError extends Schema.TaggedErrorClass<BrowserSessionPartitionDerivationError>()(
"BrowserSessionPartitionDerivationError",
{
Expand Down Expand Up @@ -120,9 +133,11 @@ export const make = Effect.gen(function* BrowserSessionMake() {
.replace(/\s*t3code\/[\d.]+/, "");
browserSession.setUserAgent(userAgent);
browserSession.setPermissionRequestHandler((_webContents, permission, callback) => {
const allowed = ["clipboard-read", "clipboard-write", "notifications", "geolocation"];
callback(allowed.includes(permission));
callback(PERMISSION_REQUEST_ALLOWLIST.has(permission));
});
browserSession.setPermissionCheckHandler((_webContents, permission) =>
PERMISSION_CHECK_ALLOWLIST.has(permission),
);
const next = new Map(sessions);
next.set(partition, browserSession);
return [browserSession, next] as const;
Expand Down
Loading