Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recovery Coremod #611

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dc7b50a
Fixed Replugged Crash
zrodevkaan Feb 8, 2024
4af4100
Merge branch 'main' of https://github.com/zrodevkaan/replugged
zrodevkaan Apr 3, 2024
5f8e166
ExperimentsFix
zrodevkaan Apr 3, 2024
e45ec7b
uhhhh. something something
zrodevkaan Apr 4, 2024
8c1b8ef
Update cspell.json
zrodevkaan Apr 4, 2024
a9345b6
Merge branch 'main' into recoveryCoreMod
zrodevkaan Apr 4, 2024
1b093b9
more things
zrodevkaan Apr 4, 2024
1963a02
info null uwu
zrodevkaan Apr 4, 2024
bdecea1
bruh a whitespace made it mad
zrodevkaan Apr 4, 2024
091036d
Merge branch 'main' into recoveryCoreMod
zrodevkaan Apr 4, 2024
b2a4a9d
fishy keyboard man. be happy
zrodevkaan Apr 4, 2024
cbf9106
Merge branch 'main' into recoveryCoreMod
zrodevkaan Apr 4, 2024
f7ce46b
could be better,
zrodevkaan Apr 5, 2024
b436456
uwu lint
zrodevkaan Apr 5, 2024
6961d2a
upgrades people upgrades
zrodevkaan Apr 5, 2024
04a05a6
i hate types
zrodevkaan Apr 8, 2024
8311921
types fixed
zrodevkaan Apr 8, 2024
0c88277
Merge branch 'main' into recoveryCoreMod
zrodevkaan Apr 8, 2024
f14ea59
Upgrades people Upgrades
zrodevkaan Apr 9, 2024
0ab9015
yes it would
zrodevkaan Apr 9, 2024
29f1e74
findInTree for children and comment removed.
zrodevkaan Apr 9, 2024
0001200
removed Falsy Check
zrodevkaan Apr 9, 2024
c7921c3
Main Resolves
zrodevkaan Apr 10, 2024
281dade
Fixed SlashScreen Crash
zrodevkaan Apr 10, 2024
9cae726
Final SplashScreen Crash
zrodevkaan Apr 10, 2024
4e6963b
fixed styles selector. + Fallback
zrodevkaan Apr 10, 2024
86f0a9e
forgot lint :)
zrodevkaan Apr 10, 2024
04f43f1
Merge branch 'main' into recoveryCoreMod
zrodevkaan Jun 21, 2024
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
488 changes: 244 additions & 244 deletions i18n/en-US.json

Large diffs are not rendered by default.

155 changes: 155 additions & 0 deletions src/renderer/coremods/recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { fluxDispatcher, parser, toast } from "@common";
import { Button } from "@components";
import { Injector, Logger } from "@replugged";
import { filters, getByProps, waitForModule } from "@webpack";
import "./styles.css";
import { AnyFunction } from "../../../types";
import { Messages } from "@common/i18n";

import { generalSettings } from "../settings/pages";
import { disable } from "../../managers/plugins";
import { Tree, findInTree } from "../../util";
const injector = new Injector();

// const URL_REGEX_FIND = /https:\/\/\S+/g;
zrodevkaan marked this conversation as resolved.
Show resolved Hide resolved
const PLUGIN_ID_FIND_REGEX = /plugin\/(.*?)\.asar/;
const FIND_ERROR_NUMBER = /invariant=(\d+)&/;
const ReactErrorList =
"https://raw.githubusercontent.com/facebook/react/v18.2.0/scripts/error-codes/codes.json";
lexisother marked this conversation as resolved.
Show resolved Hide resolved
const ReactErrorListFallback =
"https://raw.githubusercontent.com/facebook/react/v17.0.0/scripts/error-codes/codes.json";
const logger = Logger.coremod("recovery");
let ReactErrors: Record<string, string> | undefined;

interface ErrorComponentState {
error: {
message: string;
stack: string;
} | null;
info: null;
}
interface ErrorScreenClass {
prototype: {
render: AnyFunction;
};
}
interface ErrorScreenInstance {
state?: ErrorComponentState;
setState: (state: ErrorComponentState) => void;
}

interface Modals {
closeAllModals: AnyFunction;
}

interface RouteInstance {
transitionTo: (location: string) => void;
}

const ModalModule: Modals | undefined = getByProps("openModalLazy");
const RouteModule: RouteInstance | undefined = getByProps("transitionTo");

