Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
"require": "./lib/commonjs/index.js",
"types": "./lib/typescript/index.d.ts"
},
"./internal": {
"import": "./lib/module/internal.js",
"require": "./lib/commonjs/internal.js",
"types": "./lib/typescript/internal.d.ts"
},
"./metro": {
"import": "./lib/module/metro.js",
"require": "./lib/commonjs/metro.js",
Expand Down
53 changes: 53 additions & 0 deletions packages/core/src/__tests__/importSafety.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { TurboModuleRegistry } from 'react-native';

describe('native spec modules are import-safe (lazy)', () => {
const cases: Array<[string, string, string]> = [
['../specs/NativeDdSdk', 'getNativeDdSdk', 'DdSdk'],
['../specs/NativeDdRum', 'getNativeDdRum', 'DdRum'],
['../specs/NativeDdLogs', 'getNativeDdLogs', 'DdLogs'],
['../specs/NativeDdTrace', 'getNativeDdTrace', 'DdTrace'],
['../specs/NativeDdFlags', 'getNativeDdFlags', 'DdFlags']
];

it.each(cases)(
'%s does not resolve native at import but does on getter call',
(modulePath, getterName, registryName) => {
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
let mod: any;
jest.isolateModules(() => {
mod = require(modulePath);
});
expect(getSpy).not.toHaveBeenCalled(); // not at import
mod[getterName]();
expect(getSpy).toHaveBeenCalledWith(registryName); // only on use
getSpy.mockRestore();
}
);
});

describe('DdSdkInternal is import-safe', () => {
it('importing DdSdkInternal does not resolve native until a method is used', () => {
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
let internal: any;
jest.isolateModules(() => {
internal = require('../sdk/DdSdkInternal');
});
expect(getSpy).not.toHaveBeenCalled(); // import-safe
// Touch a property on the proxy -> now it resolves
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
internal.NativeDdSdk.getConstants;
expect(getSpy).toHaveBeenCalledWith('DdSdk');
getSpy.mockRestore();
});
});

describe('public barrel is import-safe', () => {
it('importing the package index performs no native TurboModule lookup', () => {
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
jest.isolateModules(() => {
require('../index');
});
expect(getSpy).not.toHaveBeenCalled();
getSpy.mockRestore();
});
});
34 changes: 34 additions & 0 deletions packages/core/src/__tests__/internalEntry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TurboModuleRegistry } from 'react-native';

describe('./internal entry', () => {
it('exports the internals react-native-vega needs', () => {
const internal = require('../internal');
const expected = [
'addDefaultValuesToAutoInstrumentationConfiguration',
'buildConfigurationFromPartialConfiguration',
'DdSdkNativeConfiguration',
'RUM_DEFAULTS',
'adaptLongTaskThreshold',
'version',
'AccountInfoSingleton',
'AttributesSingleton',
'UserInfoSingleton',
'GlobalState',
'BufferSingleton',
'DatadogProviderState',
'DdRumResourceTracking'
];
for (const name of expected) {
expect(internal[name]).toBeDefined();
}
});

it('is import-safe (no native lookup at import)', () => {
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
jest.isolateModules(() => {
require('../internal');
});
expect(getSpy).not.toHaveBeenCalled();
getSpy.mockRestore();
});
});
6 changes: 3 additions & 3 deletions packages/core/src/flags/DdFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { SdkVerbosity } from '../config/types/SdkVerbosity';
import type { DdNativeFlagsType } from '../nativeModulesTypes';
import { getGlobalInstance } from '../utils/singletonUtils';

import { getNativeDdFlags } from '../specs/NativeDdFlags';

import { FlagsClient } from './FlagsClient';
import type { DdFlagsType, FlagsConfiguration } from './types';

