pithos/desktop/main.cjs
Local Dev bd440b1d6f Pithos: open the app in a browser, also from the pithos.sia website
Pithos runs on the user's computer, so the pithos.sia website could only
describe it. Two ways in now:

- Open in browser (Theseus, desktop) opens the current page in the default
  browser with a one-time sign-in link (60 s, single use), unlike the
  host's reusable ?t= link.
- The website links to http://127.0.0.1:47621/open?path=/<user>/<drive>.
  Any page or local program could follow that link, so the host first
  asks the user in its own window (a native dialog on the desktop,
  Theseus's approval prompt with the vault unlocked). Only loopback Host
  names are accepted, one question at a time, and the website learns
  nothing either way.

Sessions started this way are marked external, so the browser does not
get host-only controls such as Back to sidebar.
2026-10-05 01:47:12 +02:00

101 lines
3.6 KiB
JavaScript

// Pithos desktop: an Electron window over the same control server the web
// console runs. The server binds a random loopback port and the window signs
// in with the one-time secret, so nothing else on the machine can drive it.
const { app, BrowserWindow, shell, Menu, dialog } = require('electron');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
let pithos;
let win;
// Taskbar pins and notifications group under Pithos, not Electron.
if (process.platform === 'win32') app.setAppUserModelId('st.silentmode.pithos');
if (!app.requestSingleInstanceLock()) {
app.quit();
} else {
app.on('second-instance', () => {
if (win) { if (win.isMinimized()) win.restore(); win.focus(); }
});
app.whenReady().then(start).catch((err) => {
dialog.showErrorBox('Pithos failed to start', String(err?.stack || err));
app.quit();
});
}
async function start() {
const { createPithos } = await import(pathToFileURL(path.join(__dirname, '..', 'core', 'server.js')).href);
pithos = await createPithos({
host: '127.0.0.1',
port: 0,
hostName: 'desktop',
configFile: process.env.S3D_CONFIG_FILE || undefined,
// The pinned s3d release installs here, outside the (read-only) app bundle.
binDir: path.join(app.getPath('userData'), 'bin'),
openExternal: (url) => shell.openExternal(url),
// The pithos.sia website's "Open my Pithos" lands here; the user decides.
confirmBrowserOpen: async () => {
if (!win) return false;
if (win.isMinimized()) win.restore();
win.show();
win.focus();
const r = await dialog.showMessageBox(win, {
type: 'question',
buttons: ['Open in browser', 'Cancel'],
defaultId: 1,
cancelId: 1,
title: 'Open Pithos in your browser?',
message: 'Open Pithos in your browser?',
detail: 'A browser on this computer asked to open Pithos, usually from "Open my Pithos" on the pithos.sia website. Allow it only if you just clicked that. That browser then has the same access as this window until you quit Pithos.',
});
return r.response === 0;
},
});
Menu.setApplicationMenu(process.platform === 'darwin' ? Menu.buildFromTemplate([
{ role: 'appMenu' }, { role: 'editMenu' }, { role: 'viewMenu' }, { role: 'windowMenu' },
]) : null);
win = new BrowserWindow({
width: 1180,
height: 800,
minWidth: 380,
minHeight: 500,
title: 'Pithos',
backgroundColor: '#0f1214',
autoHideMenuBar: true,
webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true },
});
const origin = new URL(pithos.url).origin;
// Keep the window on the console; anything else goes to the system browser.
win.webContents.on('will-navigate', (e, url) => {
if (new URL(url).origin !== origin) {
e.preventDefault();
if (url.startsWith('https://')) shell.openExternal(url);
}
});
win.webContents.setWindowOpenHandler(({ url }) => {
// HTML files opened as pages go to the system browser: the link carries
// its own ticket, and the page is sandboxed by the server.
const u = new URL(url);
if (u.origin === origin && u.pathname.startsWith('/page/')) shell.openExternal(url);
else if (url.startsWith('https://')) shell.openExternal(url);
return { action: 'deny' };
});
win.loadURL(pithos.authUrl);
win.on('closed', () => { win = null; });
}
let quitting = false;
app.on('before-quit', (e) => {
if (quitting || !pithos) return;
// Stop s3d cleanly so its upload buffer and database are flushed to disk.
e.preventDefault();
quitting = true;
pithos.close().finally(() => app.quit());
});
app.on('window-all-closed', () => app.quit());