Skip to content

Commit

Permalink
chore: merge dev to main (revenge-mod#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
PalmDevs authored Oct 26, 2024
2 parents 5980575 + 4a7e3e6 commit fd5eaa3
Show file tree
Hide file tree
Showing 22 changed files with 587 additions and 219 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# [1.4.0-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.3.1...v1.4.0-dev.1) (2024-10-26)


### Bug Fixes

* actually fix improper FAB insets ([e987723](https://github.com/revenge-mod/revenge-bundle/commit/e9877234befc837126f4a24b23c837e7ed8f6b5b))


### Features

* port ErrorBoundary from dev ([f447910](https://github.com/revenge-mod/revenge-bundle/commit/f44791049a762fbce777b03e9d930fba0c0c6570))
* **settings/pages/Developer:** show assets types, mark non-previewable assets in AssetBrowser ([b7aacf7](https://github.com/revenge-mod/revenge-bundle/commit/b7aacf734e665a7af613aa5eaae8507ea31be5cd))

## [1.3.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.3.0...v1.3.1) (2024-10-23)


Expand Down
5 changes: 3 additions & 2 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const metroDeps = await (async () => {
const args = yargs(process.argv.slice(2));
const {
"release-branch": releaseBranch,
"build-minify": buildMinify
"build-minify": buildMinify,
"dev": dev
} = args;

let context = null;
Expand All @@ -44,7 +45,7 @@ const config = {
".png": "dataurl"
},
define: {
__DEV__: JSON.stringify(releaseBranch !== "main")
__DEV__: dev ?? JSON.stringify(releaseBranch !== "main")
},
inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"],
legalComments: "none",
Expand Down
21 changes: 21 additions & 0 deletions src/core/debug/patches/patchErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ErrorBoundaryScreen from "@core/ui/reporter/components/ErrorBoundaryScreen";
import { after } from "@lib/api/patcher";
import { _lazyContextSymbol } from "@metro/lazy";
import { LazyModuleContext } from "@metro/types";
import { findByNameLazy } from "@metro/wrappers";

function getErrorBoundaryContext() {
const ctxt: LazyModuleContext = findByNameLazy("ErrorBoundary")[_lazyContextSymbol];
return new Promise(resolve => ctxt.getExports(exp => resolve(exp.prototype)));
}

export default function patchErrorBoundary() {
return after.await("render", getErrorBoundaryContext(), function (this: any) {
if (!this.state.error) return;

return <ErrorBoundaryScreen
error={this.state.error}
rerender={() => this.setState({ info: null, error: null })}
/>;
});
}
12 changes: 8 additions & 4 deletions src/core/ui/components/AddonPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { findAssetId } from "@lib/api/assets";
import { settings } from "@lib/api/settings";
import AlertModal, { AlertActionButton } from "@lib/ui/components/wrappers/AlertModal";
import isValidHttpUrl from "@lib/utils/isValidHttpUrl";
import { lazyDestructure } from "@lib/utils/lazy";
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
import { findByProps } from "@metro";
import { clipboard } from "@metro/common";
import { Button, FlashList, FloatingActionButton, HelpMessage, IconButton, SafeAreaView, Stack, Text, TextInput } from "@metro/common/components";
import { Button, FlashList, FloatingActionButton, HelpMessage, IconButton, Stack, Text, TextInput } from "@metro/common/components";
import { ErrorBoundary, Search } from "@ui/components";
import fuzzysort from "fuzzysort";
import { ComponentType, ReactNode, useCallback, useMemo } from "react";
Expand Down Expand Up @@ -39,6 +39,8 @@ interface AddonPageProps<T extends object, I = any> {
ListFooterComponent?: ComponentType<any>;
}

const useSafeAreaInsets = proxyLazy(() => findByProps("useSafeAreaInsets").useSafeAreaInsets);

function InputAlert(props: { label: string, fetchFn: (url: string) => Promise<void>; }) {
const [value, setValue] = React.useState("");
const [error, setError] = React.useState("");
Expand Down Expand Up @@ -111,6 +113,7 @@ export default function AddonPage<T extends object>({ CardComponent, ...props }:

const [search, setSearch] = React.useState("");
const [sortFn, setSortFn] = React.useState<((a: unknown, b: unknown) => number) | null>(() => null);
const insets = useSafeAreaInsets();

const results = useMemo(() => {
let values = props.items;
Expand Down Expand Up @@ -198,12 +201,13 @@ export default function AddonPage<T extends object>({ CardComponent, ...props }:
renderItem={({ item }: any) => <CardComponent item={item.obj} result={item} />}
/>
{props.installAction && (
<SafeAreaView>
<View style={{ paddingBottom: insets.bottom }}>
<FloatingActionButton
positionBottom={insets.bottom + 16}
icon={findAssetId("PlusLargeIcon")}
onPress={onInstallPress}
/>
</SafeAreaView>
</View>
)}
</ErrorBoundary>
);
Expand Down
52 changes: 52 additions & 0 deletions src/core/ui/reporter/components/ErrorBoundaryScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { hasStack, isComponentStack } from "@core/ui/reporter/utils/isStack";
import { getDebugInfo, toggleSafeMode } from "@lib/api/debug";
import { BundleUpdaterManager } from "@lib/api/native/modules";
import { settings } from "@lib/api/settings";
import { Codeblock, ErrorBoundary } from "@lib/ui/components";
import { createStyles } from "@lib/ui/styles";
import { tokens } from "@metro/common";
import { Button, Card, SafeAreaView, Text } from "@metro/common/components";
import { ScrollView, View } from "react-native";

import ErrorComponentStackCard from "./ErrorComponentStackCard";
import ErrorStackCard from "./ErrorStackCard";

const useStyles = createStyles({
container: {
flex: 1,
backgroundColor: tokens.colors.BG_BASE_SECONDARY,
paddingHorizontal: 16,
height: "100%",
padding: 8,
gap: 12
}
});


export default function ErrorBoundaryScreen(props: {
error: Error;
rerender: () => void;
}) {
const styles = useStyles();
const debugInfo = getDebugInfo();

return <ErrorBoundary>
<SafeAreaView style={styles.container}>
<View style={{ gap: 4 }}>
<Text variant="display-lg">Uh oh.</Text>
<Text variant="text-md/normal">A crash occured while rendering a component. This could be caused by a plugin, Bunny or Discord itself.</Text>
<Text variant="text-sm/normal" color="text-muted">{debugInfo.os.name}; {debugInfo.discord.build} ({debugInfo.discord.version}); {debugInfo.bunny.version}</Text>
</View>
<ScrollView fadingEdgeLength={64} contentContainerStyle={{ gap: 12 }}>
<Codeblock selectable={true}>{props.error.message}</Codeblock>
{hasStack(props.error) && <ErrorStackCard error={props.error} />}
{isComponentStack(props.error) ? <ErrorComponentStackCard componentStack={props.error.componentStack} /> : null}
</ScrollView>
<Card style={{ gap: 6 }}>
<Button text="Reload Discord" onPress={() => BundleUpdaterManager.reload()} />
{!settings.safeMode?.enabled && <Button text="Reload in Safe Mode" onPress={() => toggleSafeMode()} />}
<Button variant="destructive" text="Retry Render" onPress={() => props.rerender()} />
</Card>
</SafeAreaView>
</ErrorBoundary>;
}
44 changes: 44 additions & 0 deletions src/core/ui/reporter/components/ErrorCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Strings } from "@core/i18n";
import { Codeblock } from "@lib/ui/components";
import { showSheet } from "@lib/ui/sheets";
import { Button, Card, Stack, Text, TwinButtons } from "@metro/common/components";
import { ReactNode } from "react";

import ErrorDetailsActionSheet from "./ErrorDetailsActionSheet";

export const INDEX_BUNDLE_FILE: string = window.HermesInternal.getFunctionLocation(window.__r).fileName;

interface ErrorCardProps {
error: unknown;
header?: string | ReactNode;
onRetryRender?: () => void;
}

export default function ErrorCard(props: ErrorCardProps) {
return <Card>
<Stack>
{props.header && typeof props.header !== "string"
? props.header
: <Text variant="heading-lg/bold">{props.header ?? Strings.UH_OH}</Text>
}
<Codeblock selectable={true}>{String(props.error)}</Codeblock>
<TwinButtons>
{props.onRetryRender && <Button
variant="destructive"
// icon={findAssetId("RetryIcon")}
text={Strings.RETRY_RENDER}
onPress={props.onRetryRender}
/>}
{props.error instanceof Error ? <Button
text={"Details"}
// icon={findAssetId("CircleInformationIcon-primary")}
onPress={() => showSheet(
"BunnyErrorDetailsActionSheet",
ErrorDetailsActionSheet,
{ error: props.error as Error }
)}
/> : null}
</TwinButtons>
</Stack>
</Card>;
}
53 changes: 53 additions & 0 deletions src/core/ui/reporter/components/ErrorComponentStackCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { parseComponentStack } from "@core/ui/reporter/utils/parseComponentStack";
import { findAssetId } from "@lib/api/assets";
import { clipboard } from "@metro/common";
import { Button, Card, Text } from "@metro/common/components";
import { useState } from "react";
import { Image, View } from "react-native";

export default function ErrorComponentStackCard(props: {
componentStack: string;
}) {
const [collapsed, setCollapsed] = useState(true);

let stack: string[];
try {
stack = parseComponentStack(props.componentStack);
stack = collapsed ? stack.slice(0, 4) : stack;
} catch {
return;
}

return <Card>
<View style={{ gap: 8 }}>
<Text variant="heading-lg/bold">
Component Stack
</Text>
<View style={{ gap: 4 }}>
{stack.map(component => (
<View style={{ flexDirection: "row" }}>
<Text variant="text-md/bold" color="text-muted">{"<"}</Text>
<Text variant="text-md/bold">{component}</Text>
<Text variant="text-md/bold" color="text-muted">{"/>"}</Text>
</View>
))}
</View>
{collapsed && <Text>...</Text>}
<View style={{ gap: 8, flexDirection: "row", justifyContent: "center", alignItems: "center" }}>
<Button
variant="secondary"
text={`Show ${collapsed ? "more" : "less"}`}
icon={collapsed ? findAssetId("down_arrow") : <Image
style={{ transform: [{ rotate: `${collapsed ? 0 : 180}deg` }] }}
source={findAssetId("down_arrow")!}
/>}
onPress={() => setCollapsed(v => !v)} />
<Button
variant="secondary"
text="Copy"
icon={findAssetId("CopyIcon")}
onPress={() => clipboard.setString(props.componentStack)} />
</View>
</View>
</Card>;
}
20 changes: 20 additions & 0 deletions src/core/ui/reporter/components/ErrorDetailsActionSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { hasStack, isComponentStack } from "@core/ui/reporter/utils/isStack";
import { Codeblock } from "@lib/ui/components";
import { ActionSheet, Text } from "@metro/common/components";
import { View } from "react-native";

import ErrorComponentStackCard from "./ErrorComponentStackCard";
import ErrorStackCard from "./ErrorStackCard";

export default function ErrorDetailsActionSheet(props: {
error: Error;
}) {
return <ActionSheet>
<View style={{ gap: 12, paddingVertical: 12 }}>
<Text variant="heading-lg/extrabold">Error</Text>
<Codeblock selectable={true}>{props.error.message}</Codeblock>
{hasStack(props.error) && <ErrorStackCard error={props.error} />}
{isComponentStack(props.error) ? <ErrorComponentStackCard componentStack={props.error.componentStack} /> : null}
</View>
</ActionSheet>;
}
63 changes: 63 additions & 0 deletions src/core/ui/reporter/components/ErrorStackCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import parseErrorStack, { StackFrame } from "@core/ui/reporter/utils/parseErrorStack";
import { findAssetId } from "@lib/api/assets";
import { clipboard, constants } from "@metro/common";
import { Button, Card, Text } from "@metro/common/components";
import { useState } from "react";
import { Image, Pressable, View } from "react-native";

import { INDEX_BUNDLE_FILE } from "./ErrorCard";

export default function ErrorStackCard(props: {
error: Error & { stack: string };
}) {
const [collapsed, setCollapsed] = useState(true);

let stack: StackFrame[];

try {
const parsedErrorStack = parseErrorStack(props.error.stack);
stack = collapsed ? parsedErrorStack.slice(0, 4) : parsedErrorStack;
} catch {
return null;
}

return <Card>
<View style={{ gap: 12 }}>
<Text variant="heading-lg/bold">
Call Stack
</Text>
<View style={{ gap: 4 }}>
{stack.map((f, id) => <Line id={id} frame={f} />)}
</View>
{collapsed && <Text>...</Text>}
<View style={{ gap: 8, flexDirection: "row", justifyContent: "center", alignItems: "center" }}>
<Button
variant="secondary"
text={`Show ${collapsed ? "more" : "less"}`}
icon={collapsed ? findAssetId("down_arrow") : <Image
style={{ transform: [{ rotate: `${collapsed ? 0 : 180}deg` }] }}
source={findAssetId("down_arrow")!}
/>}
onPress={() => setCollapsed(v => !v)} />
<Button
variant="secondary"
text="Copy"
icon={findAssetId("CopyIcon")}
onPress={() => clipboard.setString(props.error.stack)} />
</View>
</View>
</Card>;
}
function Line(props: { id: number, frame: StackFrame }) {
const [collapsed, setCollapsed] = useState(true);

return <Pressable onPress={() => setCollapsed(v => !v)} key={props.id}>
<Text style={{ fontFamily: constants.Fonts.CODE_BOLD }}>
{props.frame.methodName}
</Text>
<Text style={{ fontFamily: constants.Fonts.CODE_NORMAL }} ellipsizeMode="middle" numberOfLines={collapsed ? 1 : undefined}>
<Text color="text-muted">{props.frame.file === INDEX_BUNDLE_FILE ? "jsbundle" : props.frame.file}:{props.frame.lineNumber}:{props.frame.column}</Text>
</Text>
</Pressable>;
}

6 changes: 6 additions & 0 deletions src/core/ui/reporter/utils/isStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function isComponentStack(error: Error): error is Error & { componentStack: string; } {
return "componentStack" in error && typeof error.componentStack === "string";
}
export function hasStack(error: Error): error is Error & { stack: string; } {
return !!error.stack;
}
3 changes: 3 additions & 0 deletions src/core/ui/reporter/utils/parseComponentStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function parseComponentStack(componentStack: string) {
return componentStack.split(/[\s|\n]+?in /).filter(Boolean);
}
Loading

0 comments on commit fd5eaa3

Please sign in to comment.