Skip to content

Commit dccf0aa

Browse files
authored
feat(node): Export server-side feature flag integration shims (#16735)
1 parent f2f8e1f commit dccf0aa

File tree

22 files changed

+284
-0
lines changed

22 files changed

+284
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default async function FeatureFlagServerComponent() {
6+
Sentry.buildLaunchDarklyFlagUsedHandler();
7+
Sentry.launchDarklyIntegration();
8+
Sentry.openFeatureIntegration();
9+
new Sentry.OpenFeatureIntegrationHook();
10+
// @ts-ignore - we just want to test that the statsigIntegration is imported
11+
Sentry.statsigIntegration();
12+
// @ts-ignore - we just want to test that the unleashIntegration is imported
13+
Sentry.unleashIntegration();
14+
15+
return <div>FeatureFlagServerComponent</div>;
16+
}

dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,9 @@ test('Should capture an error and transaction for a app router page', async ({ p
133133
}),
134134
);
135135
});
136+
137+
test('Should not throw error on server component when importing shimmed feature flag function', async ({ page }) => {
138+
await page.goto('/server-component/featureFlag');
139+
// tests that none of the feature flag functions throw an error when imported in a node environment
140+
await expect(page.locator('body')).toContainText('FeatureFlagServerComponent');
141+
});

packages/astro/src/index.server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ export {
138138
NODE_VERSION,
139139
featureFlagsIntegration,
140140
type FeatureFlagsIntegration,
141+
launchDarklyIntegration,
142+
buildLaunchDarklyFlagUsedHandler,
143+
openFeatureIntegration,
144+
OpenFeatureIntegrationHook,
145+
statsigIntegration,
146+
unleashIntegration,
141147
} from '@sentry/node';
142148

143149
export { init } from './server/sdk';

packages/astro/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ export declare const Span: clientSdk.Span;
2929

3030
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
3131

32+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
33+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
34+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
35+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
36+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
37+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;
38+
3239
export default sentryAstro;

packages/aws-serverless/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export {
124124
NODE_VERSION,
125125
featureFlagsIntegration,
126126
type FeatureFlagsIntegration,
127+
launchDarklyIntegration,
128+
buildLaunchDarklyFlagUsedHandler,
129+
openFeatureIntegration,
130+
OpenFeatureIntegrationHook,
131+
statsigIntegration,
132+
unleashIntegration,
127133
} from '@sentry/node';
128134