Expand All @@ -18,9 +20,7 @@ const FLAGS_MODULE = 'com.datadog.reactnative.flags';
* Implementation class for {@link DdFlagsType}. Please see the interface for documentation.
*/
class DdFlagsWrapper implements DdFlagsType {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeFlags: DdNativeFlagsType = require('../specs/NativeDdFlags')
.default;
private nativeFlags: DdNativeFlagsType = getNativeDdFlags() as DdNativeFlagsType;

private isFeatureEnabled = false;

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/flags/FlagsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { InternalLog } from '../InternalLog';
import { SdkVerbosity } from '../config/types/SdkVerbosity';
import type { DdNativeFlagsType } from '../nativeModulesTypes';

import { getNativeDdFlags } from '../specs/NativeDdFlags';

import { processEvaluationContext } from './internal';
import type { FlagCacheEntry } from './internal';
import type { JsonValue, EvaluationContext, FlagDetails } from './types';

export class FlagsClient {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeFlags: DdNativeFlagsType = require('../specs/NativeDdFlags')
.default;
private nativeFlags: DdNativeFlagsType = getNativeDdFlags() as DdNativeFlagsType;

private clientName: string;

Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

/**
* Sanctioned entry point for cross-package internals consumed by platform
* packages (e.g. @datadog/mobile-react-native-vega). NOT part of the public API
* and may change between minor versions. Everything re-exported here must be
* native-free at import (must not call TurboModuleRegistry / NativeModules at
* module load).
*/
export {
addDefaultValuesToAutoInstrumentationConfiguration
} from './config/async/AutoInstrumentationConfiguration';
export { buildConfigurationFromPartialConfiguration } from './config/async/asyncInitializationHelper';
export { DdSdkNativeConfiguration } from './config/features/CoreConfigurationNative';
export { RUM_DEFAULTS } from './config/features/RumConfiguration';
export { adaptLongTaskThreshold } from './utils/longTasksUtils';
export { version } from './version';
export { AccountInfoSingleton } from './sdk/AccountInfoSingleton/AccountInfoSingleton';
export { AttributesSingleton } from './sdk/AttributesSingleton/AttributesSingleton';
export { UserInfoSingleton } from './sdk/UserInfoSingleton/UserInfoSingleton';
export { GlobalState } from './sdk/GlobalState/GlobalState';
export { BufferSingleton } from './sdk/DatadogProvider/Buffer/BufferSingleton';
export { DatadogProviderState } from './sdk/DatadogProvider/DatadogProviderState';
export { DdRumResourceTracking } from './rum/instrumentation/resourceTracking/DdRumResourceTracking';

export type { Attributes } from './sdk/AttributesSingleton/types';
export type { LogsNativeConfiguration } from './config/features/LogsConfigurationNative';
export type { RumNativeConfiguration } from './config/features/RumConfigurationNative';
export type { TraceNativeConfiguration } from './config/features/TraceConfigurationNative';
export type { InitializationModeForTelemetry } from './config/types/InitializationModeForTelemetry';
6 changes: 3 additions & 3 deletions packages/core/src/logs/DdLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNative
import type { ErrorSource, LogEventMapper } from '../types';
import { getGlobalInstance } from '../utils/singletonUtils';

import { getNativeDdLogs } from '../specs/NativeDdLogs';

import { generateEventMapper } from './eventMapper';
import type {
DdLogsType,
Expand Down Expand Up @@ -44,9 +46,7 @@ const isLogWithError = (
};

class DdLogsWrapper implements DdLogsType {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeLogs: DdNativeLogsType = require('../specs/NativeDdLogs')
.default;
private nativeLogs: DdNativeLogsType = getNativeDdLogs() as DdNativeLogsType;
private logEventMapper = generateEventMapper(undefined);

debug = (...args: LogArguments | LogWithErrorArguments): Promise<void> => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/rum/DdRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {
getTracingContext,
getTracingContextForPropagators
} from './instrumentation/resourceTracking/distributedTracing/distributedTracingHeaders';
import { getNativeDdRum } from '../specs/NativeDdRum';

import type {
DdRumType,
FirstPartyHost,
Expand Down Expand Up @@ -77,9 +79,7 @@ const touchDataFromEvent = (
};

class DdRumWrapper implements DdRumType {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeRum: DdNativeRumType = require('../specs/NativeDdRum')
.default;
private nativeRum: DdNativeRumType = getNativeDdRum() as DdNativeRumType;
private lastActionData?: { type: RumActionType; name: string };
private errorEventMapper = generateErrorEventMapper(undefined);
private resourceEventMapper = generateResourceEventMapper(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Copyright 2016-Present Datadog, Inc.
*/

import DdSdk from '../../../specs/NativeDdSdk';
import { getNativeDdSdk } from '../../../specs/NativeDdSdk';
import { getGlobalInstance } from '../../../utils/singletonUtils';
import { DefaultTimeProvider } from '../../../utils/time-provider/DefaultTimeProvider';
import type { TimeProvider } from '../../../utils/time-provider/TimeProvider';
Expand Down Expand Up @@ -123,7 +123,7 @@ class BabelInteractionTracking {
): (...args: any[]) => any {
return (...args: any[]) => {
if (!this.telemetrySent) {
DdSdk?.sendTelemetryLog(
getNativeDdSdk()?.sendTelemetryLog(
BABEL_PLUGIN_TELEMETRY,
this.getTelemetryConfig(),
{ onlyOnce: true }
Expand All @@ -147,7 +147,7 @@ class BabelInteractionTracking {
)
.catch(e => {
if (e instanceof Error) {
DdSdk?.telemetryError(
getNativeDdSdk()?.telemetryError(
e.message,
e.stack || '',
'BabelActionTrack'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { DdBabelInteractionTracking } from '../DdBabelInteractionTracking';

jest.mock('../../../../specs/NativeDdSdk', () => ({
__esModule: true,
default: {
getNativeDdSdk: jest.fn().mockReturnValue({
sendTelemetryLog: jest.fn(),
telemetryError: jest.fn()
}
})
}));

jest.mock('../../../../utils/time-provider/DefaultTimeProvider', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import { NativeModules } from 'react-native';

import { getNativeDdSdk } from '../../specs/NativeDdSdk';
import { DatadogBatchedBridgeEventEmitter } from './DatadogBatchedBridgeEventEmitter';
import type { DatadogEventEmitter } from './DatadogEventEmitter';
import { DatadogNativeEventEmitter } from './DatadogNativeEventEmitter';
Expand All @@ -18,10 +19,7 @@ export class DatadogDefaultEventEmitter implements DatadogEventEmitter {

constructor(errorHandler: (err: any) => void) {
try {
const ddSdkModule =
NativeModules.DdSdk ||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
require('../../specs/NativeDdSdk').default;
const ddSdkModule = NativeModules.DdSdk || getNativeDdSdk();
this.eventEmitter = this.isNewArchitecture
? new DatadogNativeEventEmitter(ddSdkModule, errorHandler)
: new DatadogBatchedBridgeEventEmitter(errorHandler);
Expand Down
24 changes: 22 additions & 2 deletions packages/core/src/sdk/DdSdkInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,30 @@
import type { DdSdkNativeConfiguration } from '../config/features/CoreConfigurationNative';
import type { DdNativeSdkType } from '../nativeModulesTypes';

import { getNativeDdSdk } from '../specs/NativeDdSdk';
import type { AttributeEncoder } from './AttributesEncoding/types';

// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const NativeDdSdk: DdNativeSdkType = require('../specs/NativeDdSdk').default;
let cachedNativeDdSdk: DdNativeSdkType | null | undefined;
const resolveNativeDdSdk = (): DdNativeSdkType | null => {
if (cachedNativeDdSdk === undefined) {
cachedNativeDdSdk = getNativeDdSdk() as DdNativeSdkType | null;
}
return cachedNativeDdSdk;
};

// Lazily-backed handle. Resolution happens on first property access (runtime),
// never at import — so importing this module is safe on platforms without the
// native module (e.g. Vega). Methods are bound to the resolved module.
const NativeDdSdk: DdNativeSdkType = new Proxy({} as DdNativeSdkType, {
get: (_target, prop) => {
const resolved = resolveNativeDdSdk() as Record<string | symbol, unknown> | null;
const value = resolved ? resolved[prop] : undefined;
// Return the raw value without binding — TurboModule methods are stateless
// so `this` is irrelevant, and returning the original function reference
// preserves Jest mock-tracking in tests.
return value;
}
});

export type DdSdkType = {
readonly attributeEncoders: AttributeEncoder<any>[];
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/specs/NativeDdFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,16 @@ export interface Spec extends TurboModule {
) => Promise<void>;
}

// eslint-disable-next-line import/no-default-export
export default TurboModuleRegistry.get<Spec>('DdFlags');
let cachedModule: Spec | null | undefined;

/**
* Lazily resolves the native TurboModule on first call and caches the result.
* Resolving lazily (instead of at module load) keeps this package importable on
* platforms where the native module is absent (e.g. Vega).
*/
export const getNativeDdFlags = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdFlags');
}
return cachedModule;
};
15 changes: 13 additions & 2 deletions packages/core/src/specs/NativeDdLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,16 @@ export interface Spec extends TurboModule {
) => Promise<void>;
}

// eslint-disable-next-line import/no-default-export
export default TurboModuleRegistry.get<Spec>('DdLogs');
let cachedModule: Spec | null | undefined;

/**
* Lazily resolves the native TurboModule on first call and caches the result.
* Resolving lazily (instead of at module load) keeps this package importable on
* platforms where the native module is absent (e.g. Vega).
*/
export const getNativeDdLogs = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdLogs');
}
return cachedModule;
};
15 changes: 13 additions & 2 deletions packages/core/src/specs/NativeDdRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,5 +229,16 @@ export interface Spec extends TurboModule {
): Promise<void>;
}

// eslint-disable-next-line import/no-default-export
export default TurboModuleRegistry.get<Spec>('DdRum');
let cachedModule: Spec | null | undefined;

/**
* Lazily resolves the native TurboModule on first call and caches the result.
* Resolving lazily (instead of at module load) keeps this package importable on
* platforms where the native module is absent (e.g. Vega).
*/
export const getNativeDdRum = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdRum');
}
return cachedModule;
};
15 changes: 13 additions & 2 deletions packages/core/src/specs/NativeDdSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,16 @@ export interface Spec extends TurboModule {
removeListeners: (count: number) => void;
}

// eslint-disable-next-line import/no-default-export
export default TurboModuleRegistry.get<Spec>('DdSdk');
let cachedModule: Spec | null | undefined;

/**
* Lazily resolves the native TurboModule on first call and caches the result.
* Resolving lazily (instead of at module load) keeps this package importable on
* platforms where the native module is absent (e.g. Vega).
*/
export const getNativeDdSdk = (): Spec | null => {
if (cachedModule === undefined) {
cachedModule = TurboModuleRegistry.get<Spec>('DdSdk');
}
return cachedModule;
};
Loading