@supabase/gotrue-js 1.22.12 → 1.22.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/main/GoTrueClient.d.ts +3 -8
  2. package/dist/main/GoTrueClient.d.ts.map +1 -1
  3. package/dist/main/GoTrueClient.js +47 -17
  4. package/dist/main/GoTrueClient.js.map +1 -1
  5. package/dist/main/lib/constants.d.ts +6 -1
  6. package/dist/main/lib/constants.d.ts.map +1 -1
  7. package/dist/main/lib/constants.js +7 -2
  8. package/dist/main/lib/constants.js.map +1 -1
  9. package/dist/main/lib/fetch.d.ts.map +1 -1
  10. package/dist/main/lib/fetch.js +4 -0
  11. package/dist/main/lib/fetch.js.map +1 -1
  12. package/dist/main/lib/helpers.d.ts +5 -0
  13. package/dist/main/lib/helpers.d.ts.map +1 -1
  14. package/dist/main/lib/helpers.js +44 -1
  15. package/dist/main/lib/helpers.js.map +1 -1
  16. package/dist/main/lib/types.d.ts +9 -0
  17. package/dist/main/lib/types.d.ts.map +1 -1
  18. package/dist/main/lib/version.d.ts +1 -1
  19. package/dist/main/lib/version.js +1 -1
  20. package/dist/module/GoTrueClient.d.ts +3 -8
  21. package/dist/module/GoTrueClient.d.ts.map +1 -1
  22. package/dist/module/GoTrueClient.js +49 -19
  23. package/dist/module/GoTrueClient.js.map +1 -1
  24. package/dist/module/lib/constants.d.ts +6 -1
  25. package/dist/module/lib/constants.d.ts.map +1 -1
  26. package/dist/module/lib/constants.js +6 -1
  27. package/dist/module/lib/constants.js.map +1 -1
  28. package/dist/module/lib/fetch.d.ts.map +1 -1
  29. package/dist/module/lib/fetch.js +4 -0
  30. package/dist/module/lib/fetch.js.map +1 -1
  31. package/dist/module/lib/helpers.d.ts +5 -0
  32. package/dist/module/lib/helpers.d.ts.map +1 -1
  33. package/dist/module/lib/helpers.js +39 -0
  34. package/dist/module/lib/helpers.js.map +1 -1
  35. package/dist/module/lib/types.d.ts +9 -0
  36. package/dist/module/lib/types.d.ts.map +1 -1
  37. package/dist/module/lib/version.d.ts +1 -1
  38. package/dist/module/lib/version.js +1 -1
  39. package/package.json +1 -1
  40. package/src/GoTrueClient.ts +70 -33
  41. package/src/lib/constants.ts +6 -1
  42. package/src/lib/fetch.ts +5 -0
  43. package/src/lib/helpers.ts +36 -0
  44. package/src/lib/types.ts +13 -0
  45. package/src/lib/version.ts +1 -1
@@ -1,6 +1,20 @@
1
1
  import GoTrueApi from './GoTrueApi'
2
- import { isBrowser, getParameterByName, uuid } from './lib/helpers'
3
- import { GOTRUE_URL, DEFAULT_HEADERS, STORAGE_KEY } from './lib/constants'
2
+ import {
3
+ isBrowser,
4
+ getParameterByName,
5
+ uuid,
6
+ setItemAsync,
7
+ removeItemAsync,
8
+ getItemSynchronously,
9
+ getItemAsync,
10
+ } from './lib/helpers'
11
+ import {
12
+ GOTRUE_URL,
13
+ DEFAULT_HEADERS,
14
+ STORAGE_KEY,
15
+ EXPIRY_MARGIN,
16
+ NETWORK_FAILURE,
17
+ } from './lib/constants'
4
18
  import { polyfillGlobalThis } from './lib/polyfills'
5
19
  import { Fetch } from './lib/fetch'
6
20
 
@@ -16,6 +30,7 @@ import type {
16
30
  UserCredentials,
17
31
  VerifyOTPParams,
18
32
  OpenIDConnectCredentials,
33
+ SupportedStorage,
19
34
  } from './lib/types'
20
35
 
21
36
  polyfillGlobalThis() // Make "globalThis" available
