Skip to content

Commit a9b3bb3

Browse files
authored
Merge pull request #1 from BedrockTweaks/sentry-fixes
chore(root): clean up unused code change entries
2 parents e9f1854 + 6209462 commit a9b3bb3

11 files changed

Lines changed: 872 additions & 743 deletions

File tree

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"sentry": {
4+
"type": "http",
5+
"url": "https://mcp.sentry.dev/mcp"
6+
}
7+
}
8+
}

apps/web/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
"@emotion/react": "^11.14.0",
2020
"@sentry/tanstackstart-react": "^10.51.0",
2121
"@tanstack/react-devtools": "^0.9.13",
22-
"@tanstack/react-query": "^5.100.6",
23-
"@tanstack/react-query-devtools": "^5.100.6",
24-
"@tanstack/react-router": "^1.168.26",
25-
"@tanstack/react-router-devtools": "^1.166.13",
26-
"@tanstack/react-router-ssr-query": "^1.166.12",
27-
"@tanstack/react-start": "^1.167.52",
28-
"@tanstack/router-plugin": "^1.167.29",
22+
"@tanstack/react-query": "^5.101.4",
23+
"@tanstack/react-query-devtools": "^5.101.4",
24+
"@tanstack/react-router": "^1.170.18",
25+
"@tanstack/react-router-devtools": "^1.167.0",
26+
"@tanstack/react-router-ssr-query": "^1.167.1",
27+
"@tanstack/react-start": "^1.168.32",
28+
"@tanstack/router-plugin": "^1.168.23",
2929
"cross-env": "^10.1.0",
3030
"dotenv-cli": "^11.0.0",
3131
"lucide-react": "^0.562.0",
@@ -41,7 +41,7 @@
4141
"@chakra-ui/cli": "^3.35.0",
4242
"@sentry/vite-plugin": "^4.9.1",
4343
"@tanstack/devtools-vite": "^0.4.1",
44-
"@tanstack/eslint-plugin-query": "^5.100.6",
44+
"@tanstack/eslint-plugin-query": "^5.101.4",
4545
"@testing-library/dom": "^10.4.1",
4646
"@testing-library/react": "^16.3.2",
4747
"@types/node": "^25.6.0",

