Skip to content

Commit

Permalink
feat: use getSession from gotrue-js
Browse files Browse the repository at this point in the history
  • Loading branch information
alaister committed Jun 7, 2022
1 parent c43cf1c commit 2c3df35
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 48 deletions.
17 changes: 9 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -38,10 +38,11 @@
},
"dependencies": {
"@supabase/functions-js": "^1.3.3",
"@supabase/gotrue-js": "^1.22.14",
"@supabase/gotrue-js": "^1.23.0-next.1",
"@supabase/postgrest-js": "^0.37.2",
"@supabase/realtime-js": "^1.7.2",
"@supabase/storage-js": "^1.7.0"
"@supabase/storage-js": "^1.7.0",
"cross-fetch": "^3.1.5"
},
"devDependencies": {
"@types/jest": "^26.0.13",
Expand Down
44 changes: 22 additions & 22 deletions src/SupabaseClient.ts
@@ -1,18 +1,19 @@
import { DEFAULT_HEADERS, STORAGE_KEY } from './lib/constants'
import { stripTrailingSlash, isBrowser } from './lib/helpers'
import { Fetch, GenericObject, SupabaseClientOptions } from './lib/types'
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
import { SupabaseQueryBuilder } from './lib/SupabaseQueryBuilder'
import { SupabaseStorageClient } from '@supabase/storage-js'
import { FunctionsClient } from '@supabase/functions-js'
import { PostgrestClient } from '@supabase/postgrest-js'
import { AuthChangeEvent } from '@supabase/gotrue-js'
import { PostgrestClient } from '@supabase/postgrest-js'
import {
RealtimeChannel,
RealtimeClient,
RealtimeSubscription,
RealtimeClientOptions,
RealtimeChannel,
RealtimeSubscription,
} from '@supabase/realtime-js'
import { SupabaseStorageClient } from '@supabase/storage-js'
import { DEFAULT_HEADERS, STORAGE_KEY } from './lib/constants'
import { fetchWithAuth } from './lib/fetch'
import { isBrowser, stripTrailingSlash } from './lib/helpers'
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
import { SupabaseQueryBuilder } from './lib/SupabaseQueryBuilder'
import { Fetch, SupabaseClientOptions } from './lib/types'

const DEFAULT_OPTIONS = {
schema: 'public',
Expand Down Expand Up @@ -89,13 +90,14 @@ export default class SupabaseClient {

this.schema = settings.schema
this.multiTab = settings.multiTab
this.fetch = settings.fetch
this.headers = { ...DEFAULT_HEADERS, ...options?.headers }
this.shouldThrowOnError = settings.shouldThrowOnError || false

this.auth = this._initSupabaseAuthClient(settings)
this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })

this.fetch = fetchWithAuth(this._getAccessToken.bind(this), settings.fetch)

this._listenForAuthEvents()
this._listenForMultiTabEvents()

Expand All @@ -110,7 +112,7 @@ export default class SupabaseClient {
*/
get functions() {
return new FunctionsClient(this.functionsUrl, {
headers: this._getAuthHeaders(),
headers: this.headers,
customFetch: this.fetch,
})
}
Expand All @@ -119,7 +121,7 @@ export default class SupabaseClient {
* Supabase Storage allows you to manage user-generated content, such as photos or videos.
*/
get storage() {
return new SupabaseStorageClient(this.storageUrl, this._getAuthHeaders(), this.fetch)
return new SupabaseStorageClient(this.storageUrl, this.headers, this.fetch)
}

/**
Expand All @@ -130,7 +132,7 @@ export default class SupabaseClient {
from<T = any>(table: string): SupabaseQueryBuilder<T> {
const url = `${this.restUrl}/${table}`
return new SupabaseQueryBuilder<T>(url, {
headers: this._getAuthHeaders(),
headers: this.headers,
schema: this.schema,
realtime: this.realtime,
table,
Expand Down Expand Up @@ -227,6 +229,12 @@ export default class SupabaseClient {
return { data: { openSubscriptions: openSubCount }, error }
}

private async _getAccessToken() {
const { session } = await this.auth.getSession()

return session?.access_token ?? null
}

private async _closeSubscription(
subscription: RealtimeSubscription | RealtimeChannel
): Promise<{ error: Error | null }> {
Expand Down Expand Up @@ -297,21 +305,13 @@ export default class SupabaseClient {

private _initPostgRESTClient() {
return new PostgrestClient(this.restUrl, {
headers: this._getAuthHeaders(),
headers: this.headers,
schema: this.schema,
fetch: this.fetch,
throwOnError: this.shouldThrowOnError,
})
}

private _getAuthHeaders(): GenericObject {
const headers: GenericObject = { ...this.headers }
const authBearer = this.auth.session()?.access_token ?? this.supabaseKey
headers['apikey'] = this.supabaseKey
headers['Authorization'] = headers['Authorization'] || `Bearer ${authBearer}`
return headers
}

private _listenForMultiTabEvents() {
if (!this.multiTab || !isBrowser() || !window?.addEventListener) {
return null
Expand Down
42 changes: 42 additions & 0 deletions src/lib/fetch.ts
@@ -0,0 +1,42 @@
import crossFetch, { Headers as CrossFetchHeaders } from 'cross-fetch'

type Fetch = typeof fetch

export const resolveFetch = (customFetch?: Fetch): Fetch => {
let _fetch: Fetch
if (customFetch) {
_fetch = customFetch
} else if (typeof fetch === 'undefined') {
_fetch = crossFetch as unknown as Fetch
} else {
_fetch = fetch
}
return (...args) => _fetch(...args)
}

export const resolveHeadersConstructor = () => {
if (typeof Headers === 'undefined') {
return CrossFetchHeaders
}

return Headers
}

export const fetchWithAuth = (
getAccessToken: () => Promise<string | null>,
customFetch?: Fetch
): Fetch => {
const fetch = resolveFetch(customFetch)
const HeadersConstructor = resolveHeadersConstructor()

return async (input, init) => {
const accessToken = await getAccessToken()
let headers = new HeadersConstructor(init?.headers)

if (!headers.has('Authorization') && accessToken) {
headers.set('Authorization', `Bearer ${accessToken}`)
}

return fetch(input, { ...init, headers })
}
}
30 changes: 14 additions & 16 deletions test/client.test.ts
@@ -1,5 +1,4 @@
import { createClient, SupabaseClient } from '../src/index'
import { DEFAULT_HEADERS } from '../src/lib/constants'

const URL = 'http://localhost:3000'
const KEY = 'some.fake.key'
Expand All @@ -17,37 +16,36 @@ test('it should throw an error if no valid params are provided', async () => {
})

test('it should not cache Authorization header', async () => {
const checkHeadersSpy = jest.spyOn(SupabaseClient.prototype as any, '_getAuthHeaders')

supabase.auth.setAuth('token1')
supabase.rpc('') // Calling public method `rpc` calls private method _getAuthHeaders which result we want to test
supabase.auth.setAuth('token2')
supabase.rpc('') // Calling public method `rpc` calls private method _getAuthHeaders which result we want to test
supabase.rpc('')
expect(supabase.auth.session()?.access_token).toBe('token1')

expect(checkHeadersSpy.mock.results[0].value).toHaveProperty('Authorization', 'Bearer token1')
expect(checkHeadersSpy.mock.results[1].value).toHaveProperty('Authorization', 'Bearer token2')
supabase.auth.setAuth('token2')
supabase.rpc('')
expect(supabase.auth.session()?.access_token).toBe('token2')
})

describe('Custom Headers', () => {
test('should have custom header set', () => {
const customHeader = { 'X-Test-Header': 'value' }

const checkHeadersSpy = jest.spyOn(SupabaseClient.prototype as any, '_getAuthHeaders')
createClient(URL, KEY, { headers: customHeader }).rpc('') // Calling public method `rpc` calls private method _getAuthHeaders which result we want to test
const getHeaders = checkHeadersSpy.mock.results[0].value
const request = createClient(URL, KEY, { headers: customHeader }).rpc('')

// @ts-ignore
const getHeaders = request.headers

expect(checkHeadersSpy).toBeCalled()
expect(getHeaders).toHaveProperty('X-Test-Header', 'value')
})

test('should allow custom Authorization header', () => {
const customHeader = { Authorization: 'Bearer custom_token' }
supabase.auth.setAuth('override_me')
const checkHeadersSpy = jest.spyOn(SupabaseClient.prototype as any, '_getAuthHeaders')
createClient(URL, KEY, { headers: customHeader }).rpc('') // Calling public method `rpc` calls private method _getAuthHeaders which result we want to test
const getHeaders = checkHeadersSpy.mock.results[0].value

expect(checkHeadersSpy).toBeCalled()
const request = createClient(URL, KEY, { headers: customHeader }).rpc('')

// @ts-ignore
const getHeaders = request.headers

expect(getHeaders).toHaveProperty('Authorization', 'Bearer custom_token')
})
})
Expand Down

0 comments on commit 2c3df35

Please sign in to comment.