Skip to content

Commit ac6f0cf

Browse files
wip
1 parent 250f6cd commit ac6f0cf

Some content is hidden

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

52 files changed

+1476
-18
lines changed

eslint-local-rules/disallowSideEffects.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ const pathsWithSideEffect = new Set([
3030
`${packagesRoot}/flagging/src/entries/main.ts`,
3131
`${packagesRoot}/rum/src/entries/main.ts`,
3232
`${packagesRoot}/rum-slim/src/entries/main.ts`,
33+
`${packagesRoot}/rum-next/src/entries/bundle.ts`,
34+
`${packagesRoot}/core-next/src/entries/bundle.ts`,
35+
`${packagesRoot}/core-next/src/entries/main.ts`,
3336
])
3437

3538
// Those packages are known to have no side effects when evaluated
3639
const packagesWithoutSideEffect = new Set([
3740
'@datadog/browser-core',
3841
'@datadog/browser-rum-core',
42+
'@datadog/browser-internal-next',
43+
'@datadog/browser-rum/internal',
44+
'@datadog/browser-logs/internal',
3945
'react',
4046
'react-router-dom',
4147
])
@@ -184,6 +190,11 @@ function isAllowedCallExpression({ callee }) {
184190
return true
185191
}
186192

193+
// Allow "Symbol.for()"
194+
if (callee.type === 'MemberExpression' && callee.object.name === 'Symbol' && callee.property.name === 'for') {
195+
return true
196+
}
197+
187198
return false
188199
}
189200

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ export default tseslint.config(
418418
},
419419

