mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* docs(plugins): add microsoft 365 calendar provider plan * docs(plugins): add microsoft 365 calendar provider plan * docs(ios): plan internal testflight builds * fix(electron): assert renderer IPC boundary at window creation Every IPC trust boundary (Jira one-shot capability, plugin node-exec consent, the window.ea preload bridge) rests on the renderer main world not having require/ipcRenderer, which is guaranteed solely by contextIsolation: true + nodeIntegration: false and sub-frames not getting node integration. If that webPreferences ever silently regressed (a refactor spreading a shared options object, a bad merge), every gate would collapse at once while still looking correct in review. Add web-preferences-guard.ts (assertSecureWebPreferences) and fail closed before creating a window if the boundary is not intact. It rejects a non-true contextIsolation, a non-false nodeIntegration, and (fail-closed) a nodeIntegrationInSubFrames that is not explicitly false; it also directionally rejects an explicit sandbox: false, nodeIntegrationInWorker: true, and webviewTag: true (each off by default, so no call site is forced to set it). Wire it at all three new BrowserWindow sites (main window, task widget, full-screen blocker); the full-screen blocker previously relied on Electron defaults, so set its webPreferences explicitly. A *.test.cjs backs it with behavioral coverage plus a wiring guard that counts constructor sites vs guard calls per file, so a future window cannot silently ship without the check. Closes #9015 * fix(electron): extend webPreferences guard to webSecurity Follow-up hardening from the multi-agent review of #9018: - Reject an explicit `webSecurity: false` (directional, like the sandbox /worker/webviewTag trio). With the app's blanket Access-Control-Allow-Origin: *, disabling the same-origin policy in a node-bridged renderer would widen cross-origin reach — and no call site currently guards against it. - Broaden the wiring-guard test to also require the assert for `new BrowserView` / `new WebContentsView`, closing the tripwire's blind spot for future non-BrowserWindow renderers (none exist today). - Correct the fail() comment: the `throw` narrows the type regardless of return-vs-throw; fail() returns an Error only to DRY the message. 230/230 electron tests pass; checkFile + prettier clean.
85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron';
|
|
import { IPC } from './shared-with-frontend/ipc-events.const';
|
|
import { TakeABreakConfig } from '../src/app/features/config/global-config.model';
|
|
import { join, normalize } from 'path';
|
|
import { assertSecureWebPreferences } from './web-preferences-guard';
|
|
|
|
export const initFullScreenBlocker = (IS_DEV: boolean): void => {
|
|
let isFullScreenWindowOpen = false;
|
|
ipcMain.on(
|
|
IPC.FULL_SCREEN_BLOCKER,
|
|
(
|
|
ipcEvent,
|
|
{ msg, takeABreakCfg }: { msg: string; takeABreakCfg: TakeABreakConfig },
|
|
): void => {
|
|
if (isFullScreenWindowOpen) {
|
|
return;
|
|
}
|
|
let isClosable = false;
|
|
// This overlay loads a local file with no preload bridge, so it was
|
|
// relying on Electron's secure defaults. Set the boundary explicitly and
|
|
// assert it, so a future Electron default change can't silently open it.
|
|
const webPreferences: BrowserWindowConstructorOptions['webPreferences'] = {
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
nodeIntegrationInSubFrames: false,
|
|
};
|
|
assertSecureWebPreferences(webPreferences, 'full-screen-blocker');
|
|
const win = new BrowserWindow({
|
|
title: msg,
|
|
fullscreen: true,
|
|
alwaysOnTop: true,
|
|
transparent: true,
|
|
skipTaskbar: true,
|
|
frame: false,
|
|
webPreferences,
|
|
});
|
|
const randomImgUrl = takeABreakCfg.motivationalImgs?.length
|
|
? takeABreakCfg.motivationalImgs[
|
|
Math.floor(Math.random() * takeABreakCfg.motivationalImgs.length)
|
|
]
|
|
: '';
|
|
|
|
win.setAlwaysOnTop(true, 'floating');
|
|
win.setVisibleOnAllWorkspaces(true);
|
|
win.setFullScreenable(false);
|
|
isFullScreenWindowOpen = true;
|
|
win.loadURL(
|
|
`file://${normalize(
|
|
join(
|
|
__dirname,
|
|
IS_DEV
|
|
? '../src/static/break-reminder-overlay.html'
|
|
: '../dist/static/break-reminder-overlay.html',
|
|
),
|
|
)}` +
|
|
`#msg=${encodeURIComponent(msg)}&img=${encodeURIComponent(randomImgUrl ?? '')}&time=${
|
|
takeABreakCfg.timedFullScreenBlockerDuration
|
|
}`,
|
|
);
|
|
const closeTimeout = setTimeout(() => {
|
|
isClosable = true;
|
|
win.close();
|
|
}, takeABreakCfg.timedFullScreenBlockerDuration || 5000);
|
|
|
|
win.on('close', (evI) => {
|
|
if (isClosable) {
|
|
if (closeTimeout) {
|
|
clearTimeout(closeTimeout);
|
|
}
|
|
isFullScreenWindowOpen = false;
|
|
} else {
|
|
evI.preventDefault();
|
|
}
|
|
});
|
|
|
|
win.on('closed', () => {
|
|
// Clean up references
|
|
isFullScreenWindowOpen = false;
|
|
if (closeTimeout) {
|
|
clearTimeout(closeTimeout);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
};
|