@steambrew/client
Version:
A support library for creating plugins with Millennium.
95 lines (94 loc) • 4.46 kB
JavaScript
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;