@supabase/gotrue-js 1.22.15 → 1.23.0-next.2

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.
@@ -7,6 +7,7 @@ import {
7
7
  removeItemAsync,
8
8
  getItemSynchronously,
9
9
  getItemAsync,
10
+ Deferred,
10
11
  } from './lib/helpers'
11
12
  import {
12
13
  GOTRUE_URL,
@@ -52,20 +53,32 @@ export default class GoTrueClient {
52
53
  api: GoTrueApi
53
54
  /**
54
55
  * The currently logged in user or null.
56
+ * @deprecated use `getUser()` instead
55
57
  */
56
58
  protected currentUser: User | null
57
59
  /**
58
60
  * The session object for the currently logged in user or null.
61
+ * @deprecated use `getSession()` instead
59
62
  */
60
63
  protected currentSession: Session | null
61
64
 
65
+ /**
66
+ * The session object for the currently logged in user or null.
67
+ * Only used if persistSession is false.
68
+ */
69
+ protected inMemorySession: Session | null
70
+
62
71
  protected autoRefreshToken: boolean
63
72
  protected persistSession: boolean
64
73
  protected localStorage: SupportedStorage
65
74
  protected multiTab: boolean
66
75
  protected stateChangeEmitters: Map<string, Subscription> = new Map()
67
76
  protected refreshTokenTimer?: ReturnType<typeof setTimeout>
68
- protected networkRetries: number = 0
77
+ protected networkRetries = 0
78
+ protected refreshingDeferred: Deferred<{
79
+ data: Session
80
+ error: null
81
+ }> | null = null
69
82
 
70
83
  /**
71
84
  * Create a new client for use in the browser.
@@ -93,6 +106,7 @@ export default class GoTrueClient {
93
106
  const settings = { ...DEFAULT_OPTIONS, ...options }
94
107
  this.currentUser = null
95
108
  this.currentSession = null
109
+ this.inMemorySession = null
96
110
  this.autoRefreshToken = settings.autoRefreshToken
97
111
  this.persistSession = settings.persistSession
98
112
  this.multiTab = settings.multiTab
@@ -201,6 +215,7 @@ export default class GoTrueClient {
201
215
  shouldCreateUser?: boolean
202
216
  scopes?: string
203
217
  captchaToken?: string
218
+ queryParams?: { [key: string]: string }
204
219
  } = {}
205
220
  ): Promise<{
206
221
  session: Session | null
@@ -250,6 +265,7 @@ export default class GoTrueClient {
250
265
  return this._handleProviderSignIn(provider, {
251
266
  redirectTo: options.redirectTo,
252
267
  scopes: options.scopes,
268
+ queryParams: options.queryParams,
253
269
  })
254
270
  }
255
271
  if (oidc) {
@@ -318,6 +334,7 @@ export default class GoTrueClient {
318
334
  * Inside a browser context, `user()` will return the user data, if there is a logged in user.
319
335
  *
320
336
  * For server-side management, you can get a user through `auth.api.getUserByCookie()`
337
+ * @deprecated use `getUser()` instead
321
338
  */
322
339
  user(): User | null {
323
340
  return this.currentUser
@@ -325,11 +342,81 @@ export default class GoTrueClient {
325
342
 
326
343
  /**
327
344
  * Returns the session data, if there is an active session.
345
+ * @deprecated use `getSession()` instead
328
346
  */
329
347
  session(): Session | null {
330
348
  return this.currentSession
331
349
  }
332
350
 
351
+ /**
352
+ * Returns the session data, refreshing it if necessary.
353
+ */
354
+ async getSession(): Promise<
355
+ | {
356
+ session: Session
357
+ error: null
358
+ }
359
+ | {
360
+ session: null
361
+ error: ApiError
362
+ }
363
+ | {
364
+ session: null
365
+ error: null
366
+ }
367
+ > {
368
+ const currentSession = this.persistSession
369
+ ? ((await getItemAsync(this.localStorage, STORAGE_KEY)) as Session | null)
370
+ : this.inMemorySession
371
+
372
+ if (!currentSession) {
373
+ return { session: null, error: null }
374
+ }
375
+
376
+ const hasExpired = currentSession.expires_at
377
+ ? currentSession.expires_at <= Date.now() / 1000
378
+ : false
379
+ if (!hasExpired) {
380
+ return { session: currentSession, error: null }
381
+ }
382
+
383
+ const { data: session, error } = await this._callRefreshToken(currentSession.refresh_token)
384
+ if (error) {
385
+ return { session: null, error }
386
+ }
387
+
388
+ return { session, error: null }
389
+ }
390
+
391
+ /**
392
+ * Returns the user data, refreshing the session if necessary.
393
+ */
394
+ async getUser(): Promise<
395
+ | {
396
+ user: User
397
+ error: null
398
+ }
399
+ | {
400
+ user: null
401
+ error: ApiError
402
+ }
403
+ | {
404
+ user: null
405
+ error: null
406
+ }
407
+ > {
408
+ const { session, error } = await this.getSession()
409
+ if (error) {
410
+ return { user: null, error }
411
+ }
412
+
413
+ if (!session) {
414
+ return { user: null, error: null }
415
+ }
416
+
417
+ return { user: session.user, error: null }
418
+ }
419
+
333
420
  /**
334
421
  * Force refreshes the session including the user data in case it was updated in a different session.
335
422
  */
@@ -404,6 +491,7 @@ export default class GoTrueClient {
404
491
  /**
405
492
  * Overrides the JWT on the current client. The JWT will then be sent in all subsequent network requests.
406
493
  * @param access_token a jwt access token
494
+ * @deprecated you can use `getSession()` instead as it reads from storage every time (always fresh)
407
495
  */
408
496
  setAuth(access_token: string): Session {
409
497
  this.currentSession = {
@@ -559,11 +647,13 @@ export default class GoTrueClient {
559
647
  options: {
560
648
  redirectTo?: string
561
649
  scopes?: string
650
+ queryParams?: { [key: string]: string }
562
651
  } = {}
563
652
  ) {
564
653
  const url: string = this.api.getUrlForProvider(provider, {
565
654
  redirectTo: options.redirectTo,
566
655
  scopes: options.scopes,
656
+ queryParams: options.queryParams,
567
657
  })
568
658
 
569
659
  try {
@@ -681,6 +771,16 @@ export default class GoTrueClient {
681
771
 
682
772
  private async _callRefreshToken(refresh_token = this.currentSession?.refresh_token) {
683
773
  try {
774
+ // refreshing is already in progress
775
+ if (this.refreshingDeferred) {
776
+ return await this.refreshingDeferred.promise
777
+ }
778
+
779
+ this.refreshingDeferred = new Deferred<{
780
+ data: Session
781
+ error: null
782
+ }>()
783
+
684
784
  if (!refresh_token) {
685
785
  throw new Error('No current session.')
686
786
  }
@@ -692,7 +792,12 @@ export default class GoTrueClient {
692
792
  this._notifyAllSubscribers('TOKEN_REFRESHED')
693
793
  this._notifyAllSubscribers('SIGNED_IN')
694
794
 
695
- return { data, error: null }
795
+ const result = { data, error: null }
796
+
797
+ this.refreshingDeferred.resolve(result)
798
+ this.refreshingDeferred = null
799
+
800
+ return result
696
801
  } catch (e) {
697
802
  return { data: null, error: e as ApiError }
698
803
  }
@@ -710,6 +815,10 @@ export default class GoTrueClient {
710
815
  this.currentSession = session
711
816
  this.currentUser = session.user
712
817
 
818
+ if (!this.persistSession) {
819
+ this.inMemorySession = session
820
+ }
821
+
713
822
  const expiresAt = session.expires_at
714
823
  if (expiresAt) {
715
824
  const timeNow = Math.round(Date.now() / 1000)
@@ -733,8 +842,16 @@ export default class GoTrueClient {
733
842
  private async _removeSession() {
734
843
  this.currentSession = null
735
844
  this.currentUser = null
736
- if (this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer)
737
- removeItemAsync(this.localStorage, STORAGE_KEY)
845
+
846
+ if (this.persistSession) {
847
+ removeItemAsync(this.localStorage, STORAGE_KEY)
848
+ } else {
849
+ this.inMemorySession = null
850
+ }
851
+
852
+ if (this.refreshTokenTimer) {
853
+ clearTimeout(this.refreshTokenTimer)
854
+ }
738
855
  }
739
856
 
740
857
  /**
@@ -75,3 +75,28 @@ export const getItemSynchronously = (storage: SupportedStorage, key: string): an
75
75
  export const removeItemAsync = async (storage: SupportedStorage, key: string): Promise<void> => {
76
76
  isBrowser() && (await storage?.removeItem(key))
77
77
  }
78
+
79
+ /**
80
+ * A deferred represents some asynchronous work that is not yet finished, which
81
+ * may or may not culminate in a value.
82
+ * Taken from: https://github.com/mike-north/types/blob/master/src/async.ts
83
+ */
84
+ export class Deferred<T = any> {
85
+ public static promiseConstructor: PromiseConstructor = Promise
86
+
87
+ public readonly promise!: PromiseLike<T>
88
+
89
+ public readonly resolve!: (value?: T | PromiseLike<T>) => void
90
+
91
+ public readonly reject!: (reason?: any) => any
92
+
93
+ public constructor() {
94
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
95
+ ;(this as any).promise = new Deferred.promiseConstructor((res, rej) => {
96
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
97
+ ;(this as any).resolve = res
98
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
99
+ ;(this as any).reject = rej
100
+ })
101
+ }
102
+ }
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- export const version = '1.22.15'
2
+ export const version = '1.23.0-next.2'