@@ -29,17 +44,6 @@ const DEFAULT_OPTIONS = {
29
44
  headers: DEFAULT_HEADERS,
30
45
  }
31
46
 
32
- type AnyFunction = (...args: any[]) => any
33
- type MaybePromisify<T> = T | Promise<T>
34
-
35
- type PromisifyMethods<T> = {
36
- [K in keyof T]: T[K] extends AnyFunction
37
- ? (...args: Parameters<T[K]>) => MaybePromisify<ReturnType<T[K]>>
38
- : T[K]
39
- }
40
-
41
- type SupportedStorage = PromisifyMethods<Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>>
42
-
43
47
  export default class GoTrueClient {
44
48
  /**
45
49
  * Namespace for the GoTrue API methods.
@@ -61,6 +65,7 @@ export default class GoTrueClient {
61
65
  protected multiTab: boolean
62
66
  protected stateChangeEmitters: Map<string, Subscription> = new Map()
63
67
  protected refreshTokenTimer?: ReturnType<typeof setTimeout>
68
+ protected networkRetries: number = 0
64
69
 
65
70
  /**
66
71
  * Create a new client for use in the browser.
@@ -101,6 +106,7 @@ export default class GoTrueClient {
101
106
  this._recoverSession()
102
107
  this._recoverAndRefresh()
103
108
  this._listenForMultiTabEvents()
109
+ this._handleVisibilityChange()
104
110
 
105
111
  if (settings.detectSessionInUrl && isBrowser() && !!getParameterByName('access_token')) {
106
112
  // Handle the OAuth redirect
@@ -407,6 +413,8 @@ export default class GoTrueClient {
407
413
  user: this.user(),
408
414
  }
409
415
 
416
+ this._notifyAllSubscribers('TOKEN_REFRESHED')
417
+
410
418
  return this.currentSession
411
419
  }
412
420
 
@@ -608,16 +616,12 @@ export default class GoTrueClient {
608
616
  */
609
617
  private _recoverSession() {
610
618
  try {
611
- const json = isBrowser() && this.localStorage?.getItem(STORAGE_KEY)
612
- if (!json || typeof json !== 'string') {
613
- return null
614
- }
615
-
616
- const data = JSON.parse(json)
619
+ const data = getItemSynchronously(this.localStorage, STORAGE_KEY)
620
+ if (!data) return null
617
621
  const { currentSession, expiresAt } = data
618
622
  const timeNow = Math.round(Date.now() / 1000)
619
623
 
620
- if (expiresAt >= timeNow && currentSession?.user) {
624
+ if (expiresAt >= timeNow + EXPIRY_MARGIN && currentSession?.user) {
621
625
  this._saveSession(currentSession)
622
626
  this._notifyAllSubscribers('SIGNED_IN')
623
627
  }
@@ -632,22 +636,31 @@ export default class GoTrueClient {
632
636
  */
633
637
  private async _recoverAndRefresh() {
634
638
  try {
635
- const json = isBrowser() && (await this.localStorage.getItem(STORAGE_KEY))
636
- if (!json) {
637
- return null
638
- }
639
-
640
- const data = JSON.parse(json)
639
+ const data = await getItemAsync(this.localStorage, STORAGE_KEY)
640
+ if (!data) return null
641
641
  const { currentSession, expiresAt } = data
642
642
  const timeNow = Math.round(Date.now() / 1000)
643
643
 
644
- if (expiresAt < timeNow) {
644
+ if (expiresAt < timeNow + EXPIRY_MARGIN) {
645
645
  if (this.autoRefreshToken && currentSession.refresh_token) {
646
+ this.networkRetries++
646
647
  const { error } = await this._callRefreshToken(currentSession.refresh_token)
647
648
  if (error) {
648
649
  console.log(error.message)
650
+ if (
651
+ error.message === NETWORK_FAILURE.ERROR_MESSAGE &&
652
+ this.networkRetries < NETWORK_FAILURE.MAX_RETRIES
653
+ ) {
654
+ if (this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer)
655
+ this.refreshTokenTimer = setTimeout(
656
+ () => this._recoverAndRefresh(),
657
+ NETWORK_FAILURE.RETRY_INTERVAL ** this.networkRetries * 100 // exponential backoff
658
+ )
659
+ return
660
+ }
649
661
  await this._removeSession()
650
662
  }
663
+ this.networkRetries = 0
651
664
  } else {
652
665
  this._removeSession()
653
666
  }
@@ -701,7 +714,7 @@ export default class GoTrueClient {
701
714
  if (expiresAt) {
702
715
  const timeNow = Math.round(Date.now() / 1000)
703
716
  const expiresIn = expiresAt - timeNow
704
- const refreshDurationBeforeExpires = expiresIn > 60 ? 60 : 0.5
717
+ const refreshDurationBeforeExpires = expiresIn > EXPIRY_MARGIN ? EXPIRY_MARGIN : 0.5
705
718
  this._startAutoRefreshToken((expiresIn - refreshDurationBeforeExpires) * 1000)
706
719
  }
707
720
 
@@ -714,14 +727,14 @@ export default class GoTrueClient {
714
727
 
715
728
  private _persistSession(currentSession: Session) {
716
729
  const data = { currentSession, expiresAt: currentSession.expires_at }
717
- isBrowser() && this.localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
730
+ setItemAsync(this.localStorage, STORAGE_KEY, data)
718
731
  }
719
732
 
720
733
  private async _removeSession() {
721
734
  this.currentSession = null
722
735
  this.currentUser = null
723
736
  if (this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer)
724
- isBrowser() && (await this.localStorage.removeItem(STORAGE_KEY))
737
+ removeItemAsync(this.localStorage, STORAGE_KEY)
725
738
  }
726
739
 
727
740
  /**
@@ -732,7 +745,16 @@ export default class GoTrueClient {
732
745
  if (this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer)
733
746
  if (value <= 0 || !this.autoRefreshToken) return
734
747
 
735
- this.refreshTokenTimer = setTimeout(() => this._callRefreshToken(), value)
748
+ this.refreshTokenTimer = setTimeout(async () => {
749
+ this.networkRetries++
750
+ const { error } = await this._callRefreshToken()
751
+ if (!error) this.networkRetries = 0
752
+ if (
753
+ error?.message === NETWORK_FAILURE.ERROR_MESSAGE &&
754
+ this.networkRetries < NETWORK_FAILURE.MAX_RETRIES
755
+ )
756
+ this._startAutoRefreshToken(NETWORK_FAILURE.RETRY_INTERVAL ** this.networkRetries * 100) // exponential backoff
757
+ }, value)
736
758
  if (typeof this.refreshTokenTimer.unref === 'function') this.refreshTokenTimer.unref()
737
759
  }
738
760
 
@@ -741,7 +763,6 @@ export default class GoTrueClient {
741
763
  */
742
764
  private _listenForMultiTabEvents() {
743
765
  if (!this.multiTab || !isBrowser() || !window?.addEventListener) {
744
- // console.debug('Auth multi-tab support is disabled.')
745
766
  return false
746
767
  }
747
768
 
@@ -750,7 +771,7 @@ export default class GoTrueClient {
750
771
  if (e.key === STORAGE_KEY) {
751
772
  const newSession = JSON.parse(String(e.newValue))
752
773
  if (newSession?.currentSession?.access_token) {
753
- this._recoverAndRefresh()
774
+ this._saveSession(newSession.currentSession)
754
775
  this._notifyAllSubscribers('SIGNED_IN')
755
776
  } else {
756
777
  this._removeSession()
@@ -762,4 +783,20 @@ export default class GoTrueClient {
762
783
  console.error('_listenForMultiTabEvents', error)
763
784
  }
764
785
  }
786
+
787
+ private _handleVisibilityChange() {
788
+ if (!this.multiTab || !isBrowser() || !window?.addEventListener) {
789
+ return false
790
+ }
791
+
792
+ try {
793
+ window?.addEventListener('visibilitychange', () => {
794
+ if (document.visibilityState === 'visible') {
795
+ this._recoverAndRefresh()
796
+ }
797
+ })
798
+ } catch (error) {
799
+ console.error('_handleVisibilityChange', error)
800
+ }
801
+ }
765
802
  }
@@ -2,7 +2,12 @@ import { version } from './version'
2
2
  export const GOTRUE_URL = 'http://localhost:9999'
3
3
  export const AUDIENCE = ''
4
4
  export const DEFAULT_HEADERS = { 'X-Client-Info': `gotrue-js/${version}` }
5
- export const EXPIRY_MARGIN = 60 * 1000
5
+ export const EXPIRY_MARGIN = 10 // in seconds
6
+ export const NETWORK_FAILURE = {
7
+ ERROR_MESSAGE: 'Request Failed',
8
+ MAX_RETRIES: 10,
9
+ RETRY_INTERVAL: 2, // in deciseconds
10
+ }
6
11
  export const STORAGE_KEY = 'supabase.auth.token'
7
12
  export const COOKIE_OPTIONS = {
8
13
  name: 'sb',
package/src/lib/fetch.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { NETWORK_FAILURE } from './constants'
2
+
1
3
  export type Fetch = typeof fetch
2
4
 
3
5
  export interface FetchOptions {
@@ -13,6 +15,9 @@ const _getErrorMessage = (err: any): string =>
13
15
  err.msg || err.message || err.error_description || err.error || JSON.stringify(err)
14
16
 
15
17
  const handleError = (error: any, reject: any) => {
18
+ if (!error?.status) {
19
+ return reject({ message: NETWORK_FAILURE.ERROR_MESSAGE })
20
+ }
16
21
  if (typeof error.json !== 'function') {
17
22
  return reject(error)
18
23
  }
@@ -1,4 +1,5 @@
1
1
  import crossFetch from 'cross-fetch'
2
+ import { SupportedStorage } from './types'
2
3
 
3
4
  export function expiresAt(expiresIn: number) {
4
5
  const timeNow = Math.round(Date.now() / 1000)
@@ -39,3 +40,38 @@ export const resolveFetch = (customFetch?: Fetch): Fetch => {
39
40
  }
40
41
  return (...args) => _fetch(...args)
41
42
  }
43
+
44
+ // LocalStorage helpers
45
+ export const setItemAsync = async (
46
+ storage: SupportedStorage,
47
+ key: string,
48
+ data: any
49
+ ): Promise<void> => {
50
+ isBrowser() && (await storage?.setItem(key, JSON.stringify(data)))
51
+ }
52
+
53
+ export const getItemAsync = async (storage: SupportedStorage, key: string): Promise<any | null> => {
54
+ const value = isBrowser() && (await storage?.getItem(key))
55
+ if (!value) return null
56
+ try {
57
+ return JSON.parse(value)
58
+ } catch {
59
+ return value
60
+ }
61
+ }
62
+
63
+ export const getItemSynchronously = (storage: SupportedStorage, key: string): any | null => {
64
+ const value = isBrowser() && storage?.getItem(key)
65
+ if (!value || typeof value !== 'string') {
66
+ return null
67
+ }
68
+ try {
69
+ return JSON.parse(value)
70
+ } catch {
71
+ return value
72
+ }
73
+ }
74
+
75
+ export const removeItemAsync = async (storage: SupportedStorage, key: string): Promise<void> => {
76
+ isBrowser() && (await storage?.removeItem(key))
77
+ }
package/src/lib/types.ts CHANGED
@@ -69,6 +69,8 @@ export interface User {
69
69
  aud: string
70
70
  confirmation_sent_at?: string
71
71
  recovery_sent_at?: string
72
+ email_change_sent_at?: string
73
+ new_email?: string
72
74
  invited_at?: string
73
75
  action_link?: string
74
76
  email?: string
@@ -209,3 +211,14 @@ export interface OpenIDConnectCredentials {
209
211
  client_id?: string
210
212
  issuer?: string
211
213
  }
214
+
215
+ type AnyFunction = (...args: any[]) => any
216
+ type MaybePromisify<T> = T | Promise<T>
217
+
218
+ type PromisifyMethods<T> = {
219
+ [K in keyof T]: T[K] extends AnyFunction
220
+ ? (...args: Parameters<T[K]>) => MaybePromisify<ReturnType<T[K]>>
221
+ : T[K]
222
+ }
223
+
224
+ export type SupportedStorage = PromisifyMethods<Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>>
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- export const version = '1.22.12'
2
+ export const version = '1.22.15'