Skip to content

Commit 077c92b

Browse files
Integrate beltran.bulbarella/RUM-11247_2 (#3824) into staging-39
Integrated commit sha: e31f5cb Co-authored-by: BeltranBulbarellaDD <beltran.bulbarella@datadoghq.com>
2 parents b6557d9 + e31f5cb commit 077c92b

File tree

19 files changed

+152
-121
lines changed

19 files changed

+152
-121
lines changed

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

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { STACK_WITH_INIT_IN_EXTENSION, STACK_WITH_INIT_IN_PAGE } from '../../test'
12
import { display } from '../tools/display'
23
import {
34
isAllowedTrackingOrigins,
@@ -21,7 +22,7 @@ describe('checkForAllowedTrackingOrigins', () => {
2122
})
2223

2324
it('should not warn if not in extension environment', () => {
24-
const result = isAllowedTrackingOrigins(DEFAULT_CONFIG, 'https://app.example.com')
25+
const result = isAllowedTrackingOrigins(DEFAULT_CONFIG, STACK_WITH_INIT_IN_PAGE, 'https://app.example.com')
2526
expect(displayWarnSpy).not.toHaveBeenCalled()
2627
expect(displayErrorSpy).not.toHaveBeenCalled()
2728
expect(result).toBe(true)
@@ -34,6 +35,7 @@ describe('checkForAllowedTrackingOrigins', () => {
3435
...DEFAULT_CONFIG,
3536
allowedTrackingOrigins: ['https://app.example.com'],
3637
},
38+
STACK_WITH_INIT_IN_PAGE,
3739
'https://app.example.com'
3840
)
3941
expect(displayWarnSpy).not.toHaveBeenCalled()
@@ -47,6 +49,7 @@ describe('checkForAllowedTrackingOrigins', () => {
4749
...DEFAULT_CONFIG,
4850
allowedTrackingOrigins: [/^https:\/\/.*\.example\.com$/],
4951
},
52+
STACK_WITH_INIT_IN_PAGE,
5053
'https://app.example.com'
5154
)
5255
expect(displayWarnSpy).not.toHaveBeenCalled()
@@ -60,6 +63,7 @@ describe('checkForAllowedTrackingOrigins', () => {
6063
...DEFAULT_CONFIG,
6164
allowedTrackingOrigins: [(origin: string) => origin.includes('example.com')],
6265
},
66+
STACK_WITH_INIT_IN_PAGE,
6367
'https://app.example.com'
6468
)
6569
expect(displayWarnSpy).not.toHaveBeenCalled()
@@ -77,6 +81,7 @@ describe('checkForAllowedTrackingOrigins', () => {
7781
(origin: string) => origin.startsWith('https://app.'),
7882
],
7983
},
84+
STACK_WITH_INIT_IN_PAGE,
8085
'https://app.example.com'
8186
)
8287
expect(displayWarnSpy).not.toHaveBeenCalled()
@@ -92,8 +97,8 @@ describe('checkForAllowedTrackingOrigins', () => {
9297
...DEFAULT_CONFIG,
9398
allowedTrackingOrigins: ['https://different.com'],
9499
},
95-
'https://example.com',
96-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
100+
STACK_WITH_INIT_IN_EXTENSION,
101+
'https://example.com'
97102
)
98103
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
99104
expect(result).toBe(false)
@@ -105,8 +110,8 @@ describe('checkForAllowedTrackingOrigins', () => {
105110
...DEFAULT_CONFIG,
106111
allowedTrackingOrigins: [/^https:\/\/specific-[a-z]+\.com$/],
107112
},
108-
'https://example.com',
109-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
113+
STACK_WITH_INIT_IN_EXTENSION,
114+
'https://example.com'
110115
)
111116
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
112117
expect(result).toBe(false)
@@ -118,8 +123,8 @@ describe('checkForAllowedTrackingOrigins', () => {
118123
...DEFAULT_CONFIG,
119124
allowedTrackingOrigins: [(origin: string) => origin.includes('specific-id')],
120125
},
121-
'https://example.com',
122-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
126+
STACK_WITH_INIT_IN_EXTENSION,
127+
'https://example.com'
123128
)
124129
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
125130
expect(result).toBe(false)
@@ -135,8 +140,8 @@ describe('checkForAllowedTrackingOrigins', () => {
135140
(origin: string) => origin.includes('specific-id'),
136141
],
137142
},
138-
'https://example.com',
139-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
143+
STACK_WITH_INIT_IN_EXTENSION,
144+
'https://example.com'
140145
)
141146
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
142147
expect(result).toBe(false)
@@ -148,8 +153,8 @@ describe('checkForAllowedTrackingOrigins', () => {
148153
...DEFAULT_CONFIG,
149154
allowedTrackingOrigins: ['https://example.com'],
150155
},
151-
'https://example.com.extra.com',
152-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
156+
STACK_WITH_INIT_IN_EXTENSION,
157+
'https://example.com.extra.com'
153158
)
154159
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
155160
expect(result).toBe(false)
@@ -161,8 +166,8 @@ describe('checkForAllowedTrackingOrigins', () => {
161166
...DEFAULT_CONFIG,
162167
allowedTrackingOrigins: [/^chrome-extension:\/\//],
163168
},
164-
'chrome-extension://abcdefghijklmno',
165-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
169+
STACK_WITH_INIT_IN_EXTENSION,
170+
'chrome-extension://abcdefghijklmno'
166171
)
167172
expect(displayErrorSpy).not.toHaveBeenCalled()
168173
expect(result).toBe(true)
@@ -176,8 +181,8 @@ describe('checkForAllowedTrackingOrigins', () => {
176181
...DEFAULT_CONFIG,
177182
allowedTrackingOrigins: undefined,
178183
},
179-
'https://example.com',
180-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
184+
STACK_WITH_INIT_IN_EXTENSION,
185+
'https://example.com'
181186
)
182187
expect(displayWarnSpy).toHaveBeenCalledWith(WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN)
183188
expect(result).toBe(true)
@@ -189,8 +194,8 @@ describe('checkForAllowedTrackingOrigins', () => {
189194
...DEFAULT_CONFIG,
190195
allowedTrackingOrigins: [],
191196
},
192-
'https://example.com',
193-
'Error: at chrome-extension://abcdefghijklmno/content.js:10:15'
197+
STACK_WITH_INIT_IN_EXTENSION,
198+
'https://example.com'
194199
)
195200
expect(displayErrorSpy).toHaveBeenCalledWith(ERROR_NOT_ALLOWED_TRACKING_ORIGIN)
196201
expect(result).toBe(false)
@@ -202,8 +207,8 @@ describe('checkForAllowedTrackingOrigins', () => {
202207
...DEFAULT_CONFIG,
203208
allowedTrackingOrigins: undefined,
204209
},
205-
'https://example.com',
206-
'Error: at https://example.com/script.js:10:15'
210+
STACK_WITH_INIT_IN_PAGE,
211+
'https://example.com'
207212
)
208213
expect(displayWarnSpy).not.toHaveBeenCalled()
209214
expect(displayErrorSpy).not.toHaveBeenCalled()

packages/core/src/domain/allowedTrackingOrigins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export const ERROR_NOT_ALLOWED_TRACKING_ORIGIN = 'SDK initialized on a non-allow
1010

1111
export function isAllowedTrackingOrigins(
1212
configuration: InitConfiguration,
13-
windowOrigin = typeof location !== 'undefined' ? location.origin : '',
14-
errorStack = new Error().stack
13+
errorStack: string,
14+
windowOrigin = typeof location !== 'undefined' ? location.origin : ''
1515
): boolean {
1616
const allowedTrackingOrigins = configuration.allowedTrackingOrigins
1717
if (!allowedTrackingOrigins) {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,10 @@ export function isSampleRate(sampleRate: unknown, name: string) {
358358
return true
359359
}
360360

361-
export function validateAndBuildConfiguration(initConfiguration: InitConfiguration): Configuration | undefined {
361+
export function validateAndBuildConfiguration(
362+
initConfiguration: InitConfiguration,
363+
errorStack?: string
364+
): Configuration | undefined {
362365
if (!initConfiguration || !initConfiguration.clientToken) {
363366
display.error('Client Token is not configured, we will not send any data.')
364367
return
@@ -381,7 +384,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
381384
!isString(initConfiguration.version, 'Version') ||
382385
!isString(initConfiguration.env, 'Env') ||
383386
!isString(initConfiguration.service, 'Service') ||
384-
!isAllowedTrackingOrigins(initConfiguration)
387+
!isAllowedTrackingOrigins(initConfiguration, errorStack ?? '')
385388
) {
386389
return
387390
}
Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,67 @@
1+
import {
2+
STACK_WITH_INIT_IN_EXTENSION,
3+
STACK_WITH_INIT_IN_EXTENSION_FIREFOX,
4+
STACK_WITH_INIT_IN_PAGE,
5+
} from '../../../test'
16
import {
27
containsExtensionUrl,
38
EXTENSION_PREFIXES,
49
extractExtensionUrlFromStack,
510
isUnsupportedExtensionEnvironment,
611
} from './extensionUtils'
712

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)
13+
describe('extensionUtils', () => {
14+
describe('containsExtensionUrl', () => {
15+
it('should return true if string contains an extension URL', () => {
16+
EXTENSION_PREFIXES.forEach((prefix) => {
17+
expect(containsExtensionUrl(`${prefix}some/path`)).toBe(true)
18+
})
1219
})
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-
})
2020

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)
21+
it('should return false if string does not contain extension URL', () => {
22+
expect(containsExtensionUrl('https://example.com')).toBe(false)
23+
expect(containsExtensionUrl('')).toBe(false)
24+
})
2625
})
2726

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-
})
27+
describe('isUnsupportedExtensionEnvironment', () => {
28+
it('should return true when window location is a regular URL and error stack init is in an extension', () => {
29+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
30+
})
3331

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)
32+
it('should return false when both window location and error stack init are regular URLs', () => {
33+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_PAGE)).toBe(false)
3934
})
40-
})
4135

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

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)
40+
it('should handle each extension prefix in firefox', () => {
41+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION_FIREFOX)).toBe(true)
5142
})
52-
})
5343

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

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)
48+
it('should handle extension stack trace', () => {
49+
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
50+
})
6251
})
63-
})
6452

