aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client/src/main.js')
-rw-r--r--packages/meshbay-client/src/main.js65
1 files changed, 54 insertions, 11 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index f97be8e..8e11f1f 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -25,6 +25,7 @@ const { app, BrowserWindow, dialog, ipcMain, protocol, safeStorage, shell } =
const crypto = require('node:crypto');
const fs = require('node:fs');
const fsp = require('node:fs/promises');
+const os = require('node:os');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
@@ -247,6 +248,10 @@ function publicKeyB64(privateKey) {
return der.subarray(der.length - 32).toString('base64');
}
+// Everything the interface is allowed to ask Chromium for. Watching a film
+// full-screen is the whole list.
+const GRANTED_PERMISSIONS = new Set(['fullscreen']);
+
// ── Window ──────────────────────────────────────────────────────────────────
let mainWindow = null;
@@ -297,9 +302,23 @@ function createWindow() {
if (/^https?:$/.test(new URL(url).protocol)) shell.openExternal(url);
return { action: 'deny' };
});
- // Nothing in this application needs a camera, a microphone or a location.
+ // Deny by default, with one exception, and the exception is the point of the
+ // comment. A blanket `callback(false)` here is what stopped a film going
+ // fullscreen: Chromium's own video controls ask for `fullscreen`, and a
+ // **denial does not reject** — `requestFullscreen()` returns a promise that
+ // never settles, so the button simply does nothing and there is no error
+ // anywhere to find. Measured, not deduced: the probe reported `NEVER SETTLED`
+ // and the main process logged `PERMISSION ASKED: fullscreen`.
+ //
+ // So: enumerate what is granted rather than what is refused. A camera, a
+ // microphone, a location, notifications and MIDI are all still refused, and
+ // anything Chromium adds later arrives refused rather than quietly allowed.
win.webContents.session.setPermissionRequestHandler(
- (_wc, _permission, callback) => callback(false));
+ (_wc, permission, callback) => callback(GRANTED_PERMISSIONS.has(permission)));
+ // `Permissions.query` takes the other handler; same answer, or the two can
+ // disagree about what the page is allowed to do.
+ win.webContents.session.setPermissionCheckHandler(
+ (_wc, permission) => GRANTED_PERMISSIONS.has(permission));
win.on('close', () => {
if (!win.isMinimized() && !win.isFullScreen()) {
@@ -496,12 +515,27 @@ function registerBridge() {
return config.downloadDir;
});
- ipcMain.handle('folder:get', () => {
+ // Where downloads land when nobody has chosen anywhere. A browser does not
+ // make you pick a folder before it will save a file, and neither should this
+ // — "save automatically" that opens a dialog is not automatic.
+ function defaultDownloadDir() {
+ try { return app.getPath('downloads'); } catch { return os.homedir(); }
+ }
+
+ function chosenDownloadDir() {
const dir = config.downloadDir;
// A folder that has been removed or unmounted is not a folder any more,
// and saying so beats failing on the first chunk of a download.
if (!dir) return null;
try { return fs.statSync(dir).isDirectory() ? dir : null; } catch { return null; }
+ }
+
+ ipcMain.handle('folder:get', () => {
+ const chosen = chosenDownloadDir();
+ // `name` is what the settings row already renders, for the browser's
+ // directory handle as much as for this. `isDefault` is how it knows not to
+ // offer "forget" for a folder nobody chose.
+ return { name: chosen || defaultDownloadDir(), isDefault: !chosen };
});
ipcMain.handle('folder:forget', () => {
@@ -512,22 +546,31 @@ function registerBridge() {
ipcMain.handle('save:begin', async (_e, suggestedName, opts) => {
const wanted = path.basename(String(suggestedName || 'download'));
- const remembered = config.downloadDir;
+ const chosen = chosenDownloadDir();
let target = null;
- // "Save automatically" means exactly that: no dialog, into the folder that
- // was chosen once.
- if (opts && opts.auto && remembered) {
+ // "Save automatically" means exactly that: no dialog. Into the chosen
+ // folder if there is one, otherwise the system's Downloads folder — the
+ // first version required a folder to have been picked first, so the very
+ // first automatic download opened a dialog, which is the one thing the
+ // setting says it will not do.
+ //
+ // The one case that still asks: a folder *was* chosen and has since gone.
+ // Redirecting those files somewhere else without saying so is worse than a
+ // dialog — someone who picked an external drive wants to know it is not
+ // there, not to find the film in their home directory a week later.
+ if (opts && opts.auto && !(config.downloadDir && !chosen)) {
+ const dir = chosen || defaultDownloadDir();
try {
- if (fs.statSync(remembered).isDirectory()) {
- target = path.join(remembered, freeName(remembered, wanted));
- }
+ fs.mkdirSync(dir, { recursive: true });
+ target = path.join(dir, freeName(dir, wanted));
} catch { target = null; }
}
if (!target) {
+ const dir = chosen || defaultDownloadDir();
const result = await dialog.showSaveDialog(mainWindow, {
- defaultPath: remembered ? path.join(remembered, wanted) : wanted,
+ defaultPath: path.join(dir, wanted),
});
if (result.canceled || !result.filePath) return null;
target = result.filePath;