Files
feishin/src/renderer/hooks/use-sync-settings-to-main.ts
T
Kendall Garner 515cadb916 Better cross platform font handling (#2104)
* fix: better handling of custom font

Practically speaking, custom font seems to have only worked on Linux, because
`net.fetch` would include the mime type in the response headers which could validate the payload.
This doesn't appear to be the case on windows/macOS. Instead:

1. On Linux (or if some other system supports it), check the content type. If good, serve as normal
2. Otherwise, fetch the payload. Read the first four to five bytes and check for a valid magic number.

Additionally, to prevent arbitrary requests fetching other paths via injected content, sync the custom font path
to the main process, and then make _every_ request to `feishin:/` point to the same renderer path.
When setting the font, first send the path to the main process. This will register `feishin:/` to point
to the path provided. This is done via a promise-based set.

Finally, provide a default value for the file input (a best effort approximation for the last part of the file path)
on the file input component.

* make the linter happy
2026-06-02 19:34:16 +00:00

152 lines
5.7 KiB
TypeScript

import isElectron from 'is-electron';
import { useEffect, useRef } from 'react';
import i18n from '/@/i18n/i18n';
import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast';
import { useSettingsStore } from '/@/renderer/store/settings.store';
import { logFn } from '/@/renderer/utils/logger';
import { logMsg } from '/@/renderer/utils/logger-message';
// Synchronizes settings from the renderer store to the main process electron store
// on app initialization. If there are differences, it updates the main store and shows
// a restart required toast.
export const useSyncSettingsToMain = () => {
const hasRunRef = useRef(false);
useEffect(() => {
if (hasRunRef.current) {
return;
}
if (!isElectron() || !window.api.localSettings) {
hasRunRef.current = true;
return;
}
// Wait some before checking for differences to ensure the store is hydrated from localStorage
const timeoutId = setTimeout(() => {
if (hasRunRef.current) {
return;
}
const settingsFromStore = useSettingsStore.getState();
const settings = {
font: settingsFromStore.font,
general: settingsFromStore.general,
hotkeys: settingsFromStore.hotkeys,
lyrics: settingsFromStore.lyrics,
playback: settingsFromStore.playback,
window: settingsFromStore.window,
};
hasRunRef.current = true;
const localSettings = window.api.localSettings;
const settingsMappings: Array<{
mainStoreKey: string;
rendererValue: any;
}> = [
{
mainStoreKey: 'lyrics',
rendererValue: settings.lyrics.sources,
},
{
mainStoreKey: 'window_window_bar_style',
rendererValue: settings.window.windowBarStyle,
},
{
mainStoreKey: 'window_start_minimized',
rendererValue: settings.window.startMinimized,
},
{
mainStoreKey: 'window_exit_to_tray',
rendererValue: settings.window.exitToTray,
},
{
mainStoreKey: 'window_minimize_to_tray',
rendererValue: settings.window.minimizeToTray,
},
{
mainStoreKey: 'disable_auto_updates',
rendererValue: settings.window.disableAutoUpdate,
},
// For some reason after the application is updated, the release channel from the
// renderer is always set to the latest channel. This causes an infinite update loop
// {
// mainStoreKey: 'release_channel',
// rendererValue: settings.window.releaseChannel,
// },
{
mainStoreKey: 'window_enable_tray',
rendererValue: settings.window.tray,
},
{
mainStoreKey: 'password_store',
rendererValue: settings.general.passwordStore,
},
{
mainStoreKey: 'mediaSession',
rendererValue: settings.playback.mediaSession,
},
{
mainStoreKey: 'playbackType',
rendererValue: settings.playback.type,
},
{
mainStoreKey: 'global_media_hotkeys',
rendererValue: settings.hotkeys.globalMediaHotkeys,
},
{
mainStoreKey: 'enableNeteaseTranslation',
rendererValue: settings.lyrics.enableNeteaseTranslation,
},
{
mainStoreKey: 'local_font_path',
rendererValue: settings.font.custom,
},
];
// Compare and sync each setting
(async () => {
let hasDifferences = false;
for (const mapping of settingsMappings) {
const mainValue = await localSettings.get(mapping.mainStoreKey);
const rendererValue = mapping.rendererValue;
const mainValueNormalized = mainValue === undefined ? null : mainValue;
const rendererValueNormalized =
rendererValue === undefined ? null : rendererValue;
if (
JSON.stringify(mainValueNormalized) !==
JSON.stringify(rendererValueNormalized)
) {
hasDifferences = true;
logFn.warn(logMsg.system.settingsSynchronized, {
meta: {
mainStoreKey: mapping.mainStoreKey,
mainValue: mainValueNormalized,
rendererValue: rendererValueNormalized,
},
});
localSettings.set(mapping.mainStoreKey, rendererValue);
}
}
// Show restart toast if there were differences
if (hasDifferences) {
openRestartRequiredToast(i18n.t('error.settingsSyncError'));
}
})();
}, 5000);
return () => {
clearTimeout(timeoutId);
};
// Only run once on mount
}, []);
};