• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-js / 16887185443

11 Aug 2025 05:19PM UTC coverage: 49.734% (-25.1%) from 74.8%
16887185443

Pull #1527

github

web-flow
Merge 14e02df3f into 7876a2487
Pull Request #1527: feat: update realtime-js to 2.16.0

65 of 156 branches covered (41.67%)

Branch coverage included in aggregate %.

0 of 74 new or added lines in 2 files covered. (0.0%)

122 of 220 relevant lines covered (55.45%)

22.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/SupabaseClient.auto.ts
1
/**
2
 * SupabaseClient with auto-detection for WebSocket
3
 * This version imports from @supabase/realtime-js/auto which includes dynamic imports
4
 * for backward compatibility with Node.js < 22
5
 *
6
 * @deprecated Will be removed in v3.0.0
7
 */
8

NEW
9
import { FunctionsClient } from '@supabase/functions-js'
×
10
import { AuthChangeEvent } from '@supabase/auth-js'
NEW
11
import {
×
12
  PostgrestClient,
13
  PostgrestFilterBuilder,
14
  PostgrestQueryBuilder,
15
} from '@supabase/postgrest-js'
NEW
16
import {
×
17
  RealtimeChannel,
18
  RealtimeChannelOptions,
19
  RealtimeClient,
20
  RealtimeClientOptions,
21
  // Import from realtime-js/auto instead of main export
22
  // @ts-ignore - TypeScript doesn't understand package.json exports with Node moduleResolution
23
} from '@supabase/realtime-js/auto'
NEW
24
import { StorageClient as SupabaseStorageClient } from '@supabase/storage-js'
×
NEW
25
import {
×
26
  DEFAULT_GLOBAL_OPTIONS,
27
  DEFAULT_DB_OPTIONS,
28
  DEFAULT_AUTH_OPTIONS,
29
  DEFAULT_REALTIME_OPTIONS,
30
} from './lib/constants'
NEW
31
import { fetchWithAuth } from './lib/fetch'
×
NEW
32
import { ensureTrailingSlash, applySettingDefaults } from './lib/helpers'
×
NEW
33
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
×
34
import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types'
35

36
/**
37
 * Supabase Client.
38
 *
39
 * An isomorphic Javascript client for interacting with Postgres.
40
 */
NEW
41
export default class SupabaseClient<
×
42
  Database = any,
43
  SchemaName extends string & keyof Database = 'public' extends keyof Database
44
    ? 'public'
45
    : string & keyof Database,
46
  Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
47
    ? Database[SchemaName]
48
    : any
