diff --git a/receivers/common/web/ConnectionMonitor.ts b/receivers/common/web/ConnectionMonitor.ts index 09ef1f2..e8a5cef 100644 --- a/receivers/common/web/ConnectionMonitor.ts +++ b/receivers/common/web/ConnectionMonitor.ts @@ -7,6 +7,7 @@ let uiUpdateCallbacks = { onConnect: null, onDisconnect: null, } +const logger = window.targetAPI.logger; // Window might be re-created while devices are still connected export function setUiUpdateCallbacks(callbacks: any) { @@ -27,12 +28,12 @@ window.targetAPI.onPing((_event, value: any) => onPingPong(value)); window.targetAPI.onPong((_event, value: any) => onPingPong(value)); window.targetAPI.onConnect((_event, value: any) => { - console.log(`Device connected: ${JSON.stringify(value)}`); + logger.info(`Device connected: ${JSON.stringify(value)}`); connections.push(value.sessionId); uiUpdateCallbacks.onConnect(connections); }); window.targetAPI.onDisconnect((_event, value: any) => { - console.log(`Device disconnected: ${JSON.stringify(value)}`); + logger.info(`Device disconnected: ${JSON.stringify(value)}`); const index = connections.indexOf(value.sessionId); if (index != -1) { connections.splice(index, 1); @@ -46,7 +47,7 @@ setInterval(() => { for (const sessionId of connections) { if (heartbeatRetries[sessionId] > 3) { - console.warn(`Could not ping device with connection id ${sessionId}. Disconnecting...`); + logger.warn(`Could not ping device with connection id ${sessionId}. Disconnecting...`); window.targetAPI.disconnectDevice(sessionId); } diff --git a/receivers/common/web/DiscoveryService.ts b/receivers/common/web/DiscoveryService.ts index b78d3ad..836a8ab 100644 --- a/receivers/common/web/DiscoveryService.ts +++ b/receivers/common/web/DiscoveryService.ts @@ -1,5 +1,7 @@ import mdns from 'modules/mdns-js'; -import { Main, getComputerName } from 'src/Main'; +import { Logger, LoggerType } from 'common/Logger'; +import { getComputerName } from 'src/Main'; +const logger = new Logger('DiscoveryService', LoggerType.BACKEND); export class DiscoveryService { private serviceTcp: any; @@ -11,13 +13,7 @@ export class DiscoveryService { } const name = `FCast-${getComputerName()}`; - // Cannot reference Main during static class initialization - // @ts-ignore - if (TARGET === 'webOS') { - console.log(`Discovery service started: ${name}`); - } else { - Main.logger.info(`Discovery service started: ${name}`); - } + logger.info(`Discovery service started: ${name}`); this.serviceTcp = mdns.createAdvertisement(mdns.tcp('_fcast'), 46899, { name: name }); this.serviceTcp.start(); diff --git a/receivers/common/web/FCastSession.ts b/receivers/common/web/FCastSession.ts index 22fd70c..87325da 100644 --- a/receivers/common/web/FCastSession.ts +++ b/receivers/common/web/FCastSession.ts @@ -1,10 +1,10 @@ import * as net from 'net'; -import * as log4js from "modules/log4js"; import { EventEmitter } from 'events'; import { Opcode, PlaybackErrorMessage, PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage, VersionMessage, VolumeUpdateMessage } from 'common/Packets'; +import { Logger, LoggerType } from 'common/Logger'; import { WebSocket } from 'modules/ws'; import { v4 as uuidv4 } from 'modules/uuid'; -const logger = log4js.getLogger(); +const logger = new Logger('FCastSession', LoggerType.BACKEND); enum SessionState { Idle = 0, @@ -100,7 +100,7 @@ export class FCastSession { return; } - logger.info(`${receivedBytes.length} bytes received`); + logger.debug(`${receivedBytes.length} bytes received`); switch (this.state) { case SessionState.WaitingForLength: @@ -110,7 +110,7 @@ export class FCastSession { this.handlePacketBytes(receivedBytes); break; default: - logger.info(`Data received is unhandled in current session state ${this.state}.`); + logger.warn(`Data received is unhandled in current session state ${this.state}.`); break; } } @@ -121,20 +121,20 @@ export class FCastSession { receivedBytes.copy(this.buffer, this.bytesRead, 0, bytesToRead); this.bytesRead += bytesToRead; - logger.info(`handleLengthBytes: Read ${bytesToRead} bytes from packet`); + logger.debug(`handleLengthBytes: Read ${bytesToRead} bytes from packet`); if (this.bytesRead >= LENGTH_BYTES) { this.state = SessionState.WaitingForData; this.packetLength = this.buffer.readUInt32LE(0); this.bytesRead = 0; - logger.info(`Packet length header received from: ${this.packetLength}`); + logger.debug(`Packet length header received from: ${this.packetLength}`); if (this.packetLength > MAXIMUM_PACKET_LENGTH) { throw new Error(`Maximum packet length is 32kB: ${this.packetLength}`); } if (bytesRemaining > 0) { - logger.info(`${bytesRemaining} remaining bytes pushed to handlePacketBytes`); + logger.debug(`${bytesRemaining} remaining bytes pushed to handlePacketBytes`); this.handlePacketBytes(receivedBytes.slice(bytesToRead)); } } @@ -146,10 +146,10 @@ export class FCastSession { receivedBytes.copy(this.buffer, this.bytesRead, 0, bytesToRead); this.bytesRead += bytesToRead; - logger.info(`handlePacketBytes: Read ${bytesToRead} bytes from packet`); + logger.debug(`handlePacketBytes: Read ${bytesToRead} bytes from packet`); if (this.bytesRead >= this.packetLength) { - logger.info(`Packet finished receiving from of ${this.packetLength} bytes.`); + logger.debug(`handlePacketBytes: Finished handling packet with ${this.packetLength} bytes. Total bytes read ${this.bytesRead}.`); this.handleNextPacket(); this.state = SessionState.WaitingForLength; @@ -157,7 +157,7 @@ export class FCastSession { this.bytesRead = 0; if (bytesRemaining > 0) { - logger.info(`${bytesRemaining} remaining bytes pushed to handleLengthBytes`); + logger.debug(`${bytesRemaining} remaining bytes pushed to handleLengthBytes`); this.handleLengthBytes(receivedBytes.slice(bytesToRead)); } } @@ -206,11 +206,8 @@ export class FCastSession { } private handleNextPacket() { - logger.info(`Processing packet of ${this.bytesRead} bytes from`); - const opcode = this.buffer[0]; const body = this.packetLength > 1 ? this.buffer.toString('utf8', 1, this.packetLength) : null; - logger.info('body', body); this.handlePacket(opcode, body); } diff --git a/receivers/common/web/Logger.ts b/receivers/common/web/Logger.ts new file mode 100644 index 0000000..2809b30 --- /dev/null +++ b/receivers/common/web/Logger.ts @@ -0,0 +1,197 @@ + +export enum LoggerType { + BACKEND, + FRONTEND, +} + +export class Logger { + private static log4js = null; + private static ipcLoggerTags = { + trace: [], + debug: [], + info: [], + warn: [], + error: [], + fatal: [], + }; + private funcTable = { + trace: console.trace, + debug: console.debug, + info: console.log, + warn: console.warn, + error: console.error, + fatal: console.error, + }; + + public static initialize(config: any) { + // @ts-ignore + if (TARGET === 'electron') { + // @ts-ignore + const electronAPI = __non_webpack_require__('electron'); + + // @ts-ignore + Logger.log4js = __non_webpack_require__('log4js'); + Logger.log4js.configure(config); + let logger = Logger.log4js.getLogger(); + + electronAPI.ipcMain.on('logger-trace', (event, value) => { + if (!Logger.ipcLoggerTags.trace.includes(value.tag)) { + Logger.ipcLoggerTags.trace.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.info(value.message, ...value.optionalParams); + }); + + electronAPI.ipcMain.on('logger-debug', (event, value) => { + if (!Logger.ipcLoggerTags.debug.includes(value.tag)) { + Logger.ipcLoggerTags.debug.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.info(value.message, ...value.optionalParams); + }); + + electronAPI.ipcMain.on('logger-info', (event, value) => { + if (!Logger.ipcLoggerTags.info.includes(value.tag)) { + Logger.ipcLoggerTags.info.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.info(value.message, ...value.optionalParams); + }); + + electronAPI.ipcMain.on('logger-warn', (event, value) => { + if (!Logger.ipcLoggerTags.warn.includes(value.tag)) { + Logger.ipcLoggerTags.warn.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.warn(value.message, ...value.optionalParams); + }); + + electronAPI.ipcMain.on('logger-error', (event, value) => { + if (!Logger.ipcLoggerTags.error.includes(value.tag)) { + Logger.ipcLoggerTags.error.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.error(value.message, ...value.optionalParams); + }); + + electronAPI.ipcMain.on('logger-fatal', (event, value) => { + if (!Logger.ipcLoggerTags.fatal.includes(value.tag)) { + Logger.ipcLoggerTags.fatal.push(value.tag); + logger = Logger.log4js.getLogger(value.tag); + } + + logger.fatal(value.message, ...value.optionalParams); + }); + } + } + + constructor(tag: string = 'default', type: LoggerType = LoggerType.FRONTEND) { + // @ts-ignore + if (TARGET === 'electron') { + if (type === LoggerType.BACKEND) { + // log4js should be initialized via the static method, but providing fallback when logger is used + // before initialization as done currently by the Store module on startup. + if (!Logger.log4js) { + // @ts-ignore + Logger.log4js = __non_webpack_require__('log4js'); + } + + const logger = Logger.log4js.getLogger(tag); + this.funcTable = { + trace: (message?: any, ...optionalParams: any[]) => { + logger.trace(message, ...optionalParams); + }, + debug: (message?: any, ...optionalParams: any[]) => { + logger.debug(message, ...optionalParams); + }, + info: (message?: any, ...optionalParams: any[]) => { + logger.info(message, ...optionalParams); + }, + warn: (message?: any, ...optionalParams: any[]) => { + logger.warn(message, ...optionalParams); + }, + error: (message?: any, ...optionalParams: any[]) => { + logger.error(message, ...optionalParams); + }, + fatal: (message?: any, ...optionalParams: any[]) => { + logger.fatal(message, ...optionalParams); + }, + }; + } + else if (type === LoggerType.FRONTEND) { + // @ts-ignore + const electronAPI = __non_webpack_require__('electron'); + + this.funcTable = { + trace: (message?: any, ...optionalParams: any[]) => { + console.trace(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-trace', { tag: tag, message: message, optionalParams: optionalParams }); + }, + debug: (message?: any, ...optionalParams: any[]) => { + console.debug(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-debug', { tag: tag, message: message, optionalParams: optionalParams }); + }, + info: (message?: any, ...optionalParams: any[]) => { + console.log(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-info', { tag: tag, message: message, optionalParams: optionalParams }); + }, + warn: (message?: any, ...optionalParams: any[]) => { + console.warn(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-warn', { tag: tag, message: message, optionalParams: optionalParams }); + }, + error: (message?: any, ...optionalParams: any[]) => { + console.error(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-error', { tag: tag, message: message, optionalParams: optionalParams }); + }, + fatal: (message?: any, ...optionalParams: any[]) => { + console.error(message, ...optionalParams); + electronAPI.ipcRenderer.send('logger-fatal', { tag: tag, message: message, optionalParams: optionalParams }); + }, + }; + } + + // @ts-ignore + } else if (TARGET === 'webOS' || TARGET === 'tizenOS') { + + } else { + // @ts-ignore + console.warn(`Attempting to initialize logger on unsupported target: ${TARGET}`); + } + } + + public trace(message?: any, ...optionalParams: any[]): void { + this.funcTable.trace(message, ...optionalParams); + } + + public debug(message?: any, ...optionalParams: any[]): void { + this.funcTable.debug(message, ...optionalParams); + } + + public info(message?: any, ...optionalParams: any[]): void { + this.funcTable.info(message, ...optionalParams); + } + + public warn(message?: any, ...optionalParams: any[]): void { + this.funcTable.warn(message, ...optionalParams); + } + + public error(message?: any, ...optionalParams: any[]): void { + this.funcTable.error(message, ...optionalParams); + } + + public fatal(message?: any, ...optionalParams: any[]): void { + this.funcTable.fatal(message, ...optionalParams); + } + + public shutdown() { + // @ts-ignore + if (TARGET === 'electron') { + Logger.log4js.shutdown(); + } + } +} diff --git a/receivers/common/web/NetworkService.ts b/receivers/common/web/NetworkService.ts index 56abb3d..5386121 100644 --- a/receivers/common/web/NetworkService.ts +++ b/receivers/common/web/NetworkService.ts @@ -3,7 +3,8 @@ import * as http from 'http'; import * as url from 'url'; import { AddressInfo } from 'modules/ws'; import { v4 as uuidv4 } from 'modules/uuid'; -import { Main } from 'src/Main'; +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('NetworkService', LoggerType.BACKEND); export class NetworkService { static key: string = null; @@ -15,11 +16,11 @@ export class NetworkService { private static setupProxyServer(): Promise { return new Promise((resolve, reject) => { try { - Main.logger.info(`Proxy server starting`); + logger.info(`Proxy server starting`); const port = 0; NetworkService.proxyServer = http.createServer((req, res) => { - Main.logger.info(`Request received`); + logger.info(`Request received`); const requestUrl = `http://${req.headers.host}${req.url}`; const proxyInfo = NetworkService.proxiedFiles.get(requestUrl); @@ -60,7 +61,7 @@ export class NetworkService { req.pipe(proxyReq, { end: true }); proxyReq.on('error', (e) => { - Main.logger.error(`Problem with request: ${e.message}`); + logger.error(`Problem with request: ${e.message}`); res.writeHead(500); res.end(); }); @@ -70,7 +71,7 @@ export class NetworkService { }); NetworkService.proxyServer.listen(port, '127.0.0.1', () => { NetworkService.proxyServerAddress = NetworkService.proxyServer.address() as AddressInfo; - Main.logger.info(`Proxy server running at http://127.0.0.1:${NetworkService.proxyServerAddress.port}/`); + logger.info(`Proxy server running at http://127.0.0.1:${NetworkService.proxyServerAddress.port}/`); resolve(); }); } catch (e) { @@ -98,7 +99,7 @@ export class NetworkService { } const proxiedUrl = `http://127.0.0.1:${NetworkService.proxyServerAddress.port}/${uuidv4()}`; - Main.logger.info("Proxied url", { proxiedUrl, url, headers }); + logger.info("Proxied url", { proxiedUrl, url, headers }); NetworkService.proxiedFiles.set(proxiedUrl, { url: url, headers: headers }); return proxiedUrl; } diff --git a/receivers/common/web/TcpListenerService.ts b/receivers/common/web/TcpListenerService.ts index bf0475d..76f1d25 100644 --- a/receivers/common/web/TcpListenerService.ts +++ b/receivers/common/web/TcpListenerService.ts @@ -1,8 +1,10 @@ import * as net from 'net'; import { FCastSession } from 'common/FCastSession'; import { Opcode } from 'common/Packets'; +import { Logger, LoggerType } from 'common/Logger'; import { EventEmitter } from 'events'; -import { Main, errorHandler } from 'src/Main'; +import { errorHandler } from 'src/Main'; +const logger = new Logger('TcpListenerService', LoggerType.BACKEND); export class TcpListenerService { public static PORT = 46899; @@ -35,12 +37,12 @@ export class TcpListenerService { } send(opcode: number, message = null) { - // Main.logger.info(`Sending message ${JSON.stringify(message)}`); + // logger.info(`Sending message ${JSON.stringify(message)}`); this.sessions.forEach(session => { try { session.send(opcode, message); } catch (e) { - Main.logger.warn("Failed to send error.", e); + logger.warn("Failed to send error.", e); session.close(); } }); @@ -60,7 +62,7 @@ export class TcpListenerService { } private handleConnection(socket: net.Socket) { - Main.logger.info(`New connection from ${socket.remoteAddress}:${socket.remotePort}`); + logger.info(`New connection from ${socket.remoteAddress}:${socket.remotePort}`); const session = new FCastSession(socket, (data) => socket.write(data)); session.bindEvents(this.emitter); @@ -68,7 +70,7 @@ export class TcpListenerService { this.sessionMap.set(session.sessionId, session); socket.on("error", (err) => { - Main.logger.warn(`Error from ${socket.remoteAddress}:${socket.remotePort}.`, err); + logger.warn(`Error from ${socket.remoteAddress}:${socket.remotePort}.`, err); this.disconnect(session.sessionId); }); @@ -76,7 +78,7 @@ export class TcpListenerService { try { session.processBytes(buffer); } catch (e) { - Main.logger.warn(`Error while handling packet from ${socket.remoteAddress}:${socket.remotePort}.`, e); + logger.warn(`Error while handling packet from ${socket.remoteAddress}:${socket.remotePort}.`, e); socket.end(); } }); @@ -91,10 +93,10 @@ export class TcpListenerService { this.emitter.emit('connect', { sessionId: session.sessionId, type: 'tcp', data: { address: socket.remoteAddress, port: socket.remotePort }}); try { - Main.logger.info('Sending version'); + logger.info('Sending version'); session.send(Opcode.Version, {version: 2}); } catch (e) { - Main.logger.info('Failed to send version', e); + logger.info('Failed to send version', e); } } } diff --git a/receivers/common/web/WebSocketListenerService.ts b/receivers/common/web/WebSocketListenerService.ts index 49ea7dc..ad56130 100644 --- a/receivers/common/web/WebSocketListenerService.ts +++ b/receivers/common/web/WebSocketListenerService.ts @@ -1,8 +1,10 @@ import { FCastSession } from 'common/FCastSession'; import { Opcode } from 'common/Packets'; +import { Logger, LoggerType } from 'common/Logger'; import { EventEmitter } from 'events'; import { WebSocket, WebSocketServer } from 'modules/ws'; -import { Main, errorHandler } from 'src/Main'; +import { errorHandler } from 'src/Main'; +const logger = new Logger('WebSocketListenerService', LoggerType.BACKEND); export class WebSocketListenerService { public static PORT = 46898; @@ -39,7 +41,7 @@ export class WebSocketListenerService { try { session.send(opcode, message); } catch (e) { - Main.logger.warn("Failed to send error.", e); + logger.warn("Failed to send error.", e); session.close(); } }); @@ -59,7 +61,7 @@ export class WebSocketListenerService { } private handleConnection(socket: WebSocket) { - Main.logger.info('New WebSocket connection'); + logger.info('New WebSocket connection'); const session = new FCastSession(socket, (data) => socket.send(data)); session.bindEvents(this.emitter); @@ -67,7 +69,7 @@ export class WebSocketListenerService { this.sessionMap.set(session.sessionId, session); socket.on("error", (err) => { - Main.logger.warn(`Error.`, err); + logger.warn(`Error.`, err); this.disconnect(session.sessionId); }); @@ -76,16 +78,16 @@ export class WebSocketListenerService { if (data instanceof Buffer) { session.processBytes(data); } else { - Main.logger.warn("Received unhandled string message", data); + logger.warn("Received unhandled string message", data); } } catch (e) { - Main.logger.warn(`Error while handling packet.`, e); + logger.warn(`Error while handling packet.`, e); session.close(); } }); socket.on("close", () => { - Main.logger.info('WebSocket connection closed'); + logger.info('WebSocket connection closed'); const index = this.sessions.indexOf(session); if (index != -1) { @@ -96,10 +98,10 @@ export class WebSocketListenerService { this.emitter.emit('connect', { sessionId: session.sessionId, type: 'ws', data: { url: socket.url }}); try { - Main.logger.info('Sending version'); + logger.info('Sending version'); session.send(Opcode.Version, {version: 2}); } catch (e) { - Main.logger.info('Failed to send version'); + logger.info('Failed to send version'); } } } diff --git a/receivers/common/web/main/Preload.ts b/receivers/common/web/main/Preload.ts index 606d6a7..2b07ca0 100644 --- a/receivers/common/web/main/Preload.ts +++ b/receivers/common/web/main/Preload.ts @@ -1,6 +1,18 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Opcode } from 'common/Packets'; +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('MainWindow', LoggerType.FRONTEND); + +// Cannot directly pass the object to the renderer for some reason... +const loggerInterface = { + trace: (message?: any, ...optionalParams: any[]) => { logger.trace(message, ...optionalParams); }, + debug: (message?: any, ...optionalParams: any[]) => { logger.debug(message, ...optionalParams); }, + info: (message?: any, ...optionalParams: any[]) => { logger.info(message, ...optionalParams); }, + warn: (message?: any, ...optionalParams: any[]) => { logger.warn(message, ...optionalParams); }, + error: (message?: any, ...optionalParams: any[]) => { logger.error(message, ...optionalParams); }, + fatal: (message?: any, ...optionalParams: any[]) => { logger.fatal(message, ...optionalParams); }, +}; declare global { interface Window { @@ -32,15 +44,16 @@ if (TARGET === 'electron') { onDisconnect: (callback: any) => electronAPI.ipcRenderer.on('disconnect', callback), onPing: (callback: any) => electronAPI.ipcRenderer.on('ping', callback), onPong: (callback: any) => electronAPI.ipcRenderer.on('pong', callback), + logger: loggerInterface, }); // @ts-ignore } else if (TARGET === 'webOS' || TARGET === 'tizenOS') { preloadData = { - onDeviceInfoCb: () => { console.log('Main: Callback not set while fetching device info'); }, - onConnectCb: (_, value: any) => { console.log('Main: Callback not set while calling onConnect'); }, - onDisconnectCb: (_, value: any) => { console.log('Main: Callback not set while calling onDisconnect'); }, - onPingCb: (_, value: any) => { console.log('Main: Callback not set while calling onPing'); }, + onDeviceInfoCb: () => { logger.error('Main: Callback not set while fetching device info'); }, + onConnectCb: (_, value: any) => { logger.error('Main: Callback not set while calling onConnect'); }, + onDisconnectCb: (_, value: any) => { logger.error('Main: Callback not set while calling onDisconnect'); }, + onPingCb: (_, value: any) => { logger.error('Main: Callback not set while calling onPing'); }, }; window.targetAPI = { @@ -49,10 +62,11 @@ if (TARGET === 'electron') { onDisconnect: (callback: (_, value: any) => void) => preloadData.onDisconnectCb = callback, onPing: (callback: (_, value: any) => void) => preloadData.onPingCb = callback, getDeviceInfo: () => preloadData.deviceInfo, + logger: loggerInterface, }; } else { // @ts-ignore - console.log(`Attempting to run FCast player on unsupported target: ${TARGET}`); + logger.warn(`Attempting to run FCast player on unsupported target: ${TARGET}`); } export { diff --git a/receivers/common/web/main/Renderer.ts b/receivers/common/web/main/Renderer.ts index 3ffa354..74fbac5 100644 --- a/receivers/common/web/main/Renderer.ts +++ b/receivers/common/web/main/Renderer.ts @@ -12,6 +12,7 @@ let renderedAddresses = null; let qrCodeUrl = null; let qrWidth = null; +const logger = window.targetAPI.logger; window.addEventListener('resize', (event) => calculateQRCodeWidth()); connectionMonitor.setUiUpdateCallbacks({ @@ -37,13 +38,13 @@ connectionMonitor.setUiUpdateCallbacks({ window.targetAPI.onDeviceInfo(renderIPsAndQRCode); if(window.targetAPI.getDeviceInfo()) { - console.log('device info already present'); + logger.info('device info already present'); renderIPsAndQRCode(); } function renderIPsAndQRCode() { const value = window.targetAPI.getDeviceInfo(); - console.log(`Network Interface Info: ${JSON.stringify(value)}`); + logger.info(`Network Interface Info: ${JSON.stringify(value)}`); renderIPs(value.interfaces); const addresses = []; @@ -91,7 +92,7 @@ function renderIPsAndQRCode() { let base64 = btoa(json); base64 = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); qrCodeUrl = `fcast://r/${base64}`; - console.log('QR Code:', {json, qrCodeUrl, base64}); + logger.info('QR Code:', {json, qrCodeUrl, base64}); calculateQRCodeWidth(); if (!renderedConnectionInfo) { @@ -188,11 +189,11 @@ function renderQRCode(url: string) { }, (err) => { if (err) { - console.error(`Error rendering QR Code: ${err}`); + logger.error(`Error rendering QR Code: ${err}`); toast(`Error rendering QR Code: ${err}`, ToastIcon.ERROR); } else { - console.log(`Rendered QR Code`); + logger.info(`Rendered QR Code`); } }); diff --git a/receivers/common/web/player/Player.ts b/receivers/common/web/player/Player.ts index 9f7763d..5eb0735 100644 --- a/receivers/common/web/player/Player.ts +++ b/receivers/common/web/player/Player.ts @@ -1,6 +1,8 @@ import dashjs from 'modules/dashjs'; import Hls from 'modules/hls.js'; +const logger = window.targetAPI.logger; + export enum PlayerType { Html, Dash, @@ -26,7 +28,7 @@ export class Player { try { (this.player as dashjs.MediaPlayerClass).destroy(); } catch (e) { - console.warn("Failed to destroy dash player", e); + logger.warn("Failed to destroy dash player", e); } this.player = null; this.playerType = null; @@ -37,7 +39,7 @@ export class Player { try { this.hlsPlayer.destroy(); } catch (e) { - console.warn("Failed to destroy hls player", e); + logger.warn("Failed to destroy hls player", e); } // fall through @@ -65,7 +67,7 @@ export class Player { } } - play() { console.log("Player: play"); this.player.play(); } + play() { logger.info("Player: play"); this.player.play(); } isPaused(): boolean { if (this.playerType === PlayerType.Dash) { @@ -74,7 +76,7 @@ export class Player { return (this.player as HTMLVideoElement).paused; } } - pause() { console.log("Player: pause"); this.player.pause(); } + pause() { logger.info("Player: pause"); this.player.pause(); } getVolume(): number { if (this.playerType === PlayerType.Dash) { @@ -84,7 +86,7 @@ export class Player { } } setVolume(value: number) { - console.log(`Player: setVolume ${value}`); + logger.info(`Player: setVolume ${value}`); const sanitizedVolume = Math.min(1.0, Math.max(0.0, value)); if (this.playerType === PlayerType.Dash) { @@ -102,7 +104,7 @@ export class Player { } } setMute(value: boolean) { - console.log(`Player: setMute ${value}`); + logger.info(`Player: setMute ${value}`); if (this.playerType === PlayerType.Dash) { (this.player as dashjs.MediaPlayerClass).setMute(value); @@ -119,7 +121,7 @@ export class Player { } } setPlaybackRate(value: number) { - console.log(`Player: setPlaybackRate ${value}`); + logger.info(`Player: setPlaybackRate ${value}`); const sanitizedSpeed = Math.min(16.0, Math.max(0.0, value)); if (this.playerType === PlayerType.Dash) { @@ -147,7 +149,7 @@ export class Player { } } setCurrentTime(value: number) { - // console.log(`Player: setCurrentTime ${value}`); + // logger.info(`Player: setCurrentTime ${value}`); const sanitizedTime = Math.min(this.getDuration(), Math.max(0.0, value)); if (this.playerType === PlayerType.Dash) { diff --git a/receivers/common/web/player/Preload.ts b/receivers/common/web/player/Preload.ts index 6103bfe..3135d31 100644 --- a/receivers/common/web/player/Preload.ts +++ b/receivers/common/web/player/Preload.ts @@ -1,7 +1,18 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage, Opcode } from 'common/Packets'; -export {}; +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('PlayerWindow', LoggerType.FRONTEND); + +// Cannot directly pass the object to the renderer for some reason... +const loggerInterface = { + trace: (message?: any, ...optionalParams: any[]) => { logger.trace(message, ...optionalParams); }, + debug: (message?: any, ...optionalParams: any[]) => { logger.debug(message, ...optionalParams); }, + info: (message?: any, ...optionalParams: any[]) => { logger.info(message, ...optionalParams); }, + warn: (message?: any, ...optionalParams: any[]) => { logger.warn(message, ...optionalParams); }, + error: (message?: any, ...optionalParams: any[]) => { logger.error(message, ...optionalParams); }, + fatal: (message?: any, ...optionalParams: any[]) => { logger.fatal(message, ...optionalParams); }, +}; declare global { interface Window { @@ -37,21 +48,22 @@ if (TARGET === 'electron') { onDisconnect: (callback: any) => electronAPI.ipcRenderer.on('disconnect', callback), onPing: (callback: any) => electronAPI.ipcRenderer.on('ping', callback), onPong: (callback: any) => electronAPI.ipcRenderer.on('pong', callback), + logger: loggerInterface, }); // @ts-ignore } else if (TARGET === 'webOS' || TARGET === 'tizenOS') { preloadData = { - sendPlaybackErrorCb: () => { console.error('Player: Callback "send_playback_error" not set'); }, - sendPlaybackUpdateCb: () => { console.error('Player: Callback "send_playback_update" not set'); }, - sendVolumeUpdateCb: () => { console.error('Player: Callback "send_volume_update" not set'); }, - // onPlayCb: () => { console.error('Player: Callback "play" not set'); }, + sendPlaybackErrorCb: () => { logger.error('Player: Callback "send_playback_error" not set'); }, + sendPlaybackUpdateCb: () => { logger.error('Player: Callback "send_playback_update" not set'); }, + sendVolumeUpdateCb: () => { logger.error('Player: Callback "send_volume_update" not set'); }, + // onPlayCb: () => { logger.error('Player: Callback "play" not set'); }, onPlayCb: undefined, - onPauseCb: () => { console.error('Player: Callback "pause" not set'); }, - onResumeCb: () => { console.error('Player: Callback "resume" not set'); }, - onSeekCb: () => { console.error('Player: Callback "onseek" not set'); }, - onSetVolumeCb: () => { console.error('Player: Callback "setvolume" not set'); }, - onSetSpeedCb: () => { console.error('Player: Callback "setspeed" not set'); }, + onPauseCb: () => { logger.error('Player: Callback "pause" not set'); }, + onResumeCb: () => { logger.error('Player: Callback "resume" not set'); }, + onSeekCb: () => { logger.error('Player: Callback "onseek" not set'); }, + onSetVolumeCb: () => { logger.error('Player: Callback "setvolume" not set'); }, + onSetSpeedCb: () => { logger.error('Player: Callback "setspeed" not set'); }, }; window.targetAPI = { @@ -64,10 +76,11 @@ if (TARGET === 'electron') { onSeek: (callback: any) => { preloadData.onSeekCb = callback; }, onSetVolume: (callback: any) => { preloadData.onSetVolumeCb = callback; }, onSetSpeed: (callback: any) => { preloadData.onSetSpeedCb = callback; }, + logger: loggerInterface, }; } else { // @ts-ignore - console.log(`Attempting to run FCast player on unsupported target: ${TARGET}`); + logger.warn(`Attempting to run FCast player on unsupported target: ${TARGET}`); } export { diff --git a/receivers/common/web/player/Renderer.ts b/receivers/common/web/player/Renderer.ts index a0ab91f..0e492fc 100644 --- a/receivers/common/web/player/Renderer.ts +++ b/receivers/common/web/player/Renderer.ts @@ -12,6 +12,8 @@ import { captionsLineHeight } from 'src/player/Renderer'; +const logger = window.targetAPI.logger; + function formatDuration(duration: number) { if (isNaN(duration)) { return '00:00'; @@ -120,7 +122,7 @@ let captionsBaseHeight = 0; let captionsContentHeight = 0; function onPlay(_event, value: PlayMessage) { - console.log("Handle play message renderer", JSON.stringify(value)); + logger.info("Handle play message renderer", JSON.stringify(value)); const currentVolume = player ? player.getVolume() : null; const currentPlaybackRate = player ? player.getPlaybackRate() : null; @@ -143,7 +145,7 @@ function onPlay(_event, value: PlayMessage) { if ((value.url || value.content) && value.container && videoElement) { if (value.container === 'application/dash+xml') { - console.log("Loading dash player"); + logger.info("Loading dash player"); const dashPlayer = dashjs.MediaPlayer().create(); const source = value.content ? value.content : value.url; player = new Player(PlayerType.Dash, dashPlayer, source); @@ -236,7 +238,7 @@ function onPlay(_event, value: PlayMessage) { } } else if ((value.container === 'application/vnd.apple.mpegurl' || value.container === 'application/x-mpegURL') && !videoElement.canPlayType(value.container)) { - console.log("Loading hls player"); + logger.info("Loading hls player"); const config = { xhrSetup: function (xhr: XMLHttpRequest) { @@ -277,7 +279,7 @@ function onPlay(_event, value: PlayMessage) { // hlsPlayer.subtitleDisplay = true; } else { - console.log("Loading html player"); + logger.info("Loading html player"); player = new Player(PlayerType.Html, videoElement, value.url); videoElement.src = value.url; @@ -307,7 +309,7 @@ function onPlay(_event, value: PlayMessage) { }; videoElement.onerror = (event: Event | string, source?: string, lineno?: number, colno?: number, error?: Error) => { - console.error("Player error", {source, lineno, colno, error}); + logger.error("Player error", {source, lineno, colno, error}); }; videoElement.onloadedmetadata = (ev) => { @@ -419,7 +421,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) { break; case PlayerControlEvent.VolumeChange: { - // console.log(`VolumeChange: isMute ${player?.isMuted()}, volume: ${player?.getVolume()}`); + // logger.info(`VolumeChange: isMute ${player?.isMuted()}, volume: ${player?.getVolume()}`); const volume = Math.round(player?.getVolume() * playerCtrlVolumeBar.offsetWidth); if (player?.isMuted()) { @@ -440,7 +442,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) { } case PlayerControlEvent.TimeUpdate: { - // console.log(`TimeUpdate: Position: ${player.getCurrentTime()}, Duration: ${player.getDuration()}`); + // logger.info(`TimeUpdate: Position: ${player.getCurrentTime()}, Duration: ${player.getDuration()}`); if (isLive) { if (isLivePosition && player.getDuration() - player.getCurrentTime() > livePositionWindow) { @@ -736,7 +738,7 @@ const skipInterval = 10; const volumeIncrement = 0.1; function keyDownEventListener(event: any) { - // console.log("KeyDown", event); + // logger.info("KeyDown", event); const handledCase = targetKeyDownEventListener(event); if (handledCase) { return; diff --git a/receivers/electron/src/Main.ts b/receivers/electron/src/Main.ts index aadd9f4..e60b847 100644 --- a/receivers/electron/src/Main.ts +++ b/receivers/electron/src/Main.ts @@ -4,14 +4,15 @@ import { DiscoveryService } from 'common/DiscoveryService'; import { TcpListenerService } from 'common/TcpListenerService'; import { WebSocketListenerService } from 'common/WebSocketListenerService'; import { NetworkService } from 'common/NetworkService'; +import { Logger, LoggerType } from 'common/Logger'; import { Updater } from './Updater'; import * as os from 'os'; import * as path from 'path'; -import * as log4js from "log4js"; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { ToastIcon } from 'common/components/Toast'; const cp = require('child_process'); +let logger = null; export class Main { static shouldOpenMainWindow = true; @@ -23,7 +24,6 @@ export class Main { static webSocketListenerService: WebSocketListenerService; static discoveryService: DiscoveryService; static tray: Tray; - static logger: log4js.Logger; private static toggleMainWindow() { if (Main.mainWindow) { @@ -68,7 +68,7 @@ export class Main { }); } - Main.logger.error('Failed to check for updates:', err); + logger.error('Failed to check for updates:', err); } } @@ -180,8 +180,8 @@ export class Main { l.emitter.on("resume", () => Main.playerWindow?.webContents?.send("resume")); l.emitter.on("stop", () => { - Main.playerWindow?.close(); - Main.playerWindow = null; + Main.playerWindow?.close(); + Main.playerWindow = null; }); l.emitter.on("seek", (message) => Main.playerWindow?.webContents?.send("seek", message)); @@ -242,7 +242,7 @@ export class Main { defaultId: 0 }); - Main.logger.error('Failed to download update:', err); + logger.error('Failed to download update:', err); Main.mainWindow?.webContents?.send("download-failed"); } } @@ -342,7 +342,7 @@ export class Main { Main.mainWindow.on('closed', () => { Main.mainWindow = null; - if (!networkWorker.isDestoryed()) { + if (!networkWorker.isDestroyed()) { networkWorker.close(); } }); @@ -366,27 +366,28 @@ export class Main { }) .options({ 'no-main-window': { type: 'boolean', default: false, desc: "Start minimized to tray" }, - 'fullscreen': { type: 'boolean', default: false, desc: "Start application in fullscreen" } + 'fullscreen': { type: 'boolean', default: false, desc: "Start application in fullscreen" }, + 'log': { chocies: ['ALL', 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'MARK', 'OFF'], alias: 'loglevel', default: 'INFO', desc: "Defines the verbosity level of the logger" }, }) .parseSync(); const isUpdating = Updater.isUpdating(); const fileLogType = isUpdating ? 'fileSync' : 'file'; - log4js.configure({ + Logger.initialize({ appenders: { out: { type: 'stdout' }, log: { type: fileLogType, filename: path.join(app.getPath('logs'), 'fcast-receiver.log'), flags: 'a', maxLogSize: '5M' }, }, categories: { - default: { appenders: ['out', 'log'], level: 'info' }, + default: { appenders: ['out', 'log'], level: argv.log }, }, }); - Main.logger = log4js.getLogger(); - Main.logger.info(`Starting application: ${app.name} | ${app.getAppPath()}`); - Main.logger.info(`Version: ${app.getVersion()}`); - Main.logger.info(`Commit: ${Updater.getCommit()}`); - Main.logger.info(`Release channel: ${Updater.releaseChannel} - ${Updater.getChannelVersion()}`); - Main.logger.info(`OS: ${process.platform} ${process.arch}`); + logger = new Logger('Main', LoggerType.BACKEND); + logger.info(`Starting application: ${app.name} | ${app.getAppPath()}`); + logger.info(`Version: ${app.getVersion()}`); + logger.info(`Commit: ${Updater.getCommit()}`); + logger.info(`Release channel: ${Updater.releaseChannel} - ${Updater.getChannelVersion()}`); + logger.info(`OS: ${process.platform} ${process.arch}`); if (isUpdating) { await Updater.processUpdate(); @@ -415,7 +416,7 @@ export class Main { Main.application.on('window-all-closed', () => { }); } catch (err) { - Main.logger.error(`Error starting application: ${err}`); + logger.error(`Error starting application: ${err}`); app.exit(); } } @@ -435,15 +436,15 @@ export function getComputerName() { hostname = os.hostname(); } catch (err) { - Main.logger.warn('Error fetching hostname, trying different method...'); - Main.logger.warn(err); + logger.warn('Error fetching hostname, trying different method...'); + logger.warn(err); try { hostname = cp.execSync("hostnamectl hostname").toString().trim(); } catch (err2) { - Main.logger.warn('Error fetching hostname again, using generic name...'); - Main.logger.warn(err2); + logger.warn('Error fetching hostname again, using generic name...'); + logger.warn(err2); hostname = 'linux device'; } @@ -458,7 +459,7 @@ export function getComputerName() { } export async function errorHandler(err: NodeJS.ErrnoException) { - Main.logger.error("Application error:", err); + logger.error("Application error:", err); Main.mainWindow?.webContents?.send("toast", { message: err, icon: ToastIcon.ERROR }); const restartPrompt = await dialog.showMessageBox({ diff --git a/receivers/electron/src/Store.ts b/receivers/electron/src/Store.ts index fda7ca1..1dc7f76 100644 --- a/receivers/electron/src/Store.ts +++ b/receivers/electron/src/Store.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import storage from 'electron-json-storage'; import { app } from 'electron'; -import * as log4js from "log4js"; -const logger = log4js.getLogger(); +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('Store', LoggerType.BACKEND); export class Store { private static storeVersion = 1; diff --git a/receivers/electron/src/Updater.ts b/receivers/electron/src/Updater.ts index a8ba06a..9fe57c4 100644 --- a/receivers/electron/src/Updater.ts +++ b/receivers/electron/src/Updater.ts @@ -2,13 +2,14 @@ import * as fs from 'fs'; import * as https from 'https'; import * as path from 'path'; import * as crypto from 'crypto'; -import * as log4js from "log4js"; import { app } from 'electron'; import { Store } from './Store'; import sudo from 'sudo-prompt'; +import { Logger, LoggerType } from 'common/Logger'; + const cp = require('child_process'); const extract = require('extract-zip'); -const logger = log4js.getLogger(); +const logger = new Logger('Updater', LoggerType.BACKEND); enum UpdateState { Copy = 'copy', @@ -193,7 +194,7 @@ export class Updater { // Also does not work very well on Mac... private static relaunch(binPath: string) { logger.info(`Relaunching app binary: ${binPath}`); - log4js.shutdown(); + logger.shutdown(); let proc; if (process.platform === 'win32') { @@ -394,7 +395,8 @@ export class Updater { logger.info('Extraction complete.'); const updateInfo: UpdateInfo = { - updateState: UpdateState.Copy, + // updateState: UpdateState.Copy, + updateState: UpdateState.Cleanup, installPath: Updater.installPath, tempPath: path.dirname(destination), currentVersion: Updater.releasesJson.currentVersion, diff --git a/receivers/electron/src/main/NetworkWorker.ts b/receivers/electron/src/main/NetworkWorker.ts index 17a0b94..1d11ce0 100644 --- a/receivers/electron/src/main/NetworkWorker.ts +++ b/receivers/electron/src/main/NetworkWorker.ts @@ -1,5 +1,7 @@ import { ipcRenderer } from 'electron'; import si from 'modules/systeminformation'; +import { Logger, LoggerType } from 'common/Logger'; +const logger = new Logger('NetworkWorker', LoggerType.FRONTEND); const networkStateChangeListenerTimeout = 2500; let networkStateChangeListenerInterfaces = []; @@ -10,11 +12,11 @@ setInterval(networkStateChangeListener, networkStateChangeListenerTimeout); function networkStateChangeListener(forceUpdate: boolean) { new Promise((resolve) => { si.networkInterfaces((data) => { - // console.log(data); + // logger.info(data); const queriedInterfaces = Array.isArray(data) ? data : [data]; si.wifiConnections((data) => { - // console.log(data); + // logger.info(data); const wifiConnections = Array.isArray(data) ? data : [data]; const interfaces = []; diff --git a/receivers/electron/src/main/Renderer.ts b/receivers/electron/src/main/Renderer.ts index b5c83c2..15a61cd 100644 --- a/receivers/electron/src/main/Renderer.ts +++ b/receivers/electron/src/main/Renderer.ts @@ -1,5 +1,6 @@ import 'common/main/Renderer'; +const logger = window.targetAPI.logger; export function onQRCodeRendered() {} const updateView = document.getElementById("update-view"); @@ -14,7 +15,7 @@ const progressBarProgress = document.getElementById("progress-bar-progress"); let updaterProgressUIUpdateTimer = null; window.electronAPI.onUpdateAvailable(() => { - console.log(`Received UpdateAvailable event`); + logger.info(`Received UpdateAvailable event`); updateViewTitle.textContent = 'FCast update available'; updateText.textContent = 'Do you wish to update now?'; @@ -26,7 +27,7 @@ window.electronAPI.onUpdateAvailable(() => { }); window.electronAPI.onDownloadComplete(() => { - console.log(`Received DownloadComplete event`); + logger.info(`Received DownloadComplete event`); window.clearTimeout(updaterProgressUIUpdateTimer); updateViewTitle.textContent = 'FCast update ready'; @@ -39,7 +40,7 @@ window.electronAPI.onDownloadComplete(() => { }); window.electronAPI.onDownloadFailed(() => { - console.log(`Received DownloadFailed event`); + logger.info(`Received DownloadFailed event`); window.clearTimeout(updaterProgressUIUpdateTimer); updateView.setAttribute("style", "display: none"); });