apps/web/src/components/pack-selection/SelectedPacks.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { usePackSelection } from '@/contexts/PackSelectionContext';
22
import { CategorySelection, DownloadRequest, SECTION_NAME_MAP } from '@/models';
33
import { Button, Input, Link } from '@/theming/components';
4-
import { generatePackName } from '@/utils/packs';
4+
import { generatePackName, resolveDownloadFileName } from '@/utils/packs';
55
import {
66
Box,
77
CloseButton,
@@ -52,7 +52,7 @@ export function SelectedPacks({ compatibleVersions, onDownload, onClose }: Selec
5252
const a = document.createElement('a');
5353

5454
a.href = response.downloadUrl;
55-
a.download = new URL(response.downloadUrl).pathname.split('/').pop() ?? response.packName;
55+
a.download = resolveDownloadFileName(response.downloadUrl, response.packName);
5656
document.body.appendChild(a);
5757
a.click();
5858
document.body.removeChild(a);

apps/web/src/config/sentry.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Sentry configuration
3+
* Central place to manage which browser errors are worth reporting.
4+
*
5+
* The site embeds AdSense and is visited with all kinds of extensions and
6+
* in-app webviews injected into the page. Those scripts throw constantly and
7+
* Sentry attributes the failures to us, which buries the errors we can act on.
8+
*/
9+
10+
import type { ErrorEvent } from '@sentry/tanstackstart-react';
11+
12+
/**
13+
* Error messages that never originate from our own code.
14+
*/
15+
export const SENTRY_IGNORE_ERRORS: (string | RegExp)[] = [
16+
// Google AdSense internals
17+
'Accessing domItems after disposal',
18+
'__tcfapiCall',
19+
/googlesyndication\.com/,
20+
/contentDocument\.body/,
21+
22+
// Extensions and injected globals
23+
/\b(LIDNotify|xbrowser|swbrowser)\b is not defined/,
24+
/window\.ethereum/,
25+
'Invalid call to runtime.sendMessage',
26+
'WKWebView API client did not respond to this postMessage',
27+
28+
// Opaque cross-origin failures with no recoverable detail
29+
'Non-Error promise rejection captured',
30+
'ResizeObserver loop completed with undelivered notifications',
31+
'ResizeObserver loop limit exceeded',
32+
];
33+
34+
/**
35+
* Script origins we never want to attribute errors to.
36+
*/
37+
export const SENTRY_DENY_URLS: RegExp[] = [
38+
// Ad and analytics providers
39+
/googlesyndication\.com/,
40+
/googletagservices\.com/,
41+
/googletagmanager\.com/,
42+
/google-analytics\.com/,
43+
/doubleclick\.net/,
44+
/\/pagead\//,
45+
46+
// Affiliate banner
47+
/bisecthosting\.com/,
48+
49+
// Browser extensions
50+
/^chrome-extension:\/\//,
51+
/^moz-extension:\/\//,
52+
/^safari-(web-)?extension:\/\//,
53+
/^webkit-masked-url:/,
54+
];
55+
56+
/**
57+
* Drop events that carry no usable stack frames.
58+
*
59+
* These are the cross-origin `Script error.` class: a message, no file, no
60+
* line we can map back to a release. They are unactionable by definition, and
61+
* on this site they come from injected third-party scripts.
62+
* @param event - Event Sentry is about to send
63+
* @returns The event to send, or null to drop it
64+
*/
65+
export function dropUnactionableEvent(event: ErrorEvent): ErrorEvent | null {
66+
const values = event.exception?.values;
67+
68+
if (!values?.length) {
69+
return event;
70+
}
71+
72+
const hasUsableFrame = values.some(value =>
73+
value.stacktrace?.frames?.some(frame => Boolean(frame.filename) && frame.filename !== '<anonymous>'),
74+
);
75+
76+
return hasUsableFrame ? event : null;
77+
}

apps/web/src/routeTree.gen.ts

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,41 @@
99
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
1010

1111
import { Route as rootRouteImport } from './routes/__root'
12-
import { Route as TermsRouteImport } from './routes/terms'
13-
import { Route as ResourcePacksRouteImport } from './routes/resource-packs'
14-
import { Route as PrivacyRouteImport } from './routes/privacy'
15-
import { Route as CraftingTweaksRouteImport } from './routes/crafting-tweaks'
16-
import { Route as AddonsRouteImport } from './routes/addons'
1712
import { Route as IndexRouteImport } from './routes/index'
13+
import { Route as AddonsRouteImport } from './routes/addons'
14+
import { Route as CraftingTweaksRouteImport } from './routes/crafting-tweaks'
15+
import { Route as PrivacyRouteImport } from './routes/privacy'
16+
import { Route as ResourcePacksRouteImport } from './routes/resource-packs'
17+
import { Route as TermsRouteImport } from './routes/terms'
1818

19-
const TermsRoute = TermsRouteImport.update({
20-
id: '/terms',
21-
path: '/terms',
22-
getParentRoute: () => rootRouteImport,
23-
} as any)
24-
const ResourcePacksRoute = ResourcePacksRouteImport.update({
25-
id: '/resource-packs',
26-
path: '/resource-packs',
19+
const IndexRoute = IndexRouteImport.update({
20+
id: '/',
21+
path: '/',
2722
getParentRoute: () => rootRouteImport,
2823
} as any)
29-
const PrivacyRoute = PrivacyRouteImport.update({
30-
id: '/privacy',
31-
path: '/privacy',
24+
const AddonsRoute = AddonsRouteImport.update({
25+
id: '/addons',
26+
path: '/addons',
3227
getParentRoute: () => rootRouteImport,
3328
} as any)
3429
const CraftingTweaksRoute = CraftingTweaksRouteImport.update({
3530
id: '/crafting-tweaks',
3631
path: '/crafting-tweaks',
3732
getParentRoute: () => rootRouteImport,
3833
} as any)
39-
const AddonsRoute = AddonsRouteImport.update({
40-
id: '/addons',
41-
path: '/addons',
34+
const PrivacyRoute = PrivacyRouteImport.update({
35+
id: '/privacy',
36+
path: '/privacy',
4237
getParentRoute: () => rootRouteImport,
4338
} as any)
44-
const IndexRoute = IndexRouteImport.update({
45-
id: '/',
46-
path: '/',
39+
const ResourcePacksRoute = ResourcePacksRouteImport.update({
40+
id: '/resource-packs',
41+
path: '/resource-packs',
42+
getParentRoute: () => rootRouteImport,
43+
} as any)
44+
const TermsRoute = TermsRouteImport.update({
45+
id: '/terms',
46+
path: '/terms',
4747
getParentRoute: () => rootRouteImport,
4848
} as any)
4949

@@ -110,25 +110,18 @@ export interface RootRouteChildren {
110110

111111
declare module '@tanstack/react-router' {
112112
interface FileRoutesByPath {
113-
'/terms': {
114-
id: '/terms'
115-
path: '/terms'
116-
fullPath: '/terms'
117-
preLoaderRoute: typeof TermsRouteImport
118-
parentRoute: typeof rootRouteImport
119-
}
120-
'/resource-packs': {
121-
id: '/resource-packs'
122-
path: '/resource-packs'
123-
fullPath: '/resource-packs'
124-
preLoaderRoute: typeof ResourcePacksRouteImport
113+
'/': {
114+
id: '/'
115+
path: '/'
116+
fullPath: '/'
117+
preLoaderRoute: typeof IndexRouteImport
125118
parentRoute: typeof rootRouteImport
126119
}
127-
'/privacy': {
128-
id: '/privacy'
129-
path: '/privacy'
130-
fullPath: '/privacy'
131-
preLoaderRoute: typeof PrivacyRouteImport
120+
'/addons': {
121+
id: '/addons'
122+
path: '/addons'
123+
fullPath: '/addons'
124+
preLoaderRoute: typeof AddonsRouteImport
132125
parentRoute: typeof rootRouteImport
133126
}
134127
'/crafting-tweaks': {
@@ -138,18 +131,25 @@ declare module '@tanstack/react-router' {
138131
preLoaderRoute: typeof CraftingTweaksRouteImport
139132
parentRoute: typeof rootRouteImport
140133
}
141-
'/addons': {
142-
id: '/addons'
143-
path: '/addons'
144-
fullPath: '/addons'
145-
preLoaderRoute: typeof AddonsRouteImport
134+
'/privacy': {
135+
id: '/privacy'
136+
path: '/privacy'
137+
fullPath: '/privacy'
138+
preLoaderRoute: typeof PrivacyRouteImport
146139
parentRoute: typeof rootRouteImport
147140
}
148-
'/': {
149-
id: '/'
150-
path: '/'
151-
fullPath: '/'
152-
preLoaderRoute: typeof IndexRouteImport
141+
'/resource-packs': {
142+
id: '/resource-packs'
143+
path: '/resource-packs'
144+
fullPath: '/resource-packs'
145+
preLoaderRoute: typeof ResourcePacksRouteImport
146+
parentRoute: typeof rootRouteImport
147+
}
148+
'/terms': {
149+
id: '/terms'
150+
path: '/terms'
151+
fullPath: '/terms'
152+
preLoaderRoute: typeof TermsRouteImport
153153
parentRoute: typeof rootRouteImport
154154
}
155155
}

apps/web/src/router.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InternalServerErrorPage, NotFoundPage } from '@/components/Error';
2+
import { SENTRY_DENY_URLS, SENTRY_IGNORE_ERRORS, dropUnactionableEvent } from '@/config/sentry';
23
import * as Sentry from '@sentry/tanstackstart-react';
34
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
45
import { createRouter } from '@tanstack/react-router';
@@ -30,9 +31,17 @@ export const getRouter = () => {
3031
if (!router.isServer) {
3132
Sentry.init({
3233
dsn: import.meta.env.VITE_SENTRY_DSN,
33-
integrations: [
34+
integrations: integrations => [
35+
// BrowserApiErrors wraps addEventListener so third-party listeners throw
36+
// through our Sentry wrapper and get reported as ours. AdSense alone
37+
// accounts for most of our error volume this way. The global onerror and
38+
// onunhandledrejection handlers still catch everything we actually own.
39+
...integrations.filter(integration => integration.name !== 'BrowserApiErrors'),
3440
Sentry.tanstackRouterBrowserTracingIntegration(router),
3541
],
42+
ignoreErrors: SENTRY_IGNORE_ERRORS,
43+
denyUrls: SENTRY_DENY_URLS,
44+
beforeSend: dropUnactionableEvent,
3645
});
3746
}
3847

apps/web/src/routes/__root.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -135,21 +135,23 @@ function RootDocument({ children }: { children: React.ReactNode }): JSX.Element
135135
</AdSenseProvider>
136136
</ChakraProvider>
137137

138-
<TanStackDevtools
139-
config={{
140-
position: 'bottom-right',
141-
}}
142-
plugins={[
143-
{
144-
name: 'Tanstack Router',
145-
render: <TanStackRouterDevtoolsPanel />,
146-
},
147-
{
148-
name: 'Tanstack Query',
149-
render: <ReactQueryDevtoolsPanel />,
150-
},
151-
]}
152-
/>
138+
{import.meta.env.DEV && (
139+
<TanStackDevtools
140+
config={{
141+
position: 'bottom-right',
142+
}}
143+
plugins={[
144+
{
145+
name: 'Tanstack Router',
146+
render: <TanStackRouterDevtoolsPanel />,
147+
},
148+
{
149+
name: 'Tanstack Query',
150+
render: <ReactQueryDevtoolsPanel />,
151+
},
152+
]}
153+
/>
154+
)}
153155
<Scripts />
154156
</body>
155157
</html>

apps/web/src/utils/packs.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { resolveDownloadFileName } from '@/utils/packs';
2+
import { describe, expect, it } from 'vitest';
3+
4+
describe('resolveDownloadFileName', () => {
5+
it('takes the file name from an absolute URL', () => {
6+
expect(resolveDownloadFileName('https://bedrocktweaks.net/download/abc123/BTRP-042.mcpack', 'fallback'))
7+
.toBe('BTRP-042.mcpack');
8+
});
9+
10+
it('resolves a relative URL against the current page', () => {
11+
expect(resolveDownloadFileName('/download/abc123/BTRP-042.mcpack', 'fallback'))
12+
.toBe('BTRP-042.mcpack');
13+
});
14+
15+
it('decodes percent-encoded file names', () => {
16+
expect(resolveDownloadFileName('https://bedrocktweaks.net/download/abc123/My%20Pack.mcpack', 'fallback'))
17+
.toBe('My Pack.mcpack');
18+
});
19+
20+
it('keeps the raw segment when percent-encoding is malformed', () => {
21+
expect(resolveDownloadFileName('https://bedrocktweaks.net/download/abc123/100%.mcpack', 'fallback'))
22+
.toBe('100%.mcpack');
23+
});
24+
25+
it('strips query strings and fragments', () => {
26+
expect(resolveDownloadFileName('https://bedrocktweaks.net/download/abc123/BTRP-042.mcpack?t=1#top', 'fallback'))
27+
.toBe('BTRP-042.mcpack');
28+
});
29+
30+
// The regression behind BT-REACT-3: these used to throw out of the mutation's
31+
// onSuccess callback, which aborted the click that starts the download.
32+
it.each([
33+
['an unparseable URL', 'not a url at all'],
34+
['a malformed protocol', 'https,http://bedrocktweaks.net/download/abc/x.mcpack'],
35+
['an empty string', ''],
36+
['undefined', undefined],
37+
])('does not throw for %s', (_label, downloadUrl) => {
38+
expect(() => resolveDownloadFileName(downloadUrl, 'fallback')).not.toThrow();
39+
});
40+
41+
it('falls back to the pack name when no file name can be recovered', () => {
42+
expect(resolveDownloadFileName(undefined, 'BTRP-042')).toBe('BTRP-042');
43+
expect(resolveDownloadFileName('', 'BTRP-042')).toBe('BTRP-042');
44+
expect(resolveDownloadFileName('https://bedrocktweaks.net/download/', 'BTRP-042')).toBe('BTRP-042');
45+
});
46+
});

0 commit comments

Comments
 (0)