49
> {
50
  /**
51
   * Supabase Auth allows you to create and manage user sessions for access to data that is secured by access policies.
52
   */
53
  auth: SupabaseAuthClient
54
  realtime: RealtimeClient
55
  /**
56
   * Supabase Storage allows you to manage user-generated content, such as photos or videos.
57
   */
58
  storage: SupabaseStorageClient
59

60
  protected realtimeUrl: URL
61
  protected authUrl: URL
62
  protected storageUrl: URL
63
  protected functionsUrl: URL
64
  protected rest: PostgrestClient<Database, SchemaName, Schema>
65
  protected storageKey: string
66
  protected fetch?: Fetch
67
  protected changedAccessToken?: string
68
  protected accessToken?: () => Promise<string | null>
69

70
  protected headers: Record<string, string>
71

72
  /**
73
   * Create a new client for use in the browser.
74
   * @param supabaseUrl The unique Supabase URL which is supplied when you create a new project in your project dashboard.
75
   * @param supabaseKey The unique Supabase Key which is supplied when you create a new project in your project dashboard.
76
   * @param options.db.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
77
   * @param options.auth.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
78
   * @param options.auth.persistSession Set to "true" if you want to automatically save the user session into local storage.
79
   * @param options.auth.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
80
   * @param options.realtime Options passed along to realtime-js constructor.
81
   * @param options.storage Options passed along to the storage-js constructor.
82
   * @param options.global.fetch A custom fetch implementation.
83
   * @param options.global.headers Any additional headers to send with each network request.
84
   */
85
  constructor(
NEW
86
    protected supabaseUrl: string,
×
NEW
87
    protected supabaseKey: string,
×
88
    options?: SupabaseClientOptions<SchemaName>
89
  ) {
NEW
90
    if (!supabaseUrl) throw new Error('supabaseUrl is required.')
×
NEW
91
    if (!supabaseKey) throw new Error('supabaseKey is required.')
×
92

NEW
93
    const _supabaseUrl = ensureTrailingSlash(supabaseUrl)
×
NEW
94
    const baseUrl = new URL(_supabaseUrl)
×
95

NEW
96
    this.realtimeUrl = new URL('realtime/v1', baseUrl)
×
NEW
97
    this.realtimeUrl.protocol = this.realtimeUrl.protocol.replace('http', 'ws')
×
NEW
98
    this.authUrl = new URL('auth/v1', baseUrl)
×
NEW
99
    this.storageUrl = new URL('storage/v1', baseUrl)
×
NEW
100
    this.functionsUrl = new URL('functions/v1', baseUrl)
×
101

102
    // default storage key uses the supabase project ref as a namespace
NEW
103
    const defaultStorageKey = `sb-${baseUrl.hostname.split('.')[0]}-auth-token`
×
NEW
104
    const DEFAULTS = {
×
105
      db: DEFAULT_DB_OPTIONS,
106
      realtime: DEFAULT_REALTIME_OPTIONS,
107
      auth: { ...DEFAULT_AUTH_OPTIONS, storageKey: defaultStorageKey },
108
      global: DEFAULT_GLOBAL_OPTIONS,
109
    }
110

NEW
111
    const settings = applySettingDefaults(options ?? {}, DEFAULTS)
×
112

NEW
113
    this.storageKey = settings.auth.storageKey ?? ''
×
NEW
114
    this.headers = settings.global.headers ?? {}
×
115

NEW
116
    if (!settings.accessToken) {
×
NEW
117
      this.auth = this._initSupabaseAuthClient(
×
118
        settings.auth ?? {},
×
119
        this.headers,
120
        settings.global.fetch
121
      )
122
    } else {
NEW
123
      this.accessToken = settings.accessToken
×
124

NEW
125
      this.auth = new Proxy<SupabaseAuthClient>({} as any, {
×
126
        get: (_, prop) => {
NEW
127
          throw new Error(
×
128
            `@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.${String(
129
              prop
130
            )} is not possible`
131
          )
132
        },
133
      })
134
    }
135

NEW
136
    this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global.fetch)
×
NEW
137
    this.realtime = this._initRealtimeClient({
×
138
      headers: this.headers,
139
      accessToken: this._getAccessToken.bind(this),
140
      ...settings.realtime,
141
    })
NEW
142
    this.rest = new PostgrestClient(new URL('rest/v1', baseUrl).href, {
×
143
      headers: this.headers,
144
      schema: settings.db.schema,
145
      fetch: this.fetch,
146
    })
147

NEW
148
    this.storage = new SupabaseStorageClient(
×
149
      this.storageUrl.href,
150
      this.headers,
151
      this.fetch,
152
      options?.storage
×
153
    )
154

NEW
155
    if (!settings.accessToken) {
×
NEW
156
      this._listenForAuthEvents()
×
157
    }
158
  }
159

160
  /**
161
   * Supabase Functions allows you to deploy and invoke edge functions.
162
   */
163
  get functions(): FunctionsClient {
NEW
164
    return new FunctionsClient(this.functionsUrl.href, {
×
165
      headers: this.headers,
166
      customFetch: this.fetch,
167
    })
168
  }
169

170
  // NOTE: signatures must be kept in sync with PostgrestClient.from
171
  from<
172
    TableName extends string & keyof Schema['Tables'],
173
    Table extends Schema['Tables'][TableName]
174
  >(relation: TableName): PostgrestQueryBuilder<Schema, Table, TableName>
175
  from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
176
    relation: ViewName
177
  ): PostgrestQueryBuilder<Schema, View, ViewName>
178
  /**
179
   * Perform a query on a table or a view.
180
   *
181
   * @param relation - The table or view name to query
182
   */
183
  from(relation: string): PostgrestQueryBuilder<Schema, any, any> {
NEW
184
    return this.rest.from(relation)
×
185
  }
186

187
  // NOTE: signatures must be kept in sync with PostgrestClient.schema
188
  /**
189
   * Select a schema to query or perform an function (rpc) call.
190
   *
191
   * The schema needs to be on the list of exposed schemas inside Supabase.
192
   *
193
   * @param schema - The schema to query
194
   */
195
  schema<DynamicSchema extends string & keyof Database>(
196
    schema: DynamicSchema
197
  ): PostgrestClient<
198
    Database,
199
    DynamicSchema,
200
    Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
201
  > {
NEW
202
    return this.rest.schema<DynamicSchema>(schema)
×
203
  }
204

205
  // NOTE: signatures must be kept in sync with PostgrestClient.rpc
206
  /**
207
   * Perform a function call.
208
   *
209
   * @param fn - The function name to call
210
   * @param args - The arguments to pass to the function call
211
   * @param options - Named parameters
212
   * @param options.head - When set to `true`, `data` will not be returned.
213
   * Useful if you only need the count.
214
   * @param options.get - When set to `true`, the function will be called with
215
   * read-only access mode.
216
   * @param options.count - Count algorithm to use to count rows returned by the
217
   * function. Only applicable for [set-returning
218
   * functions](https://www.postgresql.org/docs/current/functions-srf.html).
219
   *
220
   * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
221
   * hood.
222
   *
223
   * `"planned"`: Approximated but fast count algorithm. Uses the Postgres
224
   * statistics under the hood.
225
   *
226
   * `"estimated"`: Uses exact count for low numbers and planned count for high
227
   * numbers.
228
   */
