import { Action } from 'wdk-client/Actions'; import { ActionsObservable, StateObservable } from 'redux-observable'; import { Observable, EMPTY, fromEvent, merge } from 'rxjs'; import { filter, tap, mergeMapTo, map, mapTo } from 'rxjs/operators'; import { notifyUnhandledError, clearUnhandledErrors, UnhandledError } from 'wdk-client/Actions/UnhandledErrorActions'; import { RootState } from 'wdk-client/Core/State/Types'; import { EpicDependencies } from 'wdk-client/Core/Store'; import { updateLocation } from 'wdk-client/Actions/RouterActions'; export const key = 'unhandledErrors'; export interface State { errors: UnhandledError[] } const initialState: State = { errors: [] } export function reduce(state: State = initialState, action: Action): State { switch(action.type) { case notifyUnhandledError.type: return { ...state, errors: [ ...state.errors, action.payload.unhandledError ] } case clearUnhandledErrors.type: return { errors: [] }; default: return state; } } // TODO Allow this to be configured by wdk-client consumer function ignoreError(message: string): boolean { return ( /ResizeObserver loop limit exceeded/.test(message) ) } export function observe(action$: ActionsObservable, state$: StateObservable, { wdkService }: EpicDependencies): Observable { // map unhandled promise rejections to unhandledError action const rejection$: Observable = fromEvent(window, 'unhandledrejection').pipe( filter((event: PromiseRejectionEvent) => { return !ignoreError(String(event.reason)); }), map((event: PromiseRejectionEvent) => notifyUnhandledError(event.reason)) ); // map unhandled errors to unhandledError action const error$: Observable = fromEvent(window, 'error').pipe( filter((event: ErrorEvent) => { return !ignoreError(event.message); }), map((event: ErrorEvent) => { return notifyUnhandledError(event.error ?? event.message) }) ); // clear errors when route changes const clear$: Observable = action$.pipe( filter(updateLocation.isOfType), mapTo(clearUnhandledErrors()) ); // log errors as they come in const notify$: Observable = action$.pipe( filter(notifyUnhandledError.isOfType), tap(async action => { try { const { unhandledError: { error, id, info } } = action.payload; console.error(error); await wdkService.submitErrorIfNot500(error instanceof Error ? error : new Error(String(error)), { id, info }); } catch (error) { console.error('Error logging request failed:', error); } }), mergeMapTo(EMPTY), ); return merge(rejection$, error$, clear$, notify$); }