1
1
import GoTrueAdminApi from './GoTrueAdminApi'
2
- import { DEFAULT_HEADERS , EXPIRY_MARGIN , GOTRUE_URL , STORAGE_KEY } from './lib/constants'
2
+ import {
3
+ DEFAULT_HEADERS ,
4
+ EXPIRY_MARGIN_MS ,
5
+ AUTO_REFRESH_TICK_DURATION_MS ,
6
+ AUTO_REFRESH_TICK_THRESHOLD ,
7
+ GOTRUE_URL ,
8
+ STORAGE_KEY ,
9
+ } from './lib/constants'
3
10
import {
4
11
AuthError ,
5
12
AuthImplicitGrantRedirectError ,
@@ -89,13 +96,11 @@ import type {
89
96
LockFunc ,
90
97
UserIdentity ,
91
98
SignInAnonymouslyCredentials ,
92
- } from './lib/types'
93
- import {
94
99
MFAEnrollTOTPParams ,
95
100
MFAEnrollPhoneParams ,
96
101
AuthMFAEnrollTOTPResponse ,
97
102
AuthMFAEnrollPhoneResponse ,
98
- } from './lib/internal- types'
103
+ } from './lib/types'
99
104
100
105
polyfillGlobalThis ( ) // Make "globalThis" available
101
106
@@ -111,13 +116,6 @@ const DEFAULT_OPTIONS: Omit<Required<GoTrueClientOptions>, 'fetch' | 'storage' |
111
116
hasCustomAuthorizationHeader : false ,
112
117
}
113
118
114
- /** Current session will be checked for refresh at this interval. */
115
- const AUTO_REFRESH_TICK_DURATION = 30 * 1000
116
-
117
- /**
118
- * A token refresh will be attempted this many ticks before the current session expires. */
119
- const AUTO_REFRESH_TICK_THRESHOLD = 3
120
-
121
119
async function lockNoOp < R > ( name : string , acquireTimeout : number , fn : ( ) => Promise < R > ) : Promise < R > {
122
120
return await fn ( )
123
121
}
@@ -307,8 +305,22 @@ export default class GoTrueClient {
307
305
*/
308
306
private async _initialize ( ) : Promise < InitializeResult > {
309
307
try {
310
- if ( isBrowser ( ) && this . detectSessionInUrl ) {
311
- const { data, error } = await this . _getSessionFromURL ( )
308
+ const params = parseParametersFromURL ( window . location . href )
309
+ let callbackUrlType = 'none'
310
+ if ( this . _isImplicitGrantCallback ( params ) ) {
311
+ callbackUrlType = 'implicit'
312
+ } else if ( await this . _isPKCECallback ( params ) ) {
313
+ callbackUrlType = 'pkce'
314
+ }
315
+
316
+ /**
317
+ * Attempt to get the session from the URL only if these conditions are fulfilled
318
+ *
319
+ * Note: If the URL isn't one of the callback url types (implicit or pkce),
320
+ * then there could be an existing session so we don't want to prematurely remove it
321
+ */
322
+ if ( isBrowser ( ) && this . detectSessionInUrl && callbackUrlType !== 'none' ) {
323
+ const { data, error } = await this . _getSessionFromURL ( params , callbackUrlType )
312
324
if ( error ) {
313
325
this . _debug ( '#_initialize()' , 'error detecting session from URL' , error )
314
326
@@ -1095,8 +1107,13 @@ export default class GoTrueClient {
1095
1107
return { data : { session : null } , error : null }
1096
1108
}
1097
1109
1110
+ // A session is considered expired before the access token _actually_
1111
+ // expires. When the autoRefreshToken option is off (or when the tab is
1112
+ // in the background), very eager users of getSession() -- like
1113
+ // realtime-js -- might send a valid JWT which will expire by the time it
1114
+ // reaches the server.
1098
1115
const hasExpired = currentSession . expires_at
1099
- ? currentSession . expires_at <= Date . now ( ) / 1000
1116
+ ? currentSession . expires_at * 1000 - Date . now ( ) < EXPIRY_MARGIN_MS
1100
1117
: false
1101
1118
1102
1119
this . _debug (
@@ -1411,7 +1428,10 @@ export default class GoTrueClient {
1411
1428
/**
1412
1429
* Gets the session data from a URL string
1413
1430
*/
1414
- private async _getSessionFromURL ( ) : Promise <
1431
+ private async _getSessionFromURL (
1432
+ params : { [ parameter : string ] : string } ,
1433
+ callbackUrlType : string
1434
+ ) : Promise <
1415
1435
| {
1416
1436
data : { session : Session ; redirectType : string | null }
1417
1437
error : null
@@ -1421,8 +1441,6 @@ export default class GoTrueClient {
1421
1441
try {
1422
1442
if ( ! isBrowser ( ) ) throw new AuthImplicitGrantRedirectError ( 'No browser detected.' )
1423
1443
1424
- const params = parseParametersFromURL ( window . location . href )
1425
-
1426
1444
// If there's an error in the URL, it doesn't matter what flow it is, we just return the error.
1427
1445
if ( params . error || params . error_description || params . error_code ) {
1428
1446
// The error class returned implies that the redirect is from an implicit grant flow
@@ -1436,23 +1454,25 @@ export default class GoTrueClient {
1436
1454
)
1437
1455
}
1438
1456
1439
- const isRedirectFromImplicitGrantFlow = this . _isImplicitGrantFlow ( params )
1440
- const isRedirectFromPKCEFlow = await this . _isPKCEFlow ( params )
1441
-
1442
1457
// Checks for mismatches between the flowType initialised in the client and the URL parameters
1443
- if ( ! isRedirectFromImplicitGrantFlow && ! isRedirectFromPKCEFlow ) {
1444
- if ( this . flowType === 'implicit' ) {
1445
- throw new AuthImplicitGrantRedirectError ( 'Not a valid implicit grant flow url.' )
1446
- } else if ( this . flowType === 'pkce' ) {
1447
- throw new AuthPKCEGrantCodeExchangeError ( 'Not a valid PKCE flow url.' )
1448
- } else {
1449
- throw new AuthError ( 'Invalid flow type.' )
1450
- }
1458
+ switch ( callbackUrlType ) {
1459
+ case 'implicit' :
1460
+ if ( this . flowType === 'pkce' ) {
1461
+ throw new AuthPKCEGrantCodeExchangeError ( 'Not a valid PKCE flow url.' )
1462
+ }
1463
+ break
1464
+ case 'pkce' :
1465
+ if ( this . flowType === 'implicit' ) {
1466
+ throw new AuthImplicitGrantRedirectError ( 'Not a valid implicit grant flow url.' )
1467
+ }
1468
+ break
1469
+ default :
1470
+ // there's no mismatch so we continue
1451
1471
}
1452
1472
1453
1473
// Since this is a redirect for PKCE, we attempt to retrieve the code from the URL for the code exchange
1454
- if ( isRedirectFromPKCEFlow ) {
1455
- this . _debug ( '#_initialize()' , 'begin' , 'is PKCE flow' , isRedirectFromPKCEFlow )
1474
+ if ( callbackUrlType === 'pkce' ) {
1475
+ this . _debug ( '#_initialize()' , 'begin' , 'is PKCE flow' , true )
1456
1476
if ( ! params . code ) throw new AuthPKCEGrantCodeExchangeError ( 'No code detected.' )
1457
1477
const { data, error } = await this . _exchangeCodeForSession ( params . code )
1458
1478
if ( error ) throw error
@@ -1488,7 +1508,7 @@ export default class GoTrueClient {
1488
1508
}
1489
1509
1490
1510
const actuallyExpiresIn = expiresAt - timeNow
1491
- if ( actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION ) {
1511
+ if ( actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION_MS ) {
1492
1512
console . warn (
1493
1513
`@supabase/gotrue-js: Session as retrieved from URL expires in ${ actuallyExpiresIn } s, should have been closer to ${ expiresIn } s`
1494
1514
)
@@ -1542,20 +1562,20 @@ export default class GoTrueClient {
1542
1562
/**
1543
1563
* Checks if the current URL contains parameters given by an implicit oauth grant flow (https://www.rfc-editor.org/rfc/rfc6749.html#section-4.2)
1544
1564
*/
1545
- private _isImplicitGrantFlow ( params : { [ parameter : string ] : string } ) : boolean {
1546
- return ! ! ( ( params . access_token || params . error_description ) && this . flowType === 'implicit' )
1565
+ private _isImplicitGrantCallback ( params : { [ parameter : string ] : string } ) : boolean {
1566
+ return Boolean ( params . access_token || params . error_description )
1547
1567
}
1548
1568
1549
1569
/**
1550
1570
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
1551
1571
*/
1552
- private async _isPKCEFlow ( params : { [ parameter : string ] : string } ) : Promise < boolean > {
1572
+ private async _isPKCECallback ( params : { [ parameter : string ] : string } ) : Promise < boolean > {
1553
1573
const currentStorageContent = await getItemAsync (
1554
1574
this . storage ,
1555
1575
`${ this . storageKey } -code-verifier`
1556
1576
)
1557
1577
1558
- return ! ! ( params . code && currentStorageContent && this . flowType === 'pkce' )
1578
+ return ! ! ( params . code && currentStorageContent )
1559
1579
}
1560
1580
1561
1581
/**
@@ -1835,7 +1855,7 @@ export default class GoTrueClient {
1835
1855
error &&
1836
1856
isAuthRetryableFetchError ( error ) &&
1837
1857
// retryable only if the request can be sent before the backoff overflows the tick duration
1838
- Date . now ( ) + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION
1858
+ Date . now ( ) + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION_MS
1839
1859
)
1840
1860
}
1841
1861
)
@@ -1908,12 +1928,12 @@ export default class GoTrueClient {
1908
1928
return
1909
1929
}
1910
1930
1911
- const timeNow = Math . round ( Date . now ( ) / 1000 )
1912
- const expiresWithMargin = ( currentSession . expires_at ?? Infinity ) < timeNow + EXPIRY_MARGIN
1931
+ const expiresWithMargin =
1932
+ ( currentSession . expires_at ?? Infinity ) * 1000 - Date . now ( ) < EXPIRY_MARGIN_MS
1913
1933
1914
1934
this . _debug (
1915
1935
debugName ,
1916
- `session has${ expiresWithMargin ? '' : ' not' } expired with margin of ${ EXPIRY_MARGIN } s`
1936
+ `session has${ expiresWithMargin ? '' : ' not' } expired with margin of ${ EXPIRY_MARGIN_MS } s`
1917
1937
)
1918
1938
1919
1939
if ( expiresWithMargin ) {
@@ -2086,7 +2106,7 @@ export default class GoTrueClient {
2086
2106
2087
2107
this . _debug ( '#_startAutoRefresh()' )
2088
2108
2089
- const ticker = setInterval ( ( ) => this . _autoRefreshTokenTick ( ) , AUTO_REFRESH_TICK_DURATION )
2109
+ const ticker = setInterval ( ( ) => this . _autoRefreshTokenTick ( ) , AUTO_REFRESH_TICK_DURATION_MS )
2090
2110
this . autoRefreshTicker = ticker
2091
2111
2092
2112
if ( ticker && typeof ticker === 'object' && typeof ticker . unref === 'function' ) {
@@ -2193,12 +2213,12 @@ export default class GoTrueClient {
2193
2213
2194
2214
// session will expire in this many ticks (or has already expired if <= 0)
2195
2215
const expiresInTicks = Math . floor (
2196
- ( session . expires_at * 1000 - now ) / AUTO_REFRESH_TICK_DURATION
2216
+ ( session . expires_at * 1000 - now ) / AUTO_REFRESH_TICK_DURATION_MS
2197
2217
)
2198
2218
2199
2219
this . _debug (
2200
2220
'#_autoRefreshTokenTick()' ,
2201
- `access token expires in ${ expiresInTicks } ticks, a tick lasts ${ AUTO_REFRESH_TICK_DURATION } ms, refresh threshold is ${ AUTO_REFRESH_TICK_THRESHOLD } ticks`
2221
+ `access token expires in ${ expiresInTicks } ticks, a tick lasts ${ AUTO_REFRESH_TICK_DURATION_MS } ms, refresh threshold is ${ AUTO_REFRESH_TICK_THRESHOLD } ticks`
2202
2222
)
2203
2223
2204
2224
if ( expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD ) {
0 commit comments