@@ -7,9 +7,19 @@ import {
77} from '../../internals/__mocks__/shared/api-service.mocks' ;
88import { AUTH0_SCOPE_HEADER , createProxyFetcher , createSpaFetcher } from '../api-utils' ;
99import { ContentType , HeaderName } from '../http-constants' ;
10+ import type { TelemetryConfig } from '../telemetry' ;
1011
1112import { stubFetch } from './__mocks__/api-utils.mocks' ;
1213
14+ const defaultTelemetry : TelemetryConfig = {
15+ css : 'unknown' ,
16+ distribution : 'npm' ,
17+ framework : 'react' ,
18+ enabled : true ,
19+ } ;
20+
21+ const mockGetComponent = ( ) => 'test-component' ;
22+
1323describe ( 'api-utils' , ( ) => {
1424 describe ( 'createProxyFetcher' , ( ) => {
1525 afterEach ( ( ) => {
@@ -18,7 +28,10 @@ describe('api-utils', () => {
1828
1929 it ( 'sets content-type header to application/json' , async ( ) => {
2030 const mockFetch = stubFetch ( ) ;
21- const fetcher = createProxyFetcher ( ) ;
31+ const fetcher = createProxyFetcher ( {
32+ telemetry : defaultTelemetry ,
33+ getComponent : mockGetComponent ,
34+ } ) ;
2235
2336 await fetcher ( 'https://example.com/api' , { method : 'POST' } , undefined ) ;
2437
@@ -28,7 +41,10 @@ describe('api-utils', () => {
2841
2942 it ( 'sets auth0-scope header when scope array is provided' , async ( ) => {
3043 const mockFetch = stubFetch ( ) ;
31- const fetcher = createProxyFetcher ( ) ;
44+ const fetcher = createProxyFetcher ( {
45+ telemetry : defaultTelemetry ,
46+ getComponent : mockGetComponent ,
47+ } ) ;
3248
3349 await fetcher (
3450 'https://example.com/api' ,
@@ -45,7 +61,10 @@ describe('api-utils', () => {
4561
4662 it ( 'does not set auth0-scope header when scope array is empty' , async ( ) => {
4763 const mockFetch = stubFetch ( ) ;
48- const fetcher = createProxyFetcher ( ) ;
64+ const fetcher = createProxyFetcher ( {
65+ telemetry : defaultTelemetry ,
66+ getComponent : mockGetComponent ,
67+ } ) ;
4968
5069 await fetcher ( 'https://example.com/api' , { method : 'GET' } , { scope : [ ] } ) ;
5170
@@ -55,7 +74,10 @@ describe('api-utils', () => {
5574
5675 it ( 'does not set auth0-scope header when authParams is undefined' , async ( ) => {
5776 const mockFetch = stubFetch ( ) ;
58- const fetcher = createProxyFetcher ( ) ;
77+ const fetcher = createProxyFetcher ( {
78+ telemetry : defaultTelemetry ,
79+ getComponent : mockGetComponent ,
80+ } ) ;
5981
6082 await fetcher ( 'https://example.com/api' , { method : 'GET' } , undefined ) ;
6183
@@ -65,7 +87,10 @@ describe('api-utils', () => {
6587
6688 it ( 'preserves existing headers from init' , async ( ) => {
6789 const mockFetch = stubFetch ( ) ;
68- const fetcher = createProxyFetcher ( ) ;
90+ const fetcher = createProxyFetcher ( {
91+ telemetry : defaultTelemetry ,
92+ getComponent : mockGetComponent ,
93+ } ) ;
6994 const customHeaders = new Headers ( { 'X-Custom' : 'value' } ) ;
7095
7196 await fetcher (
@@ -82,7 +107,10 @@ describe('api-utils', () => {
82107
83108 it ( 'preserves other init options' , async ( ) => {
84109 const mockFetch = stubFetch ( ) ;
85- const fetcher = createProxyFetcher ( ) ;
110+ const fetcher = createProxyFetcher ( {
111+ telemetry : defaultTelemetry ,
112+ getComponent : mockGetComponent ,
113+ } ) ;
86114 const body = JSON . stringify ( { data : 'test' } ) ;
87115
88116 await fetcher (
@@ -96,6 +124,75 @@ describe('api-utils', () => {
96124 expect ( requestInit ?. body ) . toBe ( body ) ;
97125 expect ( requestInit ?. credentials ) . toBe ( 'include' ) ;
98126 } ) ;
127+
128+ it ( 'sets Auth0-Client telemetry header with proxy mode' , async ( ) => {
129+ const mockFetch = stubFetch ( ) ;
130+ const fetcher = createProxyFetcher ( {
131+ telemetry : { css : 'tailwind' , distribution : 'npm' , framework : 'react' , enabled : true } ,
132+ getComponent : ( ) => 'user-mfa-management' ,
133+ } ) ;
134+
135+ await fetcher ( 'https://example.com/me/authentication-methods' , { method : 'GET' } , undefined ) ;
136+
137+ const [ , requestInit ] = mockFetch . mock . calls [ 0 ] ! ;
138+ const header = ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ;
139+ expect ( header ) . toBeTruthy ( ) ;
140+
141+ const decoded = JSON . parse ( atob ( header ! ) ) ;
142+ expect ( decoded . is_proxy_mode ) . toBe ( true ) ;
143+ expect ( decoded . component ) . toBe ( 'user-mfa-management' ) ;
144+ expect ( decoded . name ) . toBe ( 'universal-components' ) ;
145+ expect ( decoded . css ) . toBe ( 'tailwind' ) ;
146+ expect ( decoded . distribution ) . toBe ( 'npm' ) ;
147+ expect ( decoded . framework ) . toBe ( 'react' ) ;
148+ } ) ;
149+
150+ it ( 'uses component from getComponent callback' , async ( ) => {
151+ const mockFetch = stubFetch ( ) ;
152+ const fetcher = createProxyFetcher ( {
153+ telemetry : { css : 'scoped' , distribution : 'shadcn' , framework : 'react' , enabled : true } ,
154+ getComponent : ( ) => 'organization-sso-configuration' ,
155+ } ) ;
156+
157+ await fetcher ( 'https://example.com/my-org/identity-providers' , { method : 'GET' } , undefined ) ;
158+
159+ const [ , requestInit ] = mockFetch . mock . calls [ 0 ] ! ;
160+ const header = ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ;
161+ const decoded = JSON . parse ( atob ( header ! ) ) ;
162+ expect ( decoded . component ) . toBe ( 'organization-sso-configuration' ) ;
163+ expect ( decoded . css ) . toBe ( 'scoped' ) ;
164+ expect ( decoded . distribution ) . toBe ( 'shadcn' ) ;
165+ } ) ;
166+
167+ it ( 'uses custom fetcher when provided' , async ( ) => {
168+ const customFetcher = vi . fn ( ) . mockResolvedValue ( new Response ( ) ) ;
169+ const fetcher = createProxyFetcher ( {
170+ customFetcher,
171+ telemetry : defaultTelemetry ,
172+ getComponent : mockGetComponent ,
173+ } ) ;
174+
175+ await fetcher ( 'https://example.com/api' , { method : 'GET' } , undefined ) ;
176+
177+ expect ( customFetcher ) . toHaveBeenCalledWith (
178+ 'https://example.com/api' ,
179+ expect . objectContaining ( { method : 'GET' } ) ,
180+ undefined ,
181+ ) ;
182+ } ) ;
183+
184+ it ( 'does not set Auth0-Client header when telemetry is disabled' , async ( ) => {
185+ const mockFetch = stubFetch ( ) ;
186+ const fetcher = createProxyFetcher ( {
187+ telemetry : { ...defaultTelemetry , enabled : false } ,
188+ getComponent : mockGetComponent ,
189+ } ) ;
190+
191+ await fetcher ( 'https://example.com/api' , { method : 'GET' } , undefined ) ;
192+
193+ const [ , requestInit ] = mockFetch . mock . calls [ 0 ] ! ;
194+ expect ( ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ) . toBeNull ( ) ;
195+ } ) ;
99196 } ) ;
100197
101198 describe ( 'createSpaFetcher' , ( ) => {
@@ -124,14 +221,19 @@ describe('api-utils', () => {
124221 const config = createSpaConfig ( ) ;
125222 const dpopNonceId = '__test_dpop_nonce__' ;
126223
127- createSpaFetcher ( config , dpopNonceId ) ;
224+ createSpaFetcher ( config , dpopNonceId , defaultTelemetry , mockGetComponent ) ;
128225
129226 expect ( mockCreateFetcher ) . toHaveBeenCalledWith ( { dpopNonceId } ) ;
130227 } ) ;
131228
132229 it ( 'sets Content-Type header to application/json' , async ( ) => {
133230 const config = createSpaConfig ( ) ;
134- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
231+ const fetcher = createSpaFetcher (
232+ config ,
233+ '__test_nonce__' ,
234+ defaultTelemetry ,
235+ mockGetComponent ,
236+ ) ;
135237
136238 await fetcher ( 'https://example.com/api' , { method : 'POST' } , undefined ) ;
137239
@@ -141,7 +243,12 @@ describe('api-utils', () => {
141243
142244 it ( 'preserves existing headers from init when adding Content-Type' , async ( ) => {
143245 const config = createSpaConfig ( ) ;
144- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
246+ const fetcher = createSpaFetcher (
247+ config ,
248+ '__test_nonce__' ,
249+ defaultTelemetry ,
250+ mockGetComponent ,
251+ ) ;
145252 const customHeaders = new Headers ( { 'X-Custom' : 'value' } ) ;
146253
147254 await fetcher (
@@ -158,7 +265,12 @@ describe('api-utils', () => {
158265
159266 it ( 'preserves other init options when adding Content-Type header' , async ( ) => {
160267 const config = createSpaConfig ( ) ;
161- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
268+ const fetcher = createSpaFetcher (
269+ config ,
270+ '__test_nonce__' ,
271+ defaultTelemetry ,
272+ mockGetComponent ,
273+ ) ;
162274 const body = JSON . stringify ( { data : 'test' } ) ;
163275
164276 await fetcher (
@@ -176,7 +288,12 @@ describe('api-utils', () => {
176288
177289 it ( 'delegates to SDK fetchWithAuth with scope and audience' , async ( ) => {
178290 const config = createSpaConfig ( ) ;
179- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
291+ const fetcher = createSpaFetcher (
292+ config ,
293+ '__test_nonce__' ,
294+ defaultTelemetry ,
295+ mockGetComponent ,
296+ ) ;
180297
181298 await fetcher (
182299 'https://example.com/api' ,
@@ -193,7 +310,12 @@ describe('api-utils', () => {
193310
194311 it ( 'handles undefined authParams' , async ( ) => {
195312 const config = createSpaConfig ( ) ;
196- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
313+ const fetcher = createSpaFetcher (
314+ config ,
315+ '__test_nonce__' ,
316+ defaultTelemetry ,
317+ mockGetComponent ,
318+ ) ;
197319
198320 await fetcher ( 'https://example.com/api' , { method : 'GET' } , undefined ) ;
199321
@@ -206,7 +328,12 @@ describe('api-utils', () => {
206328
207329 it ( 'handles empty scope array' , async ( ) => {
208330 const config = createSpaConfig ( ) ;
209- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
331+ const fetcher = createSpaFetcher (
332+ config ,
333+ '__test_nonce__' ,
334+ defaultTelemetry ,
335+ mockGetComponent ,
336+ ) ;
210337
211338 await fetcher ( 'https://example.com/api' , { method : 'GET' } , { scope : [ ] } ) ;
212339
@@ -219,7 +346,12 @@ describe('api-utils', () => {
219346
220347 it ( 'handles undefined init parameter' , async ( ) => {
221348 const config = createSpaConfig ( ) ;
222- const fetcher = createSpaFetcher ( config , '__test_nonce__' ) ;
349+ const fetcher = createSpaFetcher (
350+ config ,
351+ '__test_nonce__' ,
352+ defaultTelemetry ,
353+ mockGetComponent ,
354+ ) ;
223355
224356 await fetcher ( 'https://example.com/api' , undefined , { scope : [ 'read:users' ] } ) ;
225357
@@ -232,5 +364,63 @@ describe('api-utils', () => {
232364 } ,
233365 ) ;
234366 } ) ;
367+
368+ it ( 'sets Auth0-Client telemetry header with SPA mode' , async ( ) => {
369+ const config = createSpaConfig ( ) ;
370+ const fetcher = createSpaFetcher (
371+ config ,
372+ '__test_nonce__' ,
373+ { css : 'tailwind' , distribution : 'npm' , framework : 'react' , enabled : true } ,
374+ ( ) => 'user-mfa-management' ,
375+ ) ;
376+
377+ await fetcher ( 'https://example.com/me/authentication-methods' , { method : 'GET' } , undefined ) ;
378+
379+ const [ , requestInit ] = mockFetchWithAuth . mock . calls [ 0 ] ! ;
380+ const header = ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ;
381+ expect ( header ) . toBeTruthy ( ) ;
382+
383+ const decoded = JSON . parse ( atob ( header ! ) ) ;
384+ expect ( decoded . is_proxy_mode ) . toBe ( false ) ;
385+ expect ( decoded . component ) . toBe ( 'user-mfa-management' ) ;
386+ expect ( decoded . name ) . toBe ( 'universal-components' ) ;
387+ expect ( decoded . css ) . toBe ( 'tailwind' ) ;
388+ expect ( decoded . distribution ) . toBe ( 'npm' ) ;
389+ expect ( decoded . framework ) . toBe ( 'react' ) ;
390+ } ) ;
391+
392+ it ( 'uses component from getComponent callback' , async ( ) => {
393+ const config = createSpaConfig ( ) ;
394+ const fetcher = createSpaFetcher (
395+ config ,
396+ '__test_nonce__' ,
397+ { css : 'scoped' , distribution : 'shadcn' , framework : 'react' , enabled : true } ,
398+ ( ) => 'organization-domain-management' ,
399+ ) ;
400+
401+ await fetcher ( 'https://example.com/my-org/domains' , { method : 'GET' } , undefined ) ;
402+
403+ const [ , requestInit ] = mockFetchWithAuth . mock . calls [ 0 ] ! ;
404+ const header = ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ;
405+ const decoded = JSON . parse ( atob ( header ! ) ) ;
406+ expect ( decoded . component ) . toBe ( 'organization-domain-management' ) ;
407+ expect ( decoded . css ) . toBe ( 'scoped' ) ;
408+ expect ( decoded . distribution ) . toBe ( 'shadcn' ) ;
409+ } ) ;
410+
411+ it ( 'does not set Auth0-Client header when telemetry is disabled' , async ( ) => {
412+ const config = createSpaConfig ( ) ;
413+ const fetcher = createSpaFetcher (
414+ config ,
415+ '__test_nonce__' ,
416+ { ...defaultTelemetry , enabled : false } ,
417+ mockGetComponent ,
418+ ) ;
419+
420+ await fetcher ( 'https://example.com/api' , { method : 'GET' } , undefined ) ;
421+
422+ const [ , requestInit ] = mockFetchWithAuth . mock . calls [ 0 ] ! ;
423+ expect ( ( requestInit ?. headers as Headers ) . get ( HeaderName . Auth0Client ) ) . toBeNull ( ) ;
424+ } ) ;
235425 } ) ;
236426} ) ;
0 commit comments