From fa246a3f08fc501368df9aea52fe0ccaa601dbbd Mon Sep 17 00:00:00 2001 From: Michael Hollister Date: Thu, 12 Jun 2025 14:13:05 -0500 Subject: [PATCH] Receivers: Renamed store module and added Electron option to ignore certificate errors --- receivers/common/web/Settings.ts | 98 ++++++++++++++++++++++++++ receivers/common/web/Store.ts | 72 ------------------- receivers/common/web/main/Preload.ts | 9 ++- receivers/common/web/player/Preload.ts | 7 +- receivers/electron/src/Main.ts | 25 +++++-- receivers/electron/src/Updater.ts | 10 +-- 6 files changed, 136 insertions(+), 85 deletions(-) create mode 100644 receivers/common/web/Settings.ts delete mode 100644 receivers/common/web/Store.ts diff --git a/receivers/common/web/Settings.ts b/receivers/common/web/Settings.ts new file mode 100644 index 0000000..36f279e --- /dev/null +++ b/receivers/common/web/Settings.ts @@ -0,0 +1,98 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as fs from 'fs'; +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('Settings', LoggerType.BACKEND); + +export interface LoggerSettings { + level: string, +} + +export interface NetworkSettings { + ignoreCertificateErrors: boolean, + deviceName: string, +} + +export interface UISettings { + noMainWindow: boolean, + fullscreen: boolean, + mainWindowBackground: string, +} + +export interface UpdaterSettings { + channel: string, + checkForUpdatesOnStart: boolean, +} + +export interface Settings { + storeVersion: number, + log: LoggerSettings, + network: NetworkSettings, + ui: UISettings, + updater: UpdaterSettings, +} + +export class Settings { + private static instance: Settings = null; + private static readonly version = 1; + private static path?: string = null; + public static json: Settings; + + + constructor(path?: string) { + if (!Settings.instance) { + // @ts-ignore + if (TARGET === 'electron') { + Settings.path = path; + Settings.json = JSON.parse(fs.readFileSync(path, { encoding: 'utf8', flag: 'r' })) as Settings; + logger.info('Read settings file:', Settings.json); + Settings.setDefault(); + + // @ts-ignore + } else if (TARGET === 'webOS') { + // todo + } else { + // @ts-ignore + logger.warn(`Attempting to initialize Settings on unsupported target: ${TARGET}`); + } + + Settings.instance = this; + } + } + + private static setDefault() { + Settings.json.storeVersion = Settings.json.storeVersion === undefined ? Settings.version : Settings.json.storeVersion; + Settings.json.log = Settings.json.log === undefined ? {} as LoggerSettings : Settings.json.log; + Settings.json.network = Settings.json.network === undefined ? {} as NetworkSettings : Settings.json.network; + Settings.json.ui = Settings.json.ui === undefined ? {} as UISettings : Settings.json.ui; + Settings.json.updater = Settings.json.updater === undefined ? {} as UpdaterSettings : Settings.json.updater; + + Settings.json.log.level = Settings.json.log.level === undefined ? 'INFO' : Settings.json.log.level; + + Settings.json.network.deviceName = Settings.json.network.deviceName === undefined ? '' : Settings.json.network.deviceName; + Settings.json.network.ignoreCertificateErrors = Settings.json.network.ignoreCertificateErrors === undefined ? false : Settings.json.network.ignoreCertificateErrors; + + Settings.json.ui.noMainWindow = Settings.json.ui.noMainWindow === undefined ? false : Settings.json.ui.noMainWindow; + Settings.json.ui.fullscreen = Settings.json.ui.fullscreen === undefined ? false : Settings.json.ui.fullscreen; + Settings.json.ui.mainWindowBackground = Settings.json.ui.mainWindowBackground === undefined ? '' : Settings.json.ui.mainWindowBackground; + + Settings.json.updater.channel = Settings.json.updater.channel === undefined ? '' : Settings.json.updater.channel; + Settings.json.updater.checkForUpdatesOnStart = Settings.json.updater.checkForUpdatesOnStart === undefined ? true : Settings.json.updater.checkForUpdatesOnStart; + + Settings.save(); + } + + public static save() { + // @ts-ignore + if (TARGET === 'electron') { + logger.info('Saving settings file:', Settings.json); + fs.writeFileSync(Settings.path, JSON.stringify(Settings.json, null, 4)); + + // @ts-ignore + } else if (TARGET === 'webOS') { + // todo + } else { + // @ts-ignore + logger.warn(`Attempting to initialize Settings on unsupported target: ${TARGET}`); + } + } +} diff --git a/receivers/common/web/Store.ts b/receivers/common/web/Store.ts deleted file mode 100644 index 168fd5a..0000000 --- a/receivers/common/web/Store.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import * as fs from 'fs'; -import { Logger, LoggerType } from 'common/Logger'; -const logger = new Logger('Store', LoggerType.BACKEND); - -export interface NetworkSettings { - ignoreCertificateErrors: boolean, - deviceName: string, -} - -export interface UISettings { - mainWindowBackground: string, -} - -export interface UpdaterSettings { - channel: string, - checkForUpdatesOnStart: boolean, -} - -export interface Settings { - storeVersion: number, - network: NetworkSettings, - ui: UISettings, - updater: UpdaterSettings, -} - -export class Store { - private static instance: Store = null; - private static storeVersion = 1; - private static path?: string = null; - public static settings: Settings = null; - - constructor(path?: string) { - if (!Store.instance) { - // @ts-ignore - if (TARGET === 'electron') { - Store.path = path; - Store.settings = JSON.parse(fs.readFileSync(path, { encoding: 'utf8', flag: 'r' })) as Settings; - logger.info('Read settings file:', Store.settings); - - if (Store.settings === undefined) { - Store.settings.storeVersion = Store.storeVersion; - fs.writeFileSync(Store.path, JSON.stringify(Store.settings)); - } - - // @ts-ignore - } else if (TARGET === 'webOS') { - // todo - } else { - // @ts-ignore - logger.warn(`Attempting to initialize Store on unsupported target: ${TARGET}`); - } - - Store.instance = this; - } - } - - public static saveSettings() { - // @ts-ignore - if (TARGET === 'electron') { - logger.info('Saving settings file:', Store.settings); - fs.writeFileSync(Store.path, JSON.stringify(Store.settings)); - - // @ts-ignore - } else if (TARGET === 'webOS') { - // todo - } else { - // @ts-ignore - logger.warn(`Attempting to initialize Store on unsupported target: ${TARGET}`); - } - } -} diff --git a/receivers/common/web/main/Preload.ts b/receivers/common/web/main/Preload.ts index 528dc00..3782eaf 100644 --- a/receivers/common/web/main/Preload.ts +++ b/receivers/common/web/main/Preload.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { toast, ToastIcon } from 'common/components/Toast'; import { Logger, LoggerType } from 'common/Logger'; import { EventMessage } from 'common/Packets'; const logger = new Logger('MainWindow', LoggerType.FRONTEND); @@ -34,14 +35,18 @@ if (TARGET === 'electron') { // @ts-ignore const electronAPI = __non_webpack_require__('electron'); - electronAPI.ipcRenderer.on("device-info", (_event, value: any) => { + electronAPI.ipcRenderer.on('device-info', (_event, value: any) => { preloadData.deviceInfo = value; }) - electronAPI.ipcRenderer.on("event-subscribed-keys-update", (_event, value: { keyDown: Set, keyUp: Set }) => { + electronAPI.ipcRenderer.on('event-subscribed-keys-update', (_event, value: { keyDown: Set, keyUp: Set }) => { preloadData.subscribedKeys.keyDown = value.keyDown; preloadData.subscribedKeys.keyUp = value.keyUp; }) + electronAPI.ipcRenderer.on('toast', (_event, message: string, icon: ToastIcon = ToastIcon.INFO, duration: number = 5000) => { + toast(message, icon, duration); + }) + electronAPI.contextBridge.exposeInMainWorld('targetAPI', { onDeviceInfo: (callback: any) => electronAPI.ipcRenderer.on('device-info', callback), getDeviceInfo: () => preloadData.deviceInfo, diff --git a/receivers/common/web/player/Preload.ts b/receivers/common/web/player/Preload.ts index b645182..f92931a 100644 --- a/receivers/common/web/player/Preload.ts +++ b/receivers/common/web/player/Preload.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage, EventMessage, PlayMessage } from 'common/Packets'; import { Logger, LoggerType } from 'common/Logger'; +import { toast, ToastIcon } from 'common/components/Toast'; const logger = new Logger('PlayerWindow', LoggerType.FRONTEND); // Cannot directly pass the object to the renderer for some reason... @@ -35,11 +36,15 @@ if (TARGET === 'electron') { // @ts-ignore const electronAPI = __non_webpack_require__('electron'); - electronAPI.ipcRenderer.on("event-subscribed-keys-update", (_event, value: { keyDown: Set, keyUp: Set }) => { + electronAPI.ipcRenderer.on('event-subscribed-keys-update', (_event, value: { keyDown: Set, keyUp: Set }) => { preloadData.subscribedKeys.keyDown = value.keyDown; preloadData.subscribedKeys.keyUp = value.keyUp; }) + electronAPI.ipcRenderer.on('toast', (_event, message: string, icon: ToastIcon = ToastIcon.INFO, duration: number = 5000) => { + toast(message, icon, duration); + }) + electronAPI.contextBridge.exposeInMainWorld('targetAPI', { sendPlaybackUpdate: (update: PlaybackUpdateMessage) => electronAPI.ipcRenderer.send('send-playback-update', update), sendVolumeUpdate: (update: VolumeUpdateMessage) => electronAPI.ipcRenderer.send('send-volume-update', update), diff --git a/receivers/electron/src/Main.ts b/receivers/electron/src/Main.ts index 5718d6a..2536e56 100644 --- a/receivers/electron/src/Main.ts +++ b/receivers/electron/src/Main.ts @@ -1,4 +1,5 @@ import { BrowserWindow, ipcMain, IpcMainEvent, nativeImage, Tray, Menu, dialog, shell } from 'electron'; +import { ToastIcon } from 'common/components/Toast'; import { Opcode, PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage, PlayMessage, PlayUpdateMessage, EventMessage, EventType, ContentObject, ContentType, PlaylistContent, SeekMessage, SetVolumeMessage, SetSpeedMessage, SetPlaylistItemMessage } from 'common/Packets'; import { supportedPlayerTypes } from 'common/MimeTypes'; import { DiscoveryService } from 'common/DiscoveryService'; @@ -9,7 +10,7 @@ import { ConnectionMonitor } from 'common/ConnectionMonitor'; import { Logger, LoggerType } from 'common/Logger'; import { fetchJSON } from 'common/UtilityBackend'; import { MediaCache } from 'common/MediaCache'; -import { Store } from 'common/Store'; +import { Settings } from 'common/Settings'; import { Updater } from './Updater'; import * as os from 'os'; import * as path from 'path'; @@ -464,6 +465,14 @@ export class Main { Main.cache.appName = app.name; Main.cache.appVersion = app.getVersion(); + // Using singleton classes for better compatibility running on webOS + const jsonPath = path.join(app.getPath('userData'), 'UserSettings.json'); + new Settings(jsonPath); + + if (Settings.json.network.ignoreCertificateErrors) { + app.commandLine.appendSwitch('ignore-certificate-errors'); + } + const argv = yargs(hideBin(process.argv)) .version(app.getVersion()) .parserConfiguration({ @@ -476,10 +485,6 @@ export class Main { }) .parseSync(); - // Using singleton classes for better compatibility running on webOS - const jsonPath = path.join(app.getPath('userData'), 'UserSettings.json'); - new Store(jsonPath); - new Updater(); const isUpdating = Updater.isUpdating(); const fileLogType = isUpdating ? 'fileSync' : 'file'; @@ -524,6 +529,11 @@ export class Main { Main.openMainWindow(); } }) + + Main.application.on('certificate-error', (_event, _webContents, url, error, certificate) => { + toast('Could not playback media (certificate error)', ToastIcon.ERROR); + logger.error('Could not playback media (certificate error):', { url: url, error: error, certificate: certificate }); + }); Main.application.on('ready', Main.onReady); Main.application.on('window-all-closed', () => { }); } @@ -534,6 +544,11 @@ export class Main { } } +export function toast(message: string, icon: ToastIcon = ToastIcon.INFO, duration: number = 5000) { + Main.mainWindow?.webContents?.send('toast', message, icon, duration); + Main.playerWindow?.webContents?.send('toast', message, icon, duration); +} + export function getComputerName() { switch (process.platform) { case "win32": diff --git a/receivers/electron/src/Updater.ts b/receivers/electron/src/Updater.ts index 6223e45..b684ff1 100644 --- a/receivers/electron/src/Updater.ts +++ b/receivers/electron/src/Updater.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as crypto from 'crypto'; import { app } from 'electron'; import sudo from 'sudo-prompt'; -import { Store } from 'common/Store'; +import { Settings } from 'common/Settings'; import { Logger, LoggerType } from 'common/Logger'; import { fetchJSON, downloadFile } from 'common/UtilityBackend'; @@ -50,7 +50,7 @@ interface UpdateConditions { } export class Updater { - private static instance: Store = null; + private static instance: Updater = null; private static readonly supportedReleasesJsonVersion = '1'; private static appPath: string = app.getAppPath(); @@ -78,7 +78,7 @@ export class Updater { if (!Updater.instance) { Updater.localPackageJson = JSON.parse(fs.readFileSync(path.join(Updater.appPath, './package.json'), 'utf8')); - let updaterSettings = Store.settings.updater; + let updaterSettings = Settings.json.updater; if (updaterSettings !== null) { Updater.updateChannel = updaterSettings.channel === undefined ? Updater.localPackageJson.channel : updaterSettings.channel; Updater.checkForUpdatesOnStart = updaterSettings.checkForUpdatesOnStart === undefined ? true : updaterSettings.checkForUpdatesOnStart; @@ -90,8 +90,8 @@ export class Updater { } Updater.releaseChannel = Updater.localPackageJson.channel; - Store.settings.updater = updaterSettings; - Store.saveSettings(); + Settings.json.updater = updaterSettings; + Settings.save(); Updater.instance = this; }