global-errors.web.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import { captureError } from './health';
  2. /**
  3. * Uncaught errors, unhandled rejections and the site's own scripts and stylesheets that fail
  4. * to load, for UNA's analytics. Installed before hydration by `apps/next/instrumentation-client.ts`
  5. * (and again, as a no-op, by the tracker). Listeners are added, never assigned
  6. * (`window.onerror`), so other handlers keep working.
  7. */
  8. type FlaggedWindow = Window & { __neoAnalyticsErrors?: boolean };
  9. function isSameOrigin(url: string): boolean {
  10. try {
  11. return new URL(url, window.location.href).origin === window.location.origin;
  12. } catch {
  13. return false;
  14. }
  15. }
  16. function onError(event: Event): void {
  17. try {
  18. const target = event.target;
  19. // capture phase: an element that fails to load doesn't bubble to window
  20. if (target && target !== window && target instanceof Element) {
  21. const isScript = target instanceof HTMLScriptElement;
  22. const url = isScript
  23. ? target.src
  24. : target instanceof HTMLLinkElement && /stylesheet/i.test(target.rel) ? target.href : '';
  25. // the site's own files: a blocked tracker or ad elsewhere is not the site's error
  26. if (url && isSameOrigin(url)) {
  27. captureError('resource', { message: `Failed to load ${isScript ? 'script' : 'stylesheet'} ${url}`, source: url });
  28. }
  29. return;
  30. }
  31. const errorEvent = event as ErrorEvent;
  32. captureError('js', {
  33. error: errorEvent.error,
  34. message: errorEvent.message || undefined,
  35. source: errorEvent.filename,
  36. line: errorEvent.lineno,
  37. col: errorEvent.colno,
  38. });
  39. } catch {
  40. // never throw from an error listener
  41. }
  42. }
  43. function onRejection(event: PromiseRejectionEvent): void {
  44. try {
  45. captureError('promise', { error: event.reason, nonErrorPrefix: 'Non-Error promise rejection: ' });
  46. } catch {
  47. // never throw from an error listener
  48. }
  49. }
  50. export function installGlobalErrorCapture(): void {
  51. if (typeof window === 'undefined') return;
  52. const flagged = window as FlaggedWindow;
  53. if (flagged.__neoAnalyticsErrors) return;
  54. flagged.__neoAnalyticsErrors = true;
  55. window.addEventListener('error', onError, true);
  56. window.addEventListener('unhandledrejection', onRejection);
  57. }