Merge remote-tracking branch 'upstream/dev' into fix/electron-nextjs

This commit is contained in:
Anish Sarkar 2026-05-25 18:01:06 +05:30
commit 96da8498e6
27 changed files with 362 additions and 191 deletions

View file

@ -5,6 +5,11 @@
# inside the desktop app. Set to your production frontend domain.
HOSTED_FRONTEND_URL=https://surfsense.net
# Runtime override for the above (read at app start, no rebuild required).
# Useful for self-hosters whose backend NEXT_FRONTEND_URL differs from the
# value baked into the official desktop builds. Leave empty to use HOSTED_FRONTEND_URL.
# SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE=
# PostHog analytics (leave empty to disable)
POSTHOG_KEY=
POSTHOG_HOST=https://assets.surfsense.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -55,6 +55,11 @@ mac:
NSAccessibilityUsageDescription: "SurfSense uses accessibility features to bring the app to the foreground and interact with the active application when you use desktop assists."
NSScreenCaptureUsageDescription: "SurfSense uses screen capture so you can attach a selected region to chat (Screenshot Assist) or capture the full screen from the composer."
NSAppleEventsUsageDescription: "SurfSense uses Apple Events to interact with the active application."
# `surfsense://` scheme — install-time registration for LaunchServices.
CFBundleURLTypes:
- CFBundleURLName: com.surfsense.desktop
CFBundleURLSchemes:
- surfsense
target:
- target: dmg
arch: [x64, arm64]
@ -72,7 +77,7 @@ nsis:
createDesktopShortcut: true
createStartMenuShortcut: true
linux:
icon: assets/icon.png
icon: assets/icons/
category: Utility
artifactName: "${productName}-${version}-${arch}.${ext}"
mimeTypes:

View file

@ -60,6 +60,11 @@ export function setupDeepLinks(): boolean {
app.setAsDefaultProtocolClient(PROTOCOL);
}
// Cold-start on Windows/Linux: protocol URL arrives via argv of the
// first instance, not via `second-instance` or `open-url`.
const cold = process.argv.find((arg) => arg.startsWith(`${PROTOCOL}://`));
if (cold) handleDeepLink(cold);
return true;
}

View file

@ -49,6 +49,7 @@ export async function startNextServer(): Promise<void> {
env: {
...process.env,
PORT: String(serverPort),
// Loopback bind: avoids 0.0.0.0 leaking into request.url and redirect origins.
HOSTNAME: SERVER_HOST,
NODE_ENV: 'production',
},

View file

@ -2,14 +2,31 @@ import { app, BrowserWindow, shell, session } from 'electron';
import path from 'path';
import { trackEvent } from './analytics';
import { showErrorDialog } from './errors';
import { getServerOrigin } from './server';
import { getServerOrigin, getServerPort } from './server';
import { setActiveSearchSpaceId } from './active-search-space';
const isDev = !app.isPackaged;
const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string;
const isMac = process.platform === 'darwin';
const WINDOW_TITLE = 'SurfSense';
function getHostedFrontendUrl(): string {
return (
process.env.SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE ||
process.env.HOSTED_FRONTEND_URL ||
'https://surfsense.net'
);
}
function getHostedFrontendHosts(): string[] {
try {
const host = new URL(getHostedFrontendUrl()).host;
const sibling = host.startsWith('www.') ? host.slice(4) : `www.${host}`;
return Array.from(new Set([host, sibling]));
} catch {
return [];
}
}
let mainWindow: BrowserWindow | null = null;
let isQuitting = false;
@ -68,11 +85,48 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow {
return { action: 'deny' };
});
const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] };
session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
const rewritten = details.url.replace(HOSTED_FRONTEND_URL, getServerOrigin());
callback({ redirectURL: rewritten });
});
const hostedHosts = getHostedFrontendHosts();
const rewriteFilter = {
urls: hostedHosts.flatMap((h) => [`http://${h}/*`, `https://${h}/*`]),
};
if (rewriteFilter.urls.length > 0) {
session.defaultSession.webRequest.onBeforeRequest(rewriteFilter, (details, callback) => {
try {
const u = new URL(details.url);
const originalHost = u.host;
const local = new URL(getServerOrigin());
u.protocol = local.protocol;
u.host = local.host;
trackEvent('desktop_oauth_redirect_intercepted', {
host: originalHost,
path: u.pathname,
rewritten_to_port: getServerPort(),
});
callback({ redirectURL: u.toString() });
} catch {
callback({});
}
});
}
// Diagnostic: connector callback landing somewhere other than localhost
// means the rewrite missed and the user is stranded off-app.
session.defaultSession.webRequest.onCompleted(
{ urls: ['*://*/dashboard/*/connectors/callback*'] },
(details) => {
try {
const u = new URL(details.url);
if (u.hostname === 'localhost' || u.hostname === '127.0.0.1') return;
trackEvent('desktop_oauth_redirect_missed', {
host: u.host,
path: u.pathname,
status_code: details.statusCode,
});
} catch {
// ignore malformed URLs
}
}
);
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => {
console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`);