UNPKG

@steambrew/client

Version:
95 lines (94 loc) 4.46 kB
import { ErrorBoundary } from '../components'; import Logger from '../logger'; import { callOriginal, replacePatch } from '../utils'; import { findModuleExport } from '../webpack'; import { getLikelyErrorSourceFromValveError } from './error-from-source'; class ErrorBoundaryHook extends Logger { constructor() { super('ErrorBoundaryHook'); this.doNotReportErrors = false; this.disableReportingTimer = 0; this.log('Initialized'); window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.(); window.__ERRORBOUNDARY_HOOK_INSTANCE = this; // valve writes only the sanest of code const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/; const initErrorReportingStore = findModuleExport((e) => typeof e == 'function' && e?.toString && exp.test(e.toString())); if (!initErrorReportingStore) { this.error('could not find initErrorReportingStore! error boundary hook disabled!'); return; } // will replace the existing one for us seemingly? doesnt matter anyway lol const errorReportingStore = initErrorReportingStore(); // NUH UH. // Object.defineProperty(Object.getPrototypeOf(errorReportingStore), 'reporting_enabled', { // get: () => false, // }); // errorReportingStore.m_bEnabled = false; // @ts-ignore // window.errorStore = errorReportingStore; const react15069WorkaroundRegex = / at .+\.componentDidCatch\..+\.callback /; this.errorCheckPatch = replacePatch(Object.getPrototypeOf(errorReportingStore), 'BIsBlacklisted', (args) => { const [errorSource, wasPlugin, shouldReport] = getLikelyErrorSourceFromValveError(args[0]); this.debug('Caught an error', args, { errorSource, wasPlugin, shouldReport, skipAllReporting: this.doNotReportErrors || this.disableReportingTimer, }); if (!shouldReport) this.temporarilyDisableReporting(); // react#15069 workaround. this took 2 hours to figure out. if (args[0]?.message?.[3]?.[0] && args[0]?.message?.[1]?.[0] == ' at console.error ' && react15069WorkaroundRegex.test(args[0].message[3][0])) { this.debug('ignoring early report caused by react#15069'); return true; } if (this.doNotReportErrors || this.disableReportingTimer) return true; return shouldReport ? callOriginal : true; }); if (!ErrorBoundary) { this.error('@steambrew/client could not find ErrorBoundary, skipping patch'); return; } this.errorBoundaryPatch = replacePatch(ErrorBoundary.prototype, 'render', function () { if (this.state._millenniumForceRerender) { console.debug('Forcing rerender'); const stateClone = { ...this.state, _millenniumForceRerender: null }; this.setState(stateClone); return; } // if (this.state.error) { // const store = Object.getPrototypeOf(this)?.constructor?.sm_ErrorReportingStore || errorReportingStore; // return ( // <DeckyErrorBoundary // error={this.state.error} // errorKey={this.props.errorKey} // identifier={`${store.product}_${store.version}_${this.state.identifierHash}`} // reset={() => this.Reset()} // /> // ); // } return callOriginal; }); // Small hack that gives us a lot more flexibility to force rerenders. ErrorBoundary.prototype._millenniumForceRerender = function () { this.setState({ ...this.state, _millenniumForceRerender: true }); }; } temporarilyDisableReporting() { this.debug('Reporting disabled for 30s due to a non-steam error.'); if (this.disableReportingTimer) { clearTimeout(this.disableReportingTimer); } this.disableReportingTimer = window.setTimeout(() => { this.debug('Reporting re-enabled after 30s timeout.'); this.disableReportingTimer = 0; }, 30000); } deinit() { this.errorCheckPatch?.unpatch(); this.errorBoundaryPatch?.unpatch(); } } export default ErrorBoundaryHook;