forked from ethereum/ethereum-org-website
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstrumentation-client.ts
More file actions
133 lines (117 loc) · 4.92 KB
/
Copy pathinstrumentation-client.ts
File metadata and controls
133 lines (117 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import * as Sentry from "@sentry/nextjs"
const environment = process.env.NEXT_PUBLIC_CONTEXT || "development"
/**
* Finds the closest element (including the element itself) that has an id attribute
* @param element - The starting element to search from
* @param maxDepth - Maximum number of parent levels to search (default: 3)
* @returns The first found attribute value in priority order, null otherwise
*/
function findClosestElementId(
element: Element | null | undefined,
maxDepth: number = 3
): string | null {
if (!element || maxDepth < 0) return null
const sentryId = element.getAttribute("data-testid")
if (sentryId) return sentryId
const ariaLabel = element.getAttribute("aria-label")
if (ariaLabel) return ariaLabel
const id = element.getAttribute("id")
if (id) return id
// Recursively check parent elements up to maxDepth
return findClosestElementId(element.parentElement, maxDepth - 1)
}
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.01,
debug: environment === "development",
environment,
enabled: environment === "production",
// Filter errors from browser extensions and third-party scripts
denyUrls: [
// Browser extension protocols
/chrome-extension:\/\//,
/moz-extension:\/\//,
/safari-extension:\/\//,
// Netlify RUM analytics (blocked by ad blockers, not actionable)
/\.netlify\/scripts\/rum/,
/ingesteer\.services-prod\.nsvcs\.net/,
],
// Filter common extension error messages and non-actionable errors
ignoreErrors: [
// Wallet extension proxy/property conflicts (ETHORG-Z1, ETHORG-115)
/on proxy: trap returned falsish/i,
/Cannot set property ethereum of #<Window>/,
/Cannot set property isMetaMask of #<.+> which has only a getter/,
// Extension messaging errors
/Could not establish connection\. Receiving end does not exist/,
/Attempting to use a disconnected port object/,
// Resource loading errors - network/ad blocker issues, not actionable (ETHORG-A8)
/Event `Event` \(type=error\) captured as promise rejection/,
// WebView circular reference serialization failures - wallet app injections (ETHORG-72)
/JSON\.stringify cannot serialize cyclic structures/,
],
beforeSend(event) {
// Filter extension injection script errors not caught by denyUrls
const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? []
const isExtensionScript = frames.some((f) => {
const filename = f.filename || ""
const absPath = f.abs_path || ""
return (
// Extension preload scripts
filename.includes("preload/document.js") ||
absPath.includes("preload/document.js") ||
// Wallet extension injection scripts (ETHORG-Z1: TronLink, ETHORG-115: wallet bridges)
/injected\/injected\.js/.test(filename) ||
/bridge\/inject\.js/.test(filename) ||
/content[-_]?script\.js/i.test(filename) ||
/inpage\.js/.test(filename) ||
// Generic app:// protocol used by extension injected scripts (ETHORG-117: BitVisionWeb wallet)
filename.startsWith("app:///") ||
absPath.startsWith("app:///") ||
// Extension code injected via about:blank contexts (ETHORG-96)
filename === "about:blank" ||
absPath === "about:blank"
)
})
return isExtensionScript ? null : event
},
// Normalize transaction names for parameterized routes to enable per-page analysis
// Sentry uses formats like "/:locale/:slug*" for catch-all routes
beforeSendTransaction(event) {
const op = event.contexts?.trace?.op
const transaction = event.transaction
// Matches patterns like ":locale", ":slug*", ":id", ":post", etc.
const isParameterizedRoute = transaction && /:\w+/.test(transaction)
const isPageTransaction = op === "pageload" || op === "navigation"
if (isParameterizedRoute && isPageTransaction) {
const url = event.request?.url || (event.tags?.url as string | undefined)
if (url) {
try {
const pathname = new URL(url).pathname
// Remove locale prefix (e.g., "/en/", "/fil/", "/zh-tw/", "/pt-br/"), keeping just the page path
// e.g., "/en/developers/docs" -> "/developers/docs"
// Only match complete path segments (must be followed by "/" or end of string)
const normalizedPath = pathname.replace(
/^\/[a-z]{2,3}(-[a-z]{2})?(?=\/|$)/,
""
)
event.transaction = normalizedPath || "/"
} catch {
// Keep original transaction name if URL parsing fails
}
}
}
return event
},
beforeBreadcrumb(breadcrumb, hint) {
if (breadcrumb.category === "ui.click") {
const element = hint?.event?.target
const id = findClosestElementId(element)
if (id) {
breadcrumb.message = id + " (" + breadcrumb.message + ")"
}
}
return breadcrumb
},
})
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart