Skip to content

Commit c45ba72

Browse files
Add tests constants
1 parent c8ca3b1 commit c45ba72

File tree

6 files changed

+78
-186
lines changed

6 files changed

+78
-186
lines changed

packages/core/src/domain/allowedTrackingOrigins.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN,
55
ERROR_NOT_ALLOWED_TRACKING_ORIGIN,
66
} from './allowedTrackingOrigins'
7+
import { STACK_WITH_INIT_IN_EXTENSION } from './extension/extensionUtils'
78

89
const DEFAULT_CONFIG = {
910
applicationId: 'xxx',
@@ -184,7 +185,7 @@ describe('checkForAllowedTrackingOrigins', () => {
184185
...DEFAULT_CONFIG,
185186
allowedTrackingOrigins: undefined,
186187
},
187-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15',
188+
STACK_WITH_INIT_IN_EXTENSION,
188189
'https://example.com'
189190
)
190191
expect(displayWarnSpy).toHaveBeenCalledWith(WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN)

packages/core/src/domain/configuration/configuration.spec.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
isExperimentalFeatureEnabled,
88
resetExperimentalFeatures,
99
} from '../../tools/experimentalFeatures'
10-
import { WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN } from '../allowedTrackingOrigins'
1110
import { SessionPersistence } from '../session/sessionConstants'
1211
import { TrackingConsent } from '../trackingConsent'
1312
import type { InitConfiguration } from './configuration'
@@ -234,17 +233,4 @@ describe('validateAndBuildConfiguration', () => {
234233
expect(serializedConfiguration).toEqual(SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION)
235234
})
236235
})
237-
238-
describe('validateAndBuildConfiguration errorStack threading', () => {
239-
it('uses provided errorStack in isAllowedTrackingOrigins (triggers extension warning)', () => {
240-
const warnSpy = spyOn(display, 'warn')
241-
242-
const initConfiguration = { clientToken: 't' } as any
243-
const errorStack = 'Error: at chrome-extension://abcdefgh/content.js:10:15'
244-
245-
validateAndBuildConfiguration(initConfiguration, errorStack)
246-
247-
expect(warnSpy).toHaveBeenCalledWith(WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN)
248-
})
249-
})
250236
})

packages/core/src/domain/extension/extensionUtils.spec.ts

Lines changed: 38 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,113 +3,63 @@ import {
33
EXTENSION_PREFIXES,
44
extractExtensionUrlFromStack,
55
isUnsupportedExtensionEnvironment,
6+
STACK_WITH_INIT_IN_PAGE,
7+
STACK_WITH_INIT_IN_EXTENSION,
8+
STACK_WITH_INIT_IN_EXTENSION_FIREFOX,
69
} from './extensionUtils'
710

