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

supabase / supabase-js / 7885533013

13 Feb 2024 11:05AM UTC coverage: 65.385% (-0.5%) from 65.877%
7885533013

Pull #973

github

web-flow
Merge 9c6ee410c into 4362b3fb2
Pull Request #973: fix: Use class methods instead of bound arrow functions for rest calls

47 of 92 branches covered (51.09%)

Branch coverage included in aggregate %.

89 of 116 relevant lines covered (76.72%)

4.42 hits per line

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

64.71
/src/SupabaseClient.ts
1
import { FunctionsClient } from '@supabase/functions-js'
2✔
2
import { AuthChangeEvent } from '@supabase/gotrue-js'
3
import { PostgrestClient } from '@supabase/postgrest-js'
2✔
4
import {
2✔
5
  RealtimeChannel,
6
  RealtimeChannelOptions,
7
  RealtimeClient,
8
  RealtimeClientOptions,
9
} from '@supabase/realtime-js'
10
import { StorageClient as SupabaseStorageClient } from '@supabase/storage-js'
2✔
11
import {
2✔
12
  DEFAULT_GLOBAL_OPTIONS,
13
  DEFAULT_DB_OPTIONS,
14
  DEFAULT_AUTH_OPTIONS,
15
  DEFAULT_REALTIME_OPTIONS,
16
} from './lib/constants'
17
import { fetchWithAuth } from './lib/fetch'
2✔
18
import { stripTrailingSlash, applySettingDefaults } from './lib/helpers'
2✔
19
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
2✔
20
import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types'
21

22
/**
23
 * Supabase Client.
24
 *
25
 * An isomorphic Javascript client for interacting with Postgres.
26
 */
27
export default class SupabaseClient<
2✔
28
  Database = any,
29
  SchemaName extends string & keyof Database = 'public' extends keyof Database
30
    ? 'public'
31
    : string & keyof Database,
32
  Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
33
    ? Database[SchemaName]
34
    : any
35
> {
36
  /**
37
   * Supabase Auth allows you to create and manage user sessions for access to data that is secured by access policies.
38
   */
39
  auth: SupabaseAuthClient
40
  realtime: RealtimeClient
41

42
  protected realtimeUrl: string
43
  protected authUrl: string
44
  protected storageUrl: string
45
  protected functionsUrl: string
46
  protected rest: PostgrestClient<Database, SchemaName>
47
  protected storageKey: string
48
  protected fetch?: Fetch
49
  protected changedAccessToken?: string
50

51
  protected headers: Record<string, string>
52

53
  /**
54
   * Create a new client for use in the browser.
55
   * @param supabaseUrl The unique Supabase URL which is supplied when you create a new project in your project dashboard.
56
   * @param supabaseKey The unique Supabase Key which is supplied when you create a new project in your project dashboard.
57
   * @param options.db.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
58
   * @param options.auth.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
59
   * @param options.auth.persistSession Set to "true" if you want to automatically save the user session into local storage.
60
   * @param options.auth.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
61
   * @param options.realtime Options passed along to realtime-js constructor.
62
   * @param options.global.fetch A custom fetch implementation.
63
   * @param options.global.headers Any additional headers to send with each network request.
64
   */
65
  constructor(
66
    protected supabaseUrl: string,
9✔
67
    protected supabaseKey: string,
9✔
68
    options?: SupabaseClientOptions<SchemaName>
69
  ) {
70
    if (!supabaseUrl) throw new Error('supabaseUrl is required.')
9✔
71
    if (!supabaseKey) throw new Error('supabaseKey is required.')
8✔
72

73
    const _supabaseUrl = stripTrailingSlash(supabaseUrl)
7✔
74

75
    this.realtimeUrl = `${_supabaseUrl}/realtime/v1`.replace(/^http/i, 'ws')
7✔
76
    this.authUrl = `${_supabaseUrl}/auth/v1`
7✔
77
    this.storageUrl = `${_supabaseUrl}/storage/v1`
7✔
78
    this.functionsUrl = `${_supabaseUrl}/functions/v1`
7✔
79

80
    // default storage key uses the supabase project ref as a namespace
81
    const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
7✔
82
    const DEFAULTS = {
7✔
83
      db: DEFAULT_DB_OPTIONS,
84
      realtime: DEFAULT_REALTIME_OPTIONS,
85
      auth: { ...DEFAULT_AUTH_OPTIONS, storageKey: defaultStorageKey },
86
      global: DEFAULT_GLOBAL_OPTIONS,
87
    }
88

89
    const settings = applySettingDefaults(options ?? {}, DEFAULTS)
7✔
90

91
    this.storageKey = settings.auth?.storageKey ?? ''
7!
92
    this.headers = settings.global?.headers ?? {}
7!
93

94
    this.auth = this._initSupabaseAuthClient(
7✔
95
      settings.auth ?? {},
21!
96
      this.headers,
97
      settings.global?.fetch
21!
98
    )
99
    this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global?.fetch)
7!
100

101
    this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
7✔
102
    this.rest = new PostgrestClient(`${_supabaseUrl}/rest/v1`, {
7✔
103
      headers: this.headers,
104
      schema: settings.db?.schema,
21!
105
      fetch: this.fetch,
106
    })
107

108
    this._listenForAuthEvents()
7✔
109
  }
110

111
  /**
112
   * Supabase Functions allows you to deploy and invoke edge functions.
113
   */
114
  get functions() {
115
    return new FunctionsClient(this.functionsUrl, {
×
116
      headers: this.headers,
117
      customFetch: this.fetch,
118
    })
119
  }
