From 96f50cf25967884756dc48deb7c534f29cff6670 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Thu, 29 May 2025 10:19:00 +0200 Subject: [PATCH] [FIX] Codepush: unset useAccessibilityLabel and actionNameAttribute properties --- packages/codepush/package.json | 2 +- .../codepush/src/__tests__/index.test.tsx | 196 +++++++++++++++++- packages/codepush/src/index.ts | 46 +++- packages/codepush/src/utils.ts | 37 ++++ packages/core/src/DdSdkReactNative.tsx | 19 +- packages/core/src/__tests__/mock.test.ts | 3 + packages/core/src/index.tsx | 3 +- yarn.lock | 2 +- 8 files changed, 279 insertions(+), 29 deletions(-) create mode 100644 packages/codepush/src/utils.ts diff --git a/packages/codepush/package.json b/packages/codepush/package.json index c46cdd5c0..11baa544e 100644 --- a/packages/codepush/package.json +++ b/packages/codepush/package.json @@ -38,7 +38,7 @@ "prepare": "rm -rf lib && yarn bob build" }, "devDependencies": { - "@datadog/mobile-react-native": "^2.8.0", + "@datadog/mobile-react-native": "workspace:packages/core", "@testing-library/react-native": "7.0.2", "react-native-builder-bob": "0.26.0", "react-native-code-push": "7.1.0" diff --git a/packages/codepush/src/__tests__/index.test.tsx b/packages/codepush/src/__tests__/index.test.tsx index c6172bf5f..5c94c16ef 100644 --- a/packages/codepush/src/__tests__/index.test.tsx +++ b/packages/codepush/src/__tests__/index.test.tsx @@ -1,14 +1,8 @@ -import { - DdSdkReactNative, - DdSdkReactNativeConfiguration, - DatadogProviderConfiguration -} from '@datadog/mobile-react-native'; -import { render } from '@testing-library/react-native'; -import codePush from 'react-native-code-push'; +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable global-require */ +import { render, waitFor } from '@testing-library/react-native'; import React from 'react'; -import { DatadogCodepush, DatadogCodepushProvider } from '..'; - jest.mock('react-native-code-push', () => ({ getUpdateMetadata: jest.fn() })); @@ -16,7 +10,10 @@ jest.mock('react-native-code-push', () => ({ jest.mock('@datadog/mobile-react-native', () => { const actualPackage = jest.requireActual('@datadog/mobile-react-native'); actualPackage.DdSdkReactNative.initialize = jest.fn(); + actualPackage.DdSdkReactNative._enableFeaturesFromDatadogProvider = jest.fn(); + actualPackage.DdSdkReactNative._enableFeaturesFromDatadogProviderAsync = jest.fn(); actualPackage.DdSdkReactNative._initializeFromDatadogProviderWithConfigurationAsync = jest.fn(); + actualPackage.DdSdkReactNative._initializeFromDatadogProvider = jest.fn(); return actualPackage; }); @@ -41,8 +38,16 @@ describe('AppCenter Codepush integration', () => { beforeEach(() => { jest.clearAllMocks(); }); + describe('initialize', () => { it('initializes the SDK with the correct version when using a CodePush bundle', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepush } = require('..'); + const { + DdSdkReactNativeConfiguration, + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(createCodepushPackageMock('v3')); @@ -65,6 +70,13 @@ describe('AppCenter Codepush integration', () => { }); it('initializes the SDK with the correct version when not using a CodePush bundle', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepush } = require('..'); + const { + DdSdkReactNativeConfiguration, + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(null); @@ -92,7 +104,19 @@ describe('AppCenter Codepush integration', () => { }); describe('DatadogCodepushProvider', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + it('initializes the sdk with the right codepush version when using DatadogProviderConfiguration', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepushProvider } = require('..'); + const { + DatadogProviderConfiguration, + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(createCodepushPackageMock('v4')); @@ -118,6 +142,12 @@ describe('AppCenter Codepush integration', () => { ); }); it('initializes the sdk with the right codepush version when using partial configuration', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepushProvider } = require('..'); + const { + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(createCodepushPackageMock('v5')); @@ -149,7 +179,15 @@ describe('AppCenter Codepush integration', () => { expect.objectContaining({ versionSuffix: 'codepush.v5' }) ); }); + it('initializes the sdk with commercial version when using DatadogProviderConfiguration', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepushProvider } = require('..'); + const { + DatadogProviderConfiguration, + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(createCodepushPackageMock(null)); @@ -177,6 +215,12 @@ describe('AppCenter Codepush integration', () => { ).not.toContain('versionSuffix'); }); it('initializes the sdk with commercial version when using partial configuration', async () => { + const codePush = require('react-native-code-push'); + const { DatadogCodepushProvider } = require('..'); + const { + DdSdkReactNative + } = require('@datadog/mobile-react-native'); + (codePush.getUpdateMetadata as jest.MockedFunction< typeof codePush.getUpdateMetadata >).mockResolvedValueOnce(createCodepushPackageMock(null)); @@ -210,5 +254,139 @@ describe('AppCenter Codepush integration', () => { ) ).not.toContain('versionSuffix'); }); + + it('initializes the DatadogProvider with FileBasedConfiguration & all parameters', async () => { + const { DatadogCodepushProvider } = require('..'); + const { + DdSdkReactNative, + PropagatorType, + FileBasedConfiguration + } = require('@datadog/mobile-react-native'); + + const autoInstrumentationConfig = { + trackErrors: true, + trackResources: true, + trackInteractions: true, + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: [PropagatorType.DATADOG] + } + ], + useAccessibilityLabel: true, + actionNameAttribute: 'test-action-name-attr', + resourceTracingSamplingRate: 100 + }; + + const configuration = new FileBasedConfiguration({ + configuration: { configuration: autoInstrumentationConfig } + }); + + render(); + + await flushPromises(); + await waitFor(() => { + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).toHaveBeenCalledTimes(1); + }); + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).toHaveBeenCalledWith({ + actionEventMapper: null, + logEventMapper: null, + resourceEventMapper: null, + errorEventMapper: null, + trackErrors: true, + trackResources: true, + trackInteractions: true, + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: [PropagatorType.DATADOG] + } + ], + useAccessibilityLabel: true, + actionNameAttribute: 'test-action-name-attr', + resourceTracingSamplingRate: 100 + }); + + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).not.toHaveBeenCalledWith( + expect.objectContaining({ + clientToken: expect.anything(), + env: expect.anything(), + applicationId: expect.anything() + }) + ); + }); + + it('initializes the DatadogProvider with FileBasedConfiguration & undefined parameters', async () => { + const { DatadogCodepushProvider } = require('..'); + const { + DdSdkReactNative, + PropagatorType, + FileBasedConfiguration + } = require('@datadog/mobile-react-native'); + + const autoInstrumentationConfig = { + trackErrors: true, + trackResources: true, + trackInteractions: true, + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: [PropagatorType.DATADOG] + } + ], + // useAccessibilityLabel: true, + // actionNameAttribute: 'test-action-name-attr', + resourceTracingSamplingRate: 100 + }; + + const configuration = new FileBasedConfiguration({ + configuration: { configuration: autoInstrumentationConfig } + }); + + render(); + + await flushPromises(); + await waitFor(() => { + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).toHaveBeenCalledTimes(1); + }); + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).toHaveBeenCalledWith({ + actionEventMapper: null, + logEventMapper: null, + resourceEventMapper: null, + errorEventMapper: null, + trackErrors: true, + trackResources: true, + trackInteractions: true, + firstPartyHosts: [ + { + match: 'example.com', + propagatorTypes: [PropagatorType.DATADOG] + } + ], + resourceTracingSamplingRate: 100, + actionNameAttribute: undefined, + useAccessibilityLabel: true + }); + + expect( + DdSdkReactNative._enableFeaturesFromDatadogProvider + ).not.toHaveBeenCalledWith( + expect.objectContaining({ + clientToken: expect.anything(), + env: expect.anything(), + applicationId: expect.anything() + }) + ); + }); }); }); diff --git a/packages/codepush/src/index.ts b/packages/codepush/src/index.ts index dce7b7842..ca7024ebf 100644 --- a/packages/codepush/src/index.ts +++ b/packages/codepush/src/index.ts @@ -1,11 +1,22 @@ +/* + * 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. + */ import { DatadogProvider, DatadogProviderConfiguration, DdSdkReactNative } from '@datadog/mobile-react-native'; -import type { DdSdkReactNativeConfiguration } from '@datadog/mobile-react-native'; +import type { + AutoInstrumentationConfiguration, + DdSdkReactNativeConfiguration +} from '@datadog/mobile-react-native'; import codePush from 'react-native-code-push'; +import { DISCARD_PROPERTY, removeDiscardProperties } from './utils'; +import type { RequiredOrDiscard } from './utils'; + /** * Use this class instead of DdSdkReactNative to initialize the Datadog SDK when using AppCenter CodePush. */ @@ -31,6 +42,29 @@ const initializeWithCodepushVersion = async ( DatadogProvider.initialize(configuration); }; +const buildPartialConfiguration = ( + configuration: DatadogProviderConfiguration +): AutoInstrumentationConfiguration => { + const partialConfiguration: RequiredOrDiscard = { + trackErrors: configuration.trackErrors, + trackResources: configuration.trackResources, + trackInteractions: configuration.trackInteractions, + firstPartyHosts: configuration.firstPartyHosts, + logEventMapper: configuration.logEventMapper, + errorEventMapper: configuration.errorEventMapper, + resourceEventMapper: configuration.resourceEventMapper, + actionEventMapper: configuration.actionEventMapper, + useAccessibilityLabel: configuration.useAccessibilityLabel, + resourceTracingSamplingRate: configuration.resourceTracingSamplingRate, + actionNameAttribute: + configuration.actionNameAttribute ?? DISCARD_PROPERTY + }; + + return removeDiscardProperties( + partialConfiguration + ) as AutoInstrumentationConfiguration; +}; + export const DatadogCodepushProvider: typeof DatadogProvider = ({ configuration, ...rest @@ -39,16 +73,8 @@ export const DatadogCodepushProvider: typeof DatadogProvider = ({ // We turn it to partial initialization, while in parallel we get the CodePush version and initialize the SDK. if (configuration instanceof DatadogProviderConfiguration) { initializeWithCodepushVersion(configuration); - const partialConfiguration = { - trackErrors: configuration.trackErrors, - trackResources: configuration.trackResources, - trackInteractions: configuration.trackInteractions, - firstPartyHosts: configuration.firstPartyHosts, - resourceTracingSamplingRate: - configuration.resourceTracingSamplingRate - }; return DatadogProvider({ - configuration: partialConfiguration, + configuration: buildPartialConfiguration(configuration), ...rest }); } else { diff --git a/packages/codepush/src/utils.ts b/packages/codepush/src/utils.ts new file mode 100644 index 000000000..2a1f321c5 --- /dev/null +++ b/packages/codepush/src/utils.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * A constant used to define a property that should be discarded from a {@link RequiredOrDiscard} object. + */ +export const DISCARD_PROPERTY = { _dd_meta_: 'DISCARD' }; + +/** + * Used to change the type of every property of an object to be either required or {@link DISCARD_PROPERTY}. + */ +export type RequiredOrDiscard = { + [K in keyof T]-?: T[K] | typeof DISCARD_PROPERTY; +}; + +/** + * Removes all entries of value {@link DISCARD_PROPERTY} from the given object + * @param obj The object to remove the {@link DISCARD_PROPERTY} entries from. + * @returns The object without the {@link DISCARD_PROPERTY} entries. + */ +export const removeDiscardProperties = >( + obj: T +): { + [K in keyof T]: T[K] extends null ? undefined : T[K]; +} => { + const result = {} as any; + + Object.keys(obj).forEach(key => { + const value = obj[key]; + result[key] = value === DISCARD_PROPERTY ? undefined : value; + }); + + return result; +}; diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index fce0e17ef..6969d2c5c 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -106,9 +106,9 @@ export class DdSdkReactNative { /** * FOR INTERNAL USE ONLY. */ - static async _initializeFromDatadogProvider( + static _initializeFromDatadogProvider = async ( configuration: DatadogProviderConfiguration - ): Promise { + ): Promise => { DdSdkReactNative.enableFeatures(configuration); if (configuration instanceof FileBasedConfiguration) { return DdSdkReactNative.initializeNativeSDK(configuration, { @@ -133,20 +133,25 @@ export class DdSdkReactNative { initializationModeForTelemetry: 'SYNC' }); } - } + }; /** * FOR INTERNAL USE ONLY. */ - static async _enableFeaturesFromDatadogProvider( + static _enableFeaturesFromDatadogProvider = ( + features: AutoInstrumentationConfiguration + ): void => { + DdSdkReactNative._enableFeaturesFromDatadogProviderAsync(features); + }; + + static _enableFeaturesFromDatadogProviderAsync = async ( features: AutoInstrumentationConfiguration - ): Promise { + ): Promise => { DdSdkReactNative.features = features; DdSdkReactNative.enableFeatures( addDefaultValuesToAutoInstrumentationConfiguration(features) ); - } - + }; /** * FOR INTERNAL USE ONLY. */ diff --git a/packages/core/src/__tests__/mock.test.ts b/packages/core/src/__tests__/mock.test.ts index 99eb43035..722998f10 100644 --- a/packages/core/src/__tests__/mock.test.ts +++ b/packages/core/src/__tests__/mock.test.ts @@ -50,6 +50,9 @@ const privateProperties = { 'wasAutoInstrumented', 'initializeNativeSDK', '_initializeFromDatadogProviderWithConfigurationAsync', + '_enableFeaturesFromDatadogProvider', + '_enableFeaturesFromDatadogProviderAsync', + '_initializeFromDatadogProvider', 'buildConfiguration' ] }; diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index 515cfd1f8..9a0d1cead 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -12,6 +12,7 @@ import { BatchSize, BatchProcessingLevel } from './DdSdkReactNativeConfiguration'; +import type { AutoInstrumentationConfiguration } from './DdSdkReactNativeConfiguration'; import { DdSdkReactNative } from './DdSdkReactNative'; import { InternalLog } from './InternalLog'; import { ProxyConfiguration, ProxyType } from './ProxyConfiguration'; @@ -74,4 +75,4 @@ export { DatadogTracingContext }; -export type { Timestamp, FirstPartyHost }; +export type { Timestamp, FirstPartyHost, AutoInstrumentationConfiguration }; diff --git a/yarn.lock b/yarn.lock index 379b5bb42..eb0ef8088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2381,7 +2381,7 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/mobile-react-native-code-push@workspace:packages/codepush" dependencies: - "@datadog/mobile-react-native": ^2.8.0 + "@datadog/mobile-react-native": "workspace:packages/core" "@testing-library/react-native": 7.0.2 react-native-builder-bob: 0.26.0 react-native-code-push: 7.1.0