Skip to content

Commit f16380f

Browse files
committed
change priority order for SENTRY_SPOTLIGHT
1 parent 6cb5513 commit f16380f

File tree

3 files changed

+92
-56
lines changed

3 files changed

+92
-56
lines changed

packages/browser/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,18 @@ type BrowserSpecificOptions = BrowserClientReplayOptions &
7070
*
7171
* Either set it to true, or provide a specific Spotlight Sidecar URL.
7272
*
73-
* Alternatively, you can configure Spotlight using environment variables:
74-
* - SENTRY_SPOTLIGHT (base/official name)
73+
* Alternatively, you can configure Spotlight using environment variables (checked in this order):
7574
* - PUBLIC_SENTRY_SPOTLIGHT (SvelteKit, Astro, Qwik)
7675
* - NEXT_PUBLIC_SENTRY_SPOTLIGHT (Next.js)
7776
* - VITE_SENTRY_SPOTLIGHT (Vite)
7877
* - NUXT_PUBLIC_SENTRY_SPOTLIGHT (Nuxt)
7978
* - REACT_APP_SENTRY_SPOTLIGHT (Create React App)
8079
* - VUE_APP_SENTRY_SPOTLIGHT (Vue CLI)
8180
* - GATSBY_SENTRY_SPOTLIGHT (Gatsby)
81+
* - SENTRY_SPOTLIGHT (fallback for non-framework setups)
82+
*
83+
* Framework-specific vars have higher priority to support Docker Compose setups where
84+
* backend uses SENTRY_SPOTLIGHT with Docker hostnames while frontend needs localhost.
8285
*
8386
* Precedence rules:
8487
* - If this option is `false`, Spotlight is disabled (env vars ignored)

packages/browser/src/utils/spotlightConfig.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@ import { getEnvValue } from './env';
55
/**
66
* Environment variable keys to check for Spotlight configuration, in priority order.
77
* The first one found with a value will be used.
8+
*
9+
* IMPORTANT: Framework-specific variables (PUBLIC_*, NEXT_PUBLIC_*, etc.) are prioritized
10+
* over the generic SENTRY_SPOTLIGHT to support Docker Compose setups where:
11+
* - Backend services need SENTRY_SPOTLIGHT=http://host.internal.docker:8969/stream
12+
* - Frontend code needs localhost (via framework-specific vars like NEXT_PUBLIC_SENTRY_SPOTLIGHT=http://localhost:8969/stream)
13+
*
14+
* SENTRY_SPOTLIGHT is kept as a fallback for:
15+
* - Simple non-Docker setups
16+
* - Remote Spotlight instances when no framework-specific var is set
817
*/
918
const SPOTLIGHT_ENV_KEYS = [
10-
'SENTRY_SPOTLIGHT', // Base/official name - works in Parcel, Webpack, Rspack, Rollup, Rolldown, Node.js
1119
'PUBLIC_SENTRY_SPOTLIGHT', // SvelteKit, Astro, Qwik
1220
'NEXT_PUBLIC_SENTRY_SPOTLIGHT', // Next.js
1321
'VITE_SENTRY_SPOTLIGHT', // Vite
1422
'NUXT_PUBLIC_SENTRY_SPOTLIGHT', // Nuxt
1523
'REACT_APP_SENTRY_SPOTLIGHT', // Create React App
1624
'VUE_APP_SENTRY_SPOTLIGHT', // Vue CLI
1725
'GATSBY_SENTRY_SPOTLIGHT', // Gatsby
26+
'SENTRY_SPOTLIGHT', // Fallback/base name - works in Parcel, Webpack, Rspack, Rollup, Rolldown, Node.js
1827
] as const;
1928

