mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-18 17:34:09 +02:00
Apply additional security recommendations (#2050)
* enable sandbox * enable CSP (umami tentatively works?) and reduce amount of ipc APIs exposed * remove csp from index
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
export const disableAutoUpdates = () => {
|
||||||
|
return process.env['DISABLE_AUTO_UPDATES'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isMacOS = () => {
|
||||||
|
return process.platform === 'darwin';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isWindows = () => {
|
||||||
|
return process.platform === 'win32';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLinux = () => {
|
||||||
|
return process.platform === 'linux';
|
||||||
|
};
|
||||||
@@ -7,9 +7,10 @@ import { pid } from 'node:process';
|
|||||||
import process from 'process';
|
import process from 'process';
|
||||||
|
|
||||||
import { getMainWindow, sendToastToRenderer } from '../../../index';
|
import { getMainWindow, sendToastToRenderer } from '../../../index';
|
||||||
import { createLog, isMacOS, isWindows } from '../../../utils';
|
import { createLog } from '../../../utils';
|
||||||
import { store } from '../settings';
|
import { store } from '../settings';
|
||||||
|
|
||||||
|
import { isMacOS, isWindows } from '/@/main/env';
|
||||||
import { PlayerData } from '/@/shared/types/domain-types';
|
import { PlayerData } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
declare module 'node-mpv';
|
declare module 'node-mpv';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { BrowserWindow, globalShortcut, systemPreferences } from 'electron';
|
import { BrowserWindow, globalShortcut, systemPreferences } from 'electron';
|
||||||
|
|
||||||
import { isLinux, isMacOS } from '../../../utils';
|
|
||||||
import { store } from '../settings';
|
import { store } from '../settings';
|
||||||
|
|
||||||
|
import { isLinux, isMacOS } from '/@/main/env';
|
||||||
import { PlayerType } from '/@/shared/types/types';
|
import { PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const enableMediaKeys = (window: BrowserWindow | null) => {
|
export const enableMediaKeys = (window: BrowserWindow | null) => {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { deflate, gzip } from 'zlib';
|
|||||||
|
|
||||||
import manifest from './manifest.json';
|
import manifest from './manifest.json';
|
||||||
|
|
||||||
|
import { isLinux } from '/@/main/env';
|
||||||
import { getMainWindow } from '/@/main/index';
|
import { getMainWindow } from '/@/main/index';
|
||||||
import { isLinux } from '/@/main/utils';
|
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types';
|
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types';
|
||||||
import { PlayerRepeat, PlayerStatus, SongState } from '/@/shared/types/types';
|
import { PlayerRepeat, PlayerStatus, SongState } from '/@/shared/types/types';
|
||||||
|
|||||||
+28
-10
@@ -16,6 +16,7 @@ import {
|
|||||||
protocol,
|
protocol,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
screen,
|
screen,
|
||||||
|
session,
|
||||||
shell,
|
shell,
|
||||||
Tray,
|
Tray,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
@@ -33,16 +34,9 @@ import { store } from './features/core/settings';
|
|||||||
import { canHandleVisualizerDisplayMedia } from './features/core/visualizer';
|
import { canHandleVisualizerDisplayMedia } from './features/core/visualizer';
|
||||||
import MenuBuilder, { MenuPlaybackState } from './menu';
|
import MenuBuilder, { MenuPlaybackState } from './menu';
|
||||||
import './features';
|
import './features';
|
||||||
import {
|
import { autoUpdaterLogInterface, createLog, hotkeyToElectronAccelerator } from './utils';
|
||||||
autoUpdaterLogInterface,
|
|
||||||
createLog,
|
|
||||||
disableAutoUpdates,
|
|
||||||
hotkeyToElectronAccelerator,
|
|
||||||
isLinux,
|
|
||||||
isMacOS,
|
|
||||||
isWindows,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
|
import { disableAutoUpdates, isLinux, isMacOS, isWindows } from '/@/main/env';
|
||||||
import { PlayerRepeat, PlayerStatus, PlayerType, TitleTheme } from '/@/shared/types/types';
|
import { PlayerRepeat, PlayerStatus, PlayerType, TitleTheme } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ALPHA_UPDATER_CONFIG: {
|
const ALPHA_UPDATER_CONFIG: {
|
||||||
@@ -477,6 +471,15 @@ const createTray = () => {
|
|||||||
tray.setContextMenu(contextMenu);
|
tray.setContextMenu(contextMenu);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateUrl = (url: string): boolean => {
|
||||||
|
// Minor security, really. Enforce only loading websites (http/https). file://
|
||||||
|
// URLs and the like should've already been blocked, but this is another check.
|
||||||
|
// Note that arbitrary web URLs are still allowed under this scheme, although
|
||||||
|
// that should really only be hit by Subsonic share url (or if artist homepage
|
||||||
|
// is allowed for ND extensions)
|
||||||
|
return url.startsWith('http://') || url.startsWith('https://');
|
||||||
|
};
|
||||||
|
|
||||||
async function createWindow(first = true): Promise<void> {
|
async function createWindow(first = true): Promise<void> {
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
await installExtensions().catch(console.log);
|
await installExtensions().catch(console.log);
|
||||||
@@ -518,7 +521,7 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
devTools: true,
|
devTools: true,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
preload: join(__dirname, '../preload/index.js'),
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
sandbox: false,
|
sandbox: true,
|
||||||
webSecurity: !store.get('ignore_cors'),
|
webSecurity: !store.get('ignore_cors'),
|
||||||
},
|
},
|
||||||
width: 1440,
|
width: 1440,
|
||||||
@@ -730,7 +733,9 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
|
|
||||||
// Open URLs in the user's browser
|
// Open URLs in the user's browser
|
||||||
mainWindow.webContents.setWindowOpenHandler((edata) => {
|
mainWindow.webContents.setWindowOpenHandler((edata) => {
|
||||||
|
if (validateUrl(edata.url)) {
|
||||||
shell.openExternal(edata.url);
|
shell.openExternal(edata.url);
|
||||||
|
}
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -770,7 +775,9 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
nativeTheme.themeSource = theme || 'dark';
|
nativeTheme.themeSource = theme || 'dark';
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
|
if (validateUrl(details.url)) {
|
||||||
shell.openExternal(details.url);
|
shell.openExternal(details.url);
|
||||||
|
}
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1017,6 +1024,17 @@ if (!singleInstance) {
|
|||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||||
|
callback({
|
||||||
|
responseHeaders: {
|
||||||
|
...details.responseHeaders,
|
||||||
|
'Content-Security-Policy': [
|
||||||
|
"script-src 'self' 'unsafe-inline' https://umami.jeffvli.org; style-src 'self' 'unsafe-inline'; media-src 'self' http: https: data: blob:; img-src 'self' http: https: data: blob:; connect-src 'self' http: https: ws: wss:; default-src 'self';",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
if (store.get('window_enable_tray', true)) {
|
if (store.get('window_enable_tray', true)) {
|
||||||
createTray();
|
createTray();
|
||||||
|
|||||||
@@ -18,22 +18,6 @@ if (process.env.NODE_ENV === 'development') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const disableAutoUpdates = () => {
|
|
||||||
return process.env['DISABLE_AUTO_UPDATES'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isMacOS = () => {
|
|
||||||
return process.platform === 'darwin';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isWindows = () => {
|
|
||||||
return process.platform === 'win32';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isLinux = () => {
|
|
||||||
return process.platform === 'linux';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hotkeyToElectronAccelerator = (hotkey: string) => {
|
export const hotkeyToElectronAccelerator = (hotkey: string) => {
|
||||||
let accelerator = hotkey;
|
let accelerator = hotkey;
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,11 @@ const send = (channel: string, ...args: any[]) => {
|
|||||||
ipcRenderer.send(channel, ...args);
|
ipcRenderer.send(channel, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
const invoke = (channel: string, ...args: any[]) => {
|
|
||||||
return ipcRenderer.invoke(channel, ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const on = (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
|
||||||
ipcRenderer.on(channel, listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeListener = (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
const removeListener = (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||||
ipcRenderer.removeListener(channel, listener);
|
ipcRenderer.removeListener(channel, listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ipc = {
|
export const ipc = {
|
||||||
invoke,
|
|
||||||
on,
|
|
||||||
removeAllListeners,
|
removeAllListeners,
|
||||||
removeListener,
|
removeListener,
|
||||||
send,
|
send,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ipcRenderer, IpcRendererEvent, OpenDialogOptions, webFrame } from 'electron';
|
import { ipcRenderer, OpenDialogOptions, webFrame } from 'electron';
|
||||||
|
|
||||||
import { TitleTheme } from '/@/shared/types/types';
|
import { TitleTheme } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ const setZoomFactor = (zoomFactor: number) => {
|
|||||||
webFrame.setZoomFactor(zoomFactor / 100);
|
webFrame.setZoomFactor(zoomFactor / 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fontError = (cb: (event: IpcRendererEvent, file: string) => void) => {
|
const fontError = (cb: (file: string) => void) => {
|
||||||
ipcRenderer.on('custom-font-error', cb);
|
ipcRenderer.on('custom-font-error', (_, file) => cb(file));
|
||||||
};
|
};
|
||||||
|
|
||||||
const themeSet = (theme: TitleTheme): void => {
|
const themeSet = (theme: TitleTheme): void => {
|
||||||
|
|||||||
+11
-15
@@ -1,4 +1,4 @@
|
|||||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
||||||
@@ -31,28 +31,24 @@ const updateSong = (song: QueueSong | undefined, imageUrl?: null | string) => {
|
|||||||
ipcRenderer.send('update-song', song, imageUrl);
|
ipcRenderer.send('update-song', song, imageUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestSeek = (cb: (event: IpcRendererEvent, data: { offset: number }) => void) => {
|
const requestSeek = (cb: (data: { offset: number }) => void) => {
|
||||||
ipcRenderer.on('request-seek', cb);
|
ipcRenderer.on('request-seek', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestPosition = (cb: (event: IpcRendererEvent, data: { position: number }) => void) => {
|
const requestPosition = (cb: (data: { position: number }) => void) => {
|
||||||
ipcRenderer.on('request-position', cb);
|
ipcRenderer.on('request-position', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestToggleRepeat = (
|
const requestToggleRepeat = (cb: (data: { repeat: PlayerRepeat }) => void) => {
|
||||||
cb: (event: IpcRendererEvent, data: { repeat: PlayerRepeat }) => void,
|
ipcRenderer.on('mpris-request-toggle-repeat', (_, data) => cb(data));
|
||||||
) => {
|
|
||||||
ipcRenderer.on('mpris-request-toggle-repeat', cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestToggleShuffle = (
|
const requestToggleShuffle = (cb: (data: { shuffle: boolean }) => void) => {
|
||||||
cb: (event: IpcRendererEvent, data: { shuffle: boolean }) => void,
|
ipcRenderer.on('mpris-request-toggle-shuffle', (_, data) => cb(data));
|
||||||
) => {
|
|
||||||
ipcRenderer.on('mpris-request-toggle-shuffle', cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestVolume = (cb: (event: IpcRendererEvent, data: { volume: number }) => void) => {
|
const requestVolume = (cb: (data: { volume: number }) => void) => {
|
||||||
ipcRenderer.on('request-volume', cb);
|
ipcRenderer.on('request-volume', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mpris = {
|
export const mpris = {
|
||||||
|
|||||||
+37
-37
@@ -1,4 +1,4 @@
|
|||||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
import { PlayerData } from '/@/shared/types/domain-types';
|
import { PlayerData } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
@@ -102,76 +102,76 @@ const getAudioDevices = async () => {
|
|||||||
return ipcRenderer.invoke('player-get-audio-devices');
|
return ipcRenderer.invoke('player-get-audio-devices');
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererAutoNext = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererAutoNext = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-auto-next', cb);
|
ipcRenderer.on('renderer-player-auto-next', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererCurrentTime = (cb: (event: IpcRendererEvent, data: number) => void) => {
|
const rendererCurrentTime = (cb: (data: number) => void) => {
|
||||||
ipcRenderer.on('renderer-player-current-time', cb);
|
ipcRenderer.on('renderer-player-current-time', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererNext = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererNext = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-next', cb);
|
ipcRenderer.on('renderer-player-next', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererPause = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererPause = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-pause', cb);
|
ipcRenderer.on('renderer-player-pause', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererPlay = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererPlay = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-play', cb);
|
ipcRenderer.on('renderer-player-play', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererPlayPause = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererPlayPause = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-play-pause', cb);
|
ipcRenderer.on('renderer-player-play-pause', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererPrevious = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererPrevious = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-previous', cb);
|
ipcRenderer.on('renderer-player-previous', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererStop = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererStop = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-stop', cb);
|
ipcRenderer.on('renderer-player-stop', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererSkipForward = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererSkipForward = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-skip-forward', cb);
|
ipcRenderer.on('renderer-player-skip-forward', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererSkipBackward = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererSkipBackward = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-skip-backward', cb);
|
ipcRenderer.on('renderer-player-skip-backward', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererVolumeUp = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererVolumeUp = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-volume-up', cb);
|
ipcRenderer.on('renderer-player-volume-up', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererVolumeDown = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererVolumeDown = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-volume-down', cb);
|
ipcRenderer.on('renderer-player-volume-down', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererVolumeMute = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererVolumeMute = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-volume-mute', cb);
|
ipcRenderer.on('renderer-player-volume-mute', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererToggleRepeat = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererToggleRepeat = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-toggle-repeat', cb);
|
ipcRenderer.on('renderer-player-toggle-repeat', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererToggleShuffle = (cb: (event: IpcRendererEvent, data: PlayerData) => void) => {
|
const rendererToggleShuffle = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-toggle-shuffle', cb);
|
ipcRenderer.on('renderer-player-toggle-shuffle', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererQuit = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererQuit = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-player-quit', cb);
|
ipcRenderer.on('renderer-player-quit', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => {
|
const rendererError = (cb: (data: string) => void) => {
|
||||||
ipcRenderer.on('renderer-player-error', cb);
|
ipcRenderer.on('renderer-player-error', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererPlayerFallback = (cb: (event: IpcRendererEvent, data: boolean) => void) => {
|
const rendererPlayerFallback = (cb: (data: boolean) => void) => {
|
||||||
ipcRenderer.on('renderer-player-fallback', cb);
|
ipcRenderer.on('renderer-player-fallback', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mpvPlayer = {
|
export const mpvPlayer = {
|
||||||
|
|||||||
+11
-16
@@ -1,33 +1,28 @@
|
|||||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { PlayerStatus } from '/@/shared/types/types';
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
const requestFavorite = (
|
const requestFavorite = (
|
||||||
cb: (
|
cb: (data: { favorite: boolean; id: string; serverId: string }) => void,
|
||||||
event: IpcRendererEvent,
|
|
||||||
data: { favorite: boolean; id: string; serverId: string },
|
|
||||||
) => void,
|
|
||||||
) => {
|
) => {
|
||||||
ipcRenderer.on('request-favorite', cb);
|
ipcRenderer.on('request-favorite', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestPosition = (cb: (event: IpcRendererEvent, data: { position: number }) => void) => {
|
const requestPosition = (cb: (data: { position: number }) => void) => {
|
||||||
ipcRenderer.on('request-position', cb);
|
ipcRenderer.on('request-position', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestRating = (
|
const requestRating = (cb: (data: { id: string; rating: number; serverId: string }) => void) => {
|
||||||
cb: (event: IpcRendererEvent, data: { id: string; rating: number; serverId: string }) => void,
|
ipcRenderer.on('request-rating', (_, data) => cb(data));
|
||||||
) => {
|
|
||||||
ipcRenderer.on('request-rating', cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestSeek = (cb: (event: IpcRendererEvent, data: { offset: number }) => void) => {
|
const requestSeek = (cb: (data: { offset: number }) => void) => {
|
||||||
ipcRenderer.on('request-seek', cb);
|
ipcRenderer.on('request-seek', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestVolume = (cb: (event: IpcRendererEvent, data: { volume: number }) => void) => {
|
const requestVolume = (cb: (data: { volume: number }) => void) => {
|
||||||
ipcRenderer.on('request-volume', cb);
|
ipcRenderer.on('request-volume', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const setRemoteEnabled = (enabled: boolean): Promise<null | string> => {
|
const setRemoteEnabled = (enabled: boolean): Promise<null | string> => {
|
||||||
|
|||||||
+32
-33
@@ -1,6 +1,6 @@
|
|||||||
import { ipcRenderer, IpcRendererEvent, webFrame } from 'electron';
|
import { ipcRenderer, webFrame } from 'electron';
|
||||||
|
|
||||||
import { disableAutoUpdates, isLinux, isMacOS, isWindows } from '../main/utils';
|
import { disableAutoUpdates, isLinux, isMacOS, isWindows } from '../main/env';
|
||||||
|
|
||||||
const openItem = async (path: string) => {
|
const openItem = async (path: string) => {
|
||||||
return ipcRenderer.invoke('open-item', path);
|
return ipcRenderer.invoke('open-item', path);
|
||||||
@@ -10,29 +10,14 @@ const openApplicationDirectory = async () => {
|
|||||||
return ipcRenderer.invoke('open-application-directory');
|
return ipcRenderer.invoke('open-application-directory');
|
||||||
};
|
};
|
||||||
|
|
||||||
const playerErrorListener = (cb: (event: IpcRendererEvent, data: { code: number }) => void) => {
|
const playerErrorListener = (cb: (data: { code: number }) => void) => {
|
||||||
ipcRenderer.on('player-error-listener', cb);
|
ipcRenderer.on('player-error-listener', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const mainMessageListener = (
|
const mainMessageListener = (
|
||||||
cb: (
|
cb: (data: { message: string; type: 'error' | 'info' | 'success' | 'warning' }) => void,
|
||||||
event: IpcRendererEvent,
|
|
||||||
data: { message: string; type: 'error' | 'info' | 'success' | 'warning' },
|
|
||||||
) => void,
|
|
||||||
) => {
|
) => {
|
||||||
ipcRenderer.on('toast-from-main', cb);
|
ipcRenderer.on('toast-from-main', (_, data) => cb(data));
|
||||||
};
|
|
||||||
|
|
||||||
const logger = (
|
|
||||||
cb: (
|
|
||||||
event: IpcRendererEvent,
|
|
||||||
data: {
|
|
||||||
message: string;
|
|
||||||
type: 'debug' | 'error' | 'info' | 'verbose' | 'warning';
|
|
||||||
},
|
|
||||||
) => void,
|
|
||||||
) => {
|
|
||||||
ipcRenderer.send('logger', cb);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const download = (url: string) => {
|
const download = (url: string) => {
|
||||||
@@ -43,6 +28,14 @@ const checkForUpdates = (): Promise<{ updateAvailable: boolean; version?: string
|
|||||||
return ipcRenderer.invoke('app-check-for-updates');
|
return ipcRenderer.invoke('app-check-for-updates');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startPowerSaveBlocker = (full: boolean) => {
|
||||||
|
return ipcRenderer.invoke('power-save-blocker-start', { full });
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopPowerSaveBlocker = () => {
|
||||||
|
return ipcRenderer.invoke('power-save-blocker-stop');
|
||||||
|
};
|
||||||
|
|
||||||
const forceGarbageCollection = (): boolean => {
|
const forceGarbageCollection = (): boolean => {
|
||||||
try {
|
try {
|
||||||
if (typeof global.gc === 'function') {
|
if (typeof global.gc === 'function') {
|
||||||
@@ -61,28 +54,32 @@ const forceGarbageCollection = (): boolean => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererOpenSettings = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererOpenSettings = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-open-settings', cb);
|
ipcRenderer.on('renderer-open-settings', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererOpenCommandPalette = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererOpenCommandPalette = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-open-command-palette', cb);
|
ipcRenderer.on('renderer-open-command-palette', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererOpenManageServers = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererOpenManageServers = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-open-manage-servers', cb);
|
ipcRenderer.on('renderer-open-manage-servers', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererTogglePrivateMode = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererTogglePrivateMode = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-toggle-private-mode', cb);
|
ipcRenderer.on('renderer-toggle-private-mode', cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererToggleSidebar = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererToggleSidebar = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-toggle-sidebar', cb);
|
ipcRenderer.on('renderer-toggle-sidebar', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendererOpenReleaseNotes = (cb: (event: IpcRendererEvent) => void) => {
|
const rendererOpenReleaseNotes = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-open-release-notes', cb);
|
ipcRenderer.on('renderer-open-release-notes', () => cb());
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererUpdateAvailable = (cb: (version: string) => void) => {
|
||||||
|
ipcRenderer.on('update-available', (_, version) => cb(version));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const utils = {
|
export const utils = {
|
||||||
@@ -93,7 +90,6 @@ export const utils = {
|
|||||||
isLinux,
|
isLinux,
|
||||||
isMacOS,
|
isMacOS,
|
||||||
isWindows,
|
isWindows,
|
||||||
logger,
|
|
||||||
mainMessageListener,
|
mainMessageListener,
|
||||||
openApplicationDirectory,
|
openApplicationDirectory,
|
||||||
openItem,
|
openItem,
|
||||||
@@ -104,6 +100,9 @@ export const utils = {
|
|||||||
rendererOpenSettings,
|
rendererOpenSettings,
|
||||||
rendererTogglePrivateMode,
|
rendererTogglePrivateMode,
|
||||||
rendererToggleSidebar,
|
rendererToggleSidebar,
|
||||||
|
rendererUpdateAvailable,
|
||||||
|
startPowerSaveBlocker,
|
||||||
|
stopPowerSaveBlocker,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Utils = typeof utils;
|
export type Utils = typeof utils;
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const useMainPlayerListener = () => {
|
|||||||
decreaseVolume(volumeWheelStep);
|
decreaseVolume(volumeWheelStep);
|
||||||
});
|
});
|
||||||
|
|
||||||
mpvPlayerListener.rendererError((_event: any, message: string) => {
|
mpvPlayerListener.rendererError((message: string) => {
|
||||||
handleMpvError(message);
|
handleMpvError(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ export const useMPRIS = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mpris?.requestPosition((_e: unknown, data: { position: number }) => {
|
mpris?.requestPosition((data: { position: number }) => {
|
||||||
player.mediaSeekToTimestamp(data.position);
|
player.mediaSeekToTimestamp(data.position);
|
||||||
});
|
});
|
||||||
|
|
||||||
mpris?.requestSeek((_e: unknown, data: { offset: number }) => {
|
mpris?.requestSeek((data: { offset: number }) => {
|
||||||
player.mediaSkipForward(data.offset);
|
player.mediaSkipForward(data.offset);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export const useMPRIS = () => {
|
|||||||
player.toggleShuffle();
|
player.toggleShuffle();
|
||||||
});
|
});
|
||||||
|
|
||||||
mpris?.requestVolume((_e: unknown, data: { volume: number }) => {
|
mpris?.requestVolume((data: { volume: number }) => {
|
||||||
player.setVolume(data.volume);
|
player.setVolume(data.volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,27 @@ import React, { useCallback, useEffect } from 'react';
|
|||||||
import { usePlayerStatus, useSettingsStore, useWindowSettings } from '/@/renderer/store';
|
import { usePlayerStatus, useSettingsStore, useWindowSettings } from '/@/renderer/store';
|
||||||
import { PlayerStatus } from '/@/shared/types/types';
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const utils = isElectron() ? window.api.utils : null;
|
||||||
|
|
||||||
export const usePowerSaveBlocker = () => {
|
export const usePowerSaveBlocker = () => {
|
||||||
const status = usePlayerStatus();
|
const status = usePlayerStatus();
|
||||||
const { preventSleepOnPlayback, preventSuspendOnPlayback } = useWindowSettings();
|
const { preventSleepOnPlayback, preventSuspendOnPlayback } = useWindowSettings();
|
||||||
|
|
||||||
const startPowerSaveBlocker = useCallback(async () => {
|
const startPowerSaveBlocker = useCallback(async () => {
|
||||||
if (!ipc) return;
|
if (!utils) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ipc.invoke('power-save-blocker-start', { full: preventSleepOnPlayback });
|
await utils.startPowerSaveBlocker(preventSleepOnPlayback);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start power save blocker:', error);
|
console.error('Failed to start power save blocker:', error);
|
||||||
}
|
}
|
||||||
}, [preventSleepOnPlayback]);
|
}, [preventSleepOnPlayback]);
|
||||||
|
|
||||||
const stopPowerSaveBlocker = useCallback(async () => {
|
const stopPowerSaveBlocker = useCallback(async () => {
|
||||||
if (!ipc) return;
|
if (!utils) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ipc.invoke('power-save-blocker-stop');
|
await utils.stopPowerSaveBlocker();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to stop power save blocker:', error);
|
console.error('Failed to stop power save blocker:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const useRemote = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
remote.requestPosition((_e: unknown, data: { position: number }) => {
|
remote.requestPosition((data: { position: number }) => {
|
||||||
logFn.debug(logMsg[LogCategory.REMOTE].requestPositionReceived, {
|
logFn.debug(logMsg[LogCategory.REMOTE].requestPositionReceived, {
|
||||||
category: LogCategory.REMOTE,
|
category: LogCategory.REMOTE,
|
||||||
meta: { position: data.position },
|
meta: { position: data.position },
|
||||||
@@ -73,7 +73,7 @@ export const useRemote = () => {
|
|||||||
player.mediaSeekToTimestamp(newTime);
|
player.mediaSeekToTimestamp(newTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
remote.requestSeek((_e: unknown, data: { offset: number }) => {
|
remote.requestSeek((data: { offset: number }) => {
|
||||||
logFn.debug(logMsg[LogCategory.REMOTE].requestSeekReceived, {
|
logFn.debug(logMsg[LogCategory.REMOTE].requestSeekReceived, {
|
||||||
category: LogCategory.REMOTE,
|
category: LogCategory.REMOTE,
|
||||||
meta: { offset: data.offset },
|
meta: { offset: data.offset },
|
||||||
@@ -81,17 +81,15 @@ export const useRemote = () => {
|
|||||||
mediaSkipForward(data.offset);
|
mediaSkipForward(data.offset);
|
||||||
});
|
});
|
||||||
|
|
||||||
remote.requestRating(
|
remote.requestRating((data: { id: string; rating: number; serverId: string }) => {
|
||||||
(_e: unknown, data: { id: string; rating: number; serverId: string }) => {
|
|
||||||
logFn.debug(logMsg[LogCategory.REMOTE].requestRatingReceived, {
|
logFn.debug(logMsg[LogCategory.REMOTE].requestRatingReceived, {
|
||||||
category: LogCategory.REMOTE,
|
category: LogCategory.REMOTE,
|
||||||
meta: { id: data.id, rating: data.rating, serverId: data.serverId },
|
meta: { id: data.id, rating: data.rating, serverId: data.serverId },
|
||||||
});
|
});
|
||||||
setRating(data.serverId, [data.id], LibraryItem.SONG, data.rating);
|
setRating(data.serverId, [data.id], LibraryItem.SONG, data.rating);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
remote.requestVolume((_e: unknown, data: { volume: number }) => {
|
remote.requestVolume((data: { volume: number }) => {
|
||||||
logFn.debug(logMsg[LogCategory.REMOTE].requestVolumeReceived, {
|
logFn.debug(logMsg[LogCategory.REMOTE].requestVolumeReceived, {
|
||||||
category: LogCategory.REMOTE,
|
category: LogCategory.REMOTE,
|
||||||
meta: { volume: data.volume },
|
meta: { volume: data.volume },
|
||||||
@@ -99,15 +97,12 @@ export const useRemote = () => {
|
|||||||
setVolume(data.volume);
|
setVolume(data.volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
remote.requestFavorite(
|
remote.requestFavorite((data: { favorite: boolean; id: string; serverId: string }) => {
|
||||||
(_e: unknown, data: { favorite: boolean; id: string; serverId: string }) => {
|
|
||||||
logFn.debug(logMsg[LogCategory.REMOTE].requestFavoriteReceived, {
|
logFn.debug(logMsg[LogCategory.REMOTE].requestFavoriteReceived, {
|
||||||
category: LogCategory.REMOTE,
|
category: LogCategory.REMOTE,
|
||||||
meta: { favorite: data.favorite, id: data.id, serverId: data.serverId },
|
meta: { favorite: data.favorite, id: data.id, serverId: data.serverId },
|
||||||
});
|
});
|
||||||
const mutator = data.favorite
|
const mutator = data.favorite ? addToFavoritesMutation : removeFromFavoritesMutation;
|
||||||
? addToFavoritesMutation
|
|
||||||
: removeFromFavoritesMutation;
|
|
||||||
mutator.mutate({
|
mutator.mutate({
|
||||||
apiClientProps: { serverId: data.serverId },
|
apiClientProps: { serverId: data.serverId },
|
||||||
query: {
|
query: {
|
||||||
@@ -115,8 +110,7 @@ export const useRemote = () => {
|
|||||||
type: LibraryItem.SONG,
|
type: LibraryItem.SONG,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ipc?.removeAllListeners('request-position');
|
ipc?.removeAllListeners('request-position');
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { IpcRendererEvent } from 'electron';
|
|
||||||
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
@@ -124,7 +122,7 @@ export const ApplicationSettings = memo(() => {
|
|||||||
// }, [fontSettings.custom]);
|
// }, [fontSettings.custom]);
|
||||||
|
|
||||||
const onFontError = useCallback(
|
const onFontError = useCallback(
|
||||||
(_: IpcRendererEvent, file: string) => {
|
(file: string) => {
|
||||||
toast.error({
|
toast.error({
|
||||||
message: `${file} is not a valid font file`,
|
message: `${file} is not a valid font file`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="Content-Security-Policy" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<title>Feishin</title>
|
<title>Feishin</title>
|
||||||
<% if (web) { %>
|
<% if (web) { %>
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ export const UpdateAvailableDialog = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron()) return;
|
if (!isElectron()) return;
|
||||||
|
|
||||||
const handleUpdateAvailable = (_event: any, newVersion: string) => {
|
const handleUpdateAvailable = (newVersion: string) => {
|
||||||
if (versionDismissed !== newVersion) {
|
if (versionDismissed !== newVersion) {
|
||||||
setVersion(newVersion);
|
setVersion(newVersion);
|
||||||
setOpened(true);
|
setOpened(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.api.ipc.on('update-available', handleUpdateAvailable);
|
window.api.utils.rendererUpdateAvailable(handleUpdateAvailable);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.api.ipc.removeListener?.('update-available', handleUpdateAvailable);
|
window.api.ipc.removeListener?.('update-available', handleUpdateAvailable);
|
||||||
|
|||||||
Reference in New Issue
Block a user