120

121
  /**
122
   * Supabase Storage allows you to manage user-generated content, such as photos or videos.
123
   */
124
  get storage() {
125
    return new SupabaseStorageClient(this.storageUrl, this.headers, this.fetch)
×
126
  }
127

128
  /**
129
   * Perform a query on a table or a view.
130
   *
131
   * @param relation - The table or view name to query
132
   */
133
  from(relation: string) {
134
    return this.rest.from(relation)
×
135
  }
136

137
  /**
138
   * Perform a query on a schema distinct from the default schema supplied via
139
   * the `options.db.schema` constructor parameter.
140
   *
141
   * The schema needs to be on the list of exposed schemas inside Supabase.
142
   *
143
   * @param schema - The name of the schema to query
144
   */
145
  schema<DynamicSchema extends string & keyof Database>(schema: DynamicSchema) {
146
    return this.rest.schema<DynamicSchema>(schema)
2✔
147
  }
148

149
  /**
150
   * Perform a function call.
151
   *
152
   * @param fn - The function name to call
153
   * @param args - The arguments to pass to the function call
154
   * @param options - Named parameters
155
   * @param options.head - When set to `true`, `data` will not be returned.
156
   * Useful if you only need the count.
157
   * @param options.count - Count algorithm to use to count rows returned by the
158
   * function. Only applicable for [set-returning
159
   * functions](https://www.postgresql.org/docs/current/functions-srf.html).
160
   *
161
   * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
162
   * hood.
163
   *
164
   * `"planned"`: Approximated but fast count algorithm. Uses the Postgres
165
   * statistics under the hood.
166
   *
167
   * `"estimated"`: Uses exact count for low numbers and planned count for high
168
   * numbers.
169
   */
170
  rpc<FnName extends string & keyof Schema['Functions'], Fn extends Schema['Functions'][FnName]>(
171
    fn: FnName,
172
    args: Fn['Args'] = {},
1✔
173
    options?: {
174
      head?: boolean
175
      count?: 'exact' | 'planned' | 'estimated'
176
    }
177
  ) {
178
    return this.rest.rpc(fn, args, options)
1✔
179
  }
180

181
  /**
182
   * Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
183
   *
184
   * @param {string} name - The name of the Realtime channel.
185
   * @param {Object} opts - The options to pass to the Realtime channel.
186
   *
187
   */
188
  channel(name: string, opts: RealtimeChannelOptions = { config: {} }): RealtimeChannel {
×
189
    return this.realtime.channel(name, opts)
×
190
  }
191

192
  /**
193
   * Returns all Realtime channels.
194
   */
195
  getChannels(): RealtimeChannel[] {
196
    return this.realtime.getChannels()
×
197
  }
198

199
  /**
200
   * Unsubscribes and removes Realtime channel from Realtime client.
201
   *
202
   * @param {RealtimeChannel} channel - The name of the Realtime channel.
203
   *
204
   */
205
  removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'> {
206
    return this.realtime.removeChannel(channel)
×
207
  }
208

209
  /**
210
   * Unsubscribes and removes all Realtime channels from Realtime client.
211
   */
212
  removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]> {
213
    return this.realtime.removeAllChannels()
×
214
  }
215

216
  private async _getAccessToken() {
217
    const { data } = await this.auth.getSession()
×
218

219
    return data.session?.access_token ?? null
×
220
  }
221

222
  private _initSupabaseAuthClient(
223
    {
224
      autoRefreshToken,
225
      persistSession,
226
      detectSessionInUrl,
227
      storage,
228
      storageKey,
229
      flowType,
230
      debug,
231
    }: SupabaseAuthClientOptions,
232
    headers?: Record<string, string>,
233
    fetch?: Fetch
234
  ) {
235
    const authHeaders = {
8✔
236
      Authorization: `Bearer ${this.supabaseKey}`,
237
      apikey: `${this.supabaseKey}`,
238
    }
239
    return new SupabaseAuthClient({
8✔
240
      url: this.authUrl,
241
      headers: { ...authHeaders, ...headers },
242
      storageKey: storageKey,
243
      autoRefreshToken,
244
      persistSession,
245
      detectSessionInUrl,
246
      storage,
247
      flowType,
248
      debug,
249
      fetch,
250
    })
251
  }
252

253
  private _initRealtimeClient(options: RealtimeClientOptions) {
254
    return new RealtimeClient(this.realtimeUrl, {
7✔
255
      ...options,
256
      params: { ...{ apikey: this.supabaseKey }, ...options?.params },
21!
257
    })
258
  }
259

260
  private _listenForAuthEvents() {
261
    let data = this.auth.onAuthStateChange((event, session) => {
7✔
262
      this._handleTokenChanged(event, 'CLIENT', session?.access_token)
7!
263
    })
264
    return data
7✔
265
  }
266

267
  private _handleTokenChanged(
268
    event: AuthChangeEvent,
269
    source: 'CLIENT' | 'STORAGE',
270
    token?: string
271
  ) {
272
    if (
7!
273
      (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
14!
274
      this.changedAccessToken !== token
275
    ) {
276
      // Token has changed
277
      this.realtime.setAuth(token ?? null)
×
278

279
      this.changedAccessToken = token
×
280
    } else if (event === 'SIGNED_OUT') {
7!
281
      // Token is removed
282
      this.realtime.setAuth(this.supabaseKey)
×
283
      if (source == 'STORAGE') this.auth.signOut()
×
284
      this.changedAccessToken = undefined
×
285
    }
286
  }
287
}
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

© 2026 Coveralls, Inc