Skip to content
Closed
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
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
]
},
"scripts": {
"prepare": "genversion --es6 --semi packages/core/src/version.ts && lerna run prepare",
"test": "genversion --es6 --semi packages/core/src/version.ts && jest",
"lint": "genversion --es6 --semi packages/core/src/version.ts && eslint .",
"prepare": "genversion --es6 --semi packages/core/src/version.ts && genversion --es6 --semi packages/react-native-openfeature/src/version.ts && lerna run prepare",
"test": "genversion --es6 --semi packages/core/src/version.ts && genversion --es6 --semi packages/react-native-openfeature/src/version.ts && jest",
"lint": "genversion --es6 --semi packages/core/src/version.ts && genversion --es6 --semi packages/react-native-openfeature/src/version.ts && eslint .",
"example": "yarn --cwd example",
"postinstall": "./packages/react-navigation/fix-react-navigation-import-in-dependencies.sh"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/core/release-content.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,7 @@ package/src/rum/instrumentation/resourceTracking/requestProxy/FetchProxy/FetchPr
package/src/rum/instrumentation/resourceTracking/requestProxy/FetchProxy/__tests__/FetchProxy.test.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/__tests__/ResourceReporter.test.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/__tests__/__utils__/ResourceMockFactory.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/__tests__/flaggingIntakeResourceFilter.test.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/DatadogRumResource/__tests__/internalDevResourceBlocklist.test.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/URLHostParser.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/XHRProxy.ts
Expand All @@ -1106,6 +1107,7 @@ package/src/rum/instrumentation/resourceTracking/requestProxy/XHRProxy/xhrUtils.
package/src/rum/instrumentation/resourceTracking/requestProxy/common/FetchProxyState.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/RequestContext.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/ResourceReporter.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/flaggingIntakeResourceFilter.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/internalDevResourceBlocklist.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/requestHeaders.ts
package/src/rum/instrumentation/resourceTracking/requestProxy/common/resourceTiming.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { callOriginalFetch } from '../common/FetchProxyState';
import type { RequestContext } from '../common/RequestContext';
import { createRequestContext } from '../common/RequestContext';
import { ResourceReporter } from '../common/ResourceReporter';
import { filterFlaggingIntakeResource } from '../common/flaggingIntakeResourceFilter';
import { filterDevResource } from '../common/internalDevResourceBlocklist';
import {
getInstrumentationHeaders,
Expand Down Expand Up @@ -49,7 +50,10 @@ export class FetchProxy extends RequestProxy {
return new FetchProxy({
fetchGlobal: globalThis,
headersType: Headers,
resourceReporter: new ResourceReporter([filterDevResource])
resourceReporter: new ResourceReporter([
filterDevResource,
filterFlaggingIntakeResource
])
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { filterFlaggingIntakeResource } from '../../../common/flaggingIntakeResourceFilter';

import { ResourceMockFactory } from './__utils__/ResourceMockFactory';

const resourceMockFactory = new ResourceMockFactory();

const resourceWithUrl = (url: string) =>
resourceMockFactory.getCustomResource({
request: { method: 'POST', url, kind: 'fetch' }
});

describe('filterFlaggingIntakeResource', () => {
it.each([
'https://browser-intake-datadoghq.com/api/v2/exposures?ddsource=react-native&dd-api-key=token',
'https://browser-intake-us3-datadoghq.com/api/v2/flagevaluation?ddsource=react-native&dd-api-key=token',
'https://browser-intake-datadoghq.eu/api/v2/exposures?ddsource=react-native&dd-api-key=token',
'https://proxy.example.com/intake?ddforward=%2Fapi%2Fv2%2Fexposures%3Fddsource%3Dreact-native%26dd-api-key%3Dtoken',
'https://proxy.example.com/intake?ddforward=%2Fapi%2Fv2%2Fflagevaluation%3Fddsource%3Dreact-native%26dd-api-key%3Dtoken'
])('returns null for the flagging intake request %s', url => {
expect(filterFlaggingIntakeResource(resourceWithUrl(url))).toBeNull();
});

it.each([
'https://browser-intake-datadoghq.com/api/v2/rum?ddsource=react-native',
'https://api.example.com/api/v2/exposures?ddsource=react-native',
'https://browser-intake-datadoghq.com.example.com/other/api/v2/exposures?x=1',
'https://proxy.example.com/intake?ddforward=%2Fapi%2Fv2%2Frum%3Fddsource%3Dbrowser',
'https://browser-intake-custom.example/api/v2/exposures?customer=1',
'https://browser-intake-datadoghq.com/api/v2/flagevaluation?ddsource=browser&dd-api-key=token',
'https://proxy.example.com/intake?ddforward=%2Fapi%2Fv2%2Fexposures%3Fcustomer%3D1'
])('returns the resource for %s', url => {
const resource = resourceWithUrl(url);
expect(filterFlaggingIntakeResource(resource)).toBe(resource);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isRunningWithinFetchProxy } from '../common/FetchProxyState';
import type { RequestContext } from '../common/RequestContext';
import { createRequestContext } from '../common/RequestContext';
import { ResourceReporter } from '../common/ResourceReporter';
import { filterFlaggingIntakeResource } from '../common/flaggingIntakeResourceFilter';
import { filterDevResource } from '../common/internalDevResourceBlocklist';
import {
getInstrumentationHeaders,
Expand Down Expand Up @@ -55,7 +56,10 @@ export class XHRProxy extends RequestProxy {
static createWithResourceReporter() {
return new XHRProxy({
xhrType: XMLHttpRequest,
resourceReporter: new ResourceReporter([filterDevResource])
resourceReporter: new ResourceReporter([
filterDevResource,
filterFlaggingIntakeResource
])
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 type { RUMResource } from '../interfaces/RumResource';

// The OpenFeature package's JavaScript tracking hooks send exposures and flag evaluations
// to these intake paths, either directly or through a `ddforward` proxy. They always put
// `ddsource=react-native` first, so other requests to the same paths are still tracked.
const FLAGGING_INTAKE_REGEX = new RegExp(
'^https://browser-intake-[^/?#]+/api/v2/(exposures|flagevaluation)\\?ddsource=react-native&'
);

const FLAGGING_INTAKE_PROXY_REGEX = new RegExp(
'[?&]ddforward=%2Fapi%2Fv2%2F(exposures|flagevaluation)%3Fddsource%3Dreact-native%26'
);
Comment thread
greghuels marked this conversation as resolved.

export const filterFlaggingIntakeResource = (
resource: RUMResource
): RUMResource | null => {
const url = resource.request.url;
if (
FLAGGING_INTAKE_REGEX.test(url) ||
FLAGGING_INTAKE_PROXY_REGEX.test(url)
) {
return null;
}
return resource;
};
80 changes: 75 additions & 5 deletions packages/react-native-openfeature/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ Use [OpenFeature][1] with [Datadog Feature Flags][2] to evaluate feature flags a
OpenFeature is a vendor-neutral, community-driven specification and SDK for feature flagging. It provides a unified API for feature flag evaluation that works across different providers. This enables you to switch vendors or integrate multiple feature flag systems.

This package provides an online provider backed by Datadog's Feature Flags SDK, a
`DatadogCoreProvider` for manually supplied JavaScript-evaluated configurations, and an offline
compatibility provider that delegates to `DatadogCoreProvider` while retaining native tracking.
`DatadogCoreProvider` and the rules-based parser are exported from the
`DatadogCoreProvider` for manually supplied JavaScript-evaluated configurations, optional
JavaScript tracking hooks for `DatadogCoreProvider`, and an offline compatibility provider that
delegates to `DatadogCoreProvider` while retaining native tracking.
`DatadogCoreProvider`, its tracking hooks, and the rules-based parser are exported from the
`@datadog/mobile-react-native-openfeature/rules-based` entry point, which keeps the protobuf rules
parser out of apps that do not use it.

Expand Down Expand Up @@ -293,7 +294,8 @@ export default AppWithProviders;

Use `DatadogCoreProvider` to evaluate manually supplied precomputed or rules-based configurations in
JavaScript. The provider does not fetch configuration or send exposure or RUM events. Your
application manages configuration delivery, storage, and updates.
application manages configuration delivery, storage, and updates. To send exposures, flag
evaluations, or RUM feature flag data, add the [tracking hooks](#tracking-with-javascript-hooks).

```tsx
import {
Expand Down Expand Up @@ -338,11 +340,79 @@ precomputed-only `configurationFromString` helper.
`Error`. Loading a usable configuration after an error emits `Ready`, followed by
`ConfigurationChanged`.

#### Tracking with JavaScript hooks

Compose the Datadog tracking hooks you need and register them with the OpenFeature client that uses
`DatadogCoreProvider`. The hooks run in JavaScript and do not use `DdFlags`:

- `createDatadogExposureLoggingHook(options)` sends exposures for logged allocations to Datadog.
Exposures are deduplicated in memory per subject, flag, and assignment.
- `createDatadogEvaluationLoggingHook(options)` aggregates flag evaluations and sends them to
Datadog every `flagEvaluationTrackingInterval` milliseconds (default `10000`, between `1000`
and `60000`).
- `createDatadogRumTrackingHook()` adds evaluated variants to the active RUM view with
`DdRum.addFeatureFlagEvaluation`. It requires an initialized Datadog SDK with RUM enabled.

```tsx
import {
DatadogCoreProvider,
composeDatadogTrackingHooks,
coreConfigurationFromString,
createDatadogEvaluationLoggingHook,
createDatadogExposureLoggingHook,
createDatadogRumTrackingHook
} from '@datadog/mobile-react-native-openfeature/rules-based';
import { OpenFeature } from '@openfeature/react-sdk';

const trackingOptions = {
clientToken: '<CLIENT_TOKEN>',
site: 'datadoghq.com',
service: 'my-app',
applicationId: '<RUM_APPLICATION_ID>'
};

const tracking = composeDatadogTrackingHooks(
createDatadogExposureLoggingHook(trackingOptions),
createDatadogEvaluationLoggingHook(trackingOptions),
createDatadogRumTrackingHook()
);
await tracking.initialize();

const provider = new DatadogCoreProvider();
provider.setConfiguration(coreConfigurationFromString(wire));
await OpenFeature.setProviderAndWait('datadog-core', provider, context);

const client = OpenFeature.getClient('datadog-core');
client.addHooks(...tracking.hooks);

// When this client no longer needs tracking:
client.clearHooks();
await tracking.shutdown();
```

The exposure and evaluation hooks take their options directly; they do not read the configuration
given to `DdSdkReactNative`. Pass `proxy` to send events through a proxy, as
`<proxy>?ddforward=<intake path and parameters>`.

- The exposure and evaluation hooks do not track until `initialize()` resolves. `shutdown()` sends
pending events and stops timers. Both methods are idempotent, failures do not interrupt flag
evaluation, and initializing again after a shutdown resumes tracking with the same exposure
deduplication.
- Events are sent in batches, and pending events are sent when the app leaves the foreground.
Requests that fail are not retried.
- Replacing the provider configuration lets exposures for the new configuration through.
- RUM resource tracking ignores these requests. This requires `@datadog/mobile-react-native` from
the same release; with an older version, the requests appear as RUM resources.
- Only add these hooks to clients that use `DatadogCoreProvider`. `DatadogOpenFeatureProvider` and
`DatadogOfflineOpenFeatureProvider` already track through the native SDK, so the hooks would
track their evaluations twice.

### Offline initialization

Use `DatadogOfflineOpenFeatureProvider` to evaluate manually supplied precomputed or rules-based
configurations with native exposure or RUM tracking. The provider does not fetch configuration;
supply it with `setConfiguration`. For evaluation without tracking, use `DatadogCoreProvider`.
supply it with `setConfiguration`. For evaluation without tracking, or with JavaScript tracking
hooks, use `DatadogCoreProvider`.

Rules-based evaluation requires `@datadog/mobile-react-native` and
`@datadog/mobile-react-native-openfeature` 3.10.0 or later. Update both packages together to use the
Expand Down
60 changes: 60 additions & 0 deletions packages/react-native-openfeature/release-content.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ package/lib/commonjs/rules-based.js
package/lib/commonjs/rules-based.js.map
package/lib/commonjs/rumContext.js
package/lib/commonjs/rumContext.js.map
package/lib/commonjs/tracking/configuration.js
Comment thread
greghuels marked this conversation as resolved.
package/lib/commonjs/tracking/configuration.js.map
package/lib/commonjs/tracking/exposures.js
package/lib/commonjs/tracking/exposures.js.map
package/lib/commonjs/tracking/flagEvaluations.js
package/lib/commonjs/tracking/flagEvaluations.js.map
package/lib/commonjs/tracking/index.js
package/lib/commonjs/tracking/index.js.map
package/lib/commonjs/tracking/rumIntegration.js
package/lib/commonjs/tracking/rumIntegration.js.map
package/lib/commonjs/tracking/tracking.js
package/lib/commonjs/tracking/tracking.js.map
package/lib/commonjs/tracking/transport.js
package/lib/commonjs/tracking/transport.js.map
package/lib/commonjs/version.js
package/lib/commonjs/version.js.map
package/lib/module/coreProvider.js
package/lib/module/coreProvider.js.map
package/lib/module/datadogCoreProvider.js
Expand All @@ -35,6 +51,22 @@ package/lib/module/rules-based.js
package/lib/module/rules-based.js.map
package/lib/module/rumContext.js
package/lib/module/rumContext.js.map
package/lib/module/tracking/configuration.js
package/lib/module/tracking/configuration.js.map
package/lib/module/tracking/exposures.js
package/lib/module/tracking/exposures.js.map
package/lib/module/tracking/flagEvaluations.js
package/lib/module/tracking/flagEvaluations.js.map
package/lib/module/tracking/index.js
package/lib/module/tracking/index.js.map
package/lib/module/tracking/rumIntegration.js
package/lib/module/tracking/rumIntegration.js.map
package/lib/module/tracking/tracking.js
package/lib/module/tracking/tracking.js.map
package/lib/module/tracking/transport.js
package/lib/module/tracking/transport.js.map
package/lib/module/version.js
package/lib/module/version.js.map
package/lib/typescript/core/src/DdAttributes.d.ts
package/lib/typescript/core/src/DdAttributes.d.ts.map
package/lib/typescript/core/src/DdSdkReactNative.d.ts
Expand Down Expand Up @@ -203,6 +235,8 @@ package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProx
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/RequestContext.d.ts.map
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/ResourceReporter.d.ts
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/ResourceReporter.d.ts.map
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/flaggingIntakeResourceFilter.d.ts
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/flaggingIntakeResourceFilter.d.ts.map
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/internalDevResourceBlocklist.d.ts
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/internalDevResourceBlocklist.d.ts.map
package/lib/typescript/core/src/rum/instrumentation/resourceTracking/requestProxy/common/requestHeaders.d.ts
Expand Down Expand Up @@ -321,6 +355,22 @@ package/lib/typescript/react-native-openfeature/src/rules-based.d.ts
package/lib/typescript/react-native-openfeature/src/rules-based.d.ts.map
package/lib/typescript/react-native-openfeature/src/rumContext.d.ts
package/lib/typescript/react-native-openfeature/src/rumContext.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/configuration.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/configuration.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/exposures.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/exposures.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/flagEvaluations.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/flagEvaluations.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/index.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/index.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/rumIntegration.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/rumIntegration.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/tracking.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/tracking.d.ts.map
package/lib/typescript/react-native-openfeature/src/tracking/transport.d.ts
package/lib/typescript/react-native-openfeature/src/tracking/transport.d.ts.map
package/lib/typescript/react-native-openfeature/src/version.d.ts
package/lib/typescript/react-native-openfeature/src/version.d.ts.map
package/package.json
package/rules-based/package.json
package/src/__tests__/__utils__/coreConfiguration.ts
Expand All @@ -337,6 +387,8 @@ package/src/__tests__/rumContext.compatibility.test.ts
package/src/__tests__/rumContext.integration.test.ts
package/src/__tests__/rumContext.test.ts
package/src/__tests__/rumContext.types.test.ts
package/src/__tests__/trackingHooks.integration.test.ts
package/src/__tests__/trackingHooks.test.ts
package/src/coreProvider.ts
package/src/datadogCoreProvider.ts
package/src/index.ts
Expand All @@ -346,3 +398,11 @@ package/src/offlineProvider.ts
package/src/provider.ts
package/src/rules-based.ts
package/src/rumContext.ts
package/src/tracking/configuration.ts
package/src/tracking/exposures.ts
package/src/tracking/flagEvaluations.ts
package/src/tracking/index.ts
package/src/tracking/rumIntegration.ts
package/src/tracking/tracking.ts
package/src/tracking/transport.ts
package/src/version.ts
Loading
Loading