Skip to content

Commit ff684eb

Browse files
committed
add handler, types and shim
1 parent 7d2bec8 commit ff684eb

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core';
2+
import {
3+
_INTERNAL_addFeatureFlagToActiveSpan,
4+
_INTERNAL_copyFlagsFromScopeToEvent,
5+
_INTERNAL_insertFlagToScope,
6+
defineIntegration,
7+
fill,
8+
} from '@sentry/core';
9+
10+
import type { GrowthBook, GrowthBookClass } from './types';
11+
12+
/**
13+
* Sentry integration for capturing feature flag evaluations from GrowthBook.
14+
*
15+
* See the feature flag documentation: https://develop.sentry.dev/sdk/expected-features/#feature-flags
16+
*
17+
* @example
18+
* ```
19+
* import { GrowthBook } from '@growthbook/growthbook';
20+
* import * as Sentry from '@sentry/browser';
21+
*
22+
* Sentry.init({
23+
* dsn: '___PUBLIC_DSN___',
24+
* integrations: [Sentry.growthbookIntegration({ growthbookClass: GrowthBook })],
25+
* });
26+
*
27+
* const gb = new GrowthBook();
28+
* gb.isOn('my-feature');
29+
* Sentry.captureException(new Error('something went wrong'));
30+
* ```
31+
*/
32+
export const growthbookIntegration = defineIntegration(({ growthbookClass }: { growthbookClass: GrowthBookClass }) => {
33+
return {
34+
name: 'GrowthBook',
35+
36+
setupOnce() {
37+
const proto = growthbookClass.prototype as GrowthBook;
38+
fill(proto, 'isOn', _wrapBooleanReturningMethod);
39+
fill(proto, 'getFeatureValue', _wrapBooleanReturningMethod);
40+
},
41+
42+
processEvent(event: Event, _hint: EventHint, _client: Client): Event {
43+
return _INTERNAL_copyFlagsFromScopeToEvent(event);
44+
},
45+
};
46+
}) satisfies IntegrationFn;
47+
48+
function _wrapBooleanReturningMethod(
49+
original: (this: GrowthBook, ...args: unknown[]) => unknown,
50+
): (this: GrowthBook, ...args: unknown[]) => unknown {
51+
return function (this: GrowthBook, ...args: unknown[]): unknown {
52+
const flagName = args[0];
53+
const result = original.apply(this, args);
54+
// Capture any JSON-serializable result (booleans, strings, numbers, null, plain objects/arrays).
55+
// Skip functions/symbols/undefined.
56+
if (
57+
typeof flagName === 'string' &&
58+
typeof result !== 'undefined' &&
59+
typeof result !== 'function' &&
60+
typeof result !== 'symbol'
61+
) {
62+
_INTERNAL_insertFlagToScope(flagName, result);
63+
_INTERNAL_addFeatureFlagToActiveSpan(flagName, result);
64+
}
65+
return result;
66+
};
67+
}
68+
69+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface GrowthBook {
2+
isOn(this: GrowthBook, featureKey: string): boolean;
3+
getFeatureValue(this: GrowthBook, featureKey: string, defaultValue: unknown): unknown;
4+
}
5+
6+
// We only depend on the surface we wrap; constructor args are irrelevant here.
7+
export type GrowthBookClass = new (...args: unknown[]) => GrowthBook;
8+
9+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
3+
/**
4+
* Shim for the GrowthBook integration to avoid runtime errors when imported on the server.
5+
*/
6+
export const growthbookIntegrationShim = defineIntegration((_options?: unknown) => {
7+
if (!isBrowser()) {
8+
consoleSandbox(() => {
9+
// eslint-disable-next-line no-console
10+
console.warn('The growthbookIntegration() can only be used in the browser.');
11+
});
12+
}
13+
14+
return {
15+
name: 'GrowthBook',
16+
};
17+
});
18+
19+

0 commit comments

Comments
 (0)