129135
export {

packages/bun/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ export {
142142
createSentryWinstonTransport,
143143
wrapMcpServerWithSentry,
144144
featureFlagsIntegration,
145+
launchDarklyIntegration,
146+
buildLaunchDarklyFlagUsedHandler,
147+
openFeatureIntegration,
148+
OpenFeatureIntegrationHook,
149+
statsigIntegration,
150+
unleashIntegration,
145151
} from '@sentry/node';
146152

147153
export {

packages/google-cloud-serverless/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export {
124124
NODE_VERSION,
125125
featureFlagsIntegration,
126126
type FeatureFlagsIntegration,
127+
launchDarklyIntegration,
128+
buildLaunchDarklyFlagUsedHandler,
129+
openFeatureIntegration,
130+
OpenFeatureIntegrationHook,
131+
statsigIntegration,
132+
unleashIntegration,
127133
} from '@sentry/node';
128134

129135
export {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { feedbackIntegrationShim } from './Feedback';
22
export { replayIntegrationShim } from './Replay';
33
export { browserTracingIntegrationShim } from './BrowserTracing';
4+
export { launchDarklyIntegrationShim, buildLaunchDarklyFlagUsedHandlerShim } from './launchDarkly';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
import { FAKE_FUNCTION } from './common';
3+
4+
/**
5+
* This is a shim for the LaunchDarkly integration.
6+
* We need this in order to not throw runtime errors when accidentally importing this on the server through a meta framework like Next.js.
7+
*/
8+
export const launchDarklyIntegrationShim = defineIntegration((_options?: unknown) => {
9+
if (!isBrowser()) {
10+
consoleSandbox(() => {
11+
// eslint-disable-next-line no-console
12+
console.warn('The launchDarklyIntegration() can only be used in the browser.');
13+
});
14+
}
15+
16+
return {
17+
name: 'LaunchDarkly',
18+
};
19+
});
20+
21+
/**
22+
* This is a shim for the LaunchDarkly flag used handler.
23+
*/
24+
export function buildLaunchDarklyFlagUsedHandlerShim(): unknown {
25+
if (!isBrowser()) {
26+
consoleSandbox(() => {
27+
// eslint-disable-next-line no-console
28+
console.warn('The buildLaunchDarklyFlagUsedHandler() can only be used in the browser.');
29+
});
30+
}
31+
32+
return {
33+
name: 'sentry-flag-auditor',
34+
type: 'flag-used',
35+
synchronous: true,
36+
method: FAKE_FUNCTION,
37+
};
38+
}

packages/nextjs/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,10 @@ export declare function wrapApiHandlerWithSentryVercelCrons<F extends (...args:
141141
export declare function wrapPageComponentWithSentry<C>(WrappingTarget: C): C;
142142

143143
export { captureRequestError } from './common/captureRequestError';
144+
145+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
146+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
147+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
148+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
149+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
150+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/node/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ export { amqplibIntegration } from './integrations/tracing/amqplib';
3737
export { vercelAIIntegration } from './integrations/tracing/vercelai';
3838
export { childProcessIntegration } from './integrations/childProcess';
3939
export { createSentryWinstonTransport } from './integrations/winston';
40+
export {
41+
launchDarklyIntegration,
42+
buildLaunchDarklyFlagUsedHandler,
43+
openFeatureIntegration,
44+
OpenFeatureIntegrationHook,
45+
statsigIntegration,
46+
unleashIntegration,
47+
} from './integrations/featureFlagShims';
4048

4149
export { SentryContextManager } from './otel/contextManager';
4250
export { generateInstrumentOnce } from './otel/instrument';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export {
2+
launchDarklyIntegrationShim as launchDarklyIntegration,
3+
buildLaunchDarklyFlagUsedHandlerShim as buildLaunchDarklyFlagUsedHandler,
4+
} from './launchDarkly';
5+
6+
export {
7+
openFeatureIntegrationShim as openFeatureIntegration,
8+
OpenFeatureIntegrationHookShim as OpenFeatureIntegrationHook,
9+
} from './openFeature';
10+
11+
export { statsigIntegrationShim as statsigIntegration } from './statsig';
12+
13+
export { unleashIntegrationShim as unleashIntegration } from './unleash';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
3+
/**
4+
* This is a shim for the LaunchDarkly integration.
5+
* We need this in order to not throw runtime errors when accidentally importing this on the server through a meta framework like Next.js.
6+
*/
7+
export const launchDarklyIntegrationShim = defineIntegration((_options?: unknown) => {
8+
if (!isBrowser()) {
9+
consoleSandbox(() => {
10+
// eslint-disable-next-line no-console
11+
console.warn('The launchDarklyIntegration() can only be used in the browser.');
12+
});
13+
}
14+
15+
return {
16+
name: 'LaunchDarkly',
17+
};
18+
});
19+
20+
/**
21+
* This is a shim for the LaunchDarkly flag used handler.
22+
*/
23+
export function buildLaunchDarklyFlagUsedHandlerShim(): unknown {
24+
if (!isBrowser()) {
25+
consoleSandbox(() => {
26+
// eslint-disable-next-line no-console
27+
console.warn('The buildLaunchDarklyFlagUsedHandler() can only be used in the browser.');
28+
});
29+
}
30+
31+
return {
32+
name: 'sentry-flag-auditor',
33+
type: 'flag-used',
34+
synchronous: true,
35+
method: () => null,
36+
};
37+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
3+
/**
4+
* This is a shim for the OpenFeature integration.
5+
* We need this in order to not throw runtime errors when accidentally importing this on the server through a meta framework like Next.js.
6+
*/
7+
export const openFeatureIntegrationShim = defineIntegration((_options?: unknown) => {
8+
if (!isBrowser()) {
9+
consoleSandbox(() => {
10+
// eslint-disable-next-line no-console
11+
console.warn('The openFeatureIntegration() can only be used in the browser.');
12+
});
13+
}
14+
15+
return {
16+
name: 'OpenFeature',
17+
};
18+
});
19+
20+
/**
21+
* This is a shim for the OpenFeature integration hook.
22+
*/
23+
export class OpenFeatureIntegrationHookShim {
24+
/**
25+
*
26+
*/
27+
public constructor() {
28+
if (!isBrowser()) {
29+
consoleSandbox(() => {
30+
// eslint-disable-next-line no-console
31+
console.warn('The OpenFeatureIntegrationHook can only be used in the browser.');
32+
});
33+
}
34+
}
35+
36+
/**
37+
*
38+
*/
39+
public after(): void {
40+
// No-op
41+
}
42+
43+
/**
44+
*
45+
*/
46+
public error(): void {
47+
// No-op
48+
}
49+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
3+
/**
4+
* This is a shim for the Statsig integration.
5+
* We need this in order to not throw runtime errors when accidentally importing this on the server through a meta framework like Next.js.
6+
*/
7+
export const statsigIntegrationShim = defineIntegration((_options?: unknown) => {
8+
if (!isBrowser()) {
9+
consoleSandbox(() => {
10+
// eslint-disable-next-line no-console
11+
console.warn('The statsigIntegration() can only be used in the browser.');
12+
});
13+
}
14+
15+
return {
16+
name: 'Statsig',
17+
};
18+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { consoleSandbox, defineIntegration, isBrowser } from '@sentry/core';
2+
3+
/**
4+
* This is a shim for the Unleash integration.
5+
* We need this in order to not throw runtime errors when accidentally importing this on the server through a meta framework like Next.js.
6+
*/
7+
export const unleashIntegrationShim = defineIntegration((_options?: unknown) => {
8+
if (!isBrowser()) {
9+
consoleSandbox(() => {
10+
// eslint-disable-next-line no-console
11+
console.warn('The unleashIntegration() can only be used in the browser.');
12+
});
13+
}
14+
15+
return {
16+
name: 'Unleash',
17+
};
18+
});

packages/nuxt/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ export declare const getDefaultIntegrations: (options: Options) => Integration[]
1818
export declare const defaultStackParser: StackParser;
1919

2020
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
21+
22+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
23+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
24+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
25+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
26+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
27+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/react-router/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ export declare const defaultStackParser: StackParser;
1818
export declare const getDefaultIntegrations: (options: Options) => Integration[];
1919

2020
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
21+
22+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
23+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
24+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
25+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
26+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
27+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/remix/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,10 @@ declare const runtime: 'client' | 'server';
3232
export const close = runtime === 'client' ? clientSdk.close : serverSdk.close;
3333
export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush;
3434
export const lastEventId = runtime === 'client' ? clientSdk.lastEventId : serverSdk.lastEventId;
35+
36+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
37+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
38+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
39+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
40+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
41+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/solidstart/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,10 @@ export declare function flush(timeout?: number | undefined): PromiseLike<boolean
2525
export declare function lastEventId(): string | undefined;
2626

2727
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
28+
29+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
30+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
31+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
32+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
33+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
34+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/sveltekit/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,10 @@ export declare function lastEventId(): string | undefined;
5959
export declare function trackComponent(options: clientSdk.TrackingOptions): ReturnType<typeof clientSdk.trackComponent>;
6060

6161
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
62+
63+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
64+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
65+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
66+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
67+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
68+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

packages/tanstackstart-react/src/index.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ export declare const showReportDialog: typeof clientSdk.showReportDialog;
2828
export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary;
2929

3030
export declare const logger: typeof clientSdk.logger | typeof serverSdk.logger;
31+
32+
export declare const launchDarklyIntegration: typeof clientSdk.launchDarklyIntegration;
33+
export declare const buildLaunchDarklyFlagUsedHandler: typeof clientSdk.buildLaunchDarklyFlagUsedHandler;
34+
export declare const openFeatureIntegration: typeof clientSdk.openFeatureIntegration;
35+
export declare const OpenFeatureIntegrationHook: typeof clientSdk.OpenFeatureIntegrationHook;
36+
export declare const statsigIntegration: typeof clientSdk.statsigIntegration;
37+
export declare const unleashIntegration: typeof clientSdk.unleashIntegration;

0 commit comments

Comments
 (0)