2029
/**

packages/browser/test/utils/spotlightConfig.test.ts

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,14 @@ describe('getSpotlightConfig', () => {
1616
if (originalProcess !== undefined) {
1717
globalThis.process = originalProcess;
1818
} else {
19-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20-
delete (globalThis as any).process;
19+
delete (globalThis as typeof globalThis & { process?: NodeJS.Process }).process;
2120
}
2221
});
2322

2423
it('returns undefined when no environment variables are set', () => {
2524
globalThis.process = {
2625
env: {},
27-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28-
} as any;
26+
} as NodeJS.Process;
2927

3028
expect(getSpotlightConfig()).toBeUndefined();
3129
});
@@ -34,9 +32,8 @@ describe('getSpotlightConfig', () => {
3432
globalThis.process = {
3533
env: {
3634
SENTRY_SPOTLIGHT: 'true',
37-
},
38-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39-
} as any;
35+
} as Record<string, string>,
36+
} as NodeJS.Process;
4037

4138
expect(getSpotlightConfig()).toBe(true);
4239
});
@@ -45,9 +42,8 @@ describe('getSpotlightConfig', () => {
4542
globalThis.process = {
4643
env: {
4744
SENTRY_SPOTLIGHT: 'false',
48-
},
49-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
50-
} as any;
45+
} as Record<string, string>,
46+
} as NodeJS.Process;
5147

5248
expect(getSpotlightConfig()).toBe(false);
5349
});
@@ -57,9 +53,8 @@ describe('getSpotlightConfig', () => {
5753
globalThis.process = {
5854
env: {
5955
SENTRY_SPOTLIGHT: customUrl,
60-
},
61-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
62-
} as any;
56+
} as Record<string, string>,
57+
} as NodeJS.Process;
6358

6459
expect(getSpotlightConfig()).toBe(customUrl);
6560
});
@@ -71,9 +66,8 @@ describe('getSpotlightConfig', () => {
7166
globalThis.process = {
7267
env: {
7368
SENTRY_SPOTLIGHT: value,
74-
},
75-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
76-
} as any;
69+
} as Record<string, string>,
70+
} as NodeJS.Process;
7771

7872
expect(getSpotlightConfig()).toBe(true);
7973
});
@@ -86,23 +80,21 @@ describe('getSpotlightConfig', () => {
8680
globalThis.process = {
8781
env: {
8882
SENTRY_SPOTLIGHT: value,
89-
},
90-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
91-
} as any;
83+
} as Record<string, string>,
84+
} as NodeJS.Process;
9285

9386
expect(getSpotlightConfig()).toBe(false);
9487
});
9588
});
9689

9790
describe('priority order', () => {
98-
it('prioritizes SENTRY_SPOTLIGHT over PUBLIC_SENTRY_SPOTLIGHT', () => {
91+
it('prioritizes PUBLIC_SENTRY_SPOTLIGHT over SENTRY_SPOTLIGHT', () => {
9992
globalThis.process = {
10093
env: {
101-
SENTRY_SPOTLIGHT: 'true',
102-
PUBLIC_SENTRY_SPOTLIGHT: 'false',
103-
},
104-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
105-
} as any;
94+
PUBLIC_SENTRY_SPOTLIGHT: 'true',
95+
SENTRY_SPOTLIGHT: 'false',
96+
} as Record<string, string>,
97+
} as NodeJS.Process;
10698

10799
expect(getSpotlightConfig()).toBe(true);
108100
});
@@ -112,9 +104,8 @@ describe('getSpotlightConfig', () => {
112104
env: {
113105
PUBLIC_SENTRY_SPOTLIGHT: 'true',
114106
NEXT_PUBLIC_SENTRY_SPOTLIGHT: 'false',
115-
},
116-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
117-
} as any;
107+
} as Record<string, string>,
108+
} as NodeJS.Process;
118109

119110
expect(getSpotlightConfig()).toBe(true);
120111
});
@@ -124,9 +115,8 @@ describe('getSpotlightConfig', () => {
124115
env: {
125116
NEXT_PUBLIC_SENTRY_SPOTLIGHT: 'true',
126117
VITE_SENTRY_SPOTLIGHT: 'false',
127-
},
128-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
129-
} as any;
118+
} as Record<string, string>,
119+
} as NodeJS.Process;
130120

131121
expect(getSpotlightConfig()).toBe(true);
132122
});
@@ -136,9 +126,8 @@ describe('getSpotlightConfig', () => {
136126
env: {
137127
VITE_SENTRY_SPOTLIGHT: 'true',
138128
NUXT_PUBLIC_SENTRY_SPOTLIGHT: 'false',
139-
},
140-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
141-
} as any;
129+
} as Record<string, string>,
130+
} as NodeJS.Process;
142131

143132
expect(getSpotlightConfig()).toBe(true);
144133
});
@@ -148,9 +137,8 @@ describe('getSpotlightConfig', () => {
148137
env: {
149138
NUXT_PUBLIC_SENTRY_SPOTLIGHT: 'true',
150139
REACT_APP_SENTRY_SPOTLIGHT: 'false',
151-
},
152-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153-
} as any;
140+
} as Record<string, string>,
141+
} as NodeJS.Process;
154142

155143
expect(getSpotlightConfig()).toBe(true);
156144
});
@@ -160,9 +148,8 @@ describe('getSpotlightConfig', () => {
160148
env: {
161149
REACT_APP_SENTRY_SPOTLIGHT: 'true',
162150
VUE_APP_SENTRY_SPOTLIGHT: 'false',
163-
},
164-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
165-
} as any;
151+
} as Record<string, string>,
152+
} as NodeJS.Process;
166153

167154
expect(getSpotlightConfig()).toBe(true);
168155
});
@@ -172,9 +159,8 @@ describe('getSpotlightConfig', () => {
172159
env: {
173160
VUE_APP_SENTRY_SPOTLIGHT: 'true',
174161
GATSBY_SENTRY_SPOTLIGHT: 'false',
175-
},
176-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
177-
} as any;
162+
} as Record<string, string>,
163+
} as NodeJS.Process;
178164

179165
expect(getSpotlightConfig()).toBe(true);
180166
});
@@ -183,9 +169,19 @@ describe('getSpotlightConfig', () => {
183169
globalThis.process = {
184170
env: {
185171
GATSBY_SENTRY_SPOTLIGHT: 'true',
186-
},
187-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
188-
} as any;
172+
SENTRY_SPOTLIGHT: 'false',
173+
} as Record<string, string>,
174+
} as NodeJS.Process;
175+
176+
expect(getSpotlightConfig()).toBe(true);
177+
});
178+
179+
it('uses SENTRY_SPOTLIGHT as fallback when no framework-specific vars are set', () => {
180+
globalThis.process = {
181+
env: {
182+
SENTRY_SPOTLIGHT: 'true',
183+
} as Record<string, string>,
184+
} as NodeJS.Process;
189185

190186
expect(getSpotlightConfig()).toBe(true);
191187
});
@@ -198,25 +194,53 @@ describe('getSpotlightConfig', () => {
198194
env: {
199195
PUBLIC_SENTRY_SPOTLIGHT: highPriorityUrl,
200196
GATSBY_SENTRY_SPOTLIGHT: lowPriorityUrl,
201-
},
202-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
203-
} as any;
197+
} as Record<string, string>,
198+
} as NodeJS.Process;
204199

205200
expect(getSpotlightConfig()).toBe(highPriorityUrl);
206201
});
202+
203+
it('prioritizes framework-specific URL over SENTRY_SPOTLIGHT URL (Docker Compose scenario)', () => {
204+
// Simulates Docker Compose setup where:
205+
// - SENTRY_SPOTLIGHT is set for backend services with Docker hostname
206+
// - Framework-specific var is set for frontend with localhost
207+
const dockerHostUrl = 'http://host.docker.internal:8969/stream';
208+
const localhostUrl = 'http://localhost:8969/stream';
209+
210+
globalThis.process = {
211+
env: {
212+
NEXT_PUBLIC_SENTRY_SPOTLIGHT: localhostUrl,
213+
SENTRY_SPOTLIGHT: dockerHostUrl,
214+
} as Record<string, string>,
215+
} as NodeJS.Process;
216+
217+
// Framework-specific var should be used, not SENTRY_SPOTLIGHT
218+
expect(getSpotlightConfig()).toBe(localhostUrl);
219+
});
220+
221+
it('uses SENTRY_SPOTLIGHT URL when no framework-specific vars are set (remote Spotlight)', () => {
222+
const remoteUrl = 'http://remote-spotlight.example.com:8969/stream';
223+
224+
globalThis.process = {
225+
env: {
226+
SENTRY_SPOTLIGHT: remoteUrl,
227+
} as Record<string, string>,
228+
} as NodeJS.Process;
229+
230+
// Should use SENTRY_SPOTLIGHT as fallback
231+
expect(getSpotlightConfig()).toBe(remoteUrl);
232+
});
207233
});
208234

209235
it('handles missing process object gracefully', () => {
210-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
211-
delete (globalThis as any).process;
236+
delete (globalThis as typeof globalThis & { process?: NodeJS.Process }).process;
212237

213238
expect(() => getSpotlightConfig()).not.toThrow();
214239
expect(getSpotlightConfig()).toBeUndefined();
215240
});
216241

217242
it('handles missing process.env gracefully', () => {
218-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
219-
globalThis.process = {} as any;
243+
globalThis.process = {} as NodeJS.Process;
220244

221245
expect(() => getSpotlightConfig()).not.toThrow();
222246
expect(getSpotlightConfig()).toBeUndefined();

0 commit comments

Comments
 (0)