@supabase/supabase-js 1.28.2 → 1.28.6

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 (44) hide show
  1. package/README.md +5 -2
  2. package/dist/main/SupabaseClient.d.ts +10 -0
  3. package/dist/main/SupabaseClient.d.ts.map +1 -1
  4. package/dist/main/SupabaseClient.js +75 -5
  5. package/dist/main/SupabaseClient.js.map +1 -1
  6. package/dist/main/lib/SupabaseQueryBuilder.d.ts +4 -1
  7. package/dist/main/lib/SupabaseQueryBuilder.d.ts.map +1 -1
  8. package/dist/main/lib/SupabaseQueryBuilder.js +8 -2
  9. package/dist/main/lib/SupabaseQueryBuilder.js.map +1 -1
  10. package/dist/main/lib/constants.d.ts +1 -0
  11. package/dist/main/lib/constants.d.ts.map +1 -1
  12. package/dist/main/lib/constants.js +2 -1
  13. package/dist/main/lib/constants.js.map +1 -1
  14. package/dist/main/lib/helpers.d.ts +1 -0
  15. package/dist/main/lib/helpers.d.ts.map +1 -1
  16. package/dist/main/lib/helpers.js +3 -1
  17. package/dist/main/lib/helpers.js.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/SupabaseClient.d.ts +10 -0
  21. package/dist/module/SupabaseClient.d.ts.map +1 -1
  22. package/dist/module/SupabaseClient.js +76 -6
  23. package/dist/module/SupabaseClient.js.map +1 -1
  24. package/dist/module/lib/SupabaseQueryBuilder.d.ts +4 -1
  25. package/dist/module/lib/SupabaseQueryBuilder.d.ts.map +1 -1
  26. package/dist/module/lib/SupabaseQueryBuilder.js +8 -2
  27. package/dist/module/lib/SupabaseQueryBuilder.js.map +1 -1
  28. package/dist/module/lib/constants.d.ts +1 -0
  29. package/dist/module/lib/constants.d.ts.map +1 -1
  30. package/dist/module/lib/constants.js +1 -0
  31. package/dist/module/lib/constants.js.map +1 -1
  32. package/dist/module/lib/helpers.d.ts +1 -0
  33. package/dist/module/lib/helpers.d.ts.map +1 -1
  34. package/dist/module/lib/helpers.js +1 -0
  35. package/dist/module/lib/helpers.js.map +1 -1
  36. package/dist/module/lib/version.d.ts +1 -1
  37. package/dist/module/lib/version.js +1 -1
  38. package/dist/umd/supabase.js +1 -1
  39. package/package.json +4 -3
  40. package/src/SupabaseClient.ts +86 -6
  41. package/src/lib/SupabaseQueryBuilder.ts +16 -3
  42. package/src/lib/constants.ts +1 -0
  43. package/src/lib/helpers.ts +2 -0
  44. package/src/lib/version.ts +1 -1
@@ -1,10 +1,11 @@
1
- import { DEFAULT_HEADERS } from './lib/constants'
2
- import { stripTrailingSlash } from './lib/helpers'
1
+ import { DEFAULT_HEADERS, STORAGE_KEY } from './lib/constants'
2
+ import { stripTrailingSlash, isBrowser } from './lib/helpers'
3
3
  import { Fetch, SupabaseClientOptions } from './lib/types'
4
4
  import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
5
5
  import { SupabaseQueryBuilder } from './lib/SupabaseQueryBuilder'
6
6
  import { SupabaseStorageClient } from '@supabase/storage-js'
7
7
  import { PostgrestClient } from '@supabase/postgrest-js'
8
+ import { AuthChangeEvent, Session, Subscription } from '@supabase/gotrue-js'
8
9
  import { RealtimeClient, RealtimeSubscription, RealtimeClientOptions } from '@supabase/realtime-js'
9
10
 
