Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/utils/editor-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { loadEditorAssets } from './editor-loader';
import { initializeVideoPressAjaxBridge } from './videopress-bridge';
import EditorLoadError from '../components/editor-load-error';
import { error } from './logger';
import { setUpGlobalErrorHandlers } from './global-error-handler';
import './editor-styles';

/**
Expand All @@ -15,6 +16,8 @@ import './editor-styles';
* @return {Promise} Promise that resolves when initialization is complete
*/
export function setUpEditorEnvironment() {
setUpGlobalErrorHandlers();

// Detect platform and add class to body for platform-specific styling
if ( typeof window !== 'undefined' && window.webkit ) {
document.body.classList.add( 'is-ios' );
Expand Down
86 changes: 86 additions & 0 deletions src/utils/global-error-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Internal dependencies
*/
import { logException } from './bridge';

/**
* Sets up global error handlers to catch and report unhandled errors
* and promise rejections.
*/
export function setUpGlobalErrorHandlers() {
// Catch unhandled errors
window.addEventListener( 'error', ( event ) => {
if ( isExternalError( event.filename, event.error ) ) {
return;
}

const errorObj = event.error || new Error( event.message );

logException( errorObj, {
context: {
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
},
tags: {},
isHandled: false,
handledBy: 'window.error',
} );
} );

// Catch unhandled promise rejections
window.addEventListener( 'unhandledrejection', ( event ) => {
// Convert rejection reason to Error if it isn't already
const errorObj =
event.reason instanceof Error
? event.reason
: new Error( String( event.reason ) );

if ( isExternalError( undefined, errorObj ) ) {
return;
}

logException( errorObj, {
context: {},
tags: {},
isHandled: false,
handledBy: 'unhandledrejection',
} );
} );
}

/**
* Determines if an error originated from an external third-party script.
*
* Detects external HTTP(S) URLs from different origins (third-party scripts).
* GutenbergKit errors (same-origin, file://, or unknown sources) return false.
*
* @param {string|undefined} filename - The filename from the error event
* @param {Error|undefined} errorObj - The error object with stack trace
* @return {boolean} True if the error is from an external source (should be filtered)
*/
function isExternalError( filename, errorObj ) {
// Detect external HTTP(S) URLs from different origins (third-party scripts)
if (
filename?.startsWith( 'http' ) &&
! filename.includes( window.location.origin )
) {
return true;
}

// Check stack trace for external URLs
if ( errorObj?.stack ) {
const stackLines = errorObj.stack.split( '\n' );
for ( const line of stackLines ) {
// Check if any line in stack contains external HTTP URL
if (
line.includes( 'http' ) &&
! line.includes( window.location.origin )
) {
return true;
}
}
}

return false;
}