VYPR
Low severityOSV Advisory· Published Dec 17, 2025· Updated Dec 17, 2025

Mattermost Desktop App logging sensitive information and fails to clear data on server deletion

CVE-2025-13321

Description

Mattermost Desktop App versions <6.0.0 fail to sanitize sensitive information from Mattermost logs and clear data on server deletion which allows an attacker with access to the users system to gain access to potentially sensitive information via reading the application logs.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mattermost-desktopnpm
<= 3.6.0

Affected products

1

Patches

1
722938bb6a97

[MM-65011][MM-65664] Remove extraneous logging of potentially sensitive info, clear server data on removal (#3509)

https://github.com/mattermost/desktopDevin BinnieSep 22, 2025via ghsa
38 files changed · +511 183
  • src/app/callsWidgetWindow.ts+11 11 modified
    @@ -190,7 +190,7 @@ export class CallsWidgetWindow {
             this.win?.loadURL(widgetURL, {
                 userAgent: composeUserAgent(),
             }).catch((reason) => {
    -            log.error(`failed to load: ${reason}`);
    +            log.error('failed to load', {reason});
             });
         };
     
    @@ -245,12 +245,12 @@ export class CallsWidgetWindow {
             if (url === this.getWidgetURL()) {
                 return;
             }
    -        log.warn(`prevented widget window from navigating to: ${url}`);
    +        log.warn('prevented widget window from navigating');
             ev.preventDefault();
         };
     
         private setWidgetWindowStacking = ({onTop}: {onTop: boolean}) => {
    -        log.debug('setWidgetWindowStacking', onTop);
    +        log.debug('setWidgetWindowStacking', {onTop});
     
             if (!this.win) {
                 return;
    @@ -319,7 +319,7 @@ export class CallsWidgetWindow {
                 };
             }
     
    -        log.warn(`onPopOutOpen: prevented window open to ${url}`);
    +        log.warn('onPopOutOpen: prevented window open');
             return {action: 'deny' as const};
         };
     
    @@ -365,7 +365,7 @@ export class CallsWidgetWindow {
                         userAgent: composeUserAgent(),
                     });
                 } catch (e) {
    -                log.error('did-frame-finish-load, failed to reload with correct userAgent', e);
    +                log.error('did-frame-finish-load, failed to reload with correct userAgent', {e});
                 }
             });
         };
    @@ -375,7 +375,7 @@ export class CallsWidgetWindow {
          ************************/
     
         private handleResize = (ev: IpcMainEvent, width: number, height: number) => {
    -        log.debug('handleResize', width, height);
    +        log.debug('handleResize', {width, height});
     
             if (!this.win) {
                 return;
    @@ -420,7 +420,7 @@ export class CallsWidgetWindow {
         };
     
         private handleGetDesktopSources = async (event: IpcMainInvokeEvent, opts: Electron.SourcesOptions) => {
    -        log.debug('handleGetDesktopSources', opts);
    +        log.debug('handleGetDesktopSources');
     
             // For Calls we make an extra check to ensure the event is coming from the expected window (main view).
             // Otherwise we want to allow for other plugins to ask for screen sharing sources.
    @@ -446,7 +446,7 @@ export class CallsWidgetWindow {
                     }
                     this.missingScreensharePermissions = true;
                 } catch (err) {
    -                log.error('failed to reset screen sharing permissions', err);
    +                log.error('failed to reset screen sharing permissions', {err});
                 }
             }
     
    @@ -464,7 +464,7 @@ export class CallsWidgetWindow {
                 let hasScreenPermissions = true;
                 if (systemPreferences.getMediaAccessStatus) {
                     const screenPermissions = systemPreferences.getMediaAccessStatus('screen');
    -                log.debug('screenPermissions', screenPermissions);
    +                log.debug('screenPermissions', {screenPermissions});
                     if (screenPermissions === 'denied') {
                         log.info('no screen sharing permissions');
                         hasScreenPermissions = false;
    @@ -540,7 +540,7 @@ export class CallsWidgetWindow {
     
             const promise = new Promise((resolve) => {
                 const connected = (ev: IpcMainEvent, incomingCallId: string, incomingSessionId: string) => {
    -                log.debug('onJoinedCall', incomingCallId);
    +                log.debug('onJoinedCall', {incomingCallId});
     
                     if (!this.isCallsWidget(ev.sender.id)) {
                         log.debug('onJoinedCall', 'blocked on wrong webContentsId');
    @@ -610,7 +610,7 @@ export class CallsWidgetWindow {
         };
     
         private handleCallsLinkClick = (event: IpcMainEvent, url: string) => {
    -        log.debug('handleCallsLinkClick', url);
    +        log.debug('handleCallsLinkClick');
     
             if (!this.isCallsWidget(event.sender.id)) {
                 return;
    
  • src/app/mainWindow/downloadsDropdownMenuView.ts+2 4 modified
    @@ -86,7 +86,7 @@ export class DownloadsDropdownMenuView {
         };
     
         private updateItem = (event: IpcMainEvent, item: DownloadedItem) => {
    -        log.debug('updateItem', {item});
    +        log.debug('updateItem');
     
             this.item = item;
             this.updateDownloadsDropdownMenu();
    @@ -105,16 +105,14 @@ export class DownloadsDropdownMenuView {
         };
     
         private handleOpen = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload = {} as DownloadsMenuOpenEventPayload) => {
    -        log.debug('handleOpen', {bounds: this.bounds, payload});
    +        log.debug('handleOpen');
     
             if (!(this.bounds && this.view && this.windowBounds)) {
                 return;
             }
     
             const {item, coordinates} = payload;
     
    -        log.debug('handleOpen', {item, coordinates});
    -
             this.open = true;
             this.coordinates = coordinates;
             this.item = item;
    
  • src/app/mainWindow/downloadsDropdownView.ts+2 2 modified
    @@ -75,7 +75,7 @@ export class DownloadsDropdownView {
         };
     
         private updateDownloadsDropdownMenuItem = (event: IpcMainEvent, item?: DownloadedItem) => {
    -        log.silly('updateDownloadsDropdownMenuItem', {item});
    +        log.silly('updateDownloadsDropdownMenuItem');
             this.item = item;
             this.updateDownloadsDropdown();
         };
    @@ -120,7 +120,7 @@ export class DownloadsDropdownView {
         };
     
         private openFile = (e: IpcMainEvent, item: DownloadedItem) => {
    -        log.debug('openFile', {item});
    +        log.debug('openFile');
     
             downloadsManager.openFile(item);
         };
    
  • src/app/mainWindow/mainWindow.ts+6 6 modified
    @@ -81,7 +81,7 @@ export class MainWindow extends EventEmitter {
                 title: app.name,
                 fullscreen: this.shouldStartFullScreen(),
             });
    -        log.debug('main window options', windowOptions);
    +        log.debug('main window options', {windowOptions});
     
             this.win = new BaseWindow(windowOptions);
             if (!this.win) {
    @@ -111,7 +111,7 @@ export class MainWindow extends EventEmitter {
             performanceMonitor.registerView('MainWindow', this.win.browserWindow.webContents);
             this.win.browserWindow.loadURL(localURL).catch(
                 (reason) => {
    -                log.error('failed to load', reason);
    +                log.error('failed to load', {reason});
                 });
     
             this.emit(MAIN_WINDOW_CREATED);
    @@ -189,7 +189,7 @@ export class MainWindow extends EventEmitter {
                     throw new Error('Provided bounds info file does not validate, using defaults instead.');
                 }
                 const matchingScreen = screen.getDisplayMatching(savedWindowState);
    -            log.debug('closest matching screen for main window', matchingScreen);
    +            log.debug('closest matching screen for main window', {matchingScreen});
                 if (!(isInsideRectangle(matchingScreen.bounds, savedWindowState) || savedWindowState.maximized)) {
                     throw new Error('Provided bounds info are outside the bounds of your screen, using defaults instead.');
                 }
    @@ -222,11 +222,11 @@ export class MainWindow extends EventEmitter {
                 fullscreen: window.isFullScreen(),
             };
             try {
    -            log.debug('saving window state', windowState);
    +            log.debug('saving window state', {windowState});
                 fs.writeFileSync(file, JSON.stringify(windowState));
             } catch (e) {
             // [Linux] error happens only when the window state is changed before the config dir is created.
    -            log.error('failed to save window state', e);
    +            log.error('failed to save window state', {e});
             }
         };
     
    @@ -285,7 +285,7 @@ export class MainWindow extends EventEmitter {
             if (global.willAppQuit) { // when [Ctrl|Cmd]+Q
                 this.saveWindowState(boundsInfoPath, this.win.browserWindow);
             } else { // Minimize or hide the window for close button.
    -            log.info('onClose', event);
    +            log.info('onClose', {event});
                 event.preventDefault();
                 function hideWindow(window?: BrowserWindow) {
                     window?.blur(); // To move focus to the next top-level window in Windows
    
  • src/app/mainWindow/modals/modalManager.ts+1 1 modified
    @@ -112,7 +112,7 @@ export class ModalManager {
         };
     
         handleModalFinished = (mode: 'resolve' | 'reject', event: IpcMainEvent, data: unknown) => {
    -        log.debug('handleModalFinished', {mode, data});
    +        log.debug('handleModalFinished', {mode});
     
             const requestModal = this.findModalByCaller(event);
             if (requestModal) {
    
  • src/app/navigationManager.ts+2 2 modified
    @@ -129,7 +129,7 @@ export class NavigationManager {
         };
     
         private handleBrowserHistoryPush = (e: IpcMainEvent, pathName: string) => {
    -        log.debug('handleBrowserHistoryPush', e.sender.id, pathName);
    +        log.debug('handleBrowserHistoryPush', {webContentsId: e.sender.id});
     
             const currentView = WebContentsManager.getViewByWebContentsId(e.sender.id);
             if (!currentView) {
    @@ -156,7 +156,7 @@ export class NavigationManager {
         };
     
         private handleRequestBrowserHistoryStatus = (e: IpcMainInvokeEvent) => {
    -        log.silly('handleRequestBrowserHistoryStatus', e.sender.id);
    +        log.silly('handleRequestBrowserHistoryStatus', {webContentsId: e.sender.id});
             return WebContentsManager.getViewByWebContentsId(e.sender.id)?.getBrowserHistoryStatus();
         };
     
    
  • src/app/serverHub.ts+27 8 modified
    @@ -2,7 +2,7 @@
     // See LICENSE.txt for license information.
     
     import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
    -import {ipcMain} from 'electron';
    +import {ipcMain, session} from 'electron';
     
     import MainWindow from 'app/mainWindow/mainWindow';
     import ModalManager from 'app/mainWindow/modals/modalManager';
    @@ -22,6 +22,7 @@ import {
         GET_LAST_ACTIVE,
         SERVER_SWITCHED,
         GET_CURRENT_SERVER,
    +    SERVER_REMOVED,
     } from 'common/communication';
     import {ModalConstants} from 'common/constants';
     import {SECURE_STORAGE_KEYS} from 'common/constants/secureStorage';
    @@ -59,6 +60,7 @@ export class ServerHub {
             ipcMain.handle(GET_CURRENT_SERVER, this.handleGetCurrentServer);
     
             ServerManager.on(SERVER_SWITCHED, this.handleServerCurrentChanged);
    +        ServerManager.on(SERVER_REMOVED, this.handleServerCleanup);
         }
     
         // TODO: Move me somewhere else later
    @@ -84,7 +86,7 @@ export class ServerHub {
          */
     
         showNewServerModal = (prefillURL?: string) => {
    -        log.debug('showNewServerModal', {prefillURL});
    +        log.debug('showNewServerModal');
     
             const mainWindow = MainWindow.get();
             if (!mainWindow) {
    @@ -123,7 +125,7 @@ export class ServerHub {
         private handleShowNewServerModal = () => this.showNewServerModal();
     
         private showEditServerModal = (e: IpcMainEvent, id: string) => {
    -        log.debug('showEditServerModal', id);
    +        log.debug('showEditServerModal', {id});
     
             const mainWindow = MainWindow.get();
             if (!mainWindow) {
    @@ -158,7 +160,7 @@ export class ServerHub {
         };
     
         private showRemoveServerModal = (e: IpcMainEvent, id: string) => {
    -        log.debug('handleRemoveServerModal', id);
    +        log.debug('handleRemoveServerModal', {id});
     
             const mainWindow = MainWindow.get();
             if (!mainWindow) {
    @@ -201,7 +203,7 @@ export class ServerHub {
          */
     
         private handleServerURLValidation = async (e: IpcMainInvokeEvent, url?: string, currentId?: string, preAuthSecret?: string): Promise<URLValidationResult> => {
    -        log.debug('handleServerURLValidation', url, currentId);
    +        log.verbose('handleServerURLValidation', {currentId});
     
             // If the URL is missing or null, reject
             if (!url) {
    @@ -249,6 +251,8 @@ export class ServerHub {
             if ('data' in httpsResult) {
                 remoteInfo = httpsResult.data;
             } else {
    +            log.debug('handleServerURLValidation: HTTPS test failed', {error: httpsResult.error});
    +
                 // Check if HTTPS returned 403
                 const httpsIs403 = httpsResult.error?.statusCode === 403;
     
    @@ -259,6 +263,8 @@ export class ServerHub {
                         remoteInfo = httpResult.data;
                         remoteURL = insecureURL;
                     } else {
    +                    log.debug('handleServerURLValidation: HTTP test failed', {error: httpResult.error});
    +
                         // Both HTTPS and HTTP failed
                         const httpIs403 = httpResult.error?.statusCode === 403;
                         if (httpsIs403 || httpIs403) {
    @@ -352,7 +358,7 @@ export class ServerHub {
         };
     
         private handleAddServer = async (event: IpcMainEvent, server: Server & {preAuthSecret?: string}) => {
    -        log.debug('handleAddServer', server);
    +        log.debug('handleAddServer');
     
             const newServer = ServerManager.addServer(server);
     
    @@ -361,7 +367,7 @@ export class ServerHub {
         };
     
         private handleEditServer = async (event: IpcMainEvent, server: UniqueServer, permissions?: Permissions) => {
    -        log.debug('handleEditServer', server, permissions);
    +        log.debug('handleEditServer', {serverId: server.id});
     
             if (!server.id) {
                 return;
    @@ -382,7 +388,7 @@ export class ServerHub {
         };
     
         private handleRemoveServer = async (event: IpcMainEvent, serverId: string) => {
    -        log.debug('handleRemoveServer', serverId);
    +        log.debug('handleRemoveServer', {serverId});
     
             const server = ServerManager.getServer(serverId);
             if (!server) {
    @@ -400,6 +406,19 @@ export class ServerHub {
             }
         };
     
    +    private handleServerCleanup = (serverId: string) => {
    +        log.debug('handleServerCleanup', {serverId});
    +
    +        const server = ServerManager.getServer(serverId);
    +        if (!server) {
    +            return;
    +        }
    +
    +        session.defaultSession.clearData({
    +            origins: [server.url.origin],
    +        });
    +    };
    +
         private handleGetLastActive = () => {
             const serverId = ServerManager.getCurrentServerId();
             if (!serverId) {
    
  • src/app/system/badge.ts+1 1 modified
    @@ -67,7 +67,7 @@ async function setOverlayIcon(badgeText: string | undefined, description: string
                     const dataUrl = await createDataURL(mainWindow, badgeText, small);
                     overlay = nativeImage.createFromDataURL(dataUrl);
                 } catch (err) {
    -                log.error('Could not generate a badge:', err);
    +                log.error('Could not generate a badge:', {err});
                 }
             }
             mainWindow.setOverlayIcon(overlay, description);
    
  • src/app/tabs/tabManager.ts+7 7 modified
    @@ -128,7 +128,7 @@ export class TabManager extends EventEmitter {
         };
     
         updateTabOrder = (serverId: string, viewIds: string[]) => {
    -        log.debug('updateTabOrder', serverId, viewIds);
    +        log.debug('updateTabOrder', {serverId, viewIds});
     
             if (viewIds.length === 0) {
                 this.tabOrder.delete(serverId);
    @@ -154,7 +154,7 @@ export class TabManager extends EventEmitter {
         };
     
         switchToTab = (viewId: string) => {
    -        log.debug('switchToTab', viewId);
    +        log.debug('switchToTab', {viewId});
     
             if (this.isActiveTab(viewId)) {
                 log.silly(`switchToTab: Tab ${viewId} is already active, will not show`);
    @@ -300,7 +300,7 @@ export class TabManager extends EventEmitter {
         };
     
         private handleViewUpdated = (viewId: string) => {
    -        log.debug('handleViewUpdated', viewId);
    +        log.debug('handleViewUpdated', {viewId});
     
             const view = ViewManager.getView(viewId);
             if (view && view.type === ViewType.TAB) {
    @@ -309,7 +309,7 @@ export class TabManager extends EventEmitter {
         };
     
         private handleSetCurrentTabViewBounds = (newBounds: Electron.Rectangle) => {
    -        log.silly('handleSetCurrentViewBounds', newBounds);
    +        log.silly('handleSetCurrentViewBounds', {newBounds});
     
             const currentView = this.getCurrentActiveTabView();
             if (currentView && currentView.currentURL) {
    @@ -319,7 +319,7 @@ export class TabManager extends EventEmitter {
         };
     
         private handleServerCurrentChanged = (serverId: string) => {
    -        log.debug('handleServerCurrentChanged', serverId);
    +        log.debug('handleServerCurrentChanged', {serverId});
     
             const tab = this.getCurrentTabForServer(serverId);
             if (tab) {
    @@ -430,7 +430,7 @@ export class TabManager extends EventEmitter {
         };
     
         private handleCreateNewTab = (serverId: string) => {
    -        log.debug('handleCreateNewTab', serverId);
    +        log.debug('handleCreateNewTab', {serverId});
     
             const server = ServerManager.getServer(serverId);
             if (!server) {
    @@ -444,7 +444,7 @@ export class TabManager extends EventEmitter {
             if (this.isActiveTab(viewId)) {
                 const currentTabs = this.tabOrder.get(serverId);
                 if (!currentTabs) {
    -                log.error('handleCloseTab: No tabs found for server', serverId);
    +                log.error('handleCloseTab: No tabs found for server', {serverId});
                     return;
                 }
     
    
  • src/app/views/MattermostWebContentsView.ts+7 9 modified
    @@ -94,8 +94,6 @@ export class MattermostWebContentsView extends EventEmitter {
             this.webContentsView.webContents.on('did-navigate-in-page', () => this.handlePageTitleUpdated(this.webContentsView.webContents.getTitle()));
             this.webContentsView.webContents.on('page-title-updated', (_, newTitle) => this.handlePageTitleUpdated(newTitle));
     
    -        WebContentsEventManager.addWebContentsEventListeners(this.webContentsView.webContents);
    -
             if (!DeveloperMode.get('disableContextMenu')) {
                 this.contextMenu = new ContextMenu(this.generateContextMenu(), this.webContentsView.webContents);
             }
    @@ -170,13 +168,13 @@ export class MattermostWebContentsView extends EventEmitter {
                 if (parsedURL) {
                     loadURL = parsedURL.toString();
                 } else {
    -                this.log.error('Cannot parse provided url, using current server url', someURL);
    +                this.log.error('Cannot parse provided url, using current server url');
                     loadURL = this.view.getLoadingURL()?.toString() || '';
                 }
             } else {
                 loadURL = this.view.getLoadingURL()?.toString() || '';
             }
    -        this.log.verbose(`Loading ${loadURL}`);
    +        this.log.verbose('Loading URL');
             performanceMonitor.registerServerView(`Server ${this.webContentsView.webContents.id}`, this.webContentsView.webContents, this.view.serverId);
             const loading = this.webContentsView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
             loading.then(this.loadSuccess(loadURL)).catch((err) => {
    @@ -348,7 +346,7 @@ export class MattermostWebContentsView extends EventEmitter {
                     } else {
                         this.parentWindow.webContents.send(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
                         this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
    -                    this.log.info(`Couldn't esviewlish a connection with ${loadURL}, will continue to retry in the background`, err);
    +                    this.log.info('Could not establish a connection, will continue to retry in the background', {err});
                         this.status = Status.ERROR;
                         this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
                     }
    @@ -388,14 +386,14 @@ export class MattermostWebContentsView extends EventEmitter {
         private loadRetry = (loadURL: string, err: Error) => {
             this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
             this.parentWindow.webContents.send(LOAD_RETRY, this.id, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
    -        this.log.info(`failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
    +        this.log.info(`failed loading URL: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
         };
     
         private loadSuccess = (loadURL: string) => {
             return () => {
                 const serverInfo = ServerManager.getRemoteInfo(this.view.serverId);
                 if (!serverInfo?.serverVersion || semver.gte(serverInfo.serverVersion, '9.4.0')) {
    -                this.log.verbose(`finished loading ${loadURL}`);
    +                this.log.verbose('finished loading URL');
                     this.parentWindow.webContents.send(LOAD_SUCCESS, this.id);
                     this.maxRetries = MAX_SERVER_RETRIES;
                     this.status = Status.WAITING_MM;
    @@ -417,7 +415,7 @@ export class MattermostWebContentsView extends EventEmitter {
          */
     
         private handleUpdateTarget = (e: Event, url: string) => {
    -        this.log.silly('handleUpdateTarget', e, url);
    +        this.log.silly('handleUpdateTarget');
             const parsedURL = parseURL(url);
             if (parsedURL && isInternalURL(parsedURL, this.view.getLoadingURL())) {
                 this.emit(UPDATE_TARGET_URL);
    @@ -433,7 +431,7 @@ export class MattermostWebContentsView extends EventEmitter {
         };
     
         private handlePageTitleUpdated = (newTitle: string) => {
    -        this.log.silly('handlePageTitleUpdated', newTitle);
    +        this.log.silly('handlePageTitleUpdated');
     
             if (!ServerManager.getServer(this.view.serverId)?.isLoggedIn) {
                 return;
    
  • src/app/views/pluginsPopUps.ts+8 8 modified
    @@ -40,7 +40,7 @@ export class PluginsPopUpsManager {
         generateHandleCreateWindow = (parentId: number) => (win: BrowserWindow, details: Electron.DidCreateWindowDetails) => {
             const webContentsId = win.webContents.id;
     
    -        log.debug('created popup window', details.url, webContentsId);
    +        log.debug('created popup window', {webContentsId});
             this.popups[webContentsId] = {
                 parentId,
                 win,
    @@ -51,29 +51,29 @@ export class PluginsPopUpsManager {
             // - Navigation
             // - Opening new windows
             win.webContents.on('will-redirect', (ev: Event<WebContentsWillRedirectEventParams>) => {
    -            log.warn(`prevented popup window from redirecting to: ${ev.url}`);
    +            log.warn('prevented popup window from redirecting');
                 ev.preventDefault();
             });
             win.webContents.on('will-navigate', (ev: Event<WebContentsWillNavigateEventParams>) => {
                 if (ev.url === details.url) {
                     return;
                 }
     
    -            log.warn(`prevented popup window from navigating to: ${ev.url}`);
    +            log.warn('prevented popup window from navigating');
                 ev.preventDefault();
             });
             win.webContents.on('did-start-navigation', (ev: Event<WebContentsDidStartNavigationEventParams>) => {
                 if (ev.url === details.url) {
                     return;
                 }
     
    -            log.warn(`prevented popup window from navigating to: ${ev.url}`);
    +            log.warn('prevented popup window from navigating');
                 ev.preventDefault();
             });
             win.webContents.setWindowOpenHandler(({url}): {action: 'deny'} => {
                 const parsedURL = parseURL(url);
                 if (!parsedURL) {
    -                log.warn(`Ignoring non-url ${url}`);
    +                log.warn('Ignoring non-url');
                     return {action: 'deny'};
                 }
     
    @@ -96,7 +96,7 @@ export class PluginsPopUpsManager {
                     shell.openExternal(url);
                 }
     
    -            log.warn(`prevented popup window from opening window to ${url}`);
    +            log.warn('prevented popup window from opening window');
     
                 return {action: 'deny'};
             });
    @@ -114,7 +114,7 @@ export class PluginsPopUpsManager {
     
             win.webContents.once('render-process-gone', (_, details) => {
                 if (details.reason !== 'clean-exit') {
    -                log.error('Renderer process for a webcontent is no longer available:', details.reason);
    +                log.error('Renderer process for a webcontent is no longer available:', {reason: details.reason});
                 }
                 try {
                     win.webContents.removeAllListeners();
    @@ -128,7 +128,7 @@ export class PluginsPopUpsManager {
             // Making extra explicit what we allow. This should already be enforced on
             // the calling side.
             if (details.url !== 'about:blank') {
    -            log.warn(`prevented new window creation: ${details.url}`);
    +            log.warn('prevented new window creation');
                 return {action: 'deny'};
             }
     
    
  • src/app/views/urlView.ts+2 2 modified
    @@ -32,7 +32,7 @@ export class URLView {
         }
     
         show = (url: URL | string) => {
    -        log.silly('showURLView', url);
    +        log.silly('showURLView');
     
             if (this.urlViewCancel) {
                 this.urlViewCancel();
    @@ -57,7 +57,7 @@ export class URLView {
                 };
     
                 const adjustWidth = (event: IpcMainEvent, width: number) => {
    -                log.silly('showURLView.adjustWidth', width);
    +                log.silly('showURLView.adjustWidth', {width});
     
                     if (!boundaries) {
                         return;
    
  • src/app/views/webContentEventsCommon.test.ts+307 0 added
    @@ -0,0 +1,307 @@
    +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +import type {Event, WebContentsConsoleMessageEventParams} from 'electron';
    +
    +import type {Logger} from 'common/log';
    +import {getLevel} from 'common/log';
    +import {parseURL} from 'common/utils/url';
    +
    +import {generateHandleConsoleMessage, isCustomProtocol, isMattermostProtocol} from './webContentEventsCommon';
    +
    +// Mock the electron-builder.json protocols
    +jest.mock('../../../electron-builder.json', () => ({
    +    protocols: [
    +        {
    +            name: 'Mattermost',
    +            schemes: ['mattermost'],
    +        },
    +    ],
    +}));
    +
    +// Mock the log module
    +jest.mock('common/log', () => ({
    +    getLevel: jest.fn(),
    +}));
    +
    +// Mock the parseURL function
    +jest.mock('common/utils/url', () => ({
    +    parseURL: jest.fn(),
    +}));
    +
    +const mockGetLevel = getLevel as jest.MockedFunction<typeof getLevel>;
    +const mockParseURL = parseURL as jest.MockedFunction<typeof parseURL>;
    +
    +describe('webContentEventsCommon', () => {
    +    let mockLogger: jest.Mocked<Logger>;
    +    let mockWcLog: jest.Mocked<Logger>;
    +
    +    beforeEach(() => {
    +        // Reset all mocks
    +        jest.clearAllMocks();
    +
    +        // Create mock logger
    +        mockWcLog = {
    +            debug: jest.fn(),
    +            error: jest.fn(),
    +            warn: jest.fn(),
    +            info: jest.fn(),
    +            verbose: jest.fn(),
    +            silly: jest.fn(),
    +        } as any;
    +
    +        mockLogger = {
    +            withPrefix: jest.fn().mockReturnValue(mockWcLog),
    +        } as any;
    +    });
    +
    +    describe('generateHandleConsoleMessage', () => {
    +        beforeEach(() => {
    +            mockParseURL.mockReturnValue(undefined);
    +        });
    +
    +        it('should create a handler that logs debug messages by default', () => {
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test debug message',
    +                sourceId: '/path/to/file.js',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockLogger.withPrefix).toHaveBeenCalledWith('renderer');
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test debug message');
    +        });
    +
    +        it('should log error messages using error level', () => {
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'error',
    +                message: 'Test error message',
    +                sourceId: '/path/to/file.js',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.error).toHaveBeenCalledWith('Test error message');
    +            expect(mockWcLog.debug).not.toHaveBeenCalled();
    +        });
    +
    +        it('should log warning messages using warn level', () => {
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'warning',
    +                message: 'Test warning message',
    +                sourceId: '/path/to/file.js',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.warn).toHaveBeenCalledWith('Test warning message');
    +            expect(mockWcLog.debug).not.toHaveBeenCalled();
    +        });
    +
    +        it('should include source file and line number when log level is debug', () => {
    +            mockGetLevel.mockReturnValue('debug');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test message',
    +                sourceId: '/path/to/some/file.js',
    +                lineNumber: 123,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test message', '(file.js:123)');
    +        });
    +
    +        it('should include source file and line number when log level is silly', () => {
    +            mockGetLevel.mockReturnValue('silly');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test message',
    +                sourceId: '/path/to/some/file.js',
    +                lineNumber: 123,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test message', '(file.js:123)');
    +        });
    +
    +        it('should not include source file and line number when log level is not debug or silly', () => {
    +            mockGetLevel.mockReturnValue('info');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test message',
    +                sourceId: '/path/to/some/file.js',
    +                lineNumber: 123,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test message');
    +        });
    +
    +        it('should handle error level with source file and line number when debug level is enabled', () => {
    +            mockGetLevel.mockReturnValue('debug');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'error',
    +                message: 'Test error',
    +                sourceId: '/path/to/error.js',
    +                lineNumber: 456,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.error).toHaveBeenCalledWith('Test error', '(error.js:456)');
    +        });
    +
    +        it('should handle warning level with source file and line number when debug level is enabled', () => {
    +            mockGetLevel.mockReturnValue('silly');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'warning',
    +                message: 'Test warning',
    +                sourceId: '/path/to/warning.js',
    +                lineNumber: 789,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.warn).toHaveBeenCalledWith('Test warning', '(warning.js:789)');
    +        });
    +
    +        it('should sanitize host information in console messages', () => {
    +            mockGetLevel.mockReturnValue('debug');
    +            const mockParsedURL = new URL('https://example.com/path');
    +            mockParseURL.mockReturnValue(mockParsedURL);
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Error from example.com: Connection failed',
    +                sourceId: 'https://example.com/path/file.js',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Error from <host>: Connection failed', '(file.js:42)');
    +        });
    +
    +        it('should sanitize host information in source file path when debug level is enabled', () => {
    +            mockGetLevel.mockReturnValue('debug');
    +            const mockParsedURL = new URL('https://server.com/path');
    +            mockParseURL.mockReturnValue(mockParsedURL);
    +
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test message',
    +                sourceId: 'https://server.com/path/file.js',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test message', '(file.js:42)');
    +        });
    +
    +        it('should handle parseURL returning undefined gracefully', () => {
    +            mockGetLevel.mockReturnValue('debug');
    +            const handler = generateHandleConsoleMessage(mockLogger);
    +            const event: Event<WebContentsConsoleMessageEventParams> = {
    +                level: 'log',
    +                message: 'Test message with host.com',
    +                sourceId: 'invalid-url',
    +                lineNumber: 42,
    +            } as any;
    +
    +            handler(event);
    +
    +            expect(mockWcLog.debug).toHaveBeenCalledWith('Test message with host.com', '(invalid-url:42)');
    +        });
    +    });
    +
    +    describe('isCustomProtocol', () => {
    +        it('should return false for http URLs', () => {
    +            expect(isCustomProtocol(new URL('http://example.com'))).toBe(false);
    +        });
    +
    +        it('should return false for https URLs', () => {
    +            expect(isCustomProtocol(new URL('https://example.com'))).toBe(false);
    +        });
    +
    +        it('should return false for mattermost protocol URLs', () => {
    +            expect(isCustomProtocol(new URL('mattermost://server1'))).toBe(false);
    +        });
    +
    +        it('should return true for custom protocol URLs', () => {
    +            expect(isCustomProtocol(new URL('custom://example.com'))).toBe(true);
    +        });
    +
    +        it('should return true for file protocol URLs', () => {
    +            expect(isCustomProtocol(new URL('file:///path/to/file'))).toBe(true);
    +        });
    +
    +        it('should return true for ftp protocol URLs', () => {
    +            expect(isCustomProtocol(new URL('ftp://example.com'))).toBe(true);
    +        });
    +
    +        it('should return true for data protocol URLs', () => {
    +            expect(isCustomProtocol(new URL('data:text/plain,hello'))).toBe(true);
    +        });
    +
    +        it('should handle URLs with different case protocols', () => {
    +            expect(isCustomProtocol(new URL('HTTP://example.com'))).toBe(false);
    +        });
    +
    +        it('should handle URLs with different case mattermost protocol', () => {
    +            expect(isCustomProtocol(new URL('MATTERMOST://server1'))).toBe(false);
    +        });
    +    });
    +
    +    describe('isMattermostProtocol', () => {
    +        it('should return true for mattermost protocol URLs', () => {
    +            expect(isMattermostProtocol(new URL('mattermost://server1'))).toBe(true);
    +        });
    +
    +        it('should return false for http URLs', () => {
    +            expect(isMattermostProtocol(new URL('http://example.com'))).toBe(false);
    +        });
    +
    +        it('should return false for https URLs', () => {
    +            expect(isMattermostProtocol(new URL('https://example.com'))).toBe(false);
    +        });
    +
    +        it('should return false for other custom protocol URLs', () => {
    +            expect(isMattermostProtocol(new URL('custom://example.com'))).toBe(false);
    +        });
    +
    +        it('should return false for file protocol URLs', () => {
    +            expect(isMattermostProtocol(new URL('file:///path/to/file'))).toBe(false);
    +        });
    +
    +        it('should handle URLs with different case mattermost protocol', () => {
    +            expect(isMattermostProtocol(new URL('MATTERMOST://server1'))).toBe(true);
    +        });
    +
    +        it('should handle mattermost URLs with paths and query parameters', () => {
    +            expect(isMattermostProtocol(new URL('mattermost://server1/path?param=value'))).toBe(true);
    +        });
    +
    +        it('should handle mattermost URLs with different servers', () => {
    +            expect(isMattermostProtocol(new URL('mattermost://server1'))).toBe(true);
    +            expect(isMattermostProtocol(new URL('mattermost://server2'))).toBe(true);
    +            expect(isMattermostProtocol(new URL('mattermost://my-server.com'))).toBe(true);
    +        });
    +    });
    +});
    
  • src/app/views/webContentEventsCommon.ts+11 2 modified
    @@ -6,6 +6,7 @@ import type {Event, WebContentsConsoleMessageEventParams} from 'electron';
     
     import type {Logger} from 'common/log';
     import {getLevel} from 'common/log';
    +import {parseURL} from 'common/utils/url';
     
     import {protocols} from '../../../electron-builder.json';
     
    @@ -22,14 +23,22 @@ export const generateHandleConsoleMessage = (log: Logger) => (event: Event<WebCo
         }
     
         // Only include line entries if we're debugging
    -    const entries = [event.message];
    +    const entries = [sanitizeMessage(event.sourceId, event.message)];
         if (['debug', 'silly'].includes(getLevel())) {
    -        entries.push(`(${path.basename(event.sourceId)}:${event.lineNumber})`);
    +        entries.push(sanitizeMessage(event.sourceId, `(${path.basename(event.sourceId)}:${event.lineNumber})`));
         }
     
         logFn(...entries);
     };
     
    +function sanitizeMessage(sourceURL: string, message: string) {
    +    const parsedURL = parseURL(sourceURL);
    +    if (!parsedURL) {
    +        return message;
    +    }
    +    return message.replace(parsedURL.host, '<host>');
    +}
    +
     export function isCustomProtocol(url: URL) {
         const scheme = protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0];
         return url.protocol !== 'http:' && url.protocol !== 'https:' && url.protocol !== `${scheme}:`;
    
  • src/app/views/webContentsManager.ts+7 3 modified
    @@ -6,6 +6,7 @@ import {ipcMain, shell} from 'electron';
     import isDev from 'electron-is-dev';
     
     import popoutMenu from 'app/popoutMenu';
    +import WebContentsEventManager from 'app/views/webContentEvents';
     import type BaseWindow from 'app/windows/baseWindow';
     import AppState from 'common/appState';
     import {
    @@ -101,6 +102,7 @@ export class WebContentsManager {
             webContentsView.load(view.getLoadingURL());
     
             this.addViewToMap(webContentsView);
    +        WebContentsEventManager.addWebContentsEventListeners(webContentsView.getWebContentsView().webContents);
             return webContentsView;
         };
     
    @@ -117,6 +119,8 @@ export class WebContentsManager {
         };
     
         private addViewToMap = (view: MattermostWebContentsView): void => {
    +        log.debug('addViewToMap', {viewId: view.id, webContentsId: view.webContentsId});
    +
             this.webContentsViews.set(view.id, view);
             this.webContentsIdToView.set(view.webContentsId, view);
     
    @@ -152,7 +156,7 @@ export class WebContentsManager {
         };
     
         private handleTabLoginChanged = (event: IpcMainEvent, loggedIn: boolean) => {
    -        log.debug('handleTabLoggedIn', event.sender.id);
    +        log.debug('handleTabLoggedIn', {webContentsId: event.sender.id});
             const view = this.getViewByWebContentsId(event.sender.id);
             if (!view) {
                 return;
    @@ -176,7 +180,7 @@ export class WebContentsManager {
         };
     
         private handleReactAppInitialized = (e: IpcMainEvent) => {
    -        log.debug('handleReactAppInitialized', e.sender.id);
    +        log.debug('handleReactAppInitialized', {webContentsId: e.sender.id});
     
             const view = this.getViewByWebContentsId(e.sender.id);
             if (view) {
    @@ -255,7 +259,7 @@ export class WebContentsManager {
         };
     
         private handleOpenPopoutMenu = (_: IpcMainEvent, viewId: string) => {
    -        log.debug('handleOpenPopoutMenu', viewId);
    +        log.debug('handleOpenPopoutMenu', {viewId});
     
             popoutMenu(viewId);
         };
    
  • src/app/windows/baseWindow.ts+2 2 modified
    @@ -58,7 +58,7 @@ export default class BaseWindow {
                     spellcheck: typeof Config.useSpellChecker === 'undefined' ? true : Config.useSpellChecker,
                 },
             }, options);
    -        log.debug('main window options', windowOptions);
    +        log.debug('main window options', {windowOptions});
     
             if (process.platform === 'linux') {
                 windowOptions.icon = path.join(path.resolve(app.getAppPath(), 'assets'), 'linux', 'app_icon.png');
    @@ -122,7 +122,7 @@ export default class BaseWindow {
         };
     
         handleAltKeyPressed = (_: Event, input: Input) => {
    -        log.silly('handleInputEvents', input);
    +        log.silly('handleInputEvents', {input});
     
             if (input.type === 'keyDown') {
                 this.altPressStatus = input.key === 'Alt' &&
    
  • src/app/windows/popoutManager.ts+5 5 modified
    @@ -64,7 +64,7 @@ export class PopoutManager {
         };
     
         private handleViewCreated = (viewId: string) => {
    -        log.debug('handleViewCreated', viewId);
    +        log.debug('handleViewCreated', {viewId});
     
             const view = ViewManager.getView(viewId);
             if (view && view.type === ViewType.WINDOW) {
    @@ -103,7 +103,7 @@ export class PopoutManager {
             });
             window.browserWindow.loadURL('mattermost-desktop://renderer/popout.html').catch(
                 (reason) => {
    -                log.error('failed to load', reason);
    +                log.error('failed to load', {reason});
                 });
         };
     
    @@ -165,7 +165,7 @@ export class PopoutManager {
         };
     
         private handleViewUpdated = (viewId: string) => {
    -        log.debug('handleViewUpdated', viewId);
    +        log.debug('handleViewUpdated', {viewId});
     
             const view = ViewManager.getView(viewId);
             if (view && view.type === ViewType.WINDOW) {
    @@ -180,7 +180,7 @@ export class PopoutManager {
         };
     
         private handleViewRemoved = (viewId: string) => {
    -        log.debug('handleViewRemoved', viewId);
    +        log.debug('handleViewRemoved', {viewId});
     
             const window = this.popoutWindows.get(viewId);
             if (window) {
    @@ -220,7 +220,7 @@ export class PopoutManager {
         };
     
         private handleCreateNewWindow = (serverId: string) => {
    -        log.debug('handleCreateNewTab', serverId);
    +        log.debug('handleCreateNewTab', {serverId});
     
             const server = ServerManager.getServer(serverId);
             if (!server) {
    
  • src/common/appState.ts+1 1 modified
    @@ -125,7 +125,7 @@ export class AppState extends EventEmitter {
         private emitStatusForView = (viewId: string) => {
             const view = ViewManager.getView(viewId);
             if (!view) {
    -            log.warn('emitStatusForView: view not found', viewId);
    +            log.warn('emitStatusForView: view not found', {viewId});
                 this.emitStatus();
                 return;
             }
    
  • src/common/config/index.ts+3 3 modified
    @@ -117,7 +117,7 @@ export class Config extends EventEmitter {
          * @param {array} properties an array of config properties to save
          */
         setMultiple = (newData: Partial<CurrentConfig>) => {
    -        log.debug('setMultiple', newData);
    +        log.debug('setMultiple');
     
             if (newData.darkMode && newData.darkMode !== this.darkMode) {
                 this.emit('darkModeChange', newData.darkMode);
    @@ -128,7 +128,7 @@ export class Config extends EventEmitter {
         };
     
         setServers = (servers: ConfigServer[], lastActiveServer?: number) => {
    -        log.debug('setServers', servers, lastActiveServer);
    +        log.debug('setServers');
     
             this.localConfigData = Object.assign({}, this.localConfigData, {
                 servers,
    @@ -265,7 +265,7 @@ export class Config extends EventEmitter {
          */
     
         private onLoadRegistry = (registryData: Partial<RegistryCurrentConfig>): void => {
    -        log.debug('loadRegistry', {registryData});
    +        log.debug('loadRegistry');
     
             this.registryConfigData = registryData;
             if (this.registryConfigData.servers) {
    
  • src/common/config/RegistryConfig.ts+3 3 modified
    @@ -44,7 +44,7 @@ export default class RegistryConfig extends EventEmitter {
                         this.data.servers!.push(...servers);
                     }
                 } catch (error) {
    -                log.warn('Nothing retrieved for \'DefaultServerList\'', error);
    +                log.warn('Nothing retrieved for \'DefaultServerList\'', {error});
                 }
     
                 // extract EnableServerManagement from the registry
    @@ -54,7 +54,7 @@ export default class RegistryConfig extends EventEmitter {
                         this.data.enableServerManagement = enableServerManagement;
                     }
                 } catch (error) {
    -                log.warn('Nothing retrieved for \'EnableServerManagement\'', error);
    +                log.warn('Nothing retrieved for \'EnableServerManagement\'', {error});
                 }
     
                 // extract EnableAutoUpdater from the registry
    @@ -64,7 +64,7 @@ export default class RegistryConfig extends EventEmitter {
                         this.data.enableAutoUpdater = enableAutoUpdater;
                     }
                 } catch (error) {
    -                log.warn('Nothing retrieved for \'EnableAutoUpdater\'', error);
    +                log.warn('Nothing retrieved for \'EnableAutoUpdater\'', {error});
                 }
             }
     
    
  • src/common/log.ts+1 1 modified
    @@ -14,7 +14,7 @@ export const setLoggingLevel = (level: string) => {
         if (log.transports.file.level === level) {
             return;
         }
    -    log.error('Logger', 'Log level set to:', level);
    +    log.error('Logger', 'Log level set to:', {level});
     
         log.transports.console.level = level as LevelOption;
         log.transports.file.level = level as LevelOption;
    
  • src/common/servers/serverManager.ts+17 18 modified
    @@ -14,7 +14,7 @@ import {
         SERVER_PRE_AUTH_SECRET_CHANGED,
     } from 'common/communication';
     import Config from 'common/config';
    -import {Logger, getLevel} from 'common/log';
    +import {Logger} from 'common/log';
     import {MattermostServer} from 'common/servers/MattermostServer';
     import {getFormattedPathName, isInternalURL, parseURL} from 'common/utils/url';
     
    @@ -65,15 +65,15 @@ export class ServerManager extends EventEmitter {
             if (!server) {
                 return new Logger(serverId);
             }
    -        return new Logger(...additionalPrefixes, ...this.includeId(serverId, server.name));
    +        return new Logger(...additionalPrefixes, serverId);
         };
     
         getRemoteInfo = (serverId: string) => {
             return this.remoteInfo.get(serverId);
         };
     
         lookupServerByURL = (inputURL: URL | string, ignoreScheme = false) => {
    -        log.silly('lookupViewByURL', `${inputURL}`, ignoreScheme);
    +        log.silly('lookupViewByURL', {ignoreScheme});
     
             const parsedURL = parseURL(inputURL);
             if (!parsedURL) {
    @@ -86,7 +86,7 @@ export class ServerManager extends EventEmitter {
         };
     
         addServer = (server: NewServer, initialLoadURL?: URL) => {
    -        log.debug('addServer', server, initialLoadURL);
    +        log.debug('addServer');
     
             const mattermostServer = this.createServer(server, false, initialLoadURL);
             this.addServerToMap(mattermostServer, true);
    @@ -112,6 +112,9 @@ export class ServerManager extends EventEmitter {
         };
     
         private addServerToMap = (newServer: MattermostServer, setAsCurrentServer: boolean, persist: boolean = true) => {
    +        // This is the only place where we log the server name
    +        log.debug('addServerToMap', newServer.id, newServer.name);
    +
             this.servers.set(newServer.id, newServer);
             if (!newServer.isPredefined) {
                 this.serverOrder.push(newServer.id);
    @@ -129,16 +132,16 @@ export class ServerManager extends EventEmitter {
         };
     
         editServer = (serverId: string, server: Server, preAuthSecret?: string) => {
    -        log.debug('editServer', serverId, server);
    +        log.debug('editServer', {serverId});
     
             const existingServer = this.servers.get(serverId);
             if (!existingServer) {
    -            log.warn('Server not found', serverId);
    +            log.warn('Server not found', {serverId});
                 return undefined;
             }
     
             if (existingServer.isPredefined) {
    -            log.warn('Cannot edit predefined server', existingServer.id);
    +            log.warn('Cannot edit predefined server', {serverId: existingServer.id});
                 return existingServer;
             }
     
    @@ -170,7 +173,7 @@ export class ServerManager extends EventEmitter {
         };
     
         updateRemoteInfo = (serverId: string, remoteInfo: RemoteInfo) => {
    -        log.debug('updateRemoteInfo', serverId, remoteInfo);
    +        log.debug('updateRemoteInfo', {serverId});
     
             const server = this.servers.get(serverId);
             if (!server) {
    @@ -188,7 +191,7 @@ export class ServerManager extends EventEmitter {
         };
     
         updateServerOrder = (serverOrder: string[]) => {
    -        log.debug('updateServerOrder', serverOrder);
    +        log.debug('updateServerOrder', {serverOrder});
     
             this.serverOrder = serverOrder.filter((id) => {
                 const server = this.servers.get(id);
    @@ -200,7 +203,7 @@ export class ServerManager extends EventEmitter {
     
         // Remove setCurrentServer method since we only need to persist changes when switching or removing servers
         updateCurrentServer = (serverId: string) => {
    -        log.debug('updateCurrentServer', serverId);
    +        log.debug('updateCurrentServer', {serverId});
     
             if (this.currentServerId === serverId) {
                 return;
    @@ -212,7 +215,9 @@ export class ServerManager extends EventEmitter {
         };
     
         removeServer = (serverId: string) => {
    -        log.debug('removeServer', serverId);
    +        log.debug('removeServer', {serverId});
    +
    +        this.emit(SERVER_REMOVED, serverId);
     
             const index = this.serverOrder.findIndex((id) => id === serverId);
             this.serverOrder.splice(index, 1);
    @@ -225,12 +230,11 @@ export class ServerManager extends EventEmitter {
                 this.updateCurrentServer(nextServer);
             }
     
    -        this.emit(SERVER_REMOVED, serverId);
             this.persistServers();
         };
     
         reloadServer = (serverId: string) => {
    -        log.debug('reloadServer', serverId);
    +        log.debug('reloadServer', {serverId});
     
             const index = this.serverOrder.findIndex((id) => id === serverId);
             if (index === -1) {
    @@ -305,11 +309,6 @@ export class ServerManager extends EventEmitter {
                 }));
             Config.setServers(localServers, this.currentServerId ? this.serverOrder.indexOf(this.currentServerId) : undefined);
         };
    -
    -    private includeId = (id: string, ...prefixes: string[]) => {
    -        const shouldInclude = ['debug', 'silly'].includes(getLevel());
    -        return shouldInclude ? [id, ...prefixes] : prefixes;
    -    };
     }
     
     const serverManager = new ServerManager();
    
  • src/common/views/viewManager.ts+9 14 modified
    @@ -14,7 +14,7 @@ import {
         VIEW_TYPE_ADDED,
     } from 'common/communication';
     import Config from 'common/config';
    -import {Logger, getLevel} from 'common/log';
    +import {Logger} from 'common/log';
     import type {MattermostServer} from 'common/servers/MattermostServer';
     import ServerManager from 'common/servers/serverManager';
     import {MattermostView, ViewType} from 'common/views/MattermostView';
    @@ -80,7 +80,7 @@ export class ViewManager extends EventEmitter {
         };
     
         createView = (server: MattermostServer, type: ViewType) => {
    -        log.debug('createView', server.id, server.name, type);
    +        log.debug('createView', {serverId: server.id, type});
     
             if (this.isViewLimitReached()) {
                 log.warn(`createView: View limit reached for server ${server.id}`);
    @@ -100,7 +100,7 @@ export class ViewManager extends EventEmitter {
         };
     
         updateViewTitle = (viewId: string, channelName?: string, teamName?: string) => {
    -        log.debug('updateViewTitle', viewId, channelName, teamName);
    +        log.debug('updateViewTitle', {viewId});
     
             const view = this.views.get(viewId);
             if (!view) {
    @@ -115,7 +115,7 @@ export class ViewManager extends EventEmitter {
         };
     
         updateViewType = (viewId: string, type: ViewType) => {
    -        log.debug('updateViewType', viewId, type);
    +        log.debug('updateViewType', {viewId, type});
     
             const view = this.views.get(viewId);
             if (!view || view.type === type) {
    @@ -128,7 +128,7 @@ export class ViewManager extends EventEmitter {
         };
     
         setPrimaryView = (viewId: string) => {
    -        log.debug('setPrimaryView', viewId);
    +        log.debug('setPrimaryView', {viewId});
     
             const view = this.views.get(viewId);
             if (!view) {
    @@ -140,7 +140,7 @@ export class ViewManager extends EventEmitter {
         };
     
         removeView = (viewId: string) => {
    -        log.debug('removeView', viewId);
    +        log.debug('removeView', {viewId});
     
             const view = this.views.get(viewId);
             if (!view) {
    @@ -169,18 +169,13 @@ export class ViewManager extends EventEmitter {
             }
             const server = ServerManager.getServer(view.serverId);
             if (!server) {
    -            return new Logger(...additionalPrefixes, ...this.includeId(viewId));
    +            return new Logger(...additionalPrefixes, viewId);
             }
    -        return new Logger(...additionalPrefixes, ...this.includeId(viewId, server.name));
    -    };
    -
    -    private includeId = (id: string, ...prefixes: string[]) => {
    -        const shouldInclude = ['debug', 'silly'].includes(getLevel());
    -        return shouldInclude ? [id, ...prefixes] : prefixes;
    +        return new Logger(...additionalPrefixes, server.id, viewId);
         };
     
         private handleServerWasRemoved = (serverId: string) => {
    -        log.debug('handleServerWasRemoved', serverId);
    +        log.debug('handleServerWasRemoved', {serverId});
     
             this.views.forEach((view) => {
                 if (view.serverId === serverId) {
    
  • src/main/app/app.ts+6 6 modified
    @@ -26,7 +26,7 @@ const log = new Logger('App.App');
     
     // activate first app instance, subsequent instances will quit themselves
     export function handleAppSecondInstance(event: Event, argv: string[]) {
    -    log.debug('handleAppSecondInstance', argv);
    +    log.debug('handleAppSecondInstance', {argv});
     
         // Protocol handler for win32
         // argv: An array of the second instance’s (command line / deep linked) arguments
    @@ -62,7 +62,7 @@ export function handleAppBrowserWindowCreated(event: Event, newWindow: BrowserWi
     export function handleAppWillFinishLaunching() {
         // Protocol handler for osx
         app.on('open-url', (event, url) => {
    -        log.info(`Handling deeplinking url: ${url}`);
    +        log.verbose('Handling deeplinking url');
             event.preventDefault();
             const deeplinkingUrl = getDeeplinkingURL([url]);
             if (deeplinkingUrl) {
    @@ -85,15 +85,15 @@ export function handleAppBeforeQuit() {
     }
     
     export async function handleAppCertificateError(event: Event, webContents: WebContents, url: string, error: string, certificate: Certificate, callback: (isTrusted: boolean) => void) {
    -    log.verbose('handleAppCertificateError', {url, error, certificate});
    +    log.verbose('handleAppCertificateError', {error});
     
         const parsedURL = parseURL(url);
         if (!parsedURL) {
             return;
         }
         if (CertificateStore.isExplicitlyUntrusted(parsedURL)) {
             event.preventDefault();
    -        log.warn(`Ignoring previously untrusted certificate for ${parsedURL.origin}`);
    +        log.warn('Ignoring previously untrusted certificate');
             callback(false);
         } else if (CertificateStore.isTrusted(parsedURL, certificate)) {
             event.preventDefault();
    @@ -107,7 +107,7 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
             if (server) {
                 const serverURL = parseURL(server.url);
                 if (serverURL && serverURL.origin !== parsedURL.origin) {
    -                log.warn(`Ignoring certificate for unmatched origin ${parsedURL.origin}, will not trust`);
    +                log.warn('Ignoring certificate for unmatched origin, will not trust');
                     callback(false);
                     return;
                 }
    @@ -186,5 +186,5 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
     }
     
     export function handleChildProcessGone(event: Event, details: Details) {
    -    log.error('"child-process-gone" The child process has crashed. Details: ', details);
    +    log.error('"child-process-gone" The child process has crashed. Details: ', {details});
     }
    
  • src/main/app/config.ts+6 7 modified
    @@ -41,7 +41,7 @@ export function handleGetLocalConfiguration() {
     }
     
     export function updateConfiguration(event: Electron.IpcMainEvent, properties: Array<{key: keyof CurrentConfig; data: CurrentConfig[keyof CurrentConfig]}> = []) {
    -    log.debug('updateConfiguration', properties);
    +    log.debug('updateConfiguration');
     
         if (properties.length) {
             const newData = properties.reduce((obj, data) => {
    @@ -64,7 +64,6 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
         }
     
         log.debug('handleConfigUpdate');
    -    log.silly('handleConfigUpdate', newConfig);
     
         if (!newConfig) {
             return;
    @@ -77,16 +76,16 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
             try {
                 app.setPath('downloads', newConfig.downloadLocation);
             } catch (e) {
    -            log.error(`There was a problem trying to set the default download path: ${e}`);
    +            log.error('There was a problem trying to set the default download path', {e});
             }
         }
     
         if (process.platform === 'win32' || process.platform === 'linux') {
             const autoStartTask = newConfig.autostart ? AutoLauncher.enable() : AutoLauncher.disable();
             autoStartTask.then(() => {
    -            log.info('config.autostart has been configured:', newConfig.autostart);
    +            log.info('config.autostart has been configured:', {autostart: newConfig.autostart});
             }).catch((err) => {
    -            log.error('error:', err);
    +            log.error('error:', {err});
             });
         }
     
    @@ -103,14 +102,14 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
     }
     
     export function handleDarkModeChange(darkMode: boolean) {
    -    log.debug('handleDarkModeChange', darkMode);
    +    log.debug('handleDarkModeChange', {darkMode});
     
         Tray.refreshImages(Config.trayIconTheme);
         ipcMain.emit(EMIT_CONFIGURATION, true, Config.data);
     }
     
     export function handleDeveloperModeUpdated(json: DeveloperSettings) {
    -    log.debug('handleDeveloperModeUpdated', json);
    +    log.debug('handleDeveloperModeUpdated', {json});
     
         if (['browserOnly', 'disableContextMenu'].some((key) => Object.hasOwn(json, key))) {
             ServerManager.init();
    
  • src/main/app/initialize.ts+4 4 modified
    @@ -337,7 +337,7 @@ async function initializeAfterAppReady() {
                 }),
             );
         } catch (error) {
    -        log.warn('Failed to initialize secure storage cache:', error);
    +        log.warn('Failed to initialize secure storage cache:', {error});
         }
     
         if (process.platform === 'darwin' || process.platform === 'win32') {
    @@ -391,7 +391,7 @@ async function initializeAfterAppReady() {
                     }
                 }
             } catch (error) {
    -            log.debug('Error injecting preauth secret header:', error);
    +            log.debug('Error injecting preauth secret header:', {error});
             }
     
             // If no secret found or error occurred, proceed with original headers
    @@ -453,7 +453,7 @@ async function initializeAfterAppReady() {
                 },
             }).
                 then(([react, redux]) => log.info(`Added Extension:  ${react.name}, ${redux.name}`)).
    -            catch((err) => log.error('An error occurred: ', err));
    +            catch((err) => log.error('An error occurred: ', {err}));
         }
     
         let deeplinkingURL;
    @@ -531,7 +531,7 @@ function onUserActivityStatus(status: {
         idleTime: number;
         isSystemEvent: boolean;
     }) {
    -    log.debug('UserActivityMonitor.on(status)', status);
    +    log.debug('UserActivityMonitor.on(status)', {status});
         WebContentsManager.sendToAllViews(USER_ACTIVITY_UPDATE, status.userIsActive, status.idleTime, status.isSystemEvent);
     }
     
    
  • src/main/app/intercom.ts+2 2 modified
    @@ -135,7 +135,7 @@ export function handleWelcomeScreenModal(prefillURL?: string) {
     }
     
     export function handleMentionNotification(event: IpcMainInvokeEvent, title: string, body: string, channelId: string, teamId: string, url: string, silent: boolean, soundName: string) {
    -    log.debug('handleMentionNotification', {channelId, teamId, url, silent, soundName});
    +    log.debug('handleMentionNotification', {silent, soundName});
         return NotificationManager.displayMention(title, body, channelId, teamId, url, silent, event.sender, soundName);
     }
     
    @@ -188,7 +188,7 @@ export function handleToggleSecureInput(event: IpcMainEvent, secureInput: boolea
         }
     
         // Enforce macOS to restrict processes from reading the keyboard input when in a password field
    -    log.debug('handleToggleSecureInput', secureInput);
    +    log.debug('handleToggleSecureInput', {secureInput});
         app.setSecureKeyboardEntryEnabled(secureInput);
     }
     
    
  • src/main/app/utils.ts+4 4 modified
    @@ -95,7 +95,7 @@ export function clearAppCache() {
             mainWindow.webContents.session.clearCache().
                 then(mainWindow.webContents.reload).
                 catch((err) => {
    -                log.error('clearAppCache', err);
    +                log.error('clearAppCache', {err});
                 });
         } else {
         //Wait for mainWindow
    @@ -200,7 +200,7 @@ export function migrateMacAppStore() {
                 return;
             }
         } catch (e) {
    -        log.error('MAS: Failed to check for existing Mattermost Desktop install, skipping', e);
    +        log.error('MAS: Failed to check for existing Mattermost Desktop install, skipping', {e});
             return;
         }
     
    @@ -236,7 +236,7 @@ export function migrateMacAppStore() {
             updatePaths(true);
             migrationPrefs.setValue('masConfigs', true);
         } catch (e) {
    -        log.error('MAS: An error occurred importing the existing configuration', e);
    +        log.error('MAS: An error occurred importing the existing configuration', {e});
         }
     }
     
    @@ -249,7 +249,7 @@ export async function updateServerInfos(servers: MattermostServer[]) {
                     map.set(srv.id, data);
                 }).
                 catch((error) => {
    -                log.warn('Could not get server info for', srv.name, error);
    +                log.warn('Could not get server info', {serverId: srv.id, error});
                 });
         }));
         map.forEach((serverInfo, serverId) => {
    
  • src/main/autoUpdater.ts+2 2 modified
    @@ -63,7 +63,7 @@ export class UpdateManager {
             this.cancellationToken = new CancellationToken();
     
             autoUpdater.on('error', (err: Error) => {
    -            log.error('There was an error while trying to update', err);
    +            log.error('There was an error while trying to update', {err});
             });
     
             autoUpdater.on('update-available', (info: UpdateInfo) => {
    @@ -178,7 +178,7 @@ export class UpdateManager {
                     }
                 }).catch((reason) => {
                     ipcMain.emit(NO_UPDATE_AVAILABLE);
    -                log.error('Failed to check for updates:', reason);
    +                log.error('Failed to check for updates:', {reason});
                 });
                 this.lastCheck = setTimeout(() => this.checkForUpdates(false), NEXT_CHECK);
             }
    
  • src/main/downloadsManager.ts+14 14 modified
    @@ -70,7 +70,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         private init = () => {
             // ensure data loaded from file is valid
             const validatedJSON = Validator.validateDownloads(this.json);
    -        log.debug('init', {'this.json': this.json, validatedJSON});
    +        log.debug('init');
             if (validatedJSON) {
                 this.saveAll(validatedJSON);
             } else {
    @@ -104,7 +104,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         handleNewDownload = async (event: Event, item: DownloadItem, webContents: WebContents) => {
    -        log.debug('handleNewDownload', {item, sourceURL: webContents.getURL()});
    +        log.debug('handleNewDownload');
     
             const url = item.getURL();
     
    @@ -183,7 +183,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
                         }
                     }
                 } catch (e) {
    -                log.warn('could not load bookmark', file.filename, e);
    +                log.warn('could not load bookmark', {e});
                     this.clearFile(file);
                 }
             }
    @@ -248,7 +248,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         showFileInFolder = (item?: DownloadedItem) => {
    -        log.debug('showFileInFolder', {item});
    +        log.debug('showFileInFolder');
     
             if (!item) {
                 log.debug('showFileInFolder', 'ITEM_UNDEFINED');
    @@ -274,7 +274,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         openFile = (item?: DownloadedItem) => {
    -        log.debug('openFile', {item});
    +        log.debug('openFile');
     
             if (!item) {
                 log.debug('openFile', 'FILE_UNDEFINED');
    @@ -304,7 +304,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         clearFile = (item?: DownloadedItem) => {
    -        log.debug('clearFile', {item});
    +        log.debug('clearFile');
     
             if (!item || item.type === DownloadItemTypeEnum.UPDATE) {
                 return;
    @@ -321,7 +321,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         cancelDownload = (item?: DownloadedItem) => {
    -        log.debug('cancelDownload', {item});
    +        log.debug('cancelDownload');
     
             if (!item) {
                 return;
    @@ -389,7 +389,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         private selectDefaultDownloadDirectory = async (startFrom: string, message: string) => {
    -        log.debug('handleSelectDownload', startFrom);
    +        log.debug('handleSelectDownload', {startFrom});
     
             const result = await dialog.showOpenDialog({defaultPath: startFrom || Config.downloadLocation,
                 message,
    @@ -465,7 +465,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
          *  This function return true if "downloadLocation" is undefined
          */
         private shouldShowSaveDialog = (item: DownloadItem, downloadLocation?: string) => {
    -        log.debug('shouldShowSaveDialog', {downloadLocation});
    +        log.debug('shouldShowSaveDialog');
             return !item.hasUserGesture() || !downloadLocation;
         };
     
    @@ -499,7 +499,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
     
         private upsertFileToDownloads = async (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
             const fileId = this.getFileId(item);
    -        log.debug('upsertFileToDownloads', {fileId});
    +        log.debug('upsertFileToDownloads');
             const formattedItem = await this.formatDownloadItem(item, state, overridePath);
             this.save(fileId, formattedItem);
             this.checkIfMaxFilesReached();
    @@ -542,7 +542,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
          *  DownloadItem event handlers
          */
         private updatedEventController = async (updatedEvent: Event, state: DownloadItemUpdatedEventState, item: DownloadItem) => {
    -        log.debug('updatedEventController', {state});
    +        log.debug('updatedEventController');
     
             await this.upsertFileToDownloads(item, state);
     
    @@ -554,7 +554,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
         };
     
         private doneEventController = async (doneEvent: Event, state: DownloadItemDoneEventState, item: DownloadItem, webContents: WebContents) => {
    -        log.debug('doneEventController', {state});
    +        log.debug('doneEventController');
     
             if (state === 'completed' && !this.open) {
                 const view = WebContentsManager.getViewByWebContentsId(webContents.id);
    @@ -594,7 +594,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
             this.openDownloadsDropdown();
         };
         private onUpdateDownloaded = (event: Event, info: UpdateInfo) => {
    -        log.debug('onUpdateDownloaded', {info});
    +        log.debug('onUpdateDownloaded');
     
             const {version} = info;
             const update = this.downloads[APP_UPDATE_KEY];
    @@ -606,7 +606,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
             this.openDownloadsDropdown();
         };
         private onUpdateProgress = (event: Event, progress: ProgressInfo) => {
    -        log.debug('onUpdateProgress', {progress});
    +        log.debug('onUpdateProgress');
             const {total, transferred, percent} = progress;
             const update = this.downloads[APP_UPDATE_KEY] || {...UPDATE_DOWNLOAD_ITEM};
             if (typeof update.addedAt !== 'number' || update.addedAt === 0) {
    
  • src/main/i18nManager.ts+3 3 modified
    @@ -30,15 +30,15 @@ export class I18nManager {
         }
     
         setLocale = (locale: string) => {
    -        log.debug('setLocale', locale);
    +        log.debug('setLocale', {locale});
     
             if (this.isLanguageAvailable(locale)) {
                 this.currentLanguage = this.getLanguages()[locale];
    -            log.info('Set new language', locale);
    +            log.info('Set new language', {locale});
                 return true;
             }
     
    -        log.warn('Failed to set new language', locale);
    +        log.warn('Failed to set new language', {locale});
             return false;
         };
     
    
  • src/main/notifications/index.ts+12 12 modified
    @@ -45,7 +45,7 @@ class NotificationManager {
         }
     
         public async displayMention(title: string, body: string, channelId: string, teamId: string, url: string, silent: boolean, webcontents: Electron.WebContents, soundName: string) {
    -        log.debug('displayMention', {channelId, teamId, url, silent, soundName});
    +        log.debug('displayMention', {silent, soundName});
     
             if (!Notification.isSupported()) {
                 log.error('notification not supported');
    @@ -59,17 +59,17 @@ class NotificationManager {
     
             const view = WebContentsManager.getViewByWebContentsId(webcontents.id);
             if (!view) {
    -            log.error('missing view', webcontents.id);
    +            log.error('missing view', {webcontentsId: webcontents.id});
                 return {status: 'error', reason: 'missing_view'};
             }
             const server = ServerManager.getServer(view.serverId);
             if (!server) {
    -            log.error('missing server', view.serverId);
    +            log.error('missing server', {serverId: view.serverId});
                 return {status: 'error', reason: 'missing_server'};
             }
             const serverName = server.name;
             if (!viewManager.isPrimaryView(view.id)) {
    -            log.debug('should not notify for this view', webcontents.id);
    +            log.debug('should not notify for this view', {webcontentsId: webcontents.id});
                 return {status: 'not_sent', reason: 'view_should_not_notify'};
             }
     
    @@ -81,15 +81,15 @@ class NotificationManager {
             };
     
             if (!await PermissionsManager.doPermissionRequest(webcontents.id, 'notifications', {requestingUrl: server.url.toString(), isMainFrame: false})) {
    -            log.verbose('permissions disallowed', webcontents.id, serverName, server.url.toString());
    +            log.verbose('permissions disallowed', {webcontentsId: webcontents.id, serverId: server.id});
                 return {status: 'not_sent', reason: 'notifications_permission_disallowed'};
             }
     
             const mention = new Mention(options, channelId, teamId);
             this.allActiveNotifications?.set(mention.uId, mention);
     
             mention.on('click', () => {
    -            log.debug('notification click', serverName, mention.uId);
    +            log.debug('notification click', server.id, mention.uId);
     
                 this.allActiveNotifications?.delete(mention.uId);
     
    @@ -111,7 +111,7 @@ class NotificationManager {
             return new Promise((resolve) => {
                 // If mention never shows somehow, resolve the promise after 10s
                 const timeout = setTimeout(() => {
    -                log.debug('notification timeout', serverName, mention.uId);
    +                log.debug('notification timeout', server.id, mention.uId);
                     resolve({status: 'error', reason: 'notification_timeout'});
                 }, 10000);
                 let failed = false;
    @@ -120,13 +120,13 @@ class NotificationManager {
                     // Ensure the failed event isn't also called, if it is we should resolve using its method
                     setTimeout(() => {
                         if (!failed) {
    -                        log.debug('displayMention.show', serverName, mention.uId);
    +                        log.debug('displayMention.show', server.id, mention.uId);
     
                             // On Windows, manually dismiss notifications from the same channel and only show the latest one
                             if (process.platform === 'win32') {
                                 const mentionKey = `${mention.teamId}:${mention.channelId}`;
                                 if (this.mentionsPerChannel?.has(mentionKey)) {
    -                                log.debug(`close ${mentionKey}`);
    +                                log.debug('close');
                                     this.mentionsPerChannel?.get(mentionKey)?.close();
                                     this.mentionsPerChannel?.delete(mentionKey);
                                 }
    @@ -153,7 +153,7 @@ class NotificationManager {
                         log.warn('notifications disabled in Windows settings');
                         resolve({status: 'not_sent', reason: 'windows_permissions_denied'});
                     } else {
    -                    log.error('notification failed to show', serverName, mention.uId, error);
    +                    log.error('notification failed to show', server.id, mention.uId, error);
                         resolve({status: 'error', reason: 'electron_notification_failed', data: error});
                     }
                 });
    @@ -162,7 +162,7 @@ class NotificationManager {
         }
     
         public async displayDownloadCompleted(fileName: string, path: string, serverName: string) {
    -        log.debug('displayDownloadCompleted', {fileName, path, serverName});
    +        log.debug('displayDownloadCompleted');
     
             if (!Notification.isSupported()) {
                 log.error('notification not supported');
    @@ -255,7 +255,7 @@ export async function getDoNotDisturb() {
                 const dnd = await getDarwinDoNotDisturb();
                 return dnd;
             } catch (e) {
    -            log.warn('macOS DND check threw an error', e);
    +            log.warn('macOS DND check threw an error', {e});
                 return false;
             }
         }
    
  • src/main/performanceMonitor.ts+2 2 modified
    @@ -63,15 +63,15 @@ export class PerformanceMonitor {
         };
     
         registerServerView = (name: string, webContents: WebContents, serverId: string) => {
    -        log.debug('registerServerView', webContents.id, serverId);
    +        log.debug('registerServerView', {webContentsId: webContents.id, serverId});
     
             webContents.on('did-finish-load', () => {
                 this.serverViews.set(webContents.id, {name, webContents, serverId});
             });
         };
     
         unregisterView = (webContentsId: number) => {
    -        log.debug('unregisterView', webContentsId);
    +        log.debug('unregisterView', {webContentsId});
     
             this.views.delete(webContentsId);
             this.serverViews.delete(webContentsId);
    
  • src/main/secureStorage.ts+8 8 modified
    @@ -89,7 +89,7 @@ export class SecureStorage {
                         return JSON.parse(decryptedData);
                     } catch (encryptedError: any) {
                         if (encryptedError.code !== 'ENOENT') {
    -                        log.warn('Failed to load encrypted secrets, trying plaintext fallback:', encryptedError);
    +                        log.warn('Failed to load encrypted secrets, trying plaintext fallback:', {encryptedError});
                         }
                     }
                 }
    @@ -102,12 +102,12 @@ export class SecureStorage {
                     if (plaintextError.code === 'ENOENT') {
                         log.debug('Secrets file does not exist, starting with empty storage');
                     } else {
    -                    log.warn('Failed to load plaintext secrets:', plaintextError);
    +                    log.warn('Failed to load plaintext secrets:', {plaintextError});
                     }
                     return {};
                 }
             } catch (error) {
    -            log.error('Failed to load secrets:', error);
    +            log.error('Failed to load secrets:', {error});
                 return {};
             }
         }
    @@ -140,7 +140,7 @@ export class SecureStorage {
                 // Update memory cache
                 this.memoryCache = {...secrets};
             } catch (error) {
    -            log.error('Failed to save secrets:', error);
    +            log.error('Failed to save secrets:', {error});
                 throw new Error('Failed to save secure data');
             }
         }
    @@ -170,7 +170,7 @@ export class SecureStorage {
             try {
                 const parsed = parseURL(url);
                 if (!parsed) {
    -                log.warn(`Failed to parse URL for secure storage: ${url}`);
    +                log.warn('Failed to parse URL for secure storage');
                     return url;
                 }
     
    @@ -180,7 +180,7 @@ export class SecureStorage {
                 const normalizedPath = getFormattedPathName(parsed.pathname);
                 return `${parsed.origin}${normalizedPath}`;
             } catch (error) {
    -            log.warn(`Failed to normalize URL for secure storage: ${url}`, error);
    +            log.warn('Failed to normalize URL for secure storage', {error});
     
                 // Fallback to the original URL if parsing fails
                 return url;
    @@ -236,7 +236,7 @@ export class SecureStorage {
             try {
                 return await this.getSecret(serverUrl, keySuffix as any);
             } catch (error) {
    -            log.error('Failed to get secure storage:', error);
    +            log.error('Failed to get secure storage:', {error});
                 return null;
             }
         }
    @@ -250,7 +250,7 @@ ipcMain.on(UPDATE_PATHS, () => {
         secureStorage.userDataPath = secureStoragePath;
         if (secureStorage.memoryCache) {
             secureStorage.load().catch((error) => {
    -            log.error('Failed to reload secure storage after path update:', error);
    +            log.error('Failed to reload secure storage after path update:', {error});
             });
         }
     });
    
  • src/main/security/allowProtocolDialog.ts+1 1 modified
    @@ -84,7 +84,7 @@ export class AllowProtocolDialog {
                     break;
                 }
             } catch (error) {
    -            log.warn('Could not open external URL', error);
    +            log.warn('Could not open external URL', {error});
             }
         };
     }
    
  • src/main/security/permissionsManager.ts+1 1 modified
    @@ -119,7 +119,7 @@ export class PermissionsManager extends JsonFileManager<PermissionsByOrigin> {
             permission: string,
             details: PermissionRequestHandlerHandlerDetails,
         ) => {
    -        log.debug('doPermissionRequest', permission, details);
    +        log.debug('doPermissionRequest', {webContentsId, permission});
     
             // is the requested permission type supported?
             if (!supportedPermissionTypes.includes(permission)) {
    
  • src/main/server/serverAPI.ts+3 3 modified
    @@ -25,7 +25,7 @@ export async function getServerAPI(url: URL, isAuthenticated: boolean, onSuccess
     
             if (!userId || !csrf || !authToken) {
                 // Missing cookies needed for req
    -            log.error(`Cannot authenticate, required cookies for ${url.origin} not found`);
    +            log.error('Cannot authenticate, required cookies not found');
                 return;
             }
         }
    @@ -43,11 +43,11 @@ export async function getServerAPI(url: URL, isAuthenticated: boolean, onSuccess
     
         if (onSuccess) {
             req.on('response', (response: Electron.IncomingMessage) => {
    -            log.silly('response', response);
    +            log.silly('response');
                 if (response.statusCode === 200) {
                     let raw = '';
                     response.on('data', (chunk: Buffer) => {
    -                    log.silly('response.data', `${chunk}`);
    +                    log.silly('response.data');
                         raw += `${chunk}`;
                     });
                     response.on('end', () => {
    
  • src/main/UserActivityMonitor.ts+1 1 modified
    @@ -72,7 +72,7 @@ export class UserActivityMonitor extends EventEmitter {
                 try {
                     this.updateIdleTime(powerMonitor.getSystemIdleTime());
                 } catch (err) {
    -                log.error('Error getting system idle time:', err);
    +                log.error('Error getting system idle time:', {err});
                 }
             }, this.config.updateFrequencyMs) as unknown as number;
         }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.