229
  rpc<FnName extends string & keyof Schema['Functions'], Fn extends Schema['Functions'][FnName]>(
230
    fn: FnName,
231
    args: Fn['Args'] = {},
×
232
    options: {
×
233
      head?: boolean
234
      get?: boolean
235
      count?: 'exact' | 'planned' | 'estimated'
236
    } = {}
237
  ): PostgrestFilterBuilder<
238
    Schema,
239
    Fn['Returns'] extends any[]
240
      ? Fn['Returns'][number] extends Record<string, unknown>
241
        ? Fn['Returns'][number]
242
        : never
243
      : never,
244
    Fn['Returns'],
245
    FnName,
246
    null
247
  > {
NEW
248
    return this.rest.rpc(fn, args, options)
×
249
  }
250

251
  /**
252
   * Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
253
   *
254
   * @param {string} name - The name of the Realtime channel.
255
   * @param {Object} opts - The options to pass to the Realtime channel.
256
   *
257
   */
258
  channel(name: string, opts: RealtimeChannelOptions = { config: {} }): RealtimeChannel {
×
NEW
259
    return this.realtime.channel(name, opts)
×
260
  }
261

262
  /**
263
   * Returns all Realtime channels.
264
   */
265
  getChannels(): RealtimeChannel[] {
NEW
266
    return this.realtime.getChannels()
×
267
  }
268

269
  /**
270
   * Unsubscribes and removes Realtime channel from Realtime client.
271
   *
272
   * @param {RealtimeChannel} channel - The name of the Realtime channel.
273
   *
274
   */
275
  removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'> {
NEW
276
    return this.realtime.removeChannel(channel)
×
277
  }
278

279
  /**
280
   * Unsubscribes and removes all Realtime channels from Realtime client.
281
   */
282
  removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]> {
NEW
283
    return this.realtime.removeAllChannels()
×
284
  }
285

286
  private async _getAccessToken() {
NEW
287
    if (this.accessToken) {
×
NEW
288
      return await this.accessToken()
×
289
    }
290

NEW
291
    const { data } = await this.auth.getSession()
×
292

NEW
293
    return data.session?.access_token ?? this.supabaseKey
×
294
  }
295

296
  private _initSupabaseAuthClient(
297
    {
298
      autoRefreshToken,
299
      persistSession,
300
      detectSessionInUrl,
301
      storage,
302
      storageKey,
303
      flowType,
304
      lock,
305
      debug,
306
    }: SupabaseAuthClientOptions,
307
    headers?: Record<string, string>,
308
    fetch?: Fetch
309
  ) {
NEW
310
    const authHeaders = {
×
311
      Authorization: `Bearer ${this.supabaseKey}`,
312
      apikey: `${this.supabaseKey}`,
313
    }
NEW
314
    return new SupabaseAuthClient({
×
315
      url: this.authUrl.href,
316
      headers: { ...authHeaders, ...headers },
317
      storageKey: storageKey,
318
      autoRefreshToken,
319
      persistSession,
320
      detectSessionInUrl,
321
      storage,
322
      flowType,
323
      lock,
324
      debug,
325
      fetch,
326
      // auth checks if there is a custom authorizaiton header using this flag
327
      // so it knows whether to return an error when getUser is called with no session
328
      hasCustomAuthorizationHeader: 'Authorization' in this.headers,
329
    })
330
  }
331

332
  private _initRealtimeClient(options: RealtimeClientOptions) {
NEW
333
    return new RealtimeClient(this.realtimeUrl.href, {
×
334
      ...options,
335
      params: { ...{ apikey: this.supabaseKey }, ...options?.params },
×
336
    })
337
  }
338

339
  private _listenForAuthEvents() {
NEW
340
    let data = this.auth.onAuthStateChange((event, session) => {
×
NEW
341
      this._handleTokenChanged(event, 'CLIENT', session?.access_token)
×
342
    })
NEW
343
    return data
×
344
  }
345

346
  private _handleTokenChanged(
347
    event: AuthChangeEvent,
348
    source: 'CLIENT' | 'STORAGE',
349
    token?: string
350
  ) {
NEW
351
    if (
×
352
      (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
×
353
      this.changedAccessToken !== token
354
    ) {
NEW
355
      this.changedAccessToken = token
×
NEW
356
    } else if (event === 'SIGNED_OUT') {
×
NEW
357
      this.realtime.setAuth()
×
NEW
358
      if (source == 'STORAGE') this.auth.signOut()
×
NEW
359
      this.changedAccessToken = undefined
×
360
    }
361
  }
362
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc