diff --git a/packages/core/package.json b/packages/core/package.json index 3c9948b6c..ce970b6b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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", diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 1b35fc49e..6b4491b51 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -26,7 +26,7 @@ import { AttributesSingleton } from '../sdk/AttributesSingleton/AttributesSingle import { DdSdkWrapper } from '../sdk/DdSdkInternal'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; import { UserInfoSingleton } from '../sdk/UserInfoSingleton/UserInfoSingleton'; -import NativeDdSdkSpec from '../specs/NativeDdSdk'; +import { getNativeDdSdk } from '../specs/NativeDdSdk'; import type { LogEvent } from '../types'; import { ErrorSource } from '../types'; import { version as sdkVersion } from '../version'; @@ -1318,8 +1318,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addAttribute(key, value); // THEN - expect(NativeDdSdkSpec.addAttribute).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.addAttribute).toHaveBeenCalledWith(key, { + expect(getNativeDdSdk()?.addAttribute).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.addAttribute).toHaveBeenCalledWith(key, { value }); expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( @@ -1339,8 +1339,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.removeAttribute(key); // THEN - expect(NativeDdSdkSpec.removeAttribute).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.removeAttribute).toHaveBeenCalledWith(key); + expect(getNativeDdSdk()?.removeAttribute).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.removeAttribute).toHaveBeenCalledWith(key); expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual( undefined ); @@ -1357,8 +1357,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addAttributes(attributes); // THEN - expect(NativeDdSdkSpec.addAttributes).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.addAttributes).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.addAttributes).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.addAttributes).toHaveBeenCalledWith( attributes ); expect(AttributesSingleton.getInstance().getAttributes()).toEqual({ @@ -1377,8 +1377,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.removeAttributes(['foo', 'baz']); // THEN - expect(NativeDdSdkSpec.removeAttributes).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.removeAttributes).toHaveBeenCalledWith([ + expect(getNativeDdSdk()?.removeAttributes).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.removeAttributes).toHaveBeenCalledWith([ 'foo', 'baz' ]); @@ -1404,8 +1404,10 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.setUserInfo(userInfo); // THEN - expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalledWith(userInfo); + expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalledWith( + userInfo + ); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual( userInfo ); @@ -1425,8 +1427,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addUserExtraInfo(extraInfo); // THEN - expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledWith( extraInfo ); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ @@ -1446,7 +1448,7 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.addUserExtraInfo(extraInfo); // THEN - expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledWith( extraInfo ); expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({ @@ -1475,8 +1477,8 @@ describe('DdSdkReactNative', () => { await DdSdkReactNative.clearUserInfo(); // THEN - expect(NativeDdSdkSpec.clearUserInfo).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalled(); + expect(getNativeDdSdk()?.clearUserInfo).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalled(); expect( UserInfoSingleton.getInstance().getUserInfo() ).toBeUndefined(); @@ -1493,8 +1495,10 @@ describe('DdSdkReactNative', () => { DdSdkReactNative.setTrackingConsent(consent); // THEN - expect(NativeDdSdkSpec.setTrackingConsent).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.setTrackingConsent).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.setTrackingConsent).toHaveBeenCalledTimes( + 1 + ); + expect(getNativeDdSdk()?.setTrackingConsent).toHaveBeenCalledWith( consent ); }); @@ -1506,7 +1510,7 @@ describe('DdSdkReactNative', () => { DdSdkReactNative.clearAllData(); // THEN - expect(NativeDdSdkSpec.clearAllData).toHaveBeenCalledTimes(1); + expect(getNativeDdSdk()?.clearAllData).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/core/src/__tests__/internalEntry.test.ts b/packages/core/src/__tests__/internalEntry.test.ts new file mode 100644 index 000000000..01e000762 --- /dev/null +++ b/packages/core/src/__tests__/internalEntry.test.ts @@ -0,0 +1,26 @@ +describe('./internal entry', () => { + it('exports the internals react-native-vega needs', () => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + 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(); + } + }); + + // Import safety of this entry point is covered in wrapperImportSafety.test.ts. +}); diff --git a/packages/core/src/__tests__/mock.test.ts b/packages/core/src/__tests__/mock.test.ts index 722998f10..570ee4a7e 100644 --- a/packages/core/src/__tests__/mock.test.ts +++ b/packages/core/src/__tests__/mock.test.ts @@ -22,9 +22,8 @@ import { DdLogs, DdRum, DdTrace, DdSdkReactNative } from '../index'; */ const privateProperties = { - DdTrace: ['nativeTrace'], + DdTrace: [], DdLogs: [ - 'nativeLogs', 'logEventMapper', 'printLogDroppedSdkNotInitialized', 'printLogDroppedByMapper', @@ -33,7 +32,6 @@ const privateProperties = { 'logWithError' ], DdRum: [ - 'nativeRum', 'errorEventMapper', 'resourceEventMapper', 'actionEventMapper', diff --git a/packages/core/src/__tests__/nativeSpecGetters.test.ts b/packages/core/src/__tests__/nativeSpecGetters.test.ts new file mode 100644 index 000000000..28e3bb0f5 --- /dev/null +++ b/packages/core/src/__tests__/nativeSpecGetters.test.ts @@ -0,0 +1,58 @@ +/* + * 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. + */ + +describe('native spec getters', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it.each([ + ['../specs/NativeDdSdk', 'getNativeDdSdk', 'DdSdk'], + ['../specs/NativeDdRum', 'getNativeDdRum', 'DdRum'], + ['../specs/NativeDdLogs', 'getNativeDdLogs', 'DdLogs'], + ['../specs/NativeDdTrace', 'getNativeDdTrace', 'DdTrace'], + ['../specs/NativeDdFlags', 'getNativeDdFlags', 'DdFlags'] + ])( + '%s retries a missing module and caches it once found', + (modulePath, getterName, registryName) => { + jest.isolateModules(() => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { TurboModuleRegistry } = require('react-native'); + const nativeModule = { name: registryName }; + const getSpy = jest + .spyOn(TurboModuleRegistry, 'get') + .mockReturnValueOnce(null) + .mockReturnValue(nativeModule); + // eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires + const getter = require(modulePath)[getterName]; + + expect(getter()).toBeNull(); + expect(getter()).toBe(nativeModule); + expect(getter()).toBe(nativeModule); + expect(getSpy).toHaveBeenCalledTimes(2); + expect(getSpy).toHaveBeenCalledWith(registryName); + }); + } + ); + + it('DdSdkWrapper picks up a module that was missing on first use', () => { + jest.isolateModules(() => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { TurboModuleRegistry } = require('react-native'); + const nativeModule = { clearAllData: jest.fn() }; + jest.spyOn(TurboModuleRegistry, 'get') + .mockReturnValueOnce(null) + .mockReturnValue(nativeModule); + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { DdSdkWrapper } = require('../sdk/DdSdkInternal'); + const sdk = new DdSdkWrapper(); + + expect(() => sdk.clearAllData()).toThrow(); + sdk.clearAllData(); + expect(nativeModule.clearAllData).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx b/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx index ef25f72ba..684ecdbd9 100644 --- a/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx +++ b/packages/core/src/__tests__/rum/instrumentation/DdRumUserInteractionTracking.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import type { DdNativeRumType } from '../../../nativeModulesTypes'; import { DdRumUserInteractionTracking } from '../../../rum/instrumentation/interactionTracking/DdRumUserInteractionTracking'; import { BufferSingleton } from '../../../sdk/DatadogProvider/Buffer/BufferSingleton'; -import NativeDdSdkSpec from '../../../specs/NativeDdSdk'; +import { getNativeDdSdk } from '../../../specs/NativeDdSdk'; const styles = StyleSheet.create({ button: { @@ -467,7 +467,7 @@ describe('startTracking', () => { jest.setMock('react/jsx-runtime', {}); DdRumUserInteractionTracking.startTracking({}); expect(DdRumUserInteractionTracking['isTracking']).toBe(true); - expect(NativeDdSdkSpec.telemetryDebug).toBeCalledWith( + expect(getNativeDdSdk()?.telemetryDebug).toBeCalledWith( 'React jsx runtime does not export new jsx transform' ); }); @@ -477,7 +477,7 @@ describe('startTracking', () => { DdRumUserInteractionTracking.startTracking({}); expect(DdRumUserInteractionTracking['isTracking']).toBe(true); - expect(NativeDdSdkSpec.telemetryDebug).toBeCalledWith( + expect(getNativeDdSdk()?.telemetryDebug).toBeCalledWith( 'React version does not support new jsx transform' ); }); diff --git a/packages/core/src/__tests__/wrapperImportSafety.test.ts b/packages/core/src/__tests__/wrapperImportSafety.test.ts new file mode 100644 index 000000000..53d626ed1 --- /dev/null +++ b/packages/core/src/__tests__/wrapperImportSafety.test.ts @@ -0,0 +1,129 @@ +/* + * 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. + */ + +/** + * The spy must be installed on the `react-native` instance from inside + * `jest.isolateModules`: lookups made by isolated modules go through that + * registry, not the one imported at the top of a test file. + */ +const requireCountingLookups = (modulePath: string) => { + let lookups: string[] = []; + let mod: any; + jest.isolateModules(() => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { TurboModuleRegistry } = require('react-native'); + const getSpy = jest.spyOn(TurboModuleRegistry, 'get'); + // eslint-disable-next-line global-require, import/no-dynamic-require + mod = require(modulePath); + lookups = getSpy.mock.calls.map(call => call[0] as string); + }); + return { mod, lookups }; +}; + +describe('SDK wrappers are import-safe', () => { + afterEach(() => { + jest.restoreAllMocks(); + // Singletons live on globalThis, which isolateModules does not reset. + const g = globalThis as Record; + for (const symbol of Object.getOwnPropertySymbols(g)) { + if (symbol.description?.startsWith('com.datadog.reactnative')) { + delete g[symbol]; + } + } + }); + + it.each([ + '../specs/NativeDdSdk', + '../specs/NativeDdRum', + '../specs/NativeDdLogs', + '../specs/NativeDdTrace', + '../specs/NativeDdFlags', + '../sdk/DdSdkInternal', + '../rum/DdRum', + '../logs/DdLogs', + '../trace/DdTrace', + '../flags/DdFlags' + ])('importing %s performs no native lookup', modulePath => { + expect(requireCountingLookups(modulePath).lookups).toEqual([]); + }); + + it('DdSdkWrapper only resolves the native module when a method is used', () => { + jest.isolateModules(() => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { TurboModuleRegistry } = require('react-native'); + const getSpy = jest.spyOn(TurboModuleRegistry, 'get'); + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { DdSdkWrapper } = require('../sdk/DdSdkInternal'); + + const sdk = new DdSdkWrapper(); + expect(getSpy).not.toHaveBeenCalled(); + + sdk.getConstants(); + expect(getSpy).toHaveBeenCalledWith('DdSdk'); + }); + }); + + it('importing the public barrel performs no native lookup', () => { + expect(requireCountingLookups('../index').lookups).toEqual([]); + }); + + it('importing the internal entry performs no native lookup', () => { + expect(requireCountingLookups('../internal').lookups).toEqual([]); + }); +}); + +describe('SDK wrappers do not rely on Proxy', () => { + // Older Android JSC and Hermes versions supported by the SDK lack Proxy. + it.each(['../sdk/DdSdkInternal', '../index', '../internal'])( + 'importing %s works without Proxy', + modulePath => { + const g = globalThis as Record; + const originalProxy = g.Proxy; + try { + delete g.Proxy; + expect(() => requireCountingLookups(modulePath)).not.toThrow(); + } finally { + g.Proxy = originalProxy; + } + } + ); + + it('DdSdkWrapper forwards every method to the native module', () => { + jest.isolateModules(() => { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { TurboModuleRegistry } = require('react-native'); + const nativeModule: Record = {}; + jest.spyOn(TurboModuleRegistry, 'get').mockReturnValue( + nativeModule + ); + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + const { DdSdkWrapper } = require('../sdk/DdSdkInternal'); + const sdk = new DdSdkWrapper(); + const methods = Object.getOwnPropertyNames( + DdSdkWrapper.prototype + ).filter( + name => + name !== 'constructor' && + !name.startsWith('_') && + typeof Object.getOwnPropertyDescriptor( + DdSdkWrapper.prototype, + name + )?.value === 'function' + ); + for (const name of methods) { + nativeModule[name] = jest.fn().mockReturnValue(name); + } + + for (const name of methods) { + const arg = + name === 'initialize' ? { attributeEncoders: [] } : 'arg'; + expect(sdk[name](arg)).toBe(name); + expect(nativeModule[name]).toHaveBeenCalledTimes(1); + } + expect(methods.length).toBeGreaterThan(15); + }); + }); +}); diff --git a/packages/core/src/flags/DdFlags.ts b/packages/core/src/flags/DdFlags.ts index 43714d598..89090878f 100644 --- a/packages/core/src/flags/DdFlags.ts +++ b/packages/core/src/flags/DdFlags.ts @@ -7,6 +7,7 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../config/types/SdkVerbosity'; import type { DdNativeFlagsType } from '../nativeModulesTypes'; +import { getNativeDdFlags } from '../specs/NativeDdFlags'; import { getGlobalInstance } from '../utils/singletonUtils'; import { FlagsClient } from './FlagsClient'; @@ -18,9 +19,9 @@ 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 get nativeFlags(): DdNativeFlagsType { + return getNativeDdFlags() as DdNativeFlagsType; + } private isFeatureEnabled = false; diff --git a/packages/core/src/flags/FlagsClient.ts b/packages/core/src/flags/FlagsClient.ts index c1e56ff57..7182d316d 100644 --- a/packages/core/src/flags/FlagsClient.ts +++ b/packages/core/src/flags/FlagsClient.ts @@ -7,6 +7,7 @@ import { InternalLog } from '../InternalLog'; import { SdkVerbosity } from '../config/types/SdkVerbosity'; import type { DdNativeFlagsType } from '../nativeModulesTypes'; +import { getNativeDdFlags } from '../specs/NativeDdFlags'; // Imported directly (not via the module index): context matching is an internal helper, // used here only to detect a runtime context an offline precomputed configuration cannot honor. @@ -82,9 +83,7 @@ interface OfflineFlagsEvaluator { } 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; diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts new file mode 100644 index 000000000..e13a3bbfb --- /dev/null +++ b/packages/core/src/internal.ts @@ -0,0 +1,32 @@ +/* + * 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'; diff --git a/packages/core/src/logs/DdLogs.ts b/packages/core/src/logs/DdLogs.ts index 8dcb7421a..38988cbe5 100644 --- a/packages/core/src/logs/DdLogs.ts +++ b/packages/core/src/logs/DdLogs.ts @@ -11,6 +11,7 @@ import { debugId } from '../metro/debugIdResolver'; import type { DdNativeLogsType } from '../nativeModulesTypes'; import { encodeAttributes } from '../sdk/AttributesEncoding/attributesEncoding'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; +import { getNativeDdLogs } from '../specs/NativeDdLogs'; import type { ErrorSource, LogEventMapper } from '../types'; import { getGlobalInstance } from '../utils/singletonUtils'; @@ -44,9 +45,9 @@ 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 get nativeLogs(): DdNativeLogsType { + return getNativeDdLogs() as DdNativeLogsType; + } private logEventMapper = generateEventMapper(undefined); debug = (...args: LogArguments | LogWithErrorArguments): Promise => { diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 10db74599..c7ecfe78f 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -15,6 +15,7 @@ import type { Attributes } from '../sdk/AttributesSingleton/types'; import { bufferVoidNativeCall } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; import { DdSdk } from '../sdk/DdSdk'; import { GlobalState } from '../sdk/GlobalState/GlobalState'; +import { getNativeDdRum } from '../specs/NativeDdRum'; import type { ErrorSource, FeatureOperationFailure } from '../types'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; @@ -77,9 +78,9 @@ 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 get nativeRum(): DdNativeRumType { + return getNativeDdRum() as DdNativeRumType; + } private lastActionData?: { type: RumActionType; name: string }; private errorEventMapper = generateErrorEventMapper(undefined); private resourceEventMapper = generateResourceEventMapper(undefined); diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index ea0fccf67..af8049c19 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -12,7 +12,7 @@ import { InternalLog } from '../../InternalLog'; import { SdkVerbosity } from '../../config/types'; import { BufferSingleton } from '../../sdk/DatadogProvider/Buffer/BufferSingleton'; import { GlobalState } from '../../sdk/GlobalState/GlobalState'; -import NativeDdSdkSpec from '../../specs/NativeDdSdk'; +import { getNativeDdSdk } from '../../specs/NativeDdSdk'; import { ErrorSource } from '../../types'; import { DdRum } from '../DdRum'; import type { ActionEventMapper } from '../eventMappers/actionEventMapper'; @@ -1444,7 +1444,7 @@ describe('DdRum', () => { test('does not call the native SDK when startAction has not been called before and using old API', async () => { await DdRum.stopAction({ user: 'me' }, 789); expect(NativeModules.DdRum.stopAction).not.toHaveBeenCalled(); - expect(NativeDdSdkSpec.telemetryDebug).not.toHaveBeenCalled(); + expect(getNativeDdSdk()?.telemetryDebug).not.toHaveBeenCalled(); }); test('calls the native SDK when called with old API', async () => { @@ -1456,7 +1456,7 @@ describe('DdRum', () => { { user: 'me' }, 789 ); - expect(NativeDdSdkSpec.telemetryDebug).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryDebug).toHaveBeenCalledWith( 'DDdRum.stopAction called with the old signature' ); }); @@ -1470,7 +1470,7 @@ describe('DdRum', () => { {}, 456 ); - expect(NativeDdSdkSpec.telemetryDebug).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryDebug).toHaveBeenCalledWith( 'DDdRum.stopAction called with the old signature' ); }); diff --git a/packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts b/packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts index 4cb146783..f91cb69c2 100644 --- a/packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts +++ b/packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts @@ -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'; @@ -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 } @@ -147,7 +147,7 @@ class BabelInteractionTracking { ) .catch(e => { if (e instanceof Error) { - DdSdk?.telemetryError( + getNativeDdSdk()?.telemetryError( e.message, e.stack || '', 'BabelActionTrack' diff --git a/packages/core/src/rum/instrumentation/interactionTracking/__tests__/DdBabelInteractionTracking.test.ts b/packages/core/src/rum/instrumentation/interactionTracking/__tests__/DdBabelInteractionTracking.test.ts index c74b86ba5..1cdcc6c6b 100644 --- a/packages/core/src/rum/instrumentation/interactionTracking/__tests__/DdBabelInteractionTracking.test.ts +++ b/packages/core/src/rum/instrumentation/interactionTracking/__tests__/DdBabelInteractionTracking.test.ts @@ -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', () => ({ diff --git a/packages/core/src/sdk/DatadogEventEmitter/DatadogDefaultEventEmitter.tsx b/packages/core/src/sdk/DatadogEventEmitter/DatadogDefaultEventEmitter.tsx index f04bf7ba2..7ff889b64 100644 --- a/packages/core/src/sdk/DatadogEventEmitter/DatadogDefaultEventEmitter.tsx +++ b/packages/core/src/sdk/DatadogEventEmitter/DatadogDefaultEventEmitter.tsx @@ -5,6 +5,8 @@ */ import { NativeModules } from 'react-native'; +import { getNativeDdSdk } from '../../specs/NativeDdSdk'; + import { DatadogBatchedBridgeEventEmitter } from './DatadogBatchedBridgeEventEmitter'; import type { DatadogEventEmitter } from './DatadogEventEmitter'; import { DatadogNativeEventEmitter } from './DatadogNativeEventEmitter'; @@ -18,10 +20,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); diff --git a/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx b/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx index 3dff4e7ce..455f2c30e 100644 --- a/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx +++ b/packages/core/src/sdk/DatadogInternalBridge/__tests__/DdSdkNativeBridge.test.tsx @@ -66,7 +66,9 @@ describe('DdSdkNativeBridge', () => { }) ); - jest.mock('../../../specs/NativeDdSdk', () => mockNativeDdSdkSpec); + jest.mock('../../../specs/NativeDdSdk', () => ({ + getNativeDdSdk: jest.fn(() => mockNativeDdSdkSpec) + })); }); afterEach(() => { @@ -96,8 +98,39 @@ describe('DdSdkNativeBridge', () => { expect(ddBridge.hasNativeBridge()).toBe(true); }); + it('falls back to the spec getter when NativeModules.DdSdk is missing', () => { + const { NativeModules } = require('react-native'); + const legacyModule = NativeModules.DdSdk; + delete NativeModules.DdSdk; + try { + const { + DatadogNativeEventEmitter + } = require('../../DatadogEventEmitter/DatadogNativeEventEmitter'); + const ddBridge = require('../DdSdkInternalNativeBridge'); + const defaultEventEmitter = require('../../DatadogEventEmitter/DatadogDefaultEventEmitter'); + mockNativeEventEmitter.initialize.mockReturnValueOnce(true); + + ddBridge.registerNativeBridge( + new defaultEventEmitter.DatadogDefaultEventEmitter( + mockErrorHandler + ) + ); + + expect(DatadogNativeEventEmitter).toHaveBeenCalledWith( + mockNativeDdSdkSpec, + mockErrorHandler + ); + expect(mockErrorHandler).not.toHaveBeenCalled(); + expect(ddBridge.hasNativeBridge()).toBe(true); + } finally { + NativeModules.DdSdk = legacyModule; + } + }); + it('catches errors when native event emitter init fails', () => { - jest.mock('../../../specs/NativeDdSdk', () => undefined); + jest.mock('../../../specs/NativeDdSdk', () => ({ + getNativeDdSdk: jest.fn(() => null) + })); const ddBridge = require('../DdSdkInternalNativeBridge'); const defaultEventEmitter = require('../../DatadogEventEmitter/DatadogDefaultEventEmitter'); diff --git a/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts b/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts index 6a6eeb8c6..39e0b80dc 100644 --- a/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts +++ b/packages/core/src/sdk/DatadogProvider/Buffer/__tests__/BoundedBuffer.test.ts @@ -5,7 +5,7 @@ */ import { InternalLog } from '../../../../InternalLog'; -import NativeDdSdkSpec from '../../../../specs/NativeDdSdk'; +import { getNativeDdSdk } from '../../../../specs/NativeDdSdk'; import { BoundedBuffer } from '../BoundedBuffer'; describe('BoundedBuffer', () => { @@ -126,7 +126,7 @@ describe('BoundedBuffer', () => { await buffer.drain(); expect(callbackWithId).toHaveBeenCalledTimes(1); expect(callbackWithId).toHaveBeenNthCalledWith(1, 'callbackId1'); - expect(NativeDdSdkSpec.telemetryError).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryError).toHaveBeenCalledWith( 'Could not generate enough random numbers happened 2 times.', '', 'RandomIdGenerationError' @@ -146,7 +146,7 @@ describe('BoundedBuffer', () => { await buffer.drain(); expect(fakeCallback).toHaveBeenCalledTimes(3); - expect(NativeDdSdkSpec.telemetryError).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryError).toHaveBeenCalledWith( 'Buffer overflow happened 1 times.', '', 'BufferOverflow' @@ -171,7 +171,7 @@ describe('BoundedBuffer', () => { expect(fakeCallback).toHaveBeenCalledTimes(1); expect(callbackReturningId).not.toHaveBeenCalled(); expect(callbackWithId).not.toHaveBeenCalled(); - expect(NativeDdSdkSpec.telemetryError).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryError).toHaveBeenCalledWith( 'Buffer overflow happened 2 times.', '', 'BufferOverflow' @@ -196,7 +196,7 @@ describe('BoundedBuffer', () => { expect(fakeCallback).toHaveBeenCalledTimes(1); expect(callbackReturningId).toHaveBeenCalledTimes(1); expect(callbackWithId).toHaveBeenCalledTimes(1); - expect(NativeDdSdkSpec.telemetryError).not.toHaveBeenCalled(); + expect(getNativeDdSdk()?.telemetryError).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/core/src/sdk/DdSdkInternal.ts b/packages/core/src/sdk/DdSdkInternal.ts index 2c1d74685..8d9f0d0fd 100644 --- a/packages/core/src/sdk/DdSdkInternal.ts +++ b/packages/core/src/sdk/DdSdkInternal.ts @@ -6,13 +6,10 @@ 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 NativeDdSdkSpec: DdNativeSdkType = require('../specs/NativeDdSdk') - .default; - export type DdSdkType = DdNativeSdkType & { readonly attributeEncoders: AttributeEncoder[]; }; @@ -23,51 +20,57 @@ export class DdSdkWrapper implements DdNativeSdkType { } private _attributeEncoders: AttributeEncoder[] = []; + // Resolved on use rather than at import, so importing this module is safe + // on platforms without the native module (e.g. Vega). + private get nativeSdk(): DdNativeSdkType { + return getNativeDdSdk() as DdNativeSdkType; + } + initialize(configuration: DdSdkNativeConfiguration): Promise { this._attributeEncoders = [...configuration.attributeEncoders]; - return NativeDdSdkSpec.initialize(configuration); + return this.nativeSdk.initialize(configuration); } getConstants() { - return NativeDdSdkSpec.getConstants(); + return this.nativeSdk.getConstants(); } setUserInfo(user: object): Promise { - return NativeDdSdkSpec.setUserInfo(user); + return this.nativeSdk.setUserInfo(user); } clearUserInfo(): Promise { - return NativeDdSdkSpec.clearUserInfo(); + return this.nativeSdk.clearUserInfo(); } addUserExtraInfo(extraInfo: object): Promise { - return NativeDdSdkSpec.addUserExtraInfo(extraInfo); + return this.nativeSdk.addUserExtraInfo(extraInfo); } addAttribute(key: string, value: object): Promise { - return NativeDdSdkSpec.addAttribute(key, value); + return this.nativeSdk.addAttribute(key, value); } removeAttribute(key: string): Promise { - return NativeDdSdkSpec.removeAttribute(key); + return this.nativeSdk.removeAttribute(key); } addAttributes(attributes: object): Promise { - return NativeDdSdkSpec.addAttributes(attributes); + return this.nativeSdk.addAttributes(attributes); } removeAttributes(keys: string[]): Promise { - return NativeDdSdkSpec.removeAttributes(keys); + return this.nativeSdk.removeAttributes(keys); } setAccountInfo(account: object): Promise { - return NativeDdSdkSpec.setAccountInfo(account); + return this.nativeSdk.setAccountInfo(account); } clearAccountInfo(): Promise { - return NativeDdSdkSpec.clearAccountInfo(); + return this.nativeSdk.clearAccountInfo(); } addAccountExtraInfo(extraInfo: object): Promise { - return NativeDdSdkSpec.addAccountExtraInfo(extraInfo); + return this.nativeSdk.addAccountExtraInfo(extraInfo); } setTrackingConsent(trackingConsent: string): Promise { - return NativeDdSdkSpec.setTrackingConsent(trackingConsent); + return this.nativeSdk.setTrackingConsent(trackingConsent); } sendTelemetryLog( @@ -75,11 +78,11 @@ export class DdSdkWrapper implements DdNativeSdkType { attributes: object, config: object ): Promise { - return NativeDdSdkSpec.sendTelemetryLog(message, attributes, config); + return this.nativeSdk.sendTelemetryLog(message, attributes, config); } telemetryDebug(message: string): Promise { - return NativeDdSdkSpec.telemetryDebug(message); + return this.nativeSdk.telemetryDebug(message); } telemetryError( @@ -87,23 +90,23 @@ export class DdSdkWrapper implements DdNativeSdkType { stack: string, kind: string ): Promise { - return NativeDdSdkSpec.telemetryError(message, stack, kind); + return this.nativeSdk.telemetryError(message, stack, kind); } consumeWebviewEvent(message: string): Promise { - return NativeDdSdkSpec.consumeWebviewEvent(message); + return this.nativeSdk.consumeWebviewEvent(message); } clearAllData(): Promise { - return NativeDdSdkSpec.clearAllData(); + return this.nativeSdk.clearAllData(); } addListener(eventType: string): void { - return NativeDdSdkSpec.addListener(eventType); + return this.nativeSdk.addListener(eventType); } removeListeners(count: number): void { - return NativeDdSdkSpec.removeListeners(count); + return this.nativeSdk.removeListeners(count); } _setAttributeEncodersForTesting( diff --git a/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts b/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts index 42077abce..7b67e59e1 100644 --- a/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts +++ b/packages/core/src/sdk/EventMappers/__tests__/EventMapper.test.ts @@ -4,7 +4,7 @@ * Copyright 2016-Present Datadog, Inc. */ -import NativeDdSdkSpec from '../../../specs/NativeDdSdk'; +import { getNativeDdSdk } from '../../../specs/NativeDdSdk'; import { EventMapper } from '../EventMapper'; describe('EventMapper', () => { @@ -26,7 +26,7 @@ describe('EventMapper', () => { ).toEqual({ someData: 'some data' }); - expect(NativeDdSdkSpec.telemetryDebug).toHaveBeenCalledWith( + expect(getNativeDdSdk()?.telemetryDebug).toHaveBeenCalledWith( 'Error while running the event mapper' ); }); diff --git a/packages/core/src/specs/NativeDdFlags.ts b/packages/core/src/specs/NativeDdFlags.ts index 5c8afd010..5774bf0f3 100644 --- a/packages/core/src/specs/NativeDdFlags.ts +++ b/packages/core/src/specs/NativeDdFlags.ts @@ -31,5 +31,17 @@ export interface Spec extends TurboModule { ) => Promise; } -// eslint-disable-next-line import/no-default-export -export default TurboModuleRegistry.get('DdFlags'); +let cachedModule: Spec | null = null; + +/** + * Lazily resolves the native TurboModule on first call and caches it once found. + * A missing module is not cached, so later calls retry the lookup. + * 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) { + cachedModule = TurboModuleRegistry.get('DdFlags'); + } + return cachedModule; +}; diff --git a/packages/core/src/specs/NativeDdLogs.ts b/packages/core/src/specs/NativeDdLogs.ts index f1a60f4c7..dd3b1eebb 100644 --- a/packages/core/src/specs/NativeDdLogs.ts +++ b/packages/core/src/specs/NativeDdLogs.ts @@ -107,5 +107,17 @@ export interface Spec extends TurboModule { ) => Promise; } -// eslint-disable-next-line import/no-default-export -export default TurboModuleRegistry.get('DdLogs'); +let cachedModule: Spec | null = null; + +/** + * Lazily resolves the native TurboModule on first call and caches it once found. + * A missing module is not cached, so later calls retry the lookup. + * 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) { + cachedModule = TurboModuleRegistry.get('DdLogs'); + } + return cachedModule; +}; diff --git a/packages/core/src/specs/NativeDdRum.ts b/packages/core/src/specs/NativeDdRum.ts index 00950807a..337e607e1 100644 --- a/packages/core/src/specs/NativeDdRum.ts +++ b/packages/core/src/specs/NativeDdRum.ts @@ -236,5 +236,17 @@ export interface Spec extends TurboModule { ): Promise; } -// eslint-disable-next-line import/no-default-export -export default TurboModuleRegistry.get('DdRum'); +let cachedModule: Spec | null = null; + +/** + * Lazily resolves the native TurboModule on first call and caches it once found. + * A missing module is not cached, so later calls retry the lookup. + * 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) { + cachedModule = TurboModuleRegistry.get('DdRum'); + } + return cachedModule; +}; diff --git a/packages/core/src/specs/NativeDdSdk.ts b/packages/core/src/specs/NativeDdSdk.ts index 68c9c4711..8233488db 100644 --- a/packages/core/src/specs/NativeDdSdk.ts +++ b/packages/core/src/specs/NativeDdSdk.ts @@ -134,5 +134,17 @@ export interface Spec extends TurboModule { removeListeners: (count: number) => void; } -// eslint-disable-next-line import/no-default-export -export default TurboModuleRegistry.get('DdSdk'); +let cachedModule: Spec | null = null; + +/** + * Lazily resolves the native TurboModule on first call and caches it once found. + * A missing module is not cached, so later calls retry the lookup. + * 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) { + cachedModule = TurboModuleRegistry.get('DdSdk'); + } + return cachedModule; +}; diff --git a/packages/core/src/specs/NativeDdTrace.ts b/packages/core/src/specs/NativeDdTrace.ts index 8f3526f37..a6c8d646f 100644 --- a/packages/core/src/specs/NativeDdTrace.ts +++ b/packages/core/src/specs/NativeDdTrace.ts @@ -39,5 +39,17 @@ export interface Spec extends TurboModule { ): Promise; } -// eslint-disable-next-line import/no-default-export -export default TurboModuleRegistry.get('DdTrace'); +let cachedModule: Spec | null = null; + +/** + * Lazily resolves the native TurboModule on first call and caches it once found. + * A missing module is not cached, so later calls retry the lookup. + * Resolving lazily (instead of at module load) keeps this package importable on + * platforms where the native module is absent (e.g. Vega). + */ +export const getNativeDdTrace = (): Spec | null => { + if (!cachedModule) { + cachedModule = TurboModuleRegistry.get('DdTrace'); + } + return cachedModule; +}; diff --git a/packages/core/src/trace/DdTrace.ts b/packages/core/src/trace/DdTrace.ts index 72fe2375b..c51fc6326 100644 --- a/packages/core/src/trace/DdTrace.ts +++ b/packages/core/src/trace/DdTrace.ts @@ -12,6 +12,7 @@ import { bufferNativeCallReturningId, bufferNativeCallWithId } from '../sdk/DatadogProvider/Buffer/bufferNativeCall'; +import { getNativeDdTrace } from '../specs/NativeDdTrace'; import type { DdTraceType } from '../types'; import { getGlobalInstance } from '../utils/singletonUtils'; import { DefaultTimeProvider } from '../utils/time-provider/DefaultTimeProvider'; @@ -21,9 +22,9 @@ const TRACE_MODULE = 'com.datadog.reactnative.trace'; const timeProvider = new DefaultTimeProvider(); class DdTraceWrapper implements DdTraceType { - // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires - private nativeTrace: DdNativeTraceType = require('../specs/NativeDdTrace') - .default; + private get nativeTrace(): DdNativeTraceType { + return getNativeDdTrace() as DdNativeTraceType; + } startSpan = ( operation: string, diff --git a/packages/react-native-openfeature/src/__tests__/entrypoints.test.ts b/packages/react-native-openfeature/src/__tests__/entrypoints.test.ts index be93c4d00..c71b0fd1f 100644 --- a/packages/react-native-openfeature/src/__tests__/entrypoints.test.ts +++ b/packages/react-native-openfeature/src/__tests__/entrypoints.test.ts @@ -8,11 +8,11 @@ jest.mock('../../../core/src/specs/NativeDdFlags', () => ({ __esModule: true, - default: { + getNativeDdFlags: jest.fn().mockReturnValue({ enable: jest.fn(() => Promise.resolve()), setEvaluationContext: jest.fn(() => Promise.resolve({})), trackEvaluation: jest.fn(() => Promise.resolve()) - } + }) })); // The rules parser loads Protobuf-ES. Only the `/rules-based` entry may load it. diff --git a/packages/react-native-openfeature/src/__tests__/offlineProvider.delegation.test.ts b/packages/react-native-openfeature/src/__tests__/offlineProvider.delegation.test.ts index 34580c5c5..95e8ff80f 100644 --- a/packages/react-native-openfeature/src/__tests__/offlineProvider.delegation.test.ts +++ b/packages/react-native-openfeature/src/__tests__/offlineProvider.delegation.test.ts @@ -19,7 +19,7 @@ import { ProviderStatus } from '@openfeature/web-sdk'; -import NativeDdFlagsModule from '../../../core/src/specs/NativeDdFlags'; +import { getNativeDdFlags } from '../../../core/src/specs/NativeDdFlags'; import { DatadogCoreEvaluationProvider } from '../datadogCoreProvider'; import { DatadogOfflineOpenFeatureProvider } from '../index'; import { coreConfigurationFromString } from '../rules-based'; @@ -33,15 +33,15 @@ import { jest.mock('../../../core/src/specs/NativeDdFlags', () => ({ __esModule: true, - default: { + getNativeDdFlags: jest.fn().mockReturnValue({ enable: jest.fn(() => Promise.resolve()), setEvaluationContext: jest.fn(() => Promise.resolve({})), trackEvaluation: jest.fn(() => Promise.resolve()) - } + }) })); const NativeDdFlags = jest.mocked( - NativeDdFlagsModule as NonNullable + getNativeDdFlags() as NonNullable> ); let sequence = 0; diff --git a/packages/react-native-openfeature/src/__tests__/offlineProvider.integration.test.ts b/packages/react-native-openfeature/src/__tests__/offlineProvider.integration.test.ts index 6c5d2d4d2..f1574b71d 100644 --- a/packages/react-native-openfeature/src/__tests__/offlineProvider.integration.test.ts +++ b/packages/react-native-openfeature/src/__tests__/offlineProvider.integration.test.ts @@ -20,11 +20,11 @@ import { DatadogOfflineOpenFeatureProvider } from '../offlineProvider'; // ready-state evaluation can call trackEvaluation without throwing. jest.mock('../../../core/src/specs/NativeDdFlags', () => ({ __esModule: true, - default: { + getNativeDdFlags: jest.fn().mockReturnValue({ enable: jest.fn(() => Promise.resolve()), setEvaluationContext: jest.fn(() => Promise.resolve({})), trackEvaluation: jest.fn(() => Promise.resolve()) - } + }) })); const wireFor = (targetingKey: string): string => diff --git a/packages/react-native-openfeature/src/__tests__/rumContext.integration.test.ts b/packages/react-native-openfeature/src/__tests__/rumContext.integration.test.ts index cd181cde3..d293b507b 100644 --- a/packages/react-native-openfeature/src/__tests__/rumContext.integration.test.ts +++ b/packages/react-native-openfeature/src/__tests__/rumContext.integration.test.ts @@ -10,13 +10,13 @@ import { InMemoryProvider, OpenFeature } from '@openfeature/web-sdk'; import { InternalLog } from '../../../core/src/InternalLog'; import { SdkVerbosity } from '../../../core/src/config/types/SdkVerbosity'; import { UserInfoSingleton } from '../../../core/src/sdk/UserInfoSingleton/UserInfoSingleton'; -import NativeDdFlags from '../../../core/src/specs/NativeDdFlags'; +import { getNativeDdFlags } from '../../../core/src/specs/NativeDdFlags'; import { DatadogOpenFeatureProvider } from '../provider'; import { enrichWithRumUser } from '../rumContext'; jest.mock('../../../core/src/specs/NativeDdFlags', () => ({ __esModule: true, - default: { + getNativeDdFlags: jest.fn().mockReturnValue({ enable: jest.fn(() => Promise.resolve()), setEvaluationContext: jest.fn(() => Promise.resolve({ @@ -34,15 +34,15 @@ jest.mock('../../../core/src/specs/NativeDdFlags', () => ({ }) ), trackEvaluation: jest.fn(() => Promise.resolve()) - } + }) })); jest.mock('../../../core/src/specs/NativeDdSdk', () => ({ __esModule: true, - default: { + getNativeDdSdk: jest.fn().mockReturnValue({ setUserInfo: jest.fn(() => Promise.resolve()), clearUserInfo: jest.fn(() => Promise.resolve()) - } + }) })); let testSequence = 0; @@ -87,7 +87,7 @@ describe('explicit RUM context enrichment', () => { const { clientName } = await setupProvider({}); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.setEvaluationContext).toHaveBeenCalledWith( clientName, '', {} @@ -119,12 +119,12 @@ describe('explicit RUM context enrichment', () => { company_name: 'Example, Inc.' }; expect(OpenFeature.getContext(domain)).toStrictEqual(enrichedContext); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.setEvaluationContext).toHaveBeenCalledWith( clientName, 'rum-user', expectedAttributes ); - expect(NativeDdFlags.trackEvaluation).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.trackEvaluation).toHaveBeenCalledWith( clientName, 'test-flag', expect.any(Object), @@ -185,12 +185,10 @@ describe('explicit RUM context enrichment', () => { "The evaluation context targetingKey is not a string. Using the anonymous subject ('') instead.", SdkVerbosity.WARN ); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenCalledWith( - clientName, - '', - expectedAttributes - ); - expect(NativeDdFlags.trackEvaluation).toHaveBeenCalledWith( + expect( + getNativeDdFlags()?.setEvaluationContext + ).toHaveBeenCalledWith(clientName, '', expectedAttributes); + expect(getNativeDdFlags()?.trackEvaluation).toHaveBeenCalledWith( clientName, 'test-flag', expect.any(Object), @@ -220,12 +218,12 @@ describe('explicit RUM context enrichment', () => { targetingKey: 'rum-user', ...expectedAttributes }); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.setEvaluationContext).toHaveBeenCalledWith( clientName, 'rum-user', expectedAttributes ); - expect(NativeDdFlags.trackEvaluation).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.trackEvaluation).toHaveBeenCalledWith( clientName, 'test-flag', expect.any(Object), @@ -262,17 +260,15 @@ describe('explicit RUM context enrichment', () => { region: 'us' }; expect(OpenFeature.getContext(domain)).toStrictEqual(expectedContext); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenLastCalledWith( - clientName, - 'rum-user-b', - { - email: 'b@example.com', - plan: 'pro', - region: 'us' - } - ); expect( - NativeDdFlags.trackEvaluation + getNativeDdFlags()?.setEvaluationContext + ).toHaveBeenLastCalledWith(clientName, 'rum-user-b', { + email: 'b@example.com', + plan: 'pro', + region: 'us' + }); + expect( + getNativeDdFlags()?.trackEvaluation ).toHaveBeenLastCalledWith( clientName, 'test-flag', @@ -313,10 +309,10 @@ describe('explicit RUM context enrichment', () => { applicationContext ); expect( - NativeDdFlags.setEvaluationContext + getNativeDdFlags()?.setEvaluationContext ).toHaveBeenLastCalledWith(clientName, '', { region: 'us' }); expect( - NativeDdFlags.trackEvaluation + getNativeDdFlags()?.trackEvaluation ).toHaveBeenLastCalledWith( clientName, 'test-flag', @@ -376,11 +372,9 @@ describe('explicit RUM context enrichment', () => { const { clientName } = await setupProvider(enrichWithRumUser({})); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenLastCalledWith( - clientName, - 'rum-user', - {} - ); + expect( + getNativeDdFlags()?.setEvaluationContext + ).toHaveBeenLastCalledWith(clientName, 'rum-user', {}); }); it('uses undefined application fields as tombstones for RUM attributes', async () => { @@ -398,12 +392,12 @@ describe('explicit RUM context enrichment', () => { expect(OpenFeature.getContext(domain)).toStrictEqual({ targetingKey: 'rum-user' }); - expect(NativeDdFlags.setEvaluationContext).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.setEvaluationContext).toHaveBeenCalledWith( clientName, 'rum-user', {} ); - expect(NativeDdFlags.trackEvaluation).toHaveBeenCalledWith( + expect(getNativeDdFlags()?.trackEvaluation).toHaveBeenCalledWith( clientName, 'test-flag', expect.any(Object),