mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
feat(app): redesign discovery and align skeletons with rendered UI
This commit is contained in:
1 parent
e054aa96be
commit
98c40c6979
380 files changed
+50869
-32207
No files matched your search
@@ -14,7 +14,6 @@ const MINIMIZE_TO_TRAY_STORAGE_KEY_V2 = 'minimizeToTrayV2';
|
||||
const CLOSE_TO_TRAY_STORAGE_KEY_V2 = 'closeToTrayV2';
|
||||
|
||||
interface DesktopConfig extends Record<string, unknown> {
|
||||
app_url?: string;
|
||||
chromiumSwitches?: ChromiumSwitchesSetting;
|
||||
window_behavior?: PersistedDesktopWindowBehaviorSettings;
|
||||
troubleshooting?: PersistedDesktopTroubleshootingSettings;
|
||||
@@ -144,11 +143,7 @@ function sanitizeDesktopConfig(value: unknown): DesktopConfig {
|
||||
return {};
|
||||
}
|
||||
const nextConfig: DesktopConfig = {...value};
|
||||
if (typeof value.app_url === 'string') {
|
||||
nextConfig.app_url = value.app_url;
|
||||
} else {
|
||||
delete nextConfig.app_url;
|
||||
}
|
||||
delete nextConfig.app_url;
|
||||
const chromiumSwitches = sanitizeChromiumSwitchesSetting(value.chromiumSwitches);
|
||||
if (chromiumSwitches) {
|
||||
nextConfig.chromiumSwitches = chromiumSwitches;
|
||||
@@ -300,7 +295,7 @@ function saveDesktopConfig(): void {
|
||||
try {
|
||||
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||
fs.renameSync(tempPath, configPath);
|
||||
log.debug('Saved desktop config to', configPath, {app_url: config.app_url ?? '(default)'});
|
||||
log.debug('Saved desktop config to', configPath);
|
||||
} catch (error) {
|
||||
log.error('Failed to save desktop config:', error);
|
||||
try {
|
||||
@@ -315,7 +310,7 @@ export function loadDesktopConfig(userDataPath: string): void {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const data = fs.readFileSync(configPath, 'utf-8');
|
||||
config = sanitizeDesktopConfig(JSON.parse(data));
|
||||
log.info('Loaded desktop config from', configPath, {app_url: config.app_url ?? '(default)'});
|
||||
log.info('Loaded desktop config from', configPath);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Failed to load desktop config:', error);
|
||||
@@ -326,29 +321,17 @@ export function getAppUrl(): string {
|
||||
if (runtimeAppUrlOverride) {
|
||||
return runtimeAppUrlOverride;
|
||||
}
|
||||
if (config.app_url) {
|
||||
return config.app_url;
|
||||
}
|
||||
return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL;
|
||||
}
|
||||
|
||||
export function getCustomAppUrl(): string | null {
|
||||
return runtimeAppUrlOverride ?? config.app_url ?? null;
|
||||
return runtimeAppUrlOverride;
|
||||
}
|
||||
|
||||
export function setRuntimeAppUrlOverride(appUrl: string | null): void {
|
||||
runtimeAppUrlOverride = appUrl;
|
||||
}
|
||||
|
||||
export function setCustomAppUrl(appUrl: string | null): void {
|
||||
if (appUrl) {
|
||||
config.app_url = appUrl;
|
||||
} else {
|
||||
delete config.app_url;
|
||||
}
|
||||
saveDesktopConfig();
|
||||
}
|
||||
|
||||
export function getConfiguredChromiumSwitches(): ChromiumSwitchesSetting | undefined {
|
||||
return sanitizeChromiumSwitchesSetting(config.chromiumSwitches);
|
||||
}
|
||||
|
||||
@@ -251,11 +251,6 @@ export interface DownloadFileResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface SwitchInstanceUrlOptions {
|
||||
instanceUrl: string;
|
||||
desktopHandoffCode?: string | null;
|
||||
}
|
||||
|
||||
export type MediaAccessType = 'microphone' | 'camera' | 'screen' | 'audio-capture';
|
||||
export type MediaAccessStatus = 'granted' | 'denied' | 'not-determined' | 'restricted' | 'unknown';
|
||||
export type InputMonitoringPermissionStatus = 'granted' | 'denied' | 'not-determined' | 'unsupported';
|
||||
@@ -780,8 +775,6 @@ export interface ElectronAPI {
|
||||
passkeyIsSupported: () => Promise<boolean>;
|
||||
passkeyAuthenticate: (options: PublicKeyCredentialRequestOptionsJSON) => Promise<AuthenticationResponseJSON>;
|
||||
passkeyRegister: (options: PublicKeyCredentialCreationOptionsJSON) => Promise<RegistrationResponseJSON>;
|
||||
switchInstanceUrl: (options: SwitchInstanceUrlOptions) => Promise<void>;
|
||||
consumeDesktopHandoffCode: () => Promise<string | null>;
|
||||
virtmic: VirtmicApi;
|
||||
nativeAudio: NativeAudioApi;
|
||||
nativeScreenCapture: NativeScreenCaptureApi;
|
||||
|
||||
@@ -5,14 +5,12 @@ import {
|
||||
type DesktopWindowBehaviorSettings,
|
||||
getDesktopTroubleshootingSettings,
|
||||
getDesktopWindowBehaviorSettings,
|
||||
setCustomAppUrl,
|
||||
setDesktopWindowBehaviorSettings,
|
||||
} from '@electron/common/DesktopConfig';
|
||||
import type {
|
||||
ClipboardWriteFileResult,
|
||||
DownloadFileResult,
|
||||
MediaAccessType,
|
||||
SwitchInstanceUrlOptions,
|
||||
TrayPresenceStatus,
|
||||
} from '@electron/common/Types';
|
||||
import {hasEnabledBlinkFeature, MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE} from '@electron/main/ChromiumRuntime';
|
||||
@@ -92,61 +90,10 @@ interface TrayRuntimeStateUpdate {
|
||||
buildInfo?: string | null;
|
||||
}
|
||||
|
||||
let pendingDesktopHandoffCode: string | null = null;
|
||||
|
||||
function normalizeInstanceOrigin(rawUrl: string): string {
|
||||
const trimmed = rawUrl.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error('Instance URL is required');
|
||||
}
|
||||
let candidate = trimmed;
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//.test(candidate)) {
|
||||
candidate = `https://${candidate}`;
|
||||
}
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(candidate);
|
||||
} catch {
|
||||
throw new Error('Invalid instance URL');
|
||||
}
|
||||
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
||||
throw new Error('Instance URL must use http or https');
|
||||
}
|
||||
return url.origin;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object';
|
||||
}
|
||||
|
||||
function isValidWellKnownPayload(payload: unknown): boolean {
|
||||
if (!isRecord(payload)) {
|
||||
return false;
|
||||
}
|
||||
if (!('endpoints' in payload)) {
|
||||
return false;
|
||||
}
|
||||
const endpoints = (
|
||||
payload as {
|
||||
endpoints?: unknown;
|
||||
}
|
||||
).endpoints;
|
||||
if (!endpoints || typeof endpoints !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const api = (
|
||||
endpoints as {
|
||||
api?: unknown;
|
||||
}
|
||||
).api;
|
||||
const gateway = (
|
||||
endpoints as {
|
||||
gateway?: unknown;
|
||||
}
|
||||
).gateway;
|
||||
return typeof api === 'string' && typeof gateway === 'string';
|
||||
}
|
||||
|
||||
function normalizeDesktopWindowBehaviorUpdate(value: unknown): Partial<DesktopWindowBehaviorSettings> {
|
||||
if (!isRecord(value)) {
|
||||
return {};
|
||||
@@ -190,59 +137,9 @@ function getActiveMiddleClickAutoscroll(): boolean {
|
||||
return process.platform === 'linux' && hasEnabledBlinkFeature(MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE);
|
||||
}
|
||||
|
||||
async function assertValidFluxerInstance(instanceOrigin: string): Promise<void> {
|
||||
const url = new URL('/.well-known/fluxer', instanceOrigin).toString();
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const payload = (await response.json()) as unknown;
|
||||
if (!isValidWellKnownPayload(payload)) {
|
||||
throw new Error('Malformed discovery document');
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Not a valid Fluxer instance (${message})`);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
registerVoiceDebugEventSinkPopoutIpcHandlers();
|
||||
registerVoiceBackgroundMediaCacheHandlers();
|
||||
ipcMain.handle('switch-instance-url', async (_event, options: SwitchInstanceUrlOptions): Promise<void> => {
|
||||
const instanceOrigin = normalizeInstanceOrigin(options.instanceUrl);
|
||||
await assertValidFluxerInstance(instanceOrigin);
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
throw new Error('Main window not available');
|
||||
}
|
||||
pendingDesktopHandoffCode = options.desktopHandoffCode ?? null;
|
||||
setCustomAppUrl(instanceOrigin);
|
||||
try {
|
||||
await mainWindow.loadURL(instanceOrigin);
|
||||
} catch (error) {
|
||||
setCustomAppUrl(null);
|
||||
pendingDesktopHandoffCode = null;
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Failed to load instance: ${detail}`);
|
||||
}
|
||||
});
|
||||
ipcMain.handle('consume-desktop-handoff-code', (): string | null => {
|
||||
const code = pendingDesktopHandoffCode;
|
||||
pendingDesktopHandoffCode = null;
|
||||
return code;
|
||||
});
|
||||
ipcMain.handle('get-desktop-info', () => getDesktopInfo());
|
||||
ipcMain.handle('get-gpu-info', () => getGpuInfo());
|
||||
ipcMain.handle('get-app-metrics', () => getAppMetricsSnapshot());
|
||||
|
||||
@@ -49,7 +49,6 @@ import type {
|
||||
SpellcheckState,
|
||||
StreamerModeCaptureAppStatus,
|
||||
StreamingPriorityDiagnostics,
|
||||
SwitchInstanceUrlOptions,
|
||||
TextareaContextMenuParams,
|
||||
TrayActionPayload,
|
||||
TrayRuntimeStatePayload,
|
||||
@@ -484,9 +483,6 @@ const api: ElectronAPI = {
|
||||
options: PublicKeyCredentialCreationOptionsJSON,
|
||||
requestContext?: {pin?: string},
|
||||
): Promise<RegistrationResponseJSON> => ipcRenderer.invoke('passkey-register', options, requestContext),
|
||||
switchInstanceUrl: (options: SwitchInstanceUrlOptions): Promise<void> =>
|
||||
ipcRenderer.invoke('switch-instance-url', options),
|
||||
consumeDesktopHandoffCode: (): Promise<string | null> => ipcRenderer.invoke('consume-desktop-handoff-code'),
|
||||
toggleDevTools: (): void => {
|
||||
ipcRenderer.send('toggle-devtools');
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user