function startMainRecovery(): void {
const log = (reason: string): void => logger.log(reason),
err = (reason: string): void => logger.error(reason);
log("Starting main recovery methods.");
if (!ModalModule) {
err("Could not find `openModalLazy` Module.");
return;
}

try {
// I think trying to transition first is a better move.
// Considering most errors come from patching.
RouteModule?.transitionTo("/channels/@me");
} catch {
err("Failed to transition to '/channels/@me'.");
}

try {
fluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" });
} catch {
err("ContextMenu's could not be closed.");
}

try {
ModalModule.closeAllModals();
} catch {
err("Could not close (most) modals.");
}

log("Ended main recovery.");
}
interface TreeNode {
children: React.ReactElement[];
}
export async function start(): Promise<void> {
const ErrorScreen = await waitForModule<ErrorScreenClass>(
filters.bySource(".AnalyticEvents.APP_CRASHED"),
);
void startErrors();
injector.after(
ErrorScreen.prototype,
"render",
(_args, res: React.ReactElement, instance: ErrorScreenInstance): void => {
if (generalSettings.get("automaticRecover")) {
startMainRecovery();
instance.setState({ error: null, info: null });
}
if (!instance.state?.error) return;
const {
props: { children },
}: { props: TreeNode } = findInTree(res as unknown as Tree, (x) => Boolean(x?.action))
?.action as {
props: TreeNode;
};
if (!instance.state.error) return;

Check warning on line 106 in src/renderer/coremods/recovery/index.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

Unnecessary conditional, value is always falsy
const stackError = instance.state.error.stack;
const pluginId = stackError.match(PLUGIN_ID_FIND_REGEX);
if (pluginId) {
void disable(pluginId[1]);
toast.toast(
Messages.REPLUGGED_TOAST_ADDON_DISABLE_SUCCESS.format({
name: pluginId[1],
}),
toast.Kind.SUCCESS,
);
}

const invar = stackError.match(FIND_ERROR_NUMBER);

children.push(
<>
<Button
className={`replugged-recovery-button`}
onClick={() => {
startMainRecovery();
instance.setState({ error: null, info: null });
}}>
Recover Discord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n missing here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to do here as I removed the other i18n string due to you saying you dont know what would happen with conflicts

</Button>
<div className={"recovery-parse"}>
{parser.parse(`\`\`\`${invar ? ReactErrors?.[invar[1]] : ""}\n\n${stackError}\`\`\``)}
</div>
</>,
);
},
);
}

export function stop(): void {
injector.uninjectAll();
}

