166 lines
4.7 KiB
TypeScript
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'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;
|
|
}
|