diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts index a3de589677ea..a5af1a5b3b7f 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/basic/test.ts @@ -23,7 +23,7 @@ sentryTest('Basic test with eviction, update, and no async tasks', async ({ getL await page.goto(url); await page.evaluate(bufferSize => { - const client = (window as any).initialize(); + const client = (window as any).openFeatureClient; for (let i = 1; i <= bufferSize; i++) { client.getBooleanValue(`feat${i}`, false); } diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/init.js index 971e08755fe6..606717d69dd7 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/init.js @@ -1,20 +1,25 @@ import * as Sentry from '@sentry/browser'; +window.openFeatureClient = { + _hooks: [], + + getBooleanValue(flag, value) { + this._hooks.forEach(hook => { + hook.error({ flagKey: flag, defaultValue: false }, new Error('flag eval error')); + }); + return value; + }, + + addHooks(...hooks) { + this._hooks = [...this._hooks, ...hooks]; + }, +}; + window.Sentry = Sentry; -window.sentryOpenFeatureIntegration = Sentry.openFeatureIntegration(); +window.sentryOpenFeatureIntegration = Sentry.openFeatureIntegration(window.openFeatureClient); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', sampleRate: 1.0, integrations: [window.sentryOpenFeatureIntegration], }); - -window.initialize = () => { - return { - getBooleanValue(flag, value) { - let hook = new Sentry.OpenFeatureIntegrationHook(); - hook.error({ flagKey: flag, defaultValue: false }, new Error('flag eval error')); - return value; - }, - }; -}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts index 719782d0b0ab..402f2fb48eeb 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/errorHook/test.ts @@ -23,7 +23,7 @@ sentryTest('Flag evaluation error hook', async ({ getLocalTestUrl, page }) => { await page.goto(url); await page.evaluate(bufferSize => { - const client = (window as any).initialize(); + const client = (window as any).openFeatureClient; for (let i = 1; i <= bufferSize; i++) { client.getBooleanValue(`feat${i}`, false); } diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/init.js index b2b48519b8a9..fdad88250cb7 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/init.js @@ -1,20 +1,25 @@ import * as Sentry from '@sentry/browser'; +window.openFeatureClient = { + _hooks: [], + + getBooleanValue(flag, value) { + this._hooks.forEach(hook => { + hook.after(null, { flagKey: flag, value: value }); + }); + return value; + }, + + addHooks(...hooks) { + this._hooks = [...this._hooks, ...hooks]; + }, +}; + window.Sentry = Sentry; -window.sentryOpenFeatureIntegration = Sentry.openFeatureIntegration(); +window.sentryOpenFeatureIntegration = Sentry.openFeatureIntegration(window.openFeatureClient); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', sampleRate: 1.0, integrations: [window.sentryOpenFeatureIntegration], }); - -window.initialize = () => { - return { - getBooleanValue(flag, value) { - let hook = new Sentry.OpenFeatureIntegrationHook(); - hook.after(null, { flagKey: flag, value: value }); - return value; - }, - }; -}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/withScope/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/withScope/test.ts index 7edb9b2e533b..48cdf2c32a68 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/withScope/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/openfeature/withScope/test.ts @@ -28,7 +28,7 @@ sentryTest('Flag evaluations in forked scopes are stored separately.', async ({ await page.evaluate(() => { const Sentry = (window as any).Sentry; const errorButton = document.querySelector('#error') as HTMLButtonElement; - const client = (window as any).initialize(); + const client = (window as any).openFeatureClient; client.getBooleanValue('shared', true); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index e6f57c13fe6b..907361601498 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -74,4 +74,4 @@ export { type FeatureFlagsIntegration, } from './integrations/featureFlags'; export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly'; -export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature'; +export { openFeatureIntegration } from './integrations/featureFlags/openfeature'; diff --git a/packages/browser/src/integrations/featureFlags/openfeature/index.ts b/packages/browser/src/integrations/featureFlags/openfeature/index.ts index e3d425aeac29..9c14fad73798 100644 --- a/packages/browser/src/integrations/featureFlags/openfeature/index.ts +++ b/packages/browser/src/integrations/featureFlags/openfeature/index.ts @@ -1 +1 @@ -export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integration'; +export { openFeatureIntegration } from './integration'; diff --git a/packages/browser/src/integrations/featureFlags/openfeature/integration.ts b/packages/browser/src/integrations/featureFlags/openfeature/integration.ts index 5b5b56d65d18..7d9994be2091 100644 --- a/packages/browser/src/integrations/featureFlags/openfeature/integration.ts +++ b/packages/browser/src/integrations/featureFlags/openfeature/integration.ts @@ -1,30 +1,48 @@ -/** - * OpenFeature integration. - * - * Add the openFeatureIntegration() function call to your integration lists. - * Add the integration hook to your OpenFeature object. - * - OpenFeature.getClient().addHooks(new OpenFeatureIntegrationHook()); - */ import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core'; -import type { EvaluationDetails, HookContext, HookHints, JsonValue, OpenFeatureHook } from './types'; +import type { EvaluationDetails, HookContext, HookHints, JsonValue, OpenFeatureClient, OpenFeatureHook } from './types'; import { defineIntegration } from '@sentry/core'; import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags'; -export const openFeatureIntegration = defineIntegration(() => { +/** + * Sentry integration for capturing feature flags from the OpenFeature SDK. + * + * See the [feature flag documentation](https://develop.sentry.dev/sdk/expected-features/#feature-flags) for more information. + * + * @example + * ``` + * import * as Sentry from '@sentry/browser'; + * import { OpenFeature } from '@openfeature/web-sdk'; + * + * OpenFeature.setProvider(new MyProviderOfChoice()); + * const client = OpenFeature.getClient(); + * const openFeatureIntegration = Sentry.openFeatureIntegration({openFeatureClient: client}); + * + * Sentry.init({ + * dsn: '___PUBLIC_DSN___', + * integrations: [openFeatureIntegration] + * }); + * ``` + */ +export const openFeatureIntegration = defineIntegration((openFeatureClient: OpenFeatureClient) => { return { name: 'OpenFeature', processEvent(event: Event, _hint: EventHint, _client: Client): Event { return copyFlagsFromScopeToEvent(event); }, + + setupOnce() { + openFeatureClient.addHooks(new OpenFeatureIntegrationHook()); + }, }; }) satisfies IntegrationFn; /** - * OpenFeature Hook class implementation. + * OpenFeatureHook class implementation. Listens for flag evaluations and + * updates the `flags` context in our Sentry scope. */ -export class OpenFeatureIntegrationHook implements OpenFeatureHook { +class OpenFeatureIntegrationHook implements OpenFeatureHook { /** * Successful evaluation result. */ diff --git a/packages/browser/src/integrations/featureFlags/openfeature/types.ts b/packages/browser/src/integrations/featureFlags/openfeature/types.ts index 835e684d86eb..18001f3f7386 100644 --- a/packages/browser/src/integrations/featureFlags/openfeature/types.ts +++ b/packages/browser/src/integrations/featureFlags/openfeature/types.ts @@ -87,3 +87,7 @@ export interface BaseHook>, hookHints?: HookHints): HooksReturn; } export type OpenFeatureHook = BaseHook; + +export interface OpenFeatureClient { + addHooks(...hooks: OpenFeatureHook[]): OpenFeatureClient; +}