globalCapture.ts2.4 KBView on GitHub
import { push } from './buffer';

const LONGTASK_THRESHOLD_MS = 100;

let initialized = false;
let perfObserver: PerformanceObserver | null = null;
let errorHandler: ((e: ErrorEvent) => void) | null = null;
let rejectionHandler: ((e: PromiseRejectionEvent) => void) | null = null;

export function initGlobalCapture(): void {
  if (initialized || typeof window === 'undefined') return;
  initialized = true;

  errorHandler = (e: ErrorEvent) => {
    push({
      source: 'error',
      kind: 'window',
      message: e.message || String(e.error),
      stack: e.error instanceof Error ? e.error.stack : undefined,
    });
  };
  window.addEventListener('error', errorHandler);

  rejectionHandler = (e: PromiseRejectionEvent) => {
    const reason = e.reason;
    push({
      source: 'error',
      kind: 'rejection',
      message: reason instanceof Error ? reason.message : String(reason),
      stack: reason instanceof Error ? reason.stack : undefined,
    });
  };
  window.addEventListener('unhandledrejection', rejectionHandler);

  if (typeof PerformanceObserver !== 'undefined') {
    try {
      perfObserver = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.entryType === 'longtask' && entry.duration >= LONGTASK_THRESHOLD_MS) {
            push({
              source: 'perf',
              name: 'longtask',
              durationMs: entry.duration,
              detail: {
                startTime: entry.startTime,
                attribution: (entry as PerformanceEntry & {
                  attribution?: Array<{ name?: string; containerType?: string; containerSrc?: string }>;
                }).attribution?.map((a) => ({
                  name: a.name,
                  containerType: a.containerType,
                  containerSrc: a.containerSrc,
                })),
              },
            });
          }
        }
      });
      perfObserver.observe({ entryTypes: ['longtask'] });
    } catch {
      // longtask not supported in this browser; ignore
    }
  }
}

export function disposeGlobalCapture(): void {
  if (!initialized) return;
  if (errorHandler) window.removeEventListener('error', errorHandler);
  if (rejectionHandler) window.removeEventListener('unhandledrejection', rejectionHandler);
  perfObserver?.disconnect();
  errorHandler = null;
  rejectionHandler = null;
  perfObserver = null;
  initialized = false;
}