Skip to content

Commit e1d151a

Browse files
authored
[RUM-9181] ✨ Service Worker support for Logs (#3769)
* ✨ avoid a ton of window * 🐛 last 2 windows in logs * ✨ create global object * 🐛 types of pristineWindow * 🐛 microtask types * 🐛 simplify condition * 🐛 remove ts-expect-error * ♻️ revert sandbox * ✨ e2e test for worker logs * 🐛 create global object type * 🐛 linter warnings * 🐛 improve e2e test * 🐛 fix built-in type in non-worker env * 🐛 missing exported symbol in test framework * 🐛 firefox skip service worker test * 🐛 type module is not compatible with importScripts in chromium * 🐛 apply local storage automatically on sw env * 🐛 types? * 🐛 remove test based on global * 🐛 wrong url in e2e * 🐛 test hacky wa * 🐛 test another hacky wa * 🐛 local url in bs * 🐛 typo in local url * ♻️ revert base url * 👷 test intake registry * 👷 a final test * 👷 ignore it * 👷 test with localhost * 👷 revert most of the server changes * 🐛 sw running on localhost * 🐛 pr-feedback: early return to ignore synthetics * ✨ pr-feedback: tools for testing service workers in e2e * 🐛 pr-feedback: undefined context for service worker * 🐛 pr-feedback: rename global object * 🐛 multiple imports * 🐛 optional chaining now in common context * 🐛 single sw in mocks * 🐛 forward console in worker * 🐛 typo in the options * 🐛 remove log name * 🐛 pr-feedback: improve the setup * 🐛 pr-feedback: remove unused condition * 🐛 pr-feedback: remove unused variable with fallback * 🐛 pr-feedback: check logs messages * 🐛 pr-feedback: remove localhost bridge * 👷 pr-feedback: add comments * ✨ mock session strategy * Revert "✨ mock session strategy" This reverts commit b8e5c11. * ♻️ pr-feedback: replace none storage with session manager stub * 🐛 pr-feedback: fix condition (thanks @BeltranBulbarellaDD) * 🐛 pr-feedback: ignore safari in second sw logs test * 🐛 pr-feedback: change name * 🐛 pr-feedback: fix types * 🐛 fix new event type * 🐛 fixes due to new event type * 🐛 pr-feedback: add comments * 🐛 pr-feedback: more docs * test putting back the address * 🐛 add extra var * 🐛 pr-feedback: remove listening port * 🐛 remove transition changes * 🐛 schema * 🐛 more removing transition * 🐛 pr-feedback: extract strategy condition * 🐛 pr-feedback * 🐛 pr-feedback: remove change in cookies authorized
1 parent 016a3b6 commit e1d151a

30 files changed

+260
-57
lines changed

packages/core/src/browser/fetchObservable.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Observable } from '../tools/observable'
55
import type { ClocksState } from '../tools/utils/timeUtils'
66
import { clocksNow } from '../tools/utils/timeUtils'
77
import { normalizeUrl } from '../tools/utils/urlPolyfill'
8+
import type { GlobalObject } from '../tools/globalObject'
9+
import { globalObject } from '../tools/globalObject'
810

