Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide default storage when persistSession is false or localStorage is not supported #774

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 27 additions & 40 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
supportsLocalStorage,
parseParametersFromURL,
} from './lib/helpers'
import localStorageAdapter from './lib/local-storage'
import { localStorageAdapter, memoryLocalStorageAdapter } from './lib/local-storage'
import { polyfillGlobalThis } from './lib/polyfills'
import { version } from './lib/version'
import { LockAcquireTimeoutError } from './lib/locks'
Expand Down Expand Up @@ -123,17 +123,12 @@ export default class GoTrueClient {
*/
protected storageKey: string

/**
* The session object for the currently logged in user. If null, it means there isn't a logged-in user.
* Only used if persistSession is false.
*/
protected inMemorySession: Session | null

protected flowType: AuthFlowType

protected autoRefreshToken: boolean
protected persistSession: boolean
protected storage: SupportedStorage
protected memoryStorage: { [key: string]: string } | null = null
protected stateChangeEmitters: Map<string, Subscription> = new Map()
protected autoRefreshTicker: ReturnType<typeof setInterval> | null = null
protected visibilityChangedCallback: (() => Promise<any>) | null = null
Expand Down Expand Up @@ -183,11 +178,9 @@ export default class GoTrueClient {
this.logger = settings.debug
}

this.inMemorySession = null
this.persistSession = settings.persistSession
this.storageKey = settings.storageKey
this.autoRefreshToken = settings.autoRefreshToken
this.persistSession = settings.persistSession
this.storage = settings.storage || localStorageAdapter
this.admin = new GoTrueAdminApi({
url: settings.url,
headers: settings.headers,
Expand All @@ -211,11 +204,20 @@ export default class GoTrueClient {
getAuthenticatorAssuranceLevel: this._getAuthenticatorAssuranceLevel.bind(this),
}

if (this.persistSession && this.storage === localStorageAdapter && !supportsLocalStorage()) {
console.warn(
`No storage option exists to persist the session, which may result in unexpected behavior when using auth.
If you want to set persistSession to true, please provide a storage option or you may set persistSession to false to disable this warning.`
)
if (this.persistSession) {
if (settings.storage) {
this.storage = settings.storage
} else {
if (supportsLocalStorage()) {
this.storage = localStorageAdapter
} else {
this.memoryStorage = {}
this.storage = memoryLocalStorageAdapter(this.memoryStorage)
}
}
} else {
this.memoryStorage = {}
this.storage = memoryLocalStorageAdapter(this.memoryStorage)
}

if (isBrowser() && globalThis.BroadcastChannel && this.persistSession && this.storageKey) {
Expand Down Expand Up @@ -970,22 +972,17 @@ export default class GoTrueClient {
try {
let currentSession: Session | null = null

if (this.persistSession) {
const maybeSession = await getItemAsync(this.storage, this.storageKey)
const maybeSession = await getItemAsync(this.storage, this.storageKey)

this._debug('#getSession()', 'session from storage', maybeSession)
this._debug('#getSession()', 'session from storage', maybeSession)

if (maybeSession !== null) {
if (this._isValidSession(maybeSession)) {
currentSession = maybeSession
} else {
this._debug('#getSession()', 'session from storage is not valid')
await this._removeSession()
}
if (maybeSession !== null) {
if (this._isValidSession(maybeSession)) {
currentSession = maybeSession
} else {
this._debug('#getSession()', 'session from storage is not valid')
await this._removeSession()
}
} else {
currentSession = this.inMemorySession
this._debug('#getSession()', 'session from memory', currentSession)
}

if (!currentSession) {
Expand Down Expand Up @@ -1787,13 +1784,7 @@ export default class GoTrueClient {
private async _saveSession(session: Session) {
this._debug('#_saveSession()', session)

if (!this.persistSession) {
this.inMemorySession = session
}

if (this.persistSession && session.expires_at) {
await this._persistSession(session)
}
await this._persistSession(session)
}

private _persistSession(currentSession: Session) {
Expand All @@ -1805,11 +1796,7 @@ export default class GoTrueClient {
private async _removeSession() {
this._debug('#_removeSession()')

if (this.persistSession) {
await removeItemAsync(this.storage, this.storageKey)
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay was slow to figure it out so calling it out explicitly for future ref feel free to correct me if my understanding is off.

Looks like we no longer need to check persistSession since there's a default inMemorySession when persistSession is set to false. Consequently there's no need to check for the persistSession before calling removeItemAsync since it will now default to the in memory session

this.inMemorySession = null
}
await removeItemAsync(this.storage, this.storageKey)
}

/**
Expand Down
25 changes: 23 additions & 2 deletions src/lib/local-storage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { supportsLocalStorage } from './helpers'
import { SupportedStorage } from './types'

const localStorageAdapter: SupportedStorage = {
/**
* Provides safe access to the globalThis.localStorage property.
*/
export const localStorageAdapter: SupportedStorage = {
getItem: (key) => {
if (!supportsLocalStorage()) {
return null
Expand All @@ -25,4 +28,22 @@ const localStorageAdapter: SupportedStorage = {
},
}

export default localStorageAdapter
/**
* Returns a localStorage-like object that stores the key-value pairs in
* memory.
*/
export function memoryLocalStorageAdapter(store: { [key: string]: string } = {}): SupportedStorage {
return {
getItem: (key) => {
return store[key] || null
},

setItem: (key, value) => {
store[key] = value
},

removeItem: (key) => {
delete store[key]
},
}
}
19 changes: 13 additions & 6 deletions test/GoTrueClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,19 @@ describe('GoTrueClient', () => {
expired.setMinutes(expired.getMinutes() - 1)
const expiredSeconds = Math.floor(expired.getTime() / 1000)

// @ts-expect-error 'Allow access to protected inMemorySession'
authWithSession.inMemorySession = {
// @ts-expect-error 'Allow access to protected inMemorySession'
...authWithSession.inMemorySession,
expires_at: expiredSeconds,
}
// @ts-expect-error 'Allow access to protected storage'
const storage = authWithSession.storage

// @ts-expect-error 'Allow access to protected storageKey'
const storageKey = authWithSession.storageKey

await storage.setItem(
storageKey,
JSON.stringify({
...JSON.parse((await storage.getItem(storageKey)) || 'null'),
expires_at: expiredSeconds,
})
)

// wait 1 seconds before calling getSession()
await new Promise((r) => setTimeout(r, 1000))
Expand Down
3 changes: 2 additions & 1 deletion test/lib/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export const authClient = new GoTrueClient({
export const authClientWithSession = new GoTrueClient({
url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON,
autoRefreshToken: false,
persistSession: false,
persistSession: true,
storage: new MemoryStorage(),
})

export const authSubscriptionClient = new GoTrueClient({
Expand Down
Loading