8-
describe('containsExtensionUrl', () => {
9-
it('should return true if string contains an extension URL', () => {
10-
EXTENSION_PREFIXES.forEach((prefix) => {
11-
expect(containsExtensionUrl(`${prefix}some/path`)).toBe(true)
11+
describe('extensionUtils', () => {
12+
describe('containsExtensionUrl', () => {
13+
it('should return true if string contains an extension URL', () => {
14+
EXTENSION_PREFIXES.forEach((prefix) => {
15+
expect(containsExtensionUrl(`${prefix}some/path`)).toBe(true)
16+
})
1217
})
13-
})
14-
15-
it('should return false if string does not contain extension URL', () => {
16-
expect(containsExtensionUrl('https://example.com')).toBe(false)
17-
expect(containsExtensionUrl('')).toBe(false)
18-
})
19-
})
2018

21-
describe('isUnsupportedExtensionEnvironment', () => {
22-
it('should return true when window location is a regular URL and error stack contains extension URL', () => {
23-
expect(
24-
isUnsupportedExtensionEnvironment('https://example.com', 'Error: at chrome-extension://abcdefg/content.js:10:15')
25-
).toBe(true)
19+
it('should return false if string does not contain extension URL', () => {
20+
expect(containsExtensionUrl('https://example.com')).toBe(false)
21+
expect(containsExtensionUrl('')).toBe(false)
22+
})
2623
})
2724

28-
it('should return false when both window location and error stack are regular URLs', () => {
29-
expect(
30-
isUnsupportedExtensionEnvironment('https://example.com', 'Error: at https://example.com/script.js:10:15')
31-
).toBe(false)
32-
})
25+
describe('isUnsupportedExtensionEnvironment', () => {
26+
it('should return true when window location is a regular URL and error stack init is in an extension', () => {
27+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
28+
})
3329

34-
it('should return false when window location is an extension URL', () => {
35-
EXTENSION_PREFIXES.forEach((prefix) => {
36-
expect(
37-
isUnsupportedExtensionEnvironment(`${prefix}some/path`, 'Error: at chrome-extension://abcdefg/content.js:10:15')
38-
).toBe(false)
30+
it('should return false when both window location and error stack init are regular URLs', () => {
31+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_PAGE)).toBe(false)
3932
})
40-
})
4133

42-
it('should return false when error stack is empty', () => {
43-
expect(isUnsupportedExtensionEnvironment('https://example.com', '')).toBe(false)
44-
})
34+
it('should return false when error stack is empty', () => {
35+
expect(isUnsupportedExtensionEnvironment('https://example.com', '')).toBe(false)
36+
})
4537

46-
it('should handle each extension prefix in error stack', () => {
47-
EXTENSION_PREFIXES.forEach((prefix) => {
48-
expect(
49-
isUnsupportedExtensionEnvironment('https://example.com', `Error: at ${prefix}abcdefg/content.js:10:15`)
50-
).toBe(true)
38+
it('should handle each extension prefix in firefox', () => {
39+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION_FIREFOX)).toBe(true)
5140
})
52-
})
5341

54-
it('should handle case when stack trace is undefined', () => {
55-
expect(isUnsupportedExtensionEnvironment('https://example.com')).toBe(false)
56-
})
42+
it('should handle case when stack trace is undefined', () => {
43+
expect(isUnsupportedExtensionEnvironment('https://example.com')).toBe(false)
44+
})
5745

58-
it('should handle extension stack trace', () => {
59-
expect(
60-
isUnsupportedExtensionEnvironment('https://example.com', 'Error: at chrome-extension://abcdefg/content.js:10:15')
61-
).toBe(true)
46+
it('should handle extension stack trace', () => {
47+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
48+
})
6249
})
63-
})
6450

65-
describe('extractExtensionUrlFromStack', () => {
66-
it('should extract extension URL from stack trace', () => {
67-
const stack = `Error
51+
describe('extract init caller', () => {
52+
it('should extract extension URL from stack trace', () => {
53+
const stack = `Error
6854
at foo (<anonymous>:549:44)
6955
at bar (<anonymous>:701:91)
70-
at e.init (chrome-extension://boceobohkgenpcpogecpjlnmnfbdigda/content-script-main.js:1:1009)`
71-
expect(extractExtensionUrlFromStack(stack)).toBe('chrome-extension://boceobohkgenpcpogecpjlnmnfbdigda')
72-
})
73-
74-
it('should return undefined when no extension URL found', () => {
75-
const stack = 'Error at https://example.com/script.js:10:15'
76-
expect(extractExtensionUrlFromStack(stack)).toBeUndefined()
77-
})
78-
79-
describe('isUnsupportedExtensionEnvironment caller-frame', () => {
80-
it('should return true when caller frame is an extension URL', () => {
81-
const stack = [
82-
'Error',
83-
' at callMonitored (https://cdn.datadoghq.com/rum.js:10:10)',
84-
' at Object.init (https://cdn.datadoghq.com/rum.js:9:5)',
85-
' at chrome-extension://abc123/script.js:10:15',
86-
' at https://app.example.com/app.js:200:30',
87-
].join('\n')
88-
expect(isUnsupportedExtensionEnvironment('https://example.com', stack)).toBe(true)
56+
at e.init (chrome-extension://abcd/content-script-main.js:1:1009)`
57+
expect(extractExtensionUrlFromStack(stack)).toBe('chrome-extension://abcd')
8958
})
9059

91-
it('returns false when extension frames exist but the init caller is not an extension', () => {
92-
const stack = [
93-
'Error',
94-
' at callMonitored (https://cdn.datadoghq.com/rum.js:10:10)',
95-
' at Object.init (https://cdn.datadoghq.com/rum.js:9:5)',
96-
' at https://app.example.com/app.js:3:59',
97-
' at Object.apply (chrome-extension://random-extension-id/WXkqOoBd.js:10:4624)',
98-
' at https://app.example.com/app.js:200:30',
99-
].join('\n')
100-
101-
expect(isUnsupportedExtensionEnvironment('https://app.example.com', stack)).toBeFalse()
102-
})
103-
104-
it('falls back to first non-SDK frame if init frame not found', () => {
105-
const stack = [
106-
'Error',
107-
' at someInternal (https://cdn.datadoghq.com/rum.js:10:10)',
108-
' at otherInternal (https://cdn.datadoghq.com/rum.js:9:5)',
109-
' at chrome-extension://abc123/script.js:10:15',
110-
].join('\n')
111-
112-
expect(isUnsupportedExtensionEnvironment('https://app.example.com', stack)).toBeTrue()
60+
it('should return undefined when no extension URL found', () => {
61+
const stack = 'Error at https://example.com/script.js:10:15'
62+
expect(extractExtensionUrlFromStack(stack)).toBeUndefined()
11363
})
11464
})
11565
})

packages/core/src/domain/extension/extensionUtils.ts

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,40 @@
11
export const EXTENSION_PREFIXES = ['chrome-extension://', 'moz-extension://']
22

3-
export function containsExtensionUrl(str: string): boolean {
4-
return EXTENSION_PREFIXES.some((prefix) => str.includes(prefix))
5-
}
3+
// Base case, the page has the SDK in the init and the error stack is in the page.
4+
export const STACK_WITH_INIT_IN_PAGE = `Error
5+
at Object.<anonymous> (http://localhost:8080/datadog-rum.js:6385:32)
6+
at callMonitored (http://localhost:8080/datadog-rum.js:3925:19)
7+
at Object.init (http://localhost:8080/datadog-rum.js:3919:16)
8+
at http://localhost:8080/:10:14`
69

7-
interface StackFrame {
8-
raw: string
9-
functionName?: string
10-
url?: string
11-
}
10+
// Base case for extension, the extension has the SDK in the init and the error stack is in the extension.
11+
export const STACK_WITH_INIT_IN_EXTENSION = `Error
12+
at Object.<anonymous> (chrome-extension://abcdef/dist/contentScript.js:5416:28)
13+
at callMonitored (chrome-extension://abcdef/dist/contentScript.js:259:17)
14+
at Object.init (chrome-extension://abcdef/dist/contentScript.js:254:14)
15+
at chrome-extension://abcdef/dist/contentScript.js:13304:14
16+
at chrome-extension://abcdef/dist/contentScript.js:13315:3`
1217

13-
function parseStack(stack: string = ''): StackFrame[] {
14-
return (
15-
stack
16-
.split('\n')
17-
.map((line) => {
18-
// Normalize: drop a leading "Error:" and allow optional leading spaces
19-
const normalized = line.replace(/^Error:\s*/, '')
20-
const withFn = normalized.match(/^\s*at\s+(.*?)\s+\((.*)\)/)
21-
const noFn = !withFn && normalized.match(/^\s*at\s+(.*)/)
22-
const functionName = withFn ? withFn[1].trim() : undefined
23-
const location = withFn ? withFn[2] : noFn ? noFn[1] : ''
24-
const urlMatch = location && location.match(/(chrome-extension:\/\/|moz-extension:\/\/|https?:\/\/)[^\s)]+/)
25-
return {
26-
raw: line,
27-
functionName,
28-
url: urlMatch ? urlMatch[0] : undefined,
29-
}
30-
})
31-
// Keep only lines that look like frames
32-
.filter((f) => f.functionName !== undefined || f.url !== undefined)
33-
)
34-
}
18+
export const STACK_WITH_INIT_IN_EXTENSION_FIREFOX = `Error
19+
at Object.<anonymous> (moz-extension://abcdef/dist/contentScript.js:5416:28)
20+
at callMonitored (moz-extension://abcdef/dist/contentScript.js:259:17)
21+
at Object.init (moz-extension://abcdef/dist/contentScript.js:254:14)
22+
at moz-extension://abcdef/dist/contentScript.js:13304:14
23+
at moz-extension://abcdef/dist/contentScript.js:13315:3`
3524

36-
// Function to check if the frame is an SDK internal frame
37-
function isSdkInternalFrame(frame: StackFrame): boolean {
38-
const fn = frame.functionName || ''
39-
const url = frame.url || ''
25+
// Edge case, the extension patches a function from the page.
26+
export const STACK_WITH_INIT_IN_PAGE_PATCHED = `Error
27+
at Object.‹anonymous> (http://localhost:8080/datadog-rum.js:6385:32)
28+
at callMonitored (http://localhost:8080/datadog-rum.js:3925:19)
29+
at Object.init (http://localhost:8080/datadog-rum.js:3919:16)
30+
at <anonymous>:2:23
31+
at Object.apply (chrome-extension://hgijklmn/WXkq0oBd.js:10:4624)
32+
at http://localhost:8080/:16:21`
4033

41-
return /callMonitored|monitor|Object\.init/.test(fn) || /datadog|browser-sdk|@datadog|rum\.js|logs\.js/.test(url)
34+
export function containsExtensionUrl(str: string): boolean {
35+
return EXTENSION_PREFIXES.some((prefix) => str.includes(prefix))
4236
}
4337

44-
function getInitCallerFrame(stack: string = ''): StackFrame | undefined {
45-
const frames = parseStack(stack)
46-
const idxInit = frames.findIndex(
47-
(f) => /(^|\.)init$/.test(f.functionName || '') || /Object\.init/.test(f.functionName || '')
48-
)
49-
if (idxInit >= 0 && frames[idxInit + 1]) {
50-
return frames[idxInit + 1]
51-
}
52-
// Fallback: first non-internal frame
53-
return frames.find((f) => !isSdkInternalFrame(f))
54-
}
5538
/**
5639
* Utility function to detect if the SDK is being initialized in an unsupported browser extension environment.
5740
* Note: Because we check error stack, this will not work if the error stack is too long as Browsers truncate it.
@@ -61,13 +44,16 @@ function getInitCallerFrame(stack: string = ''): StackFrame | undefined {
6144
* @returns true if running in an unsupported browser extension environment
6245
*/
6346
export function isUnsupportedExtensionEnvironment(windowLocation: string, stack: string = '') {
64-
// If we are on an extension page, we are not in an unsupported environment
47+
// If the page itself is an extension page.
6548
if (containsExtensionUrl(windowLocation)) {
6649
return false
6750
}
68-
// If the init caller frame is an extension URL, we are in an unsupported environment
69-
const initCallerFrame = getInitCallerFrame(stack)
70-
return !!(initCallerFrame && initCallerFrame.url && containsExtensionUrl(initCallerFrame.url))
51+
52+
// Consider only the 3rd frame line, which is the init caller.
53+
const frameLines = stack.split('\n').filter((l) => /^\s*at\s+/.test(l))
54+
const target = frameLines[2] || ''
55+
56+
return containsExtensionUrl(target)
7157
}
7258

7359
export function extractExtensionUrlFromStack(stack: string = ''): string | undefined {

packages/logs/src/domain/configuration.spec.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { InitConfiguration } from '@datadog/browser-core'
2-
import { display, WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN } from '@datadog/browser-core'
2+
import { display } from '@datadog/browser-core'
33
import {
44
EXHAUSTIVE_INIT_CONFIGURATION,
55
type CamelToSnakeCase,
@@ -94,19 +94,6 @@ describe('validateAndBuildLogsConfiguration', () => {
9494
})
9595
})
9696

97-
describe('validateAndBuildLogsConfiguration errorStack threading', () => {
98-
it('uses provided errorStack to detect extension context and warn', () => {
99-
const warnSpy = spyOn(display, 'warn')
100-
101-
const initConfiguration = { clientToken: 't' } as any
102-
const errorStack = 'Error: at chrome-extension://abcdefgh/content.js:10:15'
103-
104-
validateAndBuildLogsConfiguration(initConfiguration, errorStack)
105-
106-
expect(warnSpy).toHaveBeenCalledWith(WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN)
107-
})
108-
})
109-
11097
describe('validateAndBuildForwardOption', () => {
11198
let displaySpy: jasmine.Spy<typeof display.error>
11299
const allowedValues = ['foo', 'bar']

packages/rum-core/src/domain/configuration/configuration.spec.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import type { InitConfiguration } from '@datadog/browser-core'
2-
import {
3-
DefaultPrivacyLevel,
4-
display,
5-
TraceContextInjection,
6-
WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN,
7-
} from '@datadog/browser-core'
2+
import { DefaultPrivacyLevel, display, TraceContextInjection } from '@datadog/browser-core'
83
import type {
94
ExtractTelemetryConfiguration,
105
CamelToSnakeCase,
@@ -518,19 +513,6 @@ describe('validateAndBuildRumConfiguration', () => {
518513
})
519514
})
520515

521-
describe('validateAndBuildRumConfiguration errorStack threading', () => {
522-
it('uses provided errorStack to detect extension context and warn', () => {
523-
const warnSpy = spyOn(display, 'warn')
524-
525-
const initConfiguration = { clientToken: 't', applicationId: 'app' } as any
526-
const errorStack = 'Error: at chrome-extension://abcdefgh/content.js:10:15'
527-
528-
validateAndBuildRumConfiguration(initConfiguration, errorStack)
529-
530-
expect(warnSpy).toHaveBeenCalledWith(WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN)
531-
})
532-
})
533-
534516
describe('serializeRumConfiguration', () => {
535517
it('should serialize the configuration', () => {
536518
const exhaustiveRumInitConfiguration: Required<RumInitConfiguration> = {

0 commit comments

Comments
 (0)