import { EnvironmentInjector, inject, runInInjectionContext } from '@angular/core'; import type { ElectronApi } from '../../core/platform/electron/electron-api.models'; import { PerfDiagnosticsCollector, publishRendererDiagnosticsSample } from './diagnostics.collector'; import type { PerfDiagEntry, PerfDiagReporter } from './diagnostics.models'; declare global { // Registered for synchronous main-process sampling at high-memory threshold. var __collectPerfDiagSample: (() => PerfDiagEntry[]) | undefined; } const SAMPLE_INTERVAL_MS = 10_000; let started = false; let sampleTimer: ReturnType | null = null; let immediateCollectorRegistered = false; export function registerImmediatePerfDiagCollector(injector: EnvironmentInjector): void { if (immediateCollectorRegistered) { return; } let immediateSampleCollector: PerfDiagnosticsCollector | null = null; runInInjectionContext(injector, () => { immediateSampleCollector = inject(PerfDiagnosticsCollector); }); globalThis.__collectPerfDiagSample = () => { if (!immediateSampleCollector) { return []; } const sample = immediateSampleCollector.collectSample(); return sample ? immediateSampleCollector.buildEntries(sample) : []; }; immediateCollectorRegistered = true; } export async function bootstrapPerfDiagnostics( api: ElectronApi, injector: EnvironmentInjector ): Promise { registerImmediatePerfDiagCollector(injector); const reportSample = api.reportPerfDiagSample; if (started || !api.isPerfDiagEnabled || !reportSample) { return; } let enabled = false; try { enabled = await api.isPerfDiagEnabled(); } catch { return; } if (!enabled) { return; } started = true; const reporter: PerfDiagReporter = { report: (entry: PerfDiagEntry) => reportSample(entry) }; const runSample = (): void => { void runInInjectionContext(injector, async () => { try { const collector = inject(PerfDiagnosticsCollector); await publishRendererDiagnosticsSample(reporter, collector); } catch { stopPerfDiagnosticsSampling(); } }); }; scheduleSample(runSample); sampleTimer = setInterval(() => scheduleSample(runSample), SAMPLE_INTERVAL_MS); window.addEventListener('error', () => { void reporter.report({ collectedAt: Date.now(), source: 'renderer', type: 'crash', payload: { scope: 'window-error' } }); }); window.addEventListener('unhandledrejection', () => { void reporter.report({ collectedAt: Date.now(), source: 'renderer', type: 'crash', payload: { scope: 'unhandled-rejection' } }); }); } function scheduleSample(runSample: () => void): void { const idle = (globalThis as { requestIdleCallback?: (handler: () => void, options?: { timeout: number }) => number; }).requestIdleCallback; if (idle) { idle(() => runSample(), { timeout: SAMPLE_INTERVAL_MS }); return; } setTimeout(runSample, 0); } function stopPerfDiagnosticsSampling(): void { if (sampleTimer) { clearInterval(sampleTimer); sampleTimer = null; } started = false; } /** @internal Resets module state between unit tests. */ export function resetDiagnosticsBootstrapStateForTests(): void { stopPerfDiagnosticsSampling(); immediateCollectorRegistered = false; delete globalThis.__collectPerfDiagSample; }