Skip to content

Commit 871290e

Browse files
committed
Merge branch 'develop' into cg-next-refactor-withsentryconfig
2 parents e41b985 + 3ff89c6 commit 871290e

54 files changed

Lines changed: 414 additions & 139 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@
2323
Sentry.metrics.count('response_time', 283.33, { unit: 'millisecond' });
2424
```
2525

26+
- **feat(wasm): Add applicationKey option for third-party error filtering ([#18762])(https://github.com/getsentry/sentry-javascript/pull/18762)**
27+
28+
Adds support for applying an application key to WASM stack frames that can be then used in the `thirdPartyErrorFilterIntegration` for detection of first-party code.
29+
30+
Usage:
31+
32+
```js
33+
Sentry.init({
34+
integrations: [
35+
// Integration order matters: wasmIntegration needs to be before thirdPartyErrorFilterIntegration
36+
wasmIntegration({ applicationKey: 'your-custom-application-key' }), ←───┐
37+
thirdPartyErrorFilterIntegration({ │
38+
behaviour: 'drop-error-if-exclusively-contains-third-party-frames', ├─ matching keys
39+
filterKeys: ['your-custom-application-key'] ←─────────────────────────┘
40+
}),
41+
],
42+
});
43+
```
44+
2645
- ref(nextjs): Drop `resolve` dependency from the Next.js SDK ([#18618](https://github.com/getsentry/sentry-javascript/pull/18618))
2746

2847
## 10.32.1

dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page })
136136
},
137137
'sentry.sdk.version': {
138138
type: 'string',
139-
value: '10.32.1',
139+
value: expect.any(String),
140140
},
141141
'user.email': {
142142
type: 'string',
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { thirdPartyErrorFilterIntegration } from '@sentry/browser';
3+
import { wasmIntegration } from '@sentry/wasm';
4+
5+
// Simulate what the bundler plugin would inject to mark JS code as first-party
6+
var _sentryModuleMetadataGlobal =
7+
typeof window !== 'undefined'
8+
? window
9+
: typeof global !== 'undefined'
10+
? global
11+
: typeof self !== 'undefined'
12+
? self
13+
: {};
14+
15+
_sentryModuleMetadataGlobal._sentryModuleMetadata = _sentryModuleMetadataGlobal._sentryModuleMetadata || {};
16+
17+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack] = Object.assign(
18+
{},
19+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack],
20+
{
21+
'_sentryBundlerPluginAppKey:wasm-test-app': true,
22+
},
23+
);
24+
25+
window.Sentry = Sentry;
26+
27+
Sentry.init({
28+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
29+
integrations: [
30+
wasmIntegration({ applicationKey: 'wasm-test-app' }),
31+
thirdPartyErrorFilterIntegration({
32+
behaviour: 'apply-tag-if-contains-third-party-frames',
33+
filterKeys: ['wasm-test-app'],
34+
}),
35+
],
36+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Simulate what the bundler plugin would inject to mark this JS file as first-party
2+
var _sentryModuleMetadataGlobal =
3+
typeof window !== 'undefined'
4+
? window
5+
: typeof global !== 'undefined'
6+
? global
7+
: typeof self !== 'undefined'
8+
? self
9+
: {};
10+
11+
_sentryModuleMetadataGlobal._sentryModuleMetadata = _sentryModuleMetadataGlobal._sentryModuleMetadata || {};
12+
13+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack] = Object.assign(
14+
{},
15+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack],
16+
{
17+
'_sentryBundlerPluginAppKey:wasm-test-app': true,
18+
},
19+
);
20+
21+
async function runWasm() {
22+
function crash() {
23+
throw new Error('WASM triggered error');
24+
}
25+
26+
const { instance } = await WebAssembly.instantiateStreaming(fetch('https://localhost:5887/simple.wasm'), {
27+
env: {
28+
external_func: crash,
29+
},
30+
});
31+
32+
instance.exports.internal_func();
33+
}
34+
35+
runWasm();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { expect } from '@playwright/test';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import { envelopeRequestParser, waitForErrorRequest } from '../../../utils/helpers';
6+
import { shouldSkipWASMTests } from '../../../utils/wasmHelpers';
7+
8+
const bundle = process.env.PW_BUNDLE || '';
9+
// We only want to run this in non-CDN bundle mode because both
10+
// wasmIntegration and thirdPartyErrorFilterIntegration are only available in NPM packages
11+
if (bundle.startsWith('bundle')) {
12+
sentryTest.skip();
13+
}
14+
15+
sentryTest(
16+
'WASM frames should be recognized as first-party when applicationKey is configured',
17+
async ({ getLocalTestUrl, page, browserName }) => {
18+
if (shouldSkipWASMTests(browserName)) {
19+
sentryTest.skip();
20+
}
21+
22+
const url = await getLocalTestUrl({ testDir: __dirname });
23+
24+
await page.route('**/simple.wasm', route => {
25+
const wasmModule = fs.readFileSync(path.resolve(__dirname, '../simple.wasm'));
26+
27+
return route.fulfill({
28+
status: 200,
29+
body: wasmModule,
30+
headers: {
31+
'Content-Type': 'application/wasm',
32+
},
33+
});
34+
});
35+
36+
const errorEventPromise = waitForErrorRequest(page, e => {
37+
return e.exception?.values?.[0]?.value === 'WASM triggered error';
38+
});
39+
40+
await page.goto(url);
41+
42+
const errorEvent = envelopeRequestParser(await errorEventPromise);
43+
44+
expect(errorEvent.tags?.third_party_code).toBeUndefined();
45+
46+
// Verify we have WASM frames in the stacktrace
47+
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames).toEqual(
48+
expect.arrayContaining([
49+
expect.objectContaining({
50+
filename: expect.stringMatching(/simple\.wasm$/),
51+
platform: 'native',
52+
}),
53+
]),
54+
);
55+
},
56+
);

dev-packages/cloudflare-integration-tests/runner.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,13 @@ export function createRunner(...paths: string[]) {
6767
const expectedEnvelopes: Expected[] = [];
6868
// By default, we ignore session & sessions
6969
const ignored: Set<EnvelopeItemType> = new Set(['session', 'sessions', 'client_report']);
70+
let serverUrl: string | undefined;
7071

7172
return {
73+
withServerUrl: function (url: string) {
74+
serverUrl = url;
75+
return this;
76+
},
7277
expect: function (expected: Expected) {
7378
expectedEnvelopes.push(expected);
7479
return this;
@@ -186,6 +191,8 @@ export function createRunner(...paths: string[]) {
186191
'false',
187192
'--var',
188193
`SENTRY_DSN:http://public@localhost:${mockServerPort}/1337`,
194+
'--var',
195+
`SERVER_URL:${serverUrl}`,
189196
],
190197
{ stdio, signal },
191198
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
SERVER_URL: string;
6+
}
7+
8+
export default Sentry.withSentry(
9+
(env: Env) => ({
10+
dsn: env.SENTRY_DSN,
11+
propagateTraceparent: true,
12+
}),
13+
{
14+
async fetch(_request, env, _ctx) {
15+
await fetch(env.SERVER_URL);
16+
throw new Error('Test error to capture trace headers');
17+
},
18+
},
19+
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { expect, it } from 'vitest';
3+
import { eventEnvelope } from '../../../expect';
4+
import { createRunner } from '../../../runner';
5+
6+
it('Tracing headers', async ({ signal }) => {
7+
expect.assertions(5);
8+
9+
const [SERVER_URL, closeTestServer] = await createTestServer()
10+
.get('/', headers => {
11+
expect(headers['baggage']).toEqual(expect.any(String));
12+
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})-0$/));
13+
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-0');
14+
expect(headers['traceparent']).toEqual(expect.stringMatching(/^00-([a-f\d]{32})-([a-f\d]{16})-00$/));
15+
})
16+
.start();
17+
18+
const runner = createRunner(__dirname)
19+
.withServerUrl(SERVER_URL)
20+
.expect(
21+
eventEnvelope({
22+
level: 'error',
23+
exception: {
24+
values: [
25+
{
26+
type: 'Error',
27+
value: 'Test error to capture trace headers',
28+
stacktrace: {
29+
frames: expect.any(Array),
30+
},
31+
mechanism: { type: 'auto.http.cloudflare', handled: false },
32+
},
33+
],
34+
},
35+
breadcrumbs: [
36+
{
37+
category: 'fetch',
38+
data: {
39+
method: 'GET',
40+
status_code: 200,
41+
url: expect.stringContaining('http://localhost:'),
42+
},
43+
timestamp: expect.any(Number),
44+
type: 'http',
45+
},
46+
],
47+
request: {
48+
headers: expect.any(Object),
49+
method: 'GET',
50+
url: expect.any(String),
51+
},
52+
}),
53+
)
54+
.start(signal);
55+
56+
await runner.makeRequest('get', '/');
57+
await runner.completed();
58+
closeTestServer();
59+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "worker-name",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_compat"]
6+
}

dev-packages/node-core-integration-tests/suites/ipv6/scenario.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Sentry from '@sentry/node';
1+
import * as Sentry from '@sentry/node-core';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({

0 commit comments

Comments
 (0)