@@ -32,7 +32,13 @@ async function ensureToken() {
3232}
3333
3434async function resolveToken ( ) {
35- const attempts = [ fetchTokenFromApi , readTokenFromMeta , readTokenFromWindow , readTokenFromEnv ] ;
35+ const attempts = [
36+ readTokenFromUrl ,
37+ readTokenFromMeta ,
38+ readTokenFromWindow ,
39+ readTokenFromEnv ,
40+ fetchTokenFromApi ,
41+ ] ;
3642 const errors = [ ] ;
3743
3844 for ( const attempt of attempts ) {
@@ -101,6 +107,40 @@ async function fetchTokenFromApi() {
101107 }
102108}
103109
110+ function readTokenFromUrl ( ) {
111+ const location = getCurrentLocation ( ) ;
112+ if ( ! location ) {
113+ return { token : null , source : 'url' , error : new Error ( 'Location is unavailable.' ) } ;
114+ }
115+
116+ const { url, searchParams, hashParams, rawFragments } = parseLocation ( location ) ;
117+ const tokenKeys = new Set ( ) ;
118+ const candidates = [ ] ;
119+
120+ collectTokenCandidates ( searchParams , tokenKeys , candidates ) ;
121+ collectTokenCandidates ( hashParams , tokenKeys , candidates ) ;
122+
123+ if ( candidates . length === 0 && rawFragments . length > 0 ) {
124+ const regex = / ( t o k e n [ ^ = : # / ? & ] * ) ( [: = ] ) ( [ ^ # & / ? ] + ) / gi;
125+ for ( const fragment of rawFragments ) {
126+ let match ;
127+ while ( ( match = regex . exec ( fragment ) ) ) {
128+ tokenKeys . add ( match [ 1 ] ) ;
129+ candidates . push ( match [ 3 ] ) ;
130+ }
131+ }
132+ }
133+
134+ const token = extractTokenValue ( candidates ) ;
135+ if ( ! token ) {
136+ return { token : null , source : 'url' } ;
137+ }
138+
139+ sanitizeUrlToken ( location , url , tokenKeys ) ;
140+
141+ return { token, source : 'url' } ;
142+ }
143+
104144function readTokenFromMeta ( ) {
105145 if ( typeof document === 'undefined' ) {
106146 return { token : null , source : 'meta' , error : new Error ( 'Document is unavailable.' ) } ;
@@ -170,6 +210,156 @@ function readTokenFromEnv() {
170210 return { token, source : 'env' } ;
171211}
172212
213+ function getCurrentLocation ( ) {
214+ if ( typeof window !== 'undefined' && window ?. location ) {
215+ return window . location ;
216+ }
217+ if ( typeof globalThis !== 'undefined' && globalThis ?. location ) {
218+ return globalThis . location ;
219+ }
220+ return null ;
221+ }
222+
223+ function parseLocation ( location ) {
224+ const result = {
225+ url : null ,
226+ searchParams : new URLSearchParams ( ) ,
227+ hashParams : new URLSearchParams ( ) ,
228+ rawFragments : [ ] ,
229+ } ;
230+
231+ let baseHref = '' ;
232+ if ( typeof location . href === 'string' && location . href ) {
233+ baseHref = location . href ;
234+ } else {
235+ const origin = typeof location . origin === 'string' ? location . origin : 'http://localhost' ;
236+ const path = typeof location . pathname === 'string' ? location . pathname : '/' ;
237+ const search = typeof location . search === 'string' ? location . search : '' ;
238+ const hash = typeof location . hash === 'string' ? location . hash : '' ;
239+ baseHref = `${ origin . replace ( / \/ ? $ / , '' ) } ${ path . startsWith ( '/' ) ? path : `/${ path } ` } ${ search } ${ hash } ` ;
240+ }
241+
242+ try {
243+ const base = typeof location . origin === 'string' && location . origin ? location . origin : undefined ;
244+ result . url = base ? new URL ( baseHref , base ) : new URL ( baseHref ) ;
245+ } catch {
246+ try {
247+ result . url = new URL ( baseHref , 'http://localhost' ) ;
248+ } catch {
249+ result . url = null ;
250+ }
251+ }
252+
253+ if ( result . url ) {
254+ result . searchParams = new URLSearchParams ( result . url . searchParams ) ;
255+ const hash = typeof result . url . hash === 'string' ? result . url . hash . replace ( / ^ # / , '' ) : '' ;
256+ if ( hash ) {
257+ result . hashParams = new URLSearchParams ( hash ) ;
258+ result . rawFragments . push ( hash ) ;
259+ }
260+ } else {
261+ const search = typeof location . search === 'string' ? location . search . replace ( / ^ \? / , '' ) : '' ;
262+ const hash = typeof location . hash === 'string' ? location . hash . replace ( / ^ # / , '' ) : '' ;
263+ result . searchParams = new URLSearchParams ( search ) ;
264+ result . hashParams = new URLSearchParams ( hash ) ;
265+ if ( hash ) {
266+ result . rawFragments . push ( hash ) ;
267+ }
268+ }
269+
270+ const hrefFragment = typeof location . href === 'string' ? location . href : '' ;
271+ if ( hrefFragment ) {
272+ result . rawFragments . push ( hrefFragment ) ;
273+ }
274+
275+ return result ;
276+ }
277+
278+ function collectTokenCandidates ( params , tokenKeys , candidates ) {
279+ if ( ! params ) return ;
280+ for ( const key of params . keys ( ) ) {
281+ if ( typeof key !== 'string' ) continue ;
282+ if ( ! key . toLowerCase ( ) . includes ( 'token' ) ) continue ;
283+ tokenKeys . add ( key ) ;
284+ const values = params . getAll ( key ) ;
285+ for ( const value of values ) {
286+ candidates . push ( value ) ;
287+ }
288+ }
289+ }
290+
291+ function sanitizeUrlToken ( location , url , tokenKeys ) {
292+ if ( ! location || ! tokenKeys || tokenKeys . size === 0 ) {
293+ return ;
294+ }
295+
296+ const effectiveUrl = url ?? parseLocation ( location ) . url ;
297+ if ( ! effectiveUrl ) {
298+ return ;
299+ }
300+
301+ let modified = false ;
302+ for ( const key of tokenKeys ) {
303+ if ( effectiveUrl . searchParams . has ( key ) ) {
304+ effectiveUrl . searchParams . delete ( key ) ;
305+ modified = true ;
306+ }
307+ }
308+
309+ const originalHash = effectiveUrl . hash ;
310+ if ( typeof originalHash === 'string' && originalHash . length > 1 ) {
311+ const hashParams = new URLSearchParams ( originalHash . slice ( 1 ) ) ;
312+ let hashModified = false ;
313+ for ( const key of tokenKeys ) {
314+ if ( hashParams . has ( key ) ) {
315+ hashParams . delete ( key ) ;
316+ hashModified = true ;
317+ }
318+ }
319+ if ( hashModified ) {
320+ const nextHash = hashParams . toString ( ) ;
321+ effectiveUrl . hash = nextHash ? `#${ nextHash } ` : '' ;
322+ modified = true ;
323+ }
324+ }
325+
326+ if ( ! modified ) {
327+ return ;
328+ }
329+
330+ const history =
331+ ( typeof window !== 'undefined' && window ?. history ) ||
332+ ( typeof globalThis !== 'undefined' && globalThis ?. history ) ||
333+ null ;
334+ const nextUrl = effectiveUrl . toString ( ) ;
335+
336+ if ( history ?. replaceState ) {
337+ try {
338+ history . replaceState ( history . state ?? null , '' , nextUrl ) ;
339+ return ;
340+ } catch {
341+ // ignore history errors
342+ }
343+ }
344+
345+ if ( typeof location . assign === 'function' ) {
346+ try {
347+ location . assign ( nextUrl ) ;
348+ return ;
349+ } catch {
350+ // ignore assignment errors
351+ }
352+ }
353+
354+ if ( 'href' in location ) {
355+ try {
356+ location . href = nextUrl ;
357+ } catch {
358+ // ignore inability to mutate href
359+ }
360+ }
361+ }
362+
173363function determineDevelopmentEnvironment ( importMetaEnv , processEnv ) {
174364 if ( importMetaEnv && typeof importMetaEnv . DEV !== 'undefined' ) {
175365 return ! ! importMetaEnv . DEV ;
0 commit comments