export async function startErrors(): Promise<void> {
ReactErrors = await fetch(ReactErrorList)
.then((response) => response.json())
.catch(async (error) => {
logger.error("ReactErrorList Fail:", error);
return await fetch(ReactErrorListFallback).then((response) => response.json());
})
.catch((error) => {
logger.error("ReactErrorListFallback Fail:", error, "\nFalling back to {}");
return {};
});
}
21 changes: 21 additions & 0 deletions src/renderer/coremods/recovery/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[class*="errorPage"] [class*="buttons_"] {
flex-direction: column;
align-items: center;
}
[class*="errorPage"] [class*="buttons_"] [class*="button__"] {
zrodevkaan marked this conversation as resolved.
Show resolved Hide resolved
width: 400px;
margin: 5px;
}
.replugged-recovery-button {
width: var(--custom-button-button-lg-width);
height: var(--custom-button-button-lg-height);
min-width: var(--custom-button-button-lg-width);
min-height: var(--custom-button-button-lg-height);

background-color: var(--button-danger-background) !important;
}
[class*="errorPage"] [class*="scrollbarGhostHairline"] {
white-space: break-spaces;
width: 80vw;
text-align: center;
}
6 changes: 6 additions & 0 deletions src/renderer/coremods/settings/pages/General.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export const General = (): React.ReactElement => {
{Messages.REPLUGGED_SETTINGS_BADGES}
</SwitchItem>

<SwitchItem
{...util.useSetting(generalSettings, "automaticRecover")}
note={"Allows Discord to try to automatically recover from a crash."}>
{"Automatic Recovery"}
</SwitchItem>

<SwitchItem
{...util.useSetting(generalSettings, "addonEmbeds")}
note={Messages.REPLUGGED_SETTINGS_ADDON_EMBEDS_DESC}>
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/managers/coremods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export namespace coremods {
export let watcher: Coremod;
export let commands: Coremod;
export let welcome: Coremod;
export let recovery: Coremod;
}

export async function start(name: keyof typeof coremods): Promise<void> {
Expand All @@ -60,6 +61,7 @@ export async function startAll(): Promise<void> {
coremods.watcher = await import("../coremods/watcher");
coremods.commands = await import("../coremods/commands");
coremods.welcome = await import("../coremods/welcome");
coremods.recovery = await import("../coremods/recovery");
coremods.notrack = await import("../coremods/notrack");

await Promise.all(
Expand Down
27 changes: 16 additions & 11 deletions src/renderer/modules/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ enum InjectionTypes {
* @param self The module the injected function is on
* @returns New arguments to pass to the original function, or undefined to leave them unchanged
*/
export type BeforeCallback<A extends unknown[] = unknown[]> = (
export type BeforeCallback<A extends unknown[] = unknown[], I = ObjectExports> = (
args: A,
self: ObjectExports,
self: I,
) => A | undefined | void;

/**
Expand All @@ -32,10 +32,10 @@ export type BeforeCallback<A extends unknown[] = unknown[]> = (
* @param self The module the injected function is on
* @returns New result to return
*/
export type InsteadCallback<A extends unknown[] = unknown[], R = unknown> = (
export type InsteadCallback<A extends unknown[] = unknown[], R = unknown, I = ObjectExports> = (
args: A,
orig: (...args: A) => R,
self: ObjectExports,
self: I,
) => R | void;

/**
Expand All @@ -45,10 +45,10 @@ export type InsteadCallback<A extends unknown[] = unknown[], R = unknown> = (
* @param self The module the injected function is on
* @returns New result to return, or undefined to leave it unchanged
*/
export type AfterCallback<A extends unknown[] = unknown[], R = unknown> = (
export type AfterCallback<A extends unknown[] = unknown[], R = unknown, I = ObjectExports> = (
args: A,
res: R,
self: ObjectExports,
self: I,
) => R | undefined | void;

interface ObjectInjections {
Expand Down Expand Up @@ -167,7 +167,8 @@ function before<
T extends Record<U, AnyFunction>,
U extends keyof T & string,
A extends unknown[] = Parameters<T[U]>,
>(obj: T, funcName: U, cb: BeforeCallback<A>): () => void {
I = T[U],
>(obj: T, funcName: U, cb: BeforeCallback<A, I>): () => void {
// @ts-expect-error 'unknown[]' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'unknown[]'.
return inject(obj, funcName, cb as BeforeCallback, InjectionTypes.Before);
}
Expand All @@ -177,7 +178,8 @@ function instead<
U extends keyof T & string,
A extends unknown[] = Parameters<T[U]>,
R = ReturnType<T[U]>,
>(obj: T, funcName: U, cb: InsteadCallback<A, R>): () => void {
I = T[U],
>(obj: T, funcName: U, cb: InsteadCallback<A, R, I>): () => void {
// @ts-expect-error 'unknown[]' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'unknown[]'.
return inject(obj, funcName, cb, InjectionTypes.Instead);
}
Expand All @@ -187,7 +189,8 @@ function after<
U extends keyof T & string,
A extends unknown[] = Parameters<T[U]>,
R = ReturnType<T[U]>,
>(obj: T, funcName: U, cb: AfterCallback<A, R>): () => void {
I = T[U],
>(obj: T, funcName: U, cb: AfterCallback<A, R, I>): () => void {
// @ts-expect-error 'unknown[]' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'unknown[]'.
return inject(obj, funcName, cb, InjectionTypes.After);
}
Expand Down Expand Up @@ -250,7 +253,8 @@ export class Injector {
U extends keyof T & string,
A extends unknown[] = Parameters<T[U]>,
R = ReturnType<T[U]>,
>(obj: T, funcName: U, cb: InsteadCallback<A, R>): () => void {
I = T[U],
>(obj: T, funcName: U, cb: InsteadCallback<A, R, I>): () => void {
const uninjector = instead(obj, funcName, cb);
this.#uninjectors.add(uninjector);
return uninjector;
Expand All @@ -268,7 +272,8 @@ export class Injector {
U extends keyof T & string,
A extends unknown[] = Parameters<T[U]>,
R = ReturnType<T[U]>,
>(obj: T, funcName: U, cb: AfterCallback<A, R>): () => void {
I = T[U],
>(obj: T, funcName: U, cb: AfterCallback<A, R, I>): () => void {
const uninjector = after(obj, funcName, cb);
this.#uninjectors.add(uninjector);
return uninjector;
Expand Down
2 changes: 2 additions & 0 deletions src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type GeneralSettings = {
showWelcomeNoticeOnOpen?: boolean;
addonEmbeds?: boolean;
reactDevTools?: boolean;
automaticRecover?: boolean;
};

export const defaultSettings = {
Expand All @@ -24,4 +25,5 @@ export const defaultSettings = {
showWelcomeNoticeOnOpen: true,
reactDevTools: false,
addonEmbeds: true,
automaticRecover: false,
} satisfies Partial<GeneralSettings>;
Loading