Skip to content

Commit a79d0ed

Browse files
committed
move gb integration to core
1 parent e07bab4 commit a79d0ed

File tree

6 files changed

+95
-62
lines changed

6 files changed

+95
-62
lines changed
Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
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-
import type { GrowthBook, GrowthBookClass } from './types';
1+
import type { IntegrationFn } from '@sentry/core';
2+
import * as SentryCore from '@sentry/core';
3+
import type { GrowthBookClass } from './types';
104

115
/**
126
* Sentry integration for capturing feature flag evaluations from GrowthBook.
@@ -28,43 +22,7 @@ import type { GrowthBook, GrowthBookClass } from './types';
2822
* Sentry.captureException(new Error('something went wrong'));
2923
* ```
3024
*/
31-
export const growthbookIntegration = defineIntegration(({ growthbookClass }: { growthbookClass: GrowthBookClass }) => {
32-
return {
33-
name: 'GrowthBook',
25+
const _coreAny = SentryCore as unknown as Record<string, any>;
3426

35-
setupOnce() {
36-
const proto = growthbookClass.prototype as GrowthBook;
37-
fill(proto, 'isOn', _wrapBooleanReturningMethod);
38-
fill(proto, 'getFeatureValue', _wrapBooleanReturningMethod);
39-
// Also capture evalFeature when present. Not all versions have it, so guard.
40-
if (typeof (proto as unknown as Record<string, unknown>).evalFeature === 'function') {
41-
fill(proto as any, 'evalFeature', _wrapBooleanReturningMethod as any);
42-
}
43-
},
44-
45-
processEvent(event: Event, _hint: EventHint, _client: Client): Event {
46-
return _INTERNAL_copyFlagsFromScopeToEvent(event);
47-
},
48-
};
49-
}) satisfies IntegrationFn;
50-
51-
function _wrapBooleanReturningMethod(
52-
original: (this: GrowthBook, ...args: unknown[]) => unknown,
53-
): (this: GrowthBook, ...args: unknown[]) => unknown {
54-
return function (this: GrowthBook, ...args: unknown[]): unknown {
55-
const flagName = args[0];
56-
const result = original.apply(this, args);
57-
// Capture any JSON-serializable result (booleans, strings, numbers, null, plain objects/arrays).
58-
// Skip functions/symbols/undefined.
59-
if (
60-
typeof flagName === 'string' &&
61-
typeof result !== 'undefined' &&
62-
typeof result !== 'function' &&
63-
typeof result !== 'symbol'
64-
) {
65-
_INTERNAL_insertFlagToScope(flagName, result);
66-
_INTERNAL_addFeatureFlagToActiveSpan(flagName, result);
67-
}
68-
return result;
69-
};
70-
}
27+
export const growthbookIntegration = (({ growthbookClass }: { growthbookClass: GrowthBookClass }) =>
28+
_coreAny.growthbookIntegration({ growthbookClass })) satisfies IntegrationFn;

packages/browser/src/integrations/featureFlags/growthbook/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface GrowthBook {
2-
isOn(this: GrowthBook, featureKey: string): boolean;
3-
getFeatureValue(this: GrowthBook, featureKey: string, defaultValue: unknown): unknown;
2+
isOn(this: GrowthBook, featureKey: string, ...rest: unknown[]): boolean;
3+
getFeatureValue(this: GrowthBook, featureKey: string, defaultValue: unknown, ...rest: unknown[]): unknown;
44
}
55

66
// We only depend on the surface we wrap; constructor args are irrelevant here.

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export { zodErrorsIntegration } from './integrations/zoderrors';
111111
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
112112
export { consoleIntegration } from './integrations/console';
113113
export { featureFlagsIntegration, type FeatureFlagsIntegration } from './integrations/featureFlags';
114+
export { growthbookIntegration } from './integrations/featureFlags';
114115