65-
describe('extractExtensionUrlFromStack', () => {
66-
it('should extract extension URL from stack trace', () => {
67-
const stack = `Error
53+
describe('extract init caller', () => {
54+
it('should extract extension URL from stack trace', () => {
55+
const stack = `Error
6856
at foo (<anonymous>:549:44)
6957
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-
})
58+
at e.init (chrome-extension://abcd/content-script-main.js:1:1009)`
59+
expect(extractExtensionUrlFromStack(stack)).toBe('chrome-extension://abcd')
60+
})
7361

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()
62+
it('should return undefined when no extension URL found', () => {
63+
const stack = 'Error at https://example.com/script.js:10:15'
64+
expect(extractExtensionUrlFromStack(stack)).toBeUndefined()
65+
})
7766
})
7867
})

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@ export function containsExtensionUrl(str: string): boolean {
1212
* @returns true if running in an unsupported browser extension environment
1313
*/
1414
export function isUnsupportedExtensionEnvironment(windowLocation: string, stack: string = '') {
15-
// If we're on a regular web page but the error stack shows extension URLs,
16-
// then an extension is injecting RUM.
17-
return !containsExtensionUrl(windowLocation) && containsExtensionUrl(stack)
15+
// If the page itself is an extension page.
16+
if (containsExtensionUrl(windowLocation)) {
17+
return false
18+
}
19+
20+
// Since we generate the error on the init, we check the 2nd frame line.
21+
const frameLines = stack.split('\n').filter((line) => {
22+
const trimmedLine = line.trim()
23+
return trimmedLine.length && /^at\s+|@/.test(trimmedLine)
24+
})
25+
const target = frameLines[1] || ''
26+
27+
return containsExtensionUrl(target)
1828
}
1929

2030
export function extractExtensionUrlFromStack(stack: string = ''): string | undefined {
2131
for (const prefix of EXTENSION_PREFIXES) {
2232
const match = stack.match(new RegExp(`${prefix}[^/]+`))
33+
2334
if (match) {
2435
return match[0]
2536
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,4 @@ export * from './domain/deflate'
164164
export * from './domain/connectivity'
165165
export * from './tools/stackTrace/handlingStack'
166166
export * from './tools/abstractHooks'
167+
export { WARN_DOES_NOT_HAVE_ALLOWED_TRACKING_ORIGIN } from './domain/allowedTrackingOrigins'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Base case, the page has the SDK in the init and the error stack is in the page.
2+
export const STACK_WITH_INIT_IN_PAGE = `Error
3+
at Object.init (http://localhost:8080/datadog-rum.js:3919:16)
4+
at http://localhost:8080/:10:14`
5+
6+
// Base case for extension, the extension has the SDK in the init and the error stack is in the extension.
7+
export const STACK_WITH_INIT_IN_EXTENSION = `Error
8+
at Object.init (chrome-extension://abcdef/dist/contentScript.js:254:14)
9+
at chrome-extension://abcdef/dist/contentScript.js:13304:14
10+
at chrome-extension://abcdef/dist/contentScript.js:13315:3`
11+
12+
export const STACK_WITH_INIT_IN_EXTENSION_FIREFOX = `Error
13+
Object.init@moz-extension://abcdef/dist/contentScript.js:254:14
14+
@moz-extension://abcdef/dist/contentScript.js:13304:14
15+
@moz-extension://abcdef/dist/contentScript.js:13315:3`

packages/core/test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './browserChecks'
2+
export * from './browserExtension'
23
export type * from './buildEnv'
34
export * from './collectAsyncCalls'
45
export * from './cookie'

packages/logs/src/boot/logsPublicApi.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
createTrackingConsentState,
1212
defineContextMethod,
1313
startBufferingData,
14+
callMonitored,
1415
} from '@datadog/browser-core'
1516
import type { LogsInitConfiguration } from '../domain/configuration'
1617
import type { HandlerType } from '../domain/logger'
@@ -255,7 +256,7 @@ export interface LogsPublicApi extends PublicApi {
255256
}
256257

257258
export interface Strategy {
258-
init: (initConfiguration: LogsInitConfiguration) => void
259+
init: (initConfiguration: LogsInitConfiguration, errorStack?: string) => void
259260
initConfiguration: LogsInitConfiguration | undefined
260261
globalContext: ContextManager
261262
accountContext: ContextManager
@@ -293,7 +294,10 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs): LogsPublicApi {
293294
return makePublicApi<LogsPublicApi>({
294295
logger: mainLogger,
295296

296-
init: monitor((initConfiguration) => strategy.init(initConfiguration)),
297+
init: (initConfiguration) => {
298+
const errorStack = new Error().stack
299+
callMonitored(() => strategy.init(initConfiguration, errorStack))
300+
},
297301

298302
setTrackingConsent: monitor((trackingConsent) => {
299303
trackingConsentState.update(trackingConsent)

0 commit comments

Comments
 (0)