Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/services/staleAssetErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,41 @@ interface PostHogEventLike {
$exception_list?: PostHogExceptionLike[]
$exception_values?: unknown[]
$current_url?: unknown
$host?: unknown
}
}

// Local dev-server hosts. The capture gate in posthog.ts skips analytics only
// when the Supabase host is local, so a developer running the frontend against
// remote or preprod Supabase still sends events. Their transient compile and
// hot-reload errors then open production error-tracking issues. Match the page
// host to drop those events regardless of the Supabase host.
const LOCAL_DEV_HOST_PATTERNS = [
/^localhost$/i,
/^127\.0\.0\.1$/,
/\.local$/i,
]

function hostFromUrl(url: unknown): string | undefined {
if (typeof url !== 'string' || url === '')
return undefined

try {
return new URL(url).host
}
catch {
return undefined
}
}

export function isLocalDevHost(host: unknown): boolean {
if (typeof host !== 'string' || host === '')
return false

const hostname = host.replace(/:\d+$/, '')
return LOCAL_DEV_HOST_PATTERNS.some(pattern => pattern.test(hostname))
}

function stripUrlQueryAndHash(url: string | undefined): string | undefined {
if (!url)
return undefined
Expand Down Expand Up @@ -155,6 +187,9 @@ export function shouldSuppressPostHogExceptionEvent(event: PostHogEventLike): bo
if (event.event !== '$exception')
return false

if (isLocalDevHost(event.properties?.$host) || isLocalDevHost(hostFromUrl(event.properties?.$current_url)))
return true

const exception = event.properties?.$exception_list?.[0]
const exceptionValue = getErrorMessage(exception?.value) ?? getErrorMessage(exception?.$exception_value)
if (isSuppressibleNoiseErrorMessage(exceptionValue))
Expand Down
48 changes: 47 additions & 1 deletion tests/stale-asset-errors.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'

import { getErrorMessage, isComponentResolutionErrorMessage, isInjectedDocumentCodeException, isKnownCrawlerNoiseErrorMessage, isStaleAssetErrorMessage, isTransientNetworkErrorMessage, shouldSuppressPostHogExceptionEvent } from '../src/services/staleAssetErrors'
import { getErrorMessage, isComponentResolutionErrorMessage, isInjectedDocumentCodeException, isKnownCrawlerNoiseErrorMessage, isLocalDevHost, isStaleAssetErrorMessage, isTransientNetworkErrorMessage, shouldSuppressPostHogExceptionEvent } from '../src/services/staleAssetErrors'

describe('stale asset error helpers', () => {
it('matches the stale asset errors currently seen in PostHog', () => {
Expand Down Expand Up @@ -151,6 +151,52 @@ describe('stale asset error helpers', () => {
})).toBe(false)
})

it('matches local dev-server hosts regardless of port', () => {
expect(isLocalDevHost('localhost:5175')).toBe(true)
expect(isLocalDevHost('localhost')).toBe(true)
expect(isLocalDevHost('127.0.0.1:5173')).toBe(true)
expect(isLocalDevHost('capgo.local')).toBe(true)
expect(isLocalDevHost('capgo.local:3000')).toBe(true)
})

it('does not match production hosts or malformed values', () => {
expect(isLocalDevHost('console.capgo.app')).toBe(false)
expect(isLocalDevHost('capgo.app:443')).toBe(false)
expect(isLocalDevHost('notlocalhost.com')).toBe(false)
expect(isLocalDevHost('')).toBe(false)
expect(isLocalDevHost(undefined)).toBe(false)
})

it('suppresses dev-server exception events by page host', () => {
// The $host property carries the dev-server host with its port
expect(shouldSuppressPostHogExceptionEvent({
event: '$exception',
properties: {
$host: 'localhost:5175',
$exception_list: [{ value: 'ReferenceError: disableRollout is not defined' }],
},
})).toBe(true)

// Falls back to the host parsed from $current_url when $host is absent
expect(shouldSuppressPostHogExceptionEvent({
event: '$exception',
properties: {
$current_url: 'http://localhost:5175/app/1234/channel/production',
$exception_list: [{ value: 'ReferenceError: disableRollout is not defined' }],
},
})).toBe(true)

// The same exception from the production host is kept
expect(shouldSuppressPostHogExceptionEvent({
event: '$exception',
properties: {
$host: 'console.capgo.app',
$current_url: 'https://console.capgo.app/app/1234/channel/production',
$exception_list: [{ value: 'ReferenceError: disableRollout is not defined' }],
},
})).toBe(false)
})

it('extracts useful messages from arbitrary rejection values', () => {
expect(getErrorMessage(new Error('Importing a module script failed.'))).toBe('Importing a module script failed.')
expect(getErrorMessage({ message: 'Unable to preload CSS for /assets/main.css' })).toBe('Unable to preload CSS for /assets/main.css')
Expand Down
Loading