diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 42fd9d29fb..5c772e3851 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -36,6 +36,7 @@ export default defineConfig({ "@env": resolve("./src/env.ts"), }, }, + plugins: [ react(), sentryVitePlugin({ @@ -61,6 +62,7 @@ export default defineConfig({ ], build: { sourcemap: !!process.env.CI, + target: "esnext", }, define: { APP_VERSION: JSON.stringify(pkg.version), diff --git a/src/renderer/src/atoms/player.ts b/src/renderer/src/atoms/player.ts index e1f4ff4fdf..8f80e5e70c 100644 --- a/src/renderer/src/atoms/player.ts +++ b/src/renderer/src/atoms/player.ts @@ -1,5 +1,6 @@ import { createAtomHooks } from "@renderer/lib/jotai" import { getStorageNS } from "@renderer/lib/ns" +import { noop } from "foxact/noop" import { atomWithStorage, createJSONStorage } from "jotai/utils" import type { SyncStorage } from "jotai/vanilla/utils/atomWithStorage" @@ -104,7 +105,7 @@ export const AudioPlayer = { status: "playing", duration: this.audio.duration === Infinity ? 0 : this.audio.duration, }) - }) + }).catch(noop) }, teardown() { this.currentTimeTimer && clearInterval(this.currentTimeTimer) diff --git a/src/renderer/src/components/common/ErrorElement.tsx b/src/renderer/src/components/common/ErrorElement.tsx index 2f856324f3..2497326727 100644 --- a/src/renderer/src/components/common/ErrorElement.tsx +++ b/src/renderer/src/components/common/ErrorElement.tsx @@ -2,7 +2,7 @@ import { attachOpenInEditor } from "@renderer/lib/dev" import { getNewIssueUrl } from "@renderer/lib/issues" import { clearLocalPersistStoreData } from "@renderer/store/utils/clear" import { useEffect, useRef } from "react" -import { isRouteErrorResponse, useRouteError } from "react-router-dom" +import { isRouteErrorResponse, useNavigate, useRouteError } from "react-router-dom" import { toast } from "sonner" import { Button } from "../ui/button" @@ -10,6 +10,7 @@ import { PoweredByFooter } from "./PoweredByFooter" export function ErrorElement() { const error = useRouteError() + const navigate = useNavigate() const message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? @@ -46,11 +47,7 @@ export function ErrorElement() {

- Sorry, - {" "} - {APP_NAME} - {" "} - has encountered an error + Sorry, {APP_NAME} has encountered an error

{message}

@@ -77,7 +74,11 @@ export function ErrorElement() { > Reset Local Database - @@ -103,7 +104,18 @@ export const FallbackIssue = ({ className="ml-2 cursor-pointer text-theme-accent-500 duration-200 hover:text-accent" href={getNewIssueUrl({ title: `Error: ${message}`, - body: `### Error\n\n${message}\n\n### Stack\n\n\`\`\`\n${stack}\n\`\`\``, + body: [ + "### Error", + "", + message, + "", + "### Stack", + "", + "```", + stack, + "```", + + ].join("\n"), label: "bug", })} target="_blank" diff --git a/src/renderer/src/configs.ts b/src/renderer/src/configs.ts index c5d4f8b205..8c11b05245 100644 --- a/src/renderer/src/configs.ts +++ b/src/renderer/src/configs.ts @@ -29,13 +29,18 @@ export const SentryConfig: BrowserOptions = { return null } - if (error instanceof CustomSafeError) { - return null - } - if (error instanceof FetchError) { + const isPassthroughError = [CustomSafeError, FetchError].some( + (errorType) => { + if (error instanceof errorType) { + return true + } + return false + }, + ) + + if (isPassthroughError) { return null } - return event }, } diff --git a/src/renderer/src/lib/issues.ts b/src/renderer/src/lib/issues.ts index b26d6a70f0..de0a143f03 100644 --- a/src/renderer/src/lib/issues.ts +++ b/src/renderer/src/lib/issues.ts @@ -1,5 +1,7 @@ import { repository } from "@pkg" +import { detectBrowser, getOS } from "./utils" + interface IssueOptions { title: string body: string @@ -13,9 +15,27 @@ export const getNewIssueUrl = ({ const baseUrl = `${repository.url}/issues/new` const searchParams = new URLSearchParams() - if (body) searchParams.set("body", (body)) + + const ua = navigator.userAgent + const appVersion = APP_VERSION + const env = window.electron ? "electron" : "web" + const os = getOS() + const browser = detectBrowser() + + const nextBody = [ + body || "", + "", + "### Environment", + "", + `**App Version**: ${appVersion}`, + `**OS**: ${os}`, + `**User Agent**: ${ua}`, + `**Env**: ${env}`, + `**Browser**: ${browser}`, + ].join("\n") + searchParams.set("body", nextBody) if (label) searchParams.set("label", label) - if (title) searchParams.set("title", (title)) + if (title) searchParams.set("title", title) return `${baseUrl}?${searchParams.toString()}` } diff --git a/src/renderer/src/lib/utils.ts b/src/renderer/src/lib/utils.ts index 21ea42093d..448e8d3edf 100644 --- a/src/renderer/src/lib/utils.ts +++ b/src/renderer/src/lib/utils.ts @@ -76,6 +76,28 @@ export const getOS = memoize((): OS => { return os as OS }) +export function detectBrowser() { + const { userAgent } = navigator + if (userAgent.includes("Edg")) { + return "Microsoft Edge" + } else if (userAgent.includes("Chrome")) { + return "Chrome" + } else if (userAgent.includes("Firefox")) { + return "Firefox" + } else if (userAgent.includes("Safari")) { + return "Safari" + } else if (userAgent.includes("Opera")) { + return "Opera" + } else if ( + userAgent.includes("Trident") || + userAgent.includes("MSIE") + ) { + return "Internet Explorer" + } + + return "Unknown" +} + export const isSafari = memoize(() => { if (ELECTRON) return false const ua = window.navigator.userAgent diff --git a/src/renderer/src/modules/entry-content/index.tsx b/src/renderer/src/modules/entry-content/index.tsx index a60e364940..488f18a113 100644 --- a/src/renderer/src/modules/entry-content/index.tsx +++ b/src/renderer/src/modules/entry-content/index.tsx @@ -21,6 +21,7 @@ import { import { useAuthQuery, useTitle } from "@renderer/hooks/common" import { stopPropagation } from "@renderer/lib/dom" import { FeedViewType } from "@renderer/lib/enum" +import { getNewIssueUrl } from "@renderer/lib/issues" import { cn } from "@renderer/lib/utils" import type { ActiveEntryId } from "@renderer/models" import { @@ -30,6 +31,8 @@ import { import { Queries } from "@renderer/queries" import { useEntry, useEntryReadHistory } from "@renderer/store/entry" import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed" +import type { FallbackRender } from "@sentry/react" +import { ErrorBoundary } from "@sentry/react" import type { FC } from "react" import { useEffect, useLayoutEffect, useRef } from "react" @@ -205,38 +208,39 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ - -
- {(summary.isLoading || summary.data) && ( -
-
- - AI summary -
- - {summary.isLoading ? - SummaryLoadingSkeleton : - summary.data} - +
+ {(summary.isLoading || summary.data) && ( +
+
+ + AI summary
- )} - - {!isInReadabilityMode ? ( - - {content} - + {summary.isLoading ? + SummaryLoadingSkeleton : + summary.data} + +
+ )} + + {!isInReadabilityMode ? ( + + + {content} + + ) : ( )} -
- + +
{!content && (
@@ -327,7 +331,7 @@ const ReadabilityContent = ({ entryId }: { entryId: string }) => {
)} - + {result?.content ?? ""}
@@ -392,3 +396,37 @@ const ReadabilityAutoToggle = ({ url, id }: { url: string, id: string }) => { return null } + +const RenderError: FallbackRender = ({ error }) => { + const nextError = + typeof error === "string" ? new Error(error) : (error as Error) + return ( +
+ + + Render error: {nextError.message} + + + Report issue + +
+ ) +}