911
interface FetchContextBase {
1012
method: string
@@ -45,11 +47,11 @@ export function resetFetchObservable() {
4547

4648
function createFetchObservable() {
4749
return new Observable<FetchContext>((observable) => {
48-
if (!window.fetch) {
50+
if (!globalObject.fetch) {
4951
return
5052
}
5153

52-
const { stop } = instrumentMethod(window, 'fetch', (call) => beforeSend(call, observable), {
54+
const { stop } = instrumentMethod(globalObject, 'fetch', (call) => beforeSend(call, observable), {
5355
computeHandlingStack: true,
5456
})
5557

@@ -58,7 +60,7 @@ function createFetchObservable() {
5860
}
5961

6062
function beforeSend(
61-
{ parameters, onPostCall, handlingStack }: InstrumentedMethodCall<Window, 'fetch'>,
63+
{ parameters, onPostCall, handlingStack }: InstrumentedMethodCall<GlobalObject, 'fetch'>,
6264
observable: Observable<FetchContext>
6365
) {
6466
const [input, init] = parameters

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { SessionPersistence } from '../session/sessionConstants'
1313
import type { MatchOption } from '../../tools/matchOption'
1414
import { isAllowedTrackingOrigins } from '../allowedTrackingOrigins'
1515
import type { Site } from '../intakeSites'
16+
import { isWorkerEnvironment } from '../../tools/globalObject'
1617
import type { TransportConfiguration } from './transportConfiguration'
1718
import { computeTransportConfiguration } from './transportConfiguration'
1819

@@ -353,7 +354,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
353354
return {
354355
beforeSend:
355356
initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'),
356-
sessionStoreStrategyType: selectSessionStoreStrategyType(initConfiguration),
357+
sessionStoreStrategyType: isWorkerEnvironment ? undefined : selectSessionStoreStrategyType(initConfiguration),
357358
sessionSampleRate: initConfiguration.sessionSampleRate ?? 100,
358359
telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20,
359360
telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5,
@@ -383,9 +384,10 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
383384
flushTimeout: (30 * ONE_SECOND) as Duration,
384385

385386
/**
386-
* Logs intake limit
387+
* Logs intake limit. When using the SDK in a Worker Environment, we
388+
* limit the batch size to 1 to ensure it can be sent in a single event.
387389
*/
388-
batchMessagesLimit: 50,
390+
batchMessagesLimit: isWorkerEnvironment ? 1 : 50,
389391
messageBytesLimit: 256 * ONE_KIBI_BYTE,
390392

391393
/**

packages/core/src/domain/connectivity/connectivity.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { globalObject } from '../../tools/globalObject'
2+
13
export type NetworkInterface = 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown'
24
export type EffectiveType = 'slow-2g' | '2g' | '3g' | '4g'
35

@@ -19,7 +21,8 @@ export interface Connectivity {
1921
}
2022

2123
export function getConnectivity(): Connectivity {
22-
const navigator = window.navigator as BrowserNavigator
24+
const navigator = globalObject.navigator as BrowserNavigator
25+
2326
return {
2427
status: navigator.onLine ? 'connected' : 'not_connected',
2528
interfaces: navigator.connection && navigator.connection.type ? [navigator.connection.type] : undefined,

packages/core/src/domain/error/trackRuntimeError.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Observable } from '../../tools/observable'
33
import { clocksNow } from '../../tools/utils/timeUtils'
44
import type { StackTrace } from '../../tools/stackTrace/computeStackTrace'
55
import { computeStackTraceFromOnErrorMessage } from '../../tools/stackTrace/computeStackTrace'
6-
import { getGlobalObject } from '../../tools/getGlobalObject'
6+
import { getGlobalObject } from '../../tools/globalObject'
77
import { computeRawError, isError } from './error'
88
import type { RawError } from './error.types'
99
import { ErrorHandling, ErrorSource, NonErrorPrefix } from './error.types'

packages/core/src/domain/synthetics/syntheticsWorkerValues.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getInitCookie } from '../../browser/cookie'
2+
import { globalObject, isWorkerEnvironment } from '../../tools/globalObject'
23

34
export const SYNTHETICS_TEST_ID_COOKIE_NAME = 'datadog-synthetics-public-id'
45
export const SYNTHETICS_RESULT_ID_COOKIE_NAME = 'datadog-synthetics-result-id'
@@ -11,8 +12,13 @@ export interface BrowserWindow extends Window {
1112
}
1213

1314
export function willSyntheticsInjectRum(): boolean {
15+
if (isWorkerEnvironment) {
16+
// We don't expect to run synthetics tests in a worker environment
17+
return false
18+
}
19+
1420
return Boolean(
15-
(window as BrowserWindow)._DATADOG_SYNTHETICS_INJECTS_RUM || getInitCookie(SYNTHETICS_INJECTS_RUM_COOKIE_NAME)
21+
(globalObject as BrowserWindow)._DATADOG_SYNTHETICS_INJECTS_RUM || getInitCookie(SYNTHETICS_INJECTS_RUM_COOKIE_NAME)
1622
)
1723
}
1824

packages/core/src/domain/telemetry/telemetry.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { PageMayExitEvent } from '../../browser/pageMayExitObservable'
2929
import { DeflateEncoderStreamId } from '../deflate'
3030
import type { AbstractHooks, RecursivePartial } from '../../tools/abstractHooks'
3131
import { HookNames, DISCARDED } from '../../tools/abstractHooks'
32+
import { globalObject, isWorkerEnvironment } from '../../tools/globalObject'
3233
import type { TelemetryEvent } from './telemetryEvent.types'
3334
import type {
3435
RawTelemetryConfiguration,
@@ -229,8 +230,8 @@ function startTelemetryTransport(
229230

230231
function getRuntimeEnvInfo(): RuntimeEnvInfo {
231232
return {
232-
is_local_file: window.location.protocol === 'file:',
233-
is_worker: 'WorkerGlobalScope' in self,
233+
is_local_file: globalObject.location?.protocol === 'file:',
234+
is_worker: isWorkerEnvironment,
234235
}
235236
}
236237

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export * from './tools/utils/urlPolyfill'
7878
export * from './tools/utils/timeUtils'
7979
export * from './tools/utils/arrayUtils'
8080
export * from './tools/serialisation/sanitize'
81-
export * from './tools/getGlobalObject'
81+
export * from './tools/globalObject'
8282
export { AbstractLifeCycle } from './tools/abstractLifeCycle'
8383
export * from './domain/eventRateLimiter/createEventRateLimiter'
8484
export * from './tools/utils/browserDetection'

packages/core/src/tools/getZoneJsOriginalValue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getGlobalObject } from './getGlobalObject'
1+
import { getGlobalObject } from './globalObject'
22

33
export interface BrowserWindowWithZoneJs extends Window {
44
Zone?: {

packages/core/src/tools/getGlobalObject.ts renamed to packages/core/src/tools/globalObject.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
* inspired by https://mathiasbynens.be/notes/globalthis
33
*/
44

5+
// Extend/Create the WorkerGlobalScope interface to avoid issues when used in a non-browser tsconfig environment
6+
interface WorkerGlobalScope {
7+
empty: never
8+
}
9+
10+
// Utility type to enforce that exactly one of the two types is used
11+
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
12+
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U
13+
14+
export type GlobalObject = XOR<Window, WorkerGlobalScope>
15+
516
export function getGlobalObject<T = typeof globalThis>(): T {
617
if (typeof globalThis === 'object') {
718
return globalThis as unknown as T
@@ -29,3 +40,12 @@ export function getGlobalObject<T = typeof globalThis>(): T {
2940
}
3041
return globalObject as T
3142
}
43+
44+
/**
45+
* Cached reference to the global object so it can be imported and re-used without
46+
* re-evaluating the heavyweight fallback logic in `getGlobalObject()`.
47+
*/
48+
// eslint-disable-next-line local-rules/disallow-side-effects
49+
export const globalObject = getGlobalObject<GlobalObject>()
50+
51+
export const isWorkerEnvironment = !('document' in globalObject)

packages/core/src/tools/queueMicrotask.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { monitor } from './monitor'
2+
import { globalObject } from './globalObject'
23

34
export function queueMicrotask(callback: () => void) {
4-
const nativeImplementation = window.queueMicrotask
5+
const nativeImplementation = globalObject.queueMicrotask?.bind(globalObject)
6+
57
if (typeof nativeImplementation === 'function') {
68
nativeImplementation(monitor(callback))
79
} else {

0 commit comments

Comments
 (0)