feat(web): prepare the fluxer.com domain migration (#2952)

This commit is contained in:
Hampus
2026-09-25 13:43:34 +02:00
committed by GitHub
parent e62ae77643
commit 6730a242db
223 changed files with 13412 additions and 2669 deletions
+3
View File
@@ -3,6 +3,9 @@
export const APP_PROTOCOL = 'fluxer';
export const STABLE_APP_URL = 'https://web.fluxer.app';
export const CANARY_APP_URL = 'https://web.canary.fluxer.app';
export const STABLE_MIGRATED_APP_ORIGIN = 'https://fluxer.com';
export const CANARY_MIGRATED_APP_ORIGIN = 'https://canary.fluxer.com';
export const MIGRATED_APP_ENTRY_PATH = '/app';
export const STATIC_CDN_URL = 'https://fluxerstatic.com';
export const DEFAULT_WINDOW_WIDTH = 1280;
export const DEFAULT_WINDOW_HEIGHT = 800;
+53 -2
View File
@@ -3,7 +3,13 @@
import fs from 'node:fs';
import path from 'node:path';
import {BUILD_CHANNEL} from '@electron/common/BuildChannel';
import {CANARY_APP_URL, STABLE_APP_URL} from '@electron/common/Constants';
import {
CANARY_APP_URL,
CANARY_MIGRATED_APP_ORIGIN,
MIGRATED_APP_ENTRY_PATH,
STABLE_APP_URL,
STABLE_MIGRATED_APP_ORIGIN,
} from '@electron/common/Constants';
import type {DesktopTroubleshootingSettings, DesktopWindowBehaviorSettings} from '@electron/common/Types';
import log from 'electron-log';
@@ -18,6 +24,7 @@ interface DesktopConfig extends Record<string, unknown> {
window_behavior?: PersistedDesktopWindowBehaviorSettings;
troubleshooting?: PersistedDesktopTroubleshootingSettings;
theme_allowed_local_files?: Array<string>;
app_origin?: string;
}
export type ChromiumSwitchesSetting = ReadonlyArray<string> | Record<string, unknown>;
@@ -133,12 +140,34 @@ function sanitizeChromiumSwitchesSetting(value: unknown): ChromiumSwitchesSettin
return undefined;
}
function getLegacyAppUrl(): string {
return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL;
}
function getMigratedAppOrigin(): string {
return BUILD_CHANNEL === 'canary' ? CANARY_MIGRATED_APP_ORIGIN : STABLE_MIGRATED_APP_ORIGIN;
}
export function getOfficialAppOrigins(): Array<string> {
return [new URL(getLegacyAppUrl()).origin, getMigratedAppOrigin()];
}
function sanitizeAppOrigin(value: unknown): string | undefined {
return typeof value === 'string' && getOfficialAppOrigins().includes(value) ? value : undefined;
}
function sanitizeDesktopConfig(value: unknown): DesktopConfig {
if (!isRecord(value)) {
return {};
}
const nextConfig: DesktopConfig = {...value};
delete nextConfig.app_url;
const appOrigin = sanitizeAppOrigin(value.app_origin);
if (appOrigin) {
nextConfig.app_origin = appOrigin;
} else {
delete nextConfig.app_origin;
}
const chromiumSwitches = sanitizeChromiumSwitchesSetting(value.chromiumSwitches);
if (chromiumSwitches) {
nextConfig.chromiumSwitches = chromiumSwitches;
@@ -311,7 +340,29 @@ export function getAppUrl(): string {
if (runtimeAppUrlOverride) {
return runtimeAppUrlOverride;
}
return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL;
const migratedAppOrigin = getMigratedAppOrigin();
if (config.app_origin === migratedAppOrigin) {
return `${migratedAppOrigin}${MIGRATED_APP_ENTRY_PATH}`;
}
return getLegacyAppUrl();
}
export function getAppUrlFallback(url: string): string | null {
try {
return new URL(url).origin === getMigratedAppOrigin() ? getLegacyAppUrl() : null;
} catch {
return null;
}
}
export function setAppOrigin(origin: string): boolean {
const appOrigin = sanitizeAppOrigin(origin);
if (appOrigin === undefined) {
return false;
}
config.app_origin = appOrigin;
saveDesktopConfig();
return true;
}
export function getCustomAppUrl(): string | null {
+6
View File
@@ -627,6 +627,11 @@ export type TrayActionPayload =
action: 'check-for-updates';
};
export interface DomainMigrationApi {
version: 1;
setAppOrigin: (origin: string) => Promise<void>;
}
export interface ElectronAPI {
platform: NodeJS.Platform;
buildChannel: 'stable' | 'canary';
@@ -759,6 +764,7 @@ export interface ElectronAPI {
passkeyIsSupported: () => Promise<boolean>;
passkeyAuthenticate: (options: PublicKeyCredentialRequestOptionsJSON) => Promise<AuthenticationResponseJSON>;
passkeyRegister: (options: PublicKeyCredentialCreationOptionsJSON) => Promise<RegistrationResponseJSON>;
domainMigration: DomainMigrationApi;
virtmic: VirtmicApi;
nativeAudio: NativeAudioApi;
nativeScreenCapture: NativeScreenCaptureApi;
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {createRequire} from 'node:module';
import os from 'node:os';
import path from 'node:path';
import {describe, test} from 'node:test';
import {fileURLToPath} from 'node:url';
import vm from 'node:vm';
const require = createRequire(import.meta.url);
const esbuild = require('esbuild');
function transform(relativePath) {
const sourcePath = fileURLToPath(new URL(relativePath, import.meta.url));
const code = esbuild.transformSync(fs.readFileSync(sourcePath, 'utf8'), {
loader: 'ts',
format: 'cjs',
platform: 'node',
target: 'node20',
}).code;
return {sourcePath, code};
}
const constantsSource = transform('../common/Constants.ts');
const desktopConfigSource = transform('../common/DesktopConfig.ts');
const domainMigrationSource = transform('./DomainMigration.ts');
const silentLog = {debug() {}, info() {}, warn() {}, error() {}};
function runModule({sourcePath, code}, requireStub) {
const module = {exports: {}};
const context = vm.createContext({
require: requireStub,
module,
exports: module.exports,
process,
console,
URL,
JSON,
});
vm.runInContext(code, context, {filename: sourcePath});
return module.exports;
}
function loadDesktop({channel = 'stable', settings} = {}) {
const userDataPath = fs.mkdtempSync(path.join(os.tmpdir(), 'fluxer-domain-migration-test-'));
const settingsPath = path.join(userDataPath, 'settings.json');
if (settings !== undefined) {
fs.writeFileSync(settingsPath, JSON.stringify(settings), 'utf-8');
}
const constants = runModule(constantsSource, (specifier) => {
throw new Error(`Unexpected import: ${specifier}`);
});
const desktopConfig = runModule(desktopConfigSource, (specifier) => {
if (specifier === 'node:fs') return fs;
if (specifier === 'node:path') return path;
if (specifier === '@electron/common/BuildChannel') return {BUILD_CHANNEL: channel};
if (specifier === '@electron/common/Constants') return constants;
if (specifier === 'electron-log') return silentLog;
throw new Error(`Unexpected import: ${specifier}`);
});
desktopConfig.loadDesktopConfig(userDataPath);
const handlers = new Map();
runModule(domainMigrationSource, (specifier) => {
if (specifier === '@electron/common/DesktopConfig') return desktopConfig;
if (specifier === '@electron/common/Logger') return {createChildLogger: () => silentLog};
if (specifier === 'electron') {
return {
ipcMain: {
handle: (name, handler) => {
handlers.set(name, handler);
},
},
};
}
throw new Error(`Unexpected import: ${specifier}`);
}).registerDomainMigrationHandlers();
const readSettings = () => JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
const setAppOrigin = (frame, origin) => handlers.get('domain-migration:set-app-origin')({senderFrame: frame}, origin);
return {desktopConfig, readSettings, setAppOrigin, settingsPath};
}
function topLevelFrame(url) {
return {url, detached: false, parent: null};
}
describe('DesktopConfig app origin', () => {
test('keeps loading the legacy root when no app origin is stored', () => {
assert.equal(loadDesktop().desktopConfig.getAppUrl(), 'https://web.fluxer.app');
assert.equal(loadDesktop({channel: 'canary'}).desktopConfig.getAppUrl(), 'https://web.canary.fluxer.app');
});
test('loads the app entry path for a stored migrated origin', () => {
const stable = loadDesktop({settings: {app_origin: 'https://fluxer.com'}});
const canary = loadDesktop({channel: 'canary', settings: {app_origin: 'https://canary.fluxer.com'}});
assert.equal(stable.desktopConfig.getAppUrl(), 'https://fluxer.com/app');
assert.equal(canary.desktopConfig.getAppUrl(), 'https://canary.fluxer.com/app');
});
test('loads the legacy root for a stored legacy origin', () => {
const {desktopConfig} = loadDesktop({settings: {app_origin: 'https://web.fluxer.app'}});
assert.equal(desktopConfig.getAppUrl(), 'https://web.fluxer.app');
});
test('drops stored origins outside the channel allowlist', () => {
for (const appOrigin of [
'https://canary.fluxer.com',
'https://fluxer.com/',
'https://evil.example',
'http://fluxer.com',
42,
]) {
const {desktopConfig} = loadDesktop({settings: {app_origin: appOrigin}});
assert.equal(desktopConfig.getAppUrl(), 'https://web.fluxer.app');
}
});
test('falls back from the migrated origin to the legacy root', () => {
const stable = loadDesktop({settings: {app_origin: 'https://fluxer.com'}}).desktopConfig;
const canary = loadDesktop({channel: 'canary', settings: {app_origin: 'https://canary.fluxer.com'}}).desktopConfig;
assert.equal(stable.getAppUrlFallback('https://fluxer.com/app'), 'https://web.fluxer.app');
assert.equal(stable.getAppUrlFallback('https://fluxer.com/channels/@me'), 'https://web.fluxer.app');
assert.equal(canary.getAppUrlFallback('https://canary.fluxer.com/app'), 'https://web.canary.fluxer.app');
assert.equal(stable.getAppUrlFallback('https://web.fluxer.app/channels/@me'), null);
assert.equal(stable.getAppUrlFallback('https://canary.fluxer.com/app'), null);
assert.equal(stable.getAppUrlFallback('not a url'), null);
});
test('the runtime override still wins over a stored origin', () => {
const {desktopConfig} = loadDesktop({settings: {app_origin: 'https://fluxer.com'}});
desktopConfig.setRuntimeAppUrlOverride('http://localhost:8088/');
assert.equal(desktopConfig.getAppUrl(), 'http://localhost:8088/');
});
});
describe('DomainMigration set app origin IPC', () => {
test('persists an allowlisted origin from an official top-level frame', () => {
const {desktopConfig, readSettings, setAppOrigin} = loadDesktop();
setAppOrigin(topLevelFrame('https://fluxer.com/migrate/complete'), 'https://fluxer.com');
assert.equal(readSettings().app_origin, 'https://fluxer.com');
assert.equal(desktopConfig.getAppUrl(), 'https://fluxer.com/app');
});
test('accepts the legacy frame of the running channel', () => {
const {readSettings, setAppOrigin} = loadDesktop({channel: 'canary'});
setAppOrigin(topLevelFrame('https://web.canary.fluxer.app/channels/@me'), 'https://canary.fluxer.com');
assert.equal(readSettings().app_origin, 'https://canary.fluxer.com');
});
test('rejects senders that are not official top-level frames of the channel', () => {
const {setAppOrigin, settingsPath} = loadDesktop();
const rejectedFrames = [
null,
topLevelFrame('https://evil.example/'),
topLevelFrame('https://canary.fluxer.com/app'),
{url: 'https://fluxer.com/app', detached: true, parent: null},
{url: 'https://fluxer.com/app', detached: false, parent: topLevelFrame('https://fluxer.com/app')},
topLevelFrame('not a url'),
];
for (const frame of rejectedFrames) {
assert.throws(() => setAppOrigin(frame, 'https://fluxer.com'), /official app document/);
}
assert.equal(fs.existsSync(settingsPath), false);
});
test('rejects origins outside the channel allowlist', () => {
const {setAppOrigin, settingsPath} = loadDesktop();
for (const origin of ['https://canary.fluxer.com', 'https://fluxer.com/app', 'https://evil.example', null]) {
assert.throws(() => setAppOrigin(topLevelFrame('https://fluxer.com/app'), origin), /outside the allowlist/);
}
assert.equal(fs.existsSync(settingsPath), false);
});
});
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getOfficialAppOrigins, setAppOrigin} from '@electron/common/DesktopConfig';
import {createChildLogger} from '@electron/common/Logger';
import {type IpcMainInvokeEvent, ipcMain} from 'electron';
const logger = createChildLogger('DomainMigration');
const DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL = 'domain-migration:set-app-origin';
function getTopLevelSenderOrigin(event: IpcMainInvokeEvent): string | null {
const frame = event.senderFrame;
if (frame == null) {
return null;
}
try {
if (frame.detached || frame.parent != null) {
return null;
}
return new URL(frame.url).origin;
} catch {
return null;
}
}
function handleSetAppOrigin(event: IpcMainInvokeEvent, origin: unknown): void {
const officialAppOrigins = getOfficialAppOrigins();
const senderOrigin = getTopLevelSenderOrigin(event);
if (senderOrigin === null || !officialAppOrigins.includes(senderOrigin)) {
throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} is only reachable from an official app document`);
}
if (typeof origin !== 'string' || !officialAppOrigins.includes(origin)) {
throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} received an origin outside the allowlist`);
}
if (!setAppOrigin(origin)) {
throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} could not store the app origin`);
}
logger.info('Stored app origin', {origin, senderOrigin});
}
export function registerDomainMigrationHandlers(): void {
ipcMain.handle(DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL, handleSetAppOrigin);
}
+2
View File
@@ -20,6 +20,7 @@ import {
hasActiveDesktopTray,
updateTrayRuntimeState,
} from '@electron/main/DesktopTray';
import {registerDomainMigrationHandlers} from '@electron/main/DomainMigration';
import {DownloadChecksumError, downloadFile} from '@electron/main/FileDownloads';
import {
type LinuxAppearanceSnapshot,
@@ -136,6 +137,7 @@ function getActiveMiddleClickAutoscroll(): boolean {
export function registerIpcHandlers(): void {
registerVoiceDebugEventSinkPopoutIpcHandlers();
registerVoiceBackgroundMediaCacheHandlers();
registerDomainMigrationHandlers();
ipcMain.handle('get-desktop-info', () => getDesktopInfo());
ipcMain.handle('get-gpu-info', () => getGpuInfo());
ipcMain.handle('get-app-metrics', () => getAppMetricsSnapshot());
+31 -3
View File
@@ -5,13 +5,20 @@ import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {
CANARY_APP_URL,
CANARY_MIGRATED_APP_ORIGIN,
DEFAULT_WINDOW_HEIGHT,
DEFAULT_WINDOW_WIDTH,
MIN_WINDOW_HEIGHT,
MIN_WINDOW_WIDTH,
STABLE_APP_URL,
STABLE_MIGRATED_APP_ORIGIN,
} from '@electron/common/Constants';
import {getAppUrl, getCustomAppUrl, getDesktopWindowBehaviorSettings} from '@electron/common/DesktopConfig';
import {
getAppUrl,
getAppUrlFallback,
getCustomAppUrl,
getDesktopWindowBehaviorSettings,
} from '@electron/common/DesktopConfig';
import {createChildLogger} from '@electron/common/Logger';
import type {DesktopWindowBehaviorSettings} from '@electron/common/Types';
import {
@@ -60,7 +67,7 @@ const CUSTOM_TITLEBAR_TRAFFIC_LIGHT_POSITION = {
y: Math.round((CUSTOM_TITLEBAR_HEIGHT_MAC - CUSTOM_TITLEBAR_TRAFFIC_LIGHT_DIAMETER) / 2),
};
const trustedWebOrigins = new Set(
[STABLE_APP_URL, CANARY_APP_URL]
[STABLE_APP_URL, CANARY_APP_URL, STABLE_MIGRATED_APP_ORIGIN, CANARY_MIGRATED_APP_ORIGIN]
.map((url) => {
try {
return new URL(url).origin;
@@ -726,7 +733,7 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow {
const acceptFirstMouseOnFocus = isMac;
initialUseNativeTitleBar = useNativeTitleBar;
initialAllowTransparency = allowTransparency;
const appUrl = getAppUrl();
let appUrl = getAppUrl();
const windowOptions: Electron.BrowserWindowConstructorOptions = {
width: windowWidth,
height: windowHeight,
@@ -998,7 +1005,25 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow {
});
}, delay);
};
const fallBackFromMigratedAppOrigin = (failedUrl: string, detail: Record<string, unknown>): boolean => {
const fallbackUrl = getAppUrlFallback(failedUrl);
if (!mainWindow || mainWindow.isDestroyed() || fallbackUrl === null || appUrl === fallbackUrl) {
return false;
}
logger.warn('Migrated app origin failed to load, falling back to the legacy app URL', {failedUrl, ...detail});
appUrl = fallbackUrl;
clearAppLoadRetry();
mainWindow.loadURL(appUrl).catch((error) => {
scheduleAppLoadRetry('legacy-fallback-load-url-rejected', {error});
});
return true;
};
webContents.on('did-finish-load', clearAppLoadRetry);
webContents.on('did-navigate', (_event, url, httpResponseCode) => {
if (httpResponseCode >= 400) {
fallBackFromMigratedAppOrigin(url, {httpResponseCode});
}
});
webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
if (isMainFrame) {
logger.error('App main-frame load failed', {errorCode, errorDescription, validatedURL});
@@ -1006,6 +1031,9 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow {
if (!isMainFrame || !isTrustedOrigin(validatedURL) || !shouldRetryAppLoadFailure(errorCode)) {
return;
}
if (fallBackFromMigratedAppOrigin(validatedURL, {errorCode, errorDescription})) {
return;
}
scheduleAppLoadRetry('did-fail-load', {errorCode, errorDescription, validatedURL});
});
const loadAppUrl = (): void => {
+4
View File
@@ -451,6 +451,10 @@ const api: ElectronAPI = {
options: PublicKeyCredentialCreationOptionsJSON,
requestContext?: {pin?: string},
): Promise<RegistrationResponseJSON> => ipcRenderer.invoke('passkey-register', options, requestContext),
domainMigration: {
version: 1,
setAppOrigin: (origin: string): Promise<void> => ipcRenderer.invoke('domain-migration:set-app-origin', origin),
},
toggleDevTools: (): void => {
ipcRenderer.send('toggle-devtools');
},