Files
HKSingleParty/99_references/voyager-main/src/core/AppCrash.tsx
2025-05-28 09:55:51 +08:00

166 lines
4.7 KiB
TypeScript

import { styled } from "@linaria/react";
import { FallbackProps } from "react-error-boundary";
import { isInstalled, isNative } from "../helpers/device";
import { IonButton, IonIcon, IonLabel } from "@ionic/react";
import { logoGithub } from "ionicons/icons";
import { unloadServiceWorkerAndRefresh } from "../helpers/serviceWorker";
import { memoryHistory } from "../routes/common/Router";
import store from "../store";
import { loggedInSelector } from "../features/auth/authSelectors";
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-align: center;
padding: 8px;
position: absolute;
inset: 0;
overflow: auto;
padding-top: max(env(safe-area-inset-top), 8px);
padding-right: max(env(safe-area-inset-right), 8px);
padding-bottom: max(env(safe-area-inset-bottom), 8px);
padding-left: max(env(safe-area-inset-left), 8px);
color: var(--ion-text-color);
`;
const Title = styled.h2``;
const Description = styled.div``;
export default function AppCrash({ error }: FallbackProps) {
// Don't use useLocation/useAppSelector, because they are not available
// (`<AppCrash />` is at the root of the document tree)
const location = memoryHistory ? memoryHistory.location : window.location;
const loggedIn = loggedInSelector(store.getState());
let crashData = `
### Crash description
<!-- Write any information here to help us debug your crash! -->
<!-- What were you doing when the crash occurred? -->
### Device and app metadata
- window.location.href: \`${window.location.href}\`
- react-router location.pathname: \`${location.pathname}\`
- Logged in? \`${loggedIn}\`
- Native app? \`${isNative()}\`
- Installed to home screen? \`${isInstalled()}\`
- Voyager version: \`${APP_VERSION}\`
- BUILD_FOSS_ONLY: \`${BUILD_FOSS_ONLY}\`
- User agent: \`${navigator.userAgent}\`
### Crash data
Error: \`\`${error}\`\`
#### Stack trace
\`\`\`
`.trim();
crashData = `${crashData}\n${error instanceof Error ? error.stack : "Not available"}`;
async function clearData() {
if (
!confirm(
"Are you sure? This will log you out of all accounts and delete all local app data including app configuration, hidden posts and favorites.",
)
)
return;
localStorage.clear();
sessionStorage.clear();
const dbs = await window.indexedDB.databases();
for (const db of dbs) {
if (db.name) window.indexedDB.deleteDatabase(db.name);
}
alert("All data cleared.");
}
return (
<Container>
<Title>🫣 Gah! Voyager crashed!</Title>
<Description>
Voyager does not collect any data, so we would appreciate you
voluntarily submitting this crash for us to investigate.
</Description>
<IonButton
href={generateTruncatedCrashUrl(crashData)}
target="_blank"
rel="noopener noreferrer"
color="success"
>
<IonIcon icon={logoGithub} slot="start" />
<IonLabel>Open Github issue with crash data</IonLabel>
</IonButton>
<hr />
<Description>
You can also try reloading the app to see if that solves the issue.
{isNative() ? " Check the app store for an update, too." : ""}
</Description>
<IonButton onClick={unloadServiceWorkerAndRefresh}>Reload app</IonButton>
<hr />
<Description>
If this crash is affecting many people, you can probably learn more{" "}
<a
href="https://lemmy.world/c/voyagerapp"
target="_blank"
rel="noopener noreferrer"
>
at Voyager&apos;s Lemmy community
</a>
.
</Description>
<Description>As a last resort, try clearing all app data.</Description>
<IonButton color="danger" onClick={clearData}>
Clear app data
</IonButton>
</Container>
);
}
function generateCrashUrl(crashData: string): string {
return `https://github.com/aeharding/voyager/issues/new?title=Crash&body=${encodeURIComponent(
crashData,
)}`;
}
// The GitHub GET endpoint for opening a new issue
// has a restriction for maximum length of a URL: 8192 bytes
// https://github.com/cli/cli/pull/3271
// https://github.com/cli/cli/issues/1575
// https://github.com/cli/cli/blob/trunk/pkg/cmd/issue/create/create.go#L167
// https://github.com/cli/cli/blob/trunk/utils/utils.go#L84
const maxIssueBytes = 8150;
function getStrByteLength(str: string): number {
return new TextEncoder().encode(str).length;
}
function generateTruncatedCrashUrl(crashData: string): string {
let url: string;
let strLength = 1;
do {
url = generateCrashUrl(crashData.slice(0, strLength));
if (strLength === crashData.length) return url;
strLength++;
} while (getStrByteLength(url) < maxIssueBytes);
return url;
}