| ... | ... | @@ -15,6 +15,7 @@ const { fromPartition, sessions } = vi.hoisted(() => ({ |
| 15 | 15 | readonly clearStorageData: ReturnType<typeof vi.fn>; |
| 16 | 16 | readonly getUserAgent: ReturnType<typeof vi.fn>; |
| 17 | 17 | readonly setPermissionRequestHandler: ReturnType<typeof vi.fn>; |
| 18 | readonly setPermissionCheckHandler: ReturnType<typeof vi.fn>; |
| 18 | 19 | readonly setUserAgent: ReturnType<typeof vi.fn>; |
| 19 | 20 | } |
| 20 | 21 | >(), |
| ... | ... | @@ -40,6 +41,7 @@ describe("BrowserSession", () => { |
| 40 | 41 | clearStorageData: vi.fn(() => Promise.resolve()), |
| 41 | 42 | getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/0.0.27"), |
| 42 | 43 | setPermissionRequestHandler: vi.fn(), |
| 44 | setPermissionCheckHandler: vi.fn(), |
| 43 | 45 | setUserAgent: vi.fn(), |
| 44 | 46 | }; |
| 45 | 47 | sessions.set(partition, browserSession); |
| ... | ... | @@ -61,6 +63,55 @@ describe("BrowserSession", () => { |
| 61 | 63 | }).pipe(Effect.provide(layer)), |
| 62 | 64 | ); |
| 63 | 65 | |
| 66 | it.effect("grants clipboard-sanitized-write through both the request and check handlers", () => |
| 67 | Effect.gen(function* () { |
| 68 | const browserSessions = yield* BrowserSession.BrowserSession; |
| 69 | const partition = yield* browserSessions.getPartition("scope-a"); |
| 70 | yield* browserSessions.getSession("scope-a"); |
| 71 | |
| 72 | const browserSession = sessions.get(partition); |
| 73 | assert.isDefined(browserSession); |
| 74 | |
| 75 | const requestHandler = browserSession.setPermissionRequestHandler.mock.calls[0]?.[0]; |
| 76 | const checkHandler = browserSession.setPermissionCheckHandler.mock.calls[0]?.[0]; |
| 77 | assert.isFunction(requestHandler); |
| 78 | assert.isFunction(checkHandler); |
| 79 | |
| 80 | const requestAllows = (permission: string): boolean => { |
| 81 | let granted: boolean | undefined; |
| 82 | requestHandler(null, permission, (value: boolean) => { |
| 83 | granted = value; |
| 84 | }); |
| 85 | assert.isDefined(granted); |
| 86 | return granted; |
| 87 | }; |
| 88 | |
| 89 | for (const permission of [ |
| 90 | "clipboard-read", |
| 91 | "clipboard-sanitized-write", |
| 92 | "notifications", |
| 93 | "geolocation", |
| 94 | ]) { |
| 95 | assert.isTrue(requestAllows(permission), `request handler should allow ${permission}`); |
| 96 | assert.isTrue( |
| 97 | checkHandler(null, permission) as boolean, |
| 98 | `check handler should allow ${permission}`, |
| 99 | ); |
| 100 | } |
| 101 | |
| 102 | // `clipboard-write` is not a real Electron permission — the async write API |
| 103 | // uses `clipboard-sanitized-write` — so the stale name must not be granted, |
| 104 | // and unrelated permissions stay denied. |
| 105 | for (const permission of ["clipboard-write", "midi"]) { |
| 106 | assert.isFalse(requestAllows(permission), `request handler should deny ${permission}`); |
| 107 | assert.isFalse( |
| 108 | checkHandler(null, permission) as boolean, |
| 109 | `check handler should deny ${permission}`, |
| 110 | ); |
| 111 | } |
| 112 | }).pipe(Effect.provide(layer)), |
| 113 | ); |
| 114 | |
| 64 | 115 | it.effect("preserves partition scope and the platform failure chain", () => { |
| 65 | 116 | const nativeCause = new Error("native digest failed"); |
| 66 | 117 | const platformCause = PlatformError.systemError({ |