Merge pull request #1429 from CREDO23/fix-desktop-redirects
[Fixes] Packaged desktop: connector redirect + linux launcher icon
|
|
@ -33,7 +33,7 @@ Map outcomes to your `status`:
|
||||||
- Any other `"Error: …"` → `status=error` and relay the tool's message verbatim as `next_step`.
|
- Any other `"Error: …"` → `status=error` and relay the tool's message verbatim as `next_step`.
|
||||||
- HITL rejection → `status=blocked` with `next_step="User declined this filesystem action. Do not retry."`.
|
- HITL rejection → `status=blocked` with `next_step="User declined this filesystem action. Do not retry."`.
|
||||||
|
|
||||||
You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. `chunk_ids` apply only to `<priority_documents>` hits; for local-file operations leave them `null`. Never report values you did not actually see.
|
You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. Never report values you did not actually see. (`chunk_ids` is always `null` in desktop mode — see "Chunk citations in your prose" below.)
|
||||||
|
|
||||||
## Chunk citations in your prose
|
## Chunk citations in your prose
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
# inside the desktop app. Set to your production frontend domain.
|
# inside the desktop app. Set to your production frontend domain.
|
||||||
HOSTED_FRONTEND_URL=https://surfsense.net
|
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 analytics (leave empty to disable)
|
||||||
POSTHOG_KEY=
|
POSTHOG_KEY=
|
||||||
POSTHOG_HOST=https://assets.surfsense.com
|
POSTHOG_HOST=https://assets.surfsense.com
|
||||||
|
|
|
||||||
BIN
surfsense_desktop/assets/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
surfsense_desktop/assets/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
surfsense_desktop/assets/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
surfsense_desktop/assets/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
surfsense_desktop/assets/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
surfsense_desktop/assets/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
surfsense_desktop/assets/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
surfsense_desktop/assets/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
|
|
@ -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."
|
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."
|
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."
|
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:
|
||||||
- target: dmg
|
- target: dmg
|
||||||
arch: [x64, arm64]
|
arch: [x64, arm64]
|
||||||
|
|
@ -72,7 +77,7 @@ nsis:
|
||||||
createDesktopShortcut: true
|
createDesktopShortcut: true
|
||||||
createStartMenuShortcut: true
|
createStartMenuShortcut: true
|
||||||
linux:
|
linux:
|
||||||
icon: assets/icon.png
|
icon: assets/icons/
|
||||||
category: Utility
|
category: Utility
|
||||||
artifactName: "${productName}-${version}-${arch}.${ext}"
|
artifactName: "${productName}-${version}-${arch}.${ext}"
|
||||||
mimeTypes:
|
mimeTypes:
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ export function setupDeepLinks(): boolean {
|
||||||
app.setAsDefaultProtocolClient(PROTOCOL);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ export async function startNextServer(): Promise<void> {
|
||||||
const serverScript = path.join(standalonePath, 'server.js');
|
const serverScript = path.join(standalonePath, 'server.js');
|
||||||
|
|
||||||
process.env.PORT = String(serverPort);
|
process.env.PORT = String(serverPort);
|
||||||
process.env.HOSTNAME = '0.0.0.0';
|
// Loopback bind: 0.0.0.0 leaks into request.url and flips window origin via NextResponse.redirect.
|
||||||
|
process.env.HOSTNAME = 'localhost';
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
process.chdir(standalonePath);
|
process.chdir(standalonePath);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,26 @@ import { getServerPort } from './server';
|
||||||
import { setActiveSearchSpaceId } from './active-search-space';
|
import { setActiveSearchSpaceId } from './active-search-space';
|
||||||
|
|
||||||
const isDev = !app.isPackaged;
|
const isDev = !app.isPackaged;
|
||||||
const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string;
|
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
|
|
||||||
|
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 mainWindow: BrowserWindow | null = null;
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
|
|
||||||
|
|
@ -58,11 +75,47 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow {
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|
||||||
const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] };
|
const hostedHosts = getHostedFrontendHosts();
|
||||||
session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
|
const rewriteFilter = {
|
||||||
const rewritten = details.url.replace(HOSTED_FRONTEND_URL, `http://localhost:${getServerPort()}`);
|
urls: hostedHosts.flatMap((h) => [`http://${h}/*`, `https://${h}/*`]),
|
||||||
callback({ redirectURL: rewritten });
|
};
|
||||||
});
|
if (rewriteFilter.urls.length > 0) {
|
||||||
|
session.defaultSession.webRequest.onBeforeRequest(rewriteFilter, (details, callback) => {
|
||||||
|
try {
|
||||||
|
const u = new URL(details.url);
|
||||||
|
const originalHost = u.host;
|
||||||
|
u.protocol = 'http:';
|
||||||
|
u.host = `localhost:${getServerPort()}`;
|
||||||
|
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) => {
|
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => {
|
||||||
console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`);
|
console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`);
|
||||||
|
|
|
||||||