fix(desktop): remove deprecated console-message arguments
Use Electron 40's event-object console-message contract, retain one-argument listener arity, and report malformed signature drift once so renderer logging cannot fail silently. Co-authored-by: CupaJ12 <108900676+CupaJ12@users.noreply.github.com> (cherry picked from commit 378701b2b7af1e0a3accfaec7b8daa46c30832d6)
This commit is contained in:
committed by
brooklyn!
parent
ac80df1410
commit
1498ae0233
@@ -2,51 +2,85 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { attachRendererConsoleCapture, formatRendererBoundaryReport, formatRendererConsoleLine } from './renderer-log'
|
||||
|
||||
type ConsoleMessageEvent = {
|
||||
level?: unknown
|
||||
message?: unknown
|
||||
sourceId?: unknown
|
||||
lineNumber?: unknown
|
||||
}
|
||||
|
||||
type ConsoleMessageHandler = (event: ConsoleMessageEvent) => void
|
||||
|
||||
function createWindowHarness() {
|
||||
let handler: ConsoleMessageHandler | undefined
|
||||
|
||||
const win = {
|
||||
webContents: {
|
||||
on: (_event: 'console-message', listener: ConsoleMessageHandler) => {
|
||||
handler = listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { win, getHandler: () => handler }
|
||||
}
|
||||
|
||||
describe('formatRendererConsoleLine', () => {
|
||||
it('formats the canonical Electron 36+ details shape at error level', () => {
|
||||
it('formats the canonical Electron console-message event at error level', () => {
|
||||
const line = formatRendererConsoleLine('hud', {
|
||||
level: 3,
|
||||
level: 'error',
|
||||
message: 'Minified React error #310',
|
||||
sourceUrl: 'file:///app/index.js',
|
||||
sourceId: 'file:///app/index.js',
|
||||
lineNumber: 13
|
||||
})
|
||||
|
||||
expect(line).toBe('[renderer console:hud] Minified React error #310 (file:///app/index.js:13)')
|
||||
})
|
||||
|
||||
it('formats the deprecated positional shape at error level', () => {
|
||||
const line = formatRendererConsoleLine('main', 3, 'boom', 7, 'file:///app/vendor.js')
|
||||
|
||||
expect(line).toBe('[renderer console:main] boom (file:///app/vendor.js:7)')
|
||||
it('drops a canonical non-error string level', () => {
|
||||
expect(
|
||||
formatRendererConsoleLine('main', { level: 'info', message: 'x', sourceId: 's', lineNumber: 1 })
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('drops non-error levels in both shapes', () => {
|
||||
expect(formatRendererConsoleLine('main', { level: 1, message: 'x', sourceUrl: 's', lineNumber: 1 })).toBeNull()
|
||||
expect(formatRendererConsoleLine('main', 2, 'warn', 1, 's')).toBeNull()
|
||||
it('drops malformed event objects', () => {
|
||||
expect(formatRendererConsoleLine('main', {})).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('attachRendererConsoleCapture', () => {
|
||||
it('logs error-level messages and skips the rest', () => {
|
||||
it('registers a single-argument listener, logs errors, and skips the rest', () => {
|
||||
const log = vi.fn()
|
||||
let handler: ((...args: unknown[]) => void) | undefined
|
||||
const capture = createWindowHarness()
|
||||
|
||||
const win = {
|
||||
webContents: {
|
||||
on: (_event: string, listener: (...args: unknown[]) => void) => {
|
||||
handler = listener
|
||||
}
|
||||
}
|
||||
}
|
||||
attachRendererConsoleCapture(capture.win, 'quick-entry', log)
|
||||
|
||||
attachRendererConsoleCapture(win, 'quick-entry', log)
|
||||
const handler = capture.getHandler()
|
||||
expect(handler).toHaveLength(1)
|
||||
|
||||
handler?.({}, { level: 3, message: 'crash', sourceUrl: 'src', lineNumber: 2 })
|
||||
handler?.({}, { level: 0, message: 'debug', sourceUrl: 'src', lineNumber: 3 })
|
||||
handler?.({ level: 'error', message: 'crash', sourceId: 'src', lineNumber: 2 })
|
||||
handler?.({ level: 'debug', message: 'debug', sourceId: 'src', lineNumber: 3 })
|
||||
|
||||
expect(log).toHaveBeenCalledTimes(1)
|
||||
expect(log).toHaveBeenCalledWith('[renderer console:quick-entry] crash (src:2)')
|
||||
})
|
||||
|
||||
it('reports signature drift once across renderer windows', () => {
|
||||
const log = vi.fn()
|
||||
const firstCapture = createWindowHarness()
|
||||
const secondCapture = createWindowHarness()
|
||||
|
||||
attachRendererConsoleCapture(firstCapture.win, 'main', log)
|
||||
attachRendererConsoleCapture(secondCapture.win, 'hud', log)
|
||||
|
||||
firstCapture.getHandler()?.({})
|
||||
secondCapture.getHandler()?.({})
|
||||
|
||||
expect(log).toHaveBeenCalledTimes(1)
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
'[renderer console] Electron console-message signature drift detected; renderer errors may not be captured'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatRendererBoundaryReport', () => {
|
||||
|
||||
@@ -15,53 +15,75 @@
|
||||
* tokens or PII we never want on disk.
|
||||
*/
|
||||
|
||||
type ConsoleMessageLevel = 'info' | 'warning' | 'error' | 'debug'
|
||||
|
||||
interface ConsoleMessageEventLike {
|
||||
level?: unknown
|
||||
message?: unknown
|
||||
sourceId?: unknown
|
||||
lineNumber?: unknown
|
||||
}
|
||||
|
||||
interface ConsoleMessageDetails {
|
||||
level: number
|
||||
level: ConsoleMessageLevel
|
||||
message: string
|
||||
sourceUrl: string
|
||||
sourceId: string
|
||||
lineNumber: number
|
||||
}
|
||||
|
||||
interface WebContentsLike {
|
||||
on(event: 'console-message', listener: (...args: unknown[]) => void): unknown
|
||||
on(event: 'console-message', listener: (event: ConsoleMessageEventLike) => void): unknown
|
||||
}
|
||||
|
||||
interface WindowLike {
|
||||
webContents: WebContentsLike
|
||||
}
|
||||
|
||||
/** Normalize Electron's two `console-message` signatures into one line, or
|
||||
* null for non-error levels. Canonical (Electron 36+): `(event, details)`;
|
||||
* deprecated positional: `(event, level, message, line, sourceId)`.
|
||||
* `level` is numeric 0..3, where 3 === error. */
|
||||
export function formatRendererConsoleLine(
|
||||
label: string,
|
||||
detailsOrLevel: unknown,
|
||||
message?: unknown,
|
||||
line?: unknown,
|
||||
sourceId?: unknown
|
||||
): string | null {
|
||||
const details =
|
||||
detailsOrLevel && typeof detailsOrLevel === 'object' ? (detailsOrLevel as ConsoleMessageDetails) : null
|
||||
let didReportConsoleMessageSignatureDrift = false
|
||||
|
||||
const level = details ? details.level : detailsOrLevel
|
||||
function isConsoleMessageDetails(value: unknown): value is ConsoleMessageDetails {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (level !== 3) {
|
||||
const event = value as ConsoleMessageEventLike
|
||||
const isKnownLevel =
|
||||
event.level === 'info' || event.level === 'warning' || event.level === 'error' || event.level === 'debug'
|
||||
|
||||
return (
|
||||
isKnownLevel &&
|
||||
typeof event.message === 'string' &&
|
||||
typeof event.sourceId === 'string' &&
|
||||
typeof event.lineNumber === 'number'
|
||||
)
|
||||
}
|
||||
|
||||
/** Format Electron's canonical console-message event object into one line, or
|
||||
* null for non-error or malformed events. Hermes's pinned Electron 40.x line
|
||||
* puts severity and source metadata on the event object itself; accepting one
|
||||
* listener argument also avoids Electron's deprecated positional
|
||||
* `(event, level, message, line, sourceId)` path. */
|
||||
export function formatRendererConsoleLine(label: string, details: ConsoleMessageEventLike): string | null {
|
||||
if (!isConsoleMessageDetails(details) || details.level !== 'error') {
|
||||
return null
|
||||
}
|
||||
|
||||
const text = details ? details.message : message
|
||||
const src = details ? details.sourceUrl : sourceId
|
||||
const lineNo = details ? details.lineNumber : line
|
||||
|
||||
return `[renderer console:${label}] ${String(text)} (${String(src)}:${String(lineNo)})`
|
||||
return `[renderer console:${label}] ${details.message} (${details.sourceId}:${String(details.lineNumber)})`
|
||||
}
|
||||
|
||||
/** Attach the error-level console hook to a renderer window. `log` is the
|
||||
* desktop.log sink (rememberLog in main.ts). */
|
||||
export function attachRendererConsoleCapture(win: WindowLike, label: string, log: (line: string) => void): void {
|
||||
win.webContents.on('console-message', (_event, detailsOrLevel, message, line, sourceId) => {
|
||||
const formatted = formatRendererConsoleLine(label, detailsOrLevel, message, line, sourceId)
|
||||
win.webContents.on('console-message', (event) => {
|
||||
if (!isConsoleMessageDetails(event)) {
|
||||
if (!didReportConsoleMessageSignatureDrift) {
|
||||
didReportConsoleMessageSignatureDrift = true
|
||||
log('[renderer console] Electron console-message signature drift detected; renderer errors may not be captured')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const formatted = formatRendererConsoleLine(label, event)
|
||||
|
||||
if (formatted !== null) {
|
||||
log(formatted)
|
||||
|
||||
Reference in New Issue
Block a user