10
11
  const DEFAULT_OPTIONS = {
@@ -12,6 +13,7 @@ const DEFAULT_OPTIONS = {
12
13
  autoRefreshToken: true,
13
14
  persistSession: true,
14
15
  detectSessionInUrl: true,
16
+ multiTab: true,
15
17
  headers: DEFAULT_HEADERS,
16
18
  }
17
19
 
@@ -32,7 +34,9 @@ export default class SupabaseClient {
32
34
  protected authUrl: string
33
35
  protected storageUrl: string
34
36
  protected realtime: RealtimeClient
37
+ protected multiTab: boolean
35
38
  protected fetch?: Fetch
39
+ protected changedAccessToken: string | undefined
36
40
  protected headers: {
37
41
  [key: string]: string
38
42
  }
@@ -47,6 +51,7 @@ export default class SupabaseClient {
47
51
  * @param options.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
48
52
  * @param options.headers Any additional headers to send with each network request.
49
53
  * @param options.realtime Options passed along to realtime-js constructor.
54
+ * @param options.multiTab Set to "false" if you want to disable multi-tab/window events.
50
55
  * @param options.fetch A custom fetch implementation.
51
56
  */
52
57
  constructor(
@@ -65,12 +70,16 @@ export default class SupabaseClient {
65
70
  this.authUrl = `${supabaseUrl}/auth/v1`
66
71
  this.storageUrl = `${supabaseUrl}/storage/v1`
67
72
  this.schema = settings.schema
73
+ this.multiTab = settings.multiTab
68
74
  this.fetch = settings.fetch
69
75
  this.headers = { ...DEFAULT_HEADERS, ...options?.headers }
70
76
 
71
77
  this.auth = this._initSupabaseAuthClient(settings)
72
78
  this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
73
79
 
80
+ this._listenForAuthEvents()
81
+ this._listenForMultiTabEvents()
82
+
74
83
  // In the future we might allow the user to pass in a logger to receive these events.
75
84
  // this.realtime.onOpen(() => console.log('OPEN'))
76
85
  // this.realtime.onClose(() => console.log('CLOSED'))
@@ -121,6 +130,14 @@ export default class SupabaseClient {
121
130
  return rest.rpc<T>(fn, params, { head, count })
122
131
  }
123
132
 
133
+ /**
134
+ * Remove all subscriptions.
135
+ */
136
+ async removeAllSubscriptions() {
137
+ const subscriptions = this.realtime.channels.slice()
138
+ return await Promise.allSettled(subscriptions.map((sub) => this.removeSubscription(sub)))
139
+ }
140
+
124
141
  /**
125
142
  * Removes an active subscription and returns the number of open connections.
126
143
  *
@@ -131,12 +148,14 @@ export default class SupabaseClient {
131
148
  try {
132
149
  await this._closeSubscription(subscription)
133
150
 
134
- const openSubscriptions = this.getSubscriptions().length
135
- if (!openSubscriptions) {
151
+ const allSubscriptions = this.getSubscriptions()
152
+ const openSubscriptionsCount = allSubscriptions.filter((chan) => chan.isJoined()).length
153
+
154
+ if (!allSubscriptions.length) {
136
155
  const { error } = await this.realtime.disconnect()
137
156
  if (error) return resolve({ error })
138
157
  }
139
- return resolve({ error: null, data: { openSubscriptions } })
158
+ return resolve({ error: null, data: { openSubscriptions: openSubscriptionsCount } })
140
159
  } catch (error) {
141
160
  return resolve({ error })
142
161
  }
@@ -147,6 +166,11 @@ export default class SupabaseClient {
147
166
  if (!subscription.isClosed()) {
148
167
  await this._closeChannel(subscription)
149
168
  }
169
+
170
+ return new Promise((resolve) => {
171
+ this.realtime.remove(subscription)
172
+ return resolve(true)
173
+ })
150
174
  }
151
175
 
152
176
  /**
@@ -207,10 +231,66 @@ export default class SupabaseClient {
207
231
  subscription
208
232
  .unsubscribe()
209
233
  .receive('ok', () => {
210
- this.realtime.remove(subscription)
211
234
  return resolve(true)
212
235
  })
213
236
  .receive('error', (e: Error) => reject(e))
214
237
  })
215
238
  }
239
+
240
+ private _listenForMultiTabEvents() {
241
+ if (!this.multiTab || !isBrowser() || !window?.addEventListener) {
242
+ return null
243
+ }
244
+
245
+ try {
246
+ return window?.addEventListener('storage', (e: StorageEvent) => {
247
+ if (e.key === STORAGE_KEY) {
248
+ const newSession = JSON.parse(String(e.newValue))
249
+ const accessToken: string | undefined =
250
+ newSession?.currentSession?.access_token ?? undefined
251
+ const previousAccessToken = this.auth.session()?.access_token
252
+ if (!accessToken) {
253
+ this._handleTokenChanged('SIGNED_OUT', accessToken, 'STORAGE')
254
+ } else if (!previousAccessToken && accessToken) {
255
+ this._handleTokenChanged('SIGNED_IN', accessToken, 'STORAGE')
256
+ } else if (previousAccessToken !== accessToken) {
257
+ this._handleTokenChanged('TOKEN_REFRESHED', accessToken, 'STORAGE')
258
+ }
259
+ }
260
+ })
261
+ } catch (error) {
262
+ console.error('_listenForMultiTabEvents', error)
263
+ return null
264
+ }
265
+ }
266
+
267
+ private _listenForAuthEvents() {
268
+ let { data } = this.auth.onAuthStateChange((event, session) => {
269
+ this._handleTokenChanged(event, session?.access_token, 'CLIENT')
270
+ })
271
+ return data
272
+ }
273
+
274
+ private _handleTokenChanged(
275
+ event: AuthChangeEvent,
276
+ token: string | undefined,
277
+ source: 'CLIENT' | 'STORAGE'
278
+ ) {
279
+ if (
280
+ (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
281
+ this.changedAccessToken !== token
282
+ ) {
283
+ // Token has changed
284
+ this.realtime.setAuth(token!)
285
+ // Ideally we should call this.auth.recoverSession() - need to make public
286
+ // to trigger a "SIGNED_IN" event on this client.
287
+ if (source == 'STORAGE') this.auth.setAuth(token!)
288
+
289
+ this.changedAccessToken = token
290
+ } else if (event === 'SIGNED_OUT' || event === 'USER_DELETED') {
291
+ // Token is removed
292
+ this.removeAllSubscriptions()
293
+ if (source == 'STORAGE') this.auth.signOut()
294
+ }
295
+ }
216
296
  }
@@ -4,8 +4,11 @@ import { RealtimeClient } from '@supabase/realtime-js'
4
4
  import { Fetch, SupabaseEventTypes, SupabaseRealtimePayload } from './types'
5
5
 
6
6
  export class SupabaseQueryBuilder<T> extends PostgrestQueryBuilder<T> {
7
- private _subscription: SupabaseRealtimeClient
7
+ private _subscription: SupabaseRealtimeClient | null = null
8
8
  private _realtime: RealtimeClient
9
+ private _headers: { [key: string]: string }
10
+ private _schema: string
11
+ private _table: string
9
12
 
10
13
  constructor(
11
14
  url: string,
@@ -25,12 +28,14 @@ export class SupabaseQueryBuilder<T> extends PostgrestQueryBuilder<T> {
25
28
  ) {
26
29
  super(url, { headers, schema, fetch })
27
30
 
28
- this._subscription = new SupabaseRealtimeClient(realtime, headers, schema, table)
29
31
  this._realtime = realtime
32
+ this._headers = headers
33
+ this._schema = schema
34
+ this._table = table
30
35
  }
31
36
 
32
37
  /**
33
- * Subscribe to realtime changes in your databse.
38
+ * Subscribe to realtime changes in your database.
34
39
  * @param event The database event which you would like to receive updates for, or you can use the special wildcard `*` to listen to all changes.
35
40
  * @param callback A callback that will handle the payload that is sent whenever your database changes.
36
41
  */
@@ -41,6 +46,14 @@ export class SupabaseQueryBuilder<T> extends PostgrestQueryBuilder<T> {
41
46
  if (!this._realtime.isConnected()) {
42
47
  this._realtime.connect()
43
48
  }
49
+ if (!this._subscription) {
50
+ this._subscription = new SupabaseRealtimeClient(
51
+ this._realtime,
52
+ this._headers,
53
+ this._schema,
54
+ this._table
55
+ )
56
+ }
44
57
  return this._subscription.on(event, callback)
45
58
  }
46
59
  }
@@ -1,3 +1,4 @@
1
1
  // constants.ts
2
2
  import { version } from './version'
3
3
  export const DEFAULT_HEADERS = { 'X-Client-Info': `supabase-js/${version}` }
4
+ export const STORAGE_KEY = 'supabase.auth.token'
@@ -11,3 +11,5 @@ export function uuid() {
11
11
  export function stripTrailingSlash(url: string) {
12
12
  return url.replace(/\/$/, '')
13
13
  }
14
+
15
+ export const isBrowser = () => typeof window !== 'undefined'
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- export const version = '1.28.2'
2
+ export const version = '1.28.6'