115116
export { profiler } from './profiling';
116117
export { instrumentFetchRequest } from './fetch';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { Client } from '../../client';
2+
import { defineIntegration } from '../../integration';
3+
import type { Event, EventHint } from '../../types-hoist/event';
4+
import type { IntegrationFn } from '../../types-hoist/integration';
5+
import {
6+
_INTERNAL_addFeatureFlagToActiveSpan,
7+
_INTERNAL_copyFlagsFromScopeToEvent,
8+
_INTERNAL_insertFlagToScope,
9+
} from '../../utils/featureFlags';
10+
import { fill } from '../../utils/object';
11+
12+
interface GrowthBookLike {
13+
isOn(this: GrowthBookLike, featureKey: string, ...rest: unknown[]): boolean;
14+
getFeatureValue(this: GrowthBookLike, featureKey: string, defaultValue: unknown, ...rest: unknown[]): unknown;
15+
}
16+
17+
export type GrowthBookClassLike = new (...args: unknown[]) => GrowthBookLike;
18+
19+
/**
20+
* Sentry integration for capturing feature flag evaluations from GrowthBook.
21+
*
22+
* Only boolean results are captured at this time.
23+
*/
24+
export const growthbookIntegration = defineIntegration(
25+
({ growthbookClass }: { growthbookClass: GrowthBookClassLike }) => {
26+
return {
27+
name: 'GrowthBook',
28+
29+
setupOnce() {
30+
const proto = growthbookClass.prototype as GrowthBookLike;
31+
32+
// Type guard and wrap isOn
33+
if (typeof proto.isOn === 'function') {
34+
fill(proto, 'isOn', _wrapAndCaptureBooleanResult);
35+
}
36+
37+
// Type guard and wrap getFeatureValue
38+
if (typeof proto.getFeatureValue === 'function') {
39+
fill(proto, 'getFeatureValue', _wrapAndCaptureBooleanResult);
40+
}
41+
42+
// Type guard and wrap evalFeature if present
43+
const maybeEval = (proto as unknown as Record<string, unknown>).evalFeature;
44+
if (typeof maybeEval === 'function') {
45+
fill(proto as unknown as Record<string, unknown>, 'evalFeature', _wrapAndCaptureBooleanResult as any);
46+
}
47+
},
48+
49+
processEvent(event: Event, _hint: EventHint, _client: Client): Event {
50+
return _INTERNAL_copyFlagsFromScopeToEvent(event);
51+
},
52+
};
53+
},
54+
) satisfies IntegrationFn;
55+
56+
function _wrapAndCaptureBooleanResult(
57+
original: (this: GrowthBookLike, ...args: unknown[]) => unknown,
58+
): (this: GrowthBookLike, ...args: unknown[]) => unknown {
59+
return function (this: GrowthBookLike, ...args: unknown[]): unknown {
60+
const flagName = args[0];
61+
const result = original.apply(this, args);
62+
63+
if (typeof flagName === 'string' && typeof result === 'boolean') {
64+
_INTERNAL_insertFlagToScope(flagName, result);
65+
_INTERNAL_addFeatureFlagToActiveSpan(flagName, result);
66+
}
67+
68+
return result;
69+
};
70+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { featureFlagsIntegration, type FeatureFlagsIntegration } from './featureFlagsIntegration';
2+
export { growthbookIntegration } from './growthbook';
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
1+
import {
2+
defineIntegration,
3+
growthbookIntegration as coreGrowthbookIntegration,
4+
isBrowser,
5+
} from '@sentry/core';
26

37
/**
48
* Shim for the GrowthBook integration to avoid runtime errors when imported on the server.
59
*/
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-
}
10+
export const growthbookIntegrationShim = defineIntegration(
11+
(options: Parameters<typeof coreGrowthbookIntegration>[0]) => {
12+
if (!isBrowser()) {
13+
// On Node, just return the core integration so Node SDKs can also use it.
14+
return coreGrowthbookIntegration(options);
15+
}
1316

14-
return {
15-
name: 'GrowthBook',
16-
};
17-
});
17+
// In browser, still return the integration to preserve behavior.
18+
return coreGrowthbookIntegration(options);
19+
},
20+
);

0 commit comments

Comments
 (0)