420420
{
421-
files: ['packages/{rum,logs,flagging,rum-slim}/src/entries/*.ts'],
421+
files: ['packages/{rum,logs,flagging,rum-slim}/src/entries/main.ts'],
422422
rules: {
423423
'local-rules/disallow-enum-exports': 'error',
424424
},

packages/core-next/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@datadog/browser-core-next",
3+
"private": true,
4+
"main": "cjs/entries/main.js",
5+
"module": "esm/entries/main.js",
6+
"types": "cjs/entries/main.d.ts",
7+
"scripts": {
8+
"build": "node ../../scripts/build/build-package.ts --modules --bundle datadog-core-next.js",
9+
"build:bundle": "node ../../scripts/build/build-package.ts --bundle datadog-core-next.js"
10+
},
11+
"dependencies": {
12+
"@datadog/browser-core": "6.25.0",
13+
"@datadog/browser-internal-next": "workspace:*",
14+
"@datadog/browser-logs-next": "workspace:*",
15+
"@datadog/browser-profiling-next": "workspace:*",
16+
"@datadog/browser-rum": "6.25.0",
17+
"@datadog/browser-rum-core": "6.25.0",
18+
"@datadog/browser-rum-next": "workspace:*"
19+
}
20+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { definePublicApiGlobal } from '@datadog/browser-internal-next'
2+
import * as main from './main'
3+
4+
export * from './main'
5+
6+
definePublicApiGlobal(main)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { CoreContextType as CoreContextType, getInternalApi, MessageType } from '@datadog/browser-internal-next'
2+
import { addEventListener } from '@datadog/browser-core'
3+
4+
export { initialize } from '../initialize'
5+
6+
// TODO: move this somewhere else
7+
// We don't use `trackRuntimeError` from browser-core because we don't want to deal with raw errors
8+
// parsing at this layer.
9+
// Also, let's try to use event listeners instead of intstrumenting handlers, as it sounds cleaner
10+
// and smaller than bringing instrumentation tooling.
11+
function trackRuntimeErrors() {
12+
addEventListener({}, globalThis, 'error', (event) => {
13+
getInternalApi().notify({
14+
type: MessageType.RUNTIME_ERROR,
15+
error: event.error,
16+
event,
17+
})
18+
})
19+
20+
addEventListener({}, globalThis, 'unhandledrejection', (event) => {
21+
getInternalApi().notify({
22+
type: MessageType.RUNTIME_ERROR,
23+
error: event.reason || 'Empty reason',
24+
})
25+
})
26+
}
27+
28+
trackRuntimeErrors()
29+
30+
export function setGlobalContext(value: object) {
31+
setContext(CoreContextType.GLOBAL, value)
32+
}
33+
34+
export function setGlobalContextProperty(key: string, value: unknown) {
35+
setContextProperty(CoreContextType.GLOBAL, key, value)
36+
}
37+
38+
export function clearGlobalContext() {
39+
clearContext(CoreContextType.GLOBAL)
40+
}
41+
42+
export function setAccount(value: object) {
43+
setContext(CoreContextType.ACCOUNT, value)
44+
}
45+
46+
export function setAccountProperty(key: string, value: unknown) {
47+
setContextProperty(CoreContextType.ACCOUNT, key, value)
48+
}
49+
50+
export function clearAccount() {
51+
clearContext(CoreContextType.ACCOUNT)
52+
}
53+
54+
export function setUser(user: object) {
55+
setContext(CoreContextType.USER, user)
56+
}
57+
58+
export function setUserProperty(key: string, value: unknown) {
59+
setContextProperty(CoreContextType.USER, key, value)
60+
}
61+
62+
export function clearUser() {
63+
clearContext(CoreContextType.USER)
64+
}
65+
66+
function setContext(context: CoreContextType, value: object) {
67+
getInternalApi().notify({ type: MessageType.CORE_SET_CONTEXT, context, value })
68+
}
69+
70+
function setContextProperty(context: CoreContextType, key: string, value: unknown) {
71+
getInternalApi().notify({ type: MessageType.CORE_SET_CONTEXT_PROPERTY, context, key, value })
72+
}
73+
74+
function clearContext(context: CoreContextType) {
75+
getInternalApi().notify({ type: MessageType.CORE_CLEAR_CONTEXT, context })
76+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { display, monitorError } from '@datadog/browser-core'
2+
import { getInternalApi } from '@datadog/browser-internal-next'
3+
import type { CoreInitializeConfiguration } from '@datadog/browser-internal-next'
4+
5+
export function initialize(initializeConfiguration: CoreInitializeConfiguration) {
6+
const promises = [
7+
import('./lazy'),
8+
import('./lazyCompression'),
9+
initializeConfiguration.rum && import('@datadog/browser-rum-next/lazy'),
10+
initializeConfiguration.profiling && import('@datadog/browser-profiling-next/lazy'),
11+
initializeConfiguration.logs && import('@datadog/browser-logs-next/lazy'),
12+
] as const
13+
14+
// eslint-disable-next-line @typescript-eslint/await-thenable
15+
Promise.all(promises)
16+
.then(([lazyCoreModule, lazyCompressionModule, rumModule, profilingModule, logsModule]) => {
17+
const lazyApi = lazyCoreModule.initialize(
18+
initializeConfiguration,
19+
lazyCompressionModule.initialize(initializeConfiguration)?.createEncoder
20+
)
21+
if (!lazyApi) {
22+
return
23+
}
24+
25+
if (rumModule) {
26+
rumModule.initialize(lazyApi)
27+
}
28+
29+
if (profilingModule) {
30+
profilingModule.initialize(lazyApi)
31+
}
32+
33+
if (logsModule) {
34+
logsModule.initialize(lazyApi)
35+
}
36+
})
37+
.finally(() => {
38+
getInternalApi().bus.unbuffer()
39+
})
40+
.catch((error) => {
41+
display.error('Failed to load lazy chunks', error)
42+
monitorError(error)
43+
})
44+
}

packages/core-next/src/lazy.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import type {
2+
Configuration,
3+
DeflateEncoderStreamId,
4+
Encoder,
5+
EndpointBuilder,
6+
TrackingConsentState,
7+
} from '@datadog/browser-core'
8+
import {
9+
abstractHooks,
10+
buildAccountContextManager,
11+
buildGlobalContextManager,
12+
buildUserContextManager,
13+
createBatch,
14+
createFlushController,
15+
createHttpRequest,
16+
createIdentityEncoder,
17+
createPageMayExitObservable,
18+
createTrackingConsentState,
19+
startSessionManager,
20+
startTelemetry,
21+
TelemetryService,
22+
validateAndBuildConfiguration,
23+
} from '@datadog/browser-core'
24+
import type { CoreInitializeConfiguration, CoreSessionManager } from '@datadog/browser-internal-next'
25+
import { CoreContextType, getInternalApi, MessageType } from '@datadog/browser-internal-next'
26+
import { SessionReplayState } from '@datadog/browser-rum-core'
27+
28+
export function initialize(
29+
initializeConfiguration: CoreInitializeConfiguration,
30+
createEncoder: (streamId: DeflateEncoderStreamId) => Encoder = createIdentityEncoder
31+
) {
32+
const configuration = validateAndBuildConfiguration(initializeConfiguration)
33+
if (!configuration) {
34+
return
35+
}
36+
37+
const internalApi = getInternalApi()
38+
const hooks = abstractHooks() // TODO: specialized hook
39+
const contexts = startContexts()
40+
41+
const reportError = () => {
42+
// TODO
43+
}
44+
const pageMayExitObservable = createPageMayExitObservable(configuration)
45+
const telemetry = startTelemetry(
46+
TelemetryService.RUM,
47+
configuration,
48+
hooks,
49+
reportError,
50+
pageMayExitObservable,
51+
createIdentityEncoder // Keep using the identity encoder here, so we can sent telemetry even if deflate isn't working
52+
)
53+
54+
const trackingConsentState = createTrackingConsentState()
55+
56+
// TODO: handle bridge
57+
58+
const sessionManager = startCoreSessionManager(configuration, trackingConsentState)
59+
60+
return {
61+
coreInitializeConfiguration: initializeConfiguration,
62+
createEncoder,
63+
internalApi,
64+
hooks,
65+
contexts,
66+
telemetry,
67+
sessionManager,
68+
createBatch: (endpoints: EndpointBuilder[]) =>
69+
createBatch({
70+
encoder: createEncoder(
71+
Math.random() as any // TODO: remove named stream id
72+
),
73+
request: createHttpRequest(endpoints, reportError),
74+
flushController: createFlushController({
75+
pageMayExitObservable,
76+
sessionExpireObservable: sessionManager.expireObservable,
77+
}),
78+
}),
79+
}
80+
}
81+
82+
function startContexts() {
83+
const global = buildGlobalContextManager()
84+
const user = buildUserContextManager()
85+
const account = buildAccountContextManager()
86+
87+
const contextsByType = {
88+
[CoreContextType.GLOBAL]: global,
89+
[CoreContextType.USER]: user,
90+
[CoreContextType.ACCOUNT]: account,
91+
}
92+
93+
getInternalApi().bus.subscribe(({ message }) => {
94+
switch (message.type) {
95+
case MessageType.CORE_SET_CONTEXT:
96+
contextsByType[message.context].setContext(message.value)
97+
break
98+
99+
case MessageType.CORE_SET_CONTEXT_PROPERTY:
100+
contextsByType[message.context].setContextProperty(message.key, message.value)
101+
break
102+
103+
case MessageType.CORE_CLEAR_CONTEXT:
104+
contextsByType[message.context].clearContext()
105+
break
106+
}
107+
})
108+
109+
return {
110+
global,
111+
user,
112+
account,
113+
}
114+
}
115+
116+
function startCoreSessionManager(
117+
configuration: Configuration,
118+
trackingConsentState: TrackingConsentState
119+
): CoreSessionManager {
120+
// TODO: we should use a fallback if:
121+
// * there is an event bridge
122+
// * configuration.sessionStoreStrategyType is undefined (ex: no cookie access)
123+
124+
const sessionManager = startSessionManager<string>(
125+
configuration,
126+
// TODO: product type will be removed in the future session manager
127+
'x',
128+
() => 'x',
129+
trackingConsentState
130+
)
131+
132+
sessionManager.sessionStateUpdateObservable.subscribe(({ previousState, newState }) => {
133+
if (!previousState.forcedReplay && newState.forcedReplay) {
134+
const sessionEntity = sessionManager.findSession()
135+
if (sessionEntity) {
136+
sessionEntity.isReplayForced = true
137+
}
138+
}
139+
})
140+
141+
return {
142+
...sessionManager,
143+
findTrackedSession: (startTime) => {
144+
const session = sessionManager.findSession(startTime)
145+
if (!session) {
146+
return
147+
}
148+
return {
149+
id: session.id,
150+
sessionReplay: SessionReplayState.OFF, // TODO
151+
anonymousId: session.anonymousId,
152+
}
153+
},
154+
setForcedReplay: () => sessionManager.updateSessionState({ forcedReplay: '1' }),
155+
}
156+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { DeflateEncoderStreamId } from '@datadog/browser-core'
2+
import { noop } from '@datadog/browser-core'
3+
import type { CoreInitializeConfiguration } from '@datadog/browser-internal-next'
4+
import { createDeflateEncoder, startDeflateWorker } from '@datadog/browser-rum/internal'
5+
6+
export function initialize(configuration: CoreInitializeConfiguration) {
7+
const deflateWorker = startDeflateWorker(
8+
configuration as any,
9+
'Datadog Session Replay',
10+
// Report worker creation failure?
11+
noop
12+
)
13+
if (!deflateWorker) {
14+
return
15+
}
16+
17+
return {
18+
createEncoder: (streamId: DeflateEncoderStreamId) =>
19+
createDeflateEncoder(configuration as any, deflateWorker, streamId),
20+
}
21+
}

packages/core-next/typedoc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://typedoc.org/schema.json",
3+
"entryPoints": ["src/entries/main.ts"]
4+
}

packages/core/src/browser/addEventListener.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const enum DOM_EVENT {
3939
SECURITY_POLICY_VIOLATION = 'securitypolicyviolation',
4040
SELECTION_CHANGE = 'selectionchange',
4141
STORAGE = 'storage',
42+
ERROR = 'error',
4243
}
4344

4445
interface AddEventListenerOptions {
@@ -47,7 +48,7 @@ interface AddEventListenerOptions {
4748
passive?: boolean
4849
}
4950

50-
type EventMapFor<T> = T extends Window
51+
type EventMapFor<T> = T extends Window | typeof globalThis
5152
? WindowEventMap & {
5253
// TS 4.9.5 does not support `freeze` and `resume` events yet
5354
freeze: Event

0 commit comments

Comments
 (0)