diff --git a/src/GoTrueClient.ts b/src/GoTrueClient.ts index 0b7d5d0d..749240a9 100644 --- a/src/GoTrueClient.ts +++ b/src/GoTrueClient.ts @@ -107,6 +107,7 @@ const DEFAULT_OPTIONS: Omit, 'fetch' | 'storage' | flowType: 'implicit', debug: false, hasCustomAuthorizationHeader: false, + throwOnError: false, } /** Current session will be checked for refresh at this interval. */ @@ -167,6 +168,7 @@ export default class GoTrueClient { protected lock: LockFunc protected lockAcquired = false protected pendingInLock: Promise[] = [] + protected throwOnError: boolean /** * Used to broadcast state change events to other tabs listening. @@ -212,6 +214,7 @@ export default class GoTrueClient { this.detectSessionInUrl = settings.detectSessionInUrl this.flowType = settings.flowType this.hasCustomAuthorizationHeader = settings.hasCustomAuthorizationHeader + this.throwOnError = settings.throwOnError if (settings.lock) { this.lock = settings.lock @@ -399,6 +402,7 @@ export default class GoTrueClient { const { data, error } = res if (error || !data) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error: error } } const session: Session | null = data.session @@ -412,6 +416,7 @@ export default class GoTrueClient { return { data: { user, session }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } @@ -477,6 +482,7 @@ export default class GoTrueClient { const { data, error } = res if (error || !data) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error: error } } @@ -491,6 +497,7 @@ export default class GoTrueClient { return { data: { user, session }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } @@ -541,9 +548,12 @@ export default class GoTrueClient { const { data, error } = res if (error) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } else if (!data || !data.session || !data.user) { - return { data: { user: null, session: null }, error: new AuthInvalidTokenResponseError() } + const invalidTokenError = new AuthInvalidTokenResponseError() + if (this.throwOnError) throw invalidTokenError + return { data: { user: null, session: null }, error: invalidTokenError } } if (data.session) { await this._saveSession(data.session) @@ -559,6 +569,7 @@ export default class GoTrueClient { } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } throw error @@ -659,11 +670,14 @@ export default class GoTrueClient { const { data, error } = res if (error) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } else if (!data || !data.session || !data.user) { + const invalidTokenError = new AuthInvalidTokenResponseError() + if (this.throwOnError) throw invalidTokenError return { data: { user: null, session: null }, - error: new AuthInvalidTokenResponseError(), + error: invalidTokenError, } } if (data.session) { @@ -673,6 +687,7 @@ export default class GoTrueClient { return { data, error } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } throw error @@ -720,6 +735,7 @@ export default class GoTrueClient { }, redirectTo: options?.emailRedirectTo, }) + if (this.throwOnError && error) throw error return { data: { user: null, session: null }, error } } if ('phone' in credentials) { @@ -734,11 +750,13 @@ export default class GoTrueClient { channel: options?.channel ?? 'sms', }, }) + if (this.throwOnError && error) throw error return { data: { user: null, session: null, messageId: data?.message_id }, error } } throw new AuthInvalidCredentialsError('You must provide either an email or phone number.') } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } @@ -768,11 +786,14 @@ export default class GoTrueClient { }) if (error) { + if (this.throwOnError) throw error throw error } if (!data) { - throw new Error('An error occurred on token verification.') + const tokenVerificationError = new Error('An error occurred on token verification.') + if (this.throwOnError) throw tokenVerificationError + throw tokenVerificationError } const session: Session | null = data.session @@ -789,6 +810,7 @@ export default class GoTrueClient { return { data: { user, session }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } @@ -838,6 +860,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -870,10 +893,12 @@ export default class GoTrueClient { headers: this.headers, jwt: session.access_token, }) + if (this.throwOnError && error) throw error return { data: { user: null, session: null }, error } }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } throw error @@ -897,6 +922,7 @@ export default class GoTrueClient { }, redirectTo: options?.emailRedirectTo, }) + if (this.throwOnError && error) throw error return { data: { user: null, session: null }, error } } else if ('phone' in credentials) { const { phone, type, options } = credentials @@ -908,6 +934,7 @@ export default class GoTrueClient { gotrue_meta_security: { captcha_token: options?.captchaToken }, }, }) + if (this.throwOnError && error) throw error return { data: { user: null, session: null, messageId: data?.message_id }, error } } throw new AuthInvalidCredentialsError( @@ -915,6 +942,7 @@ export default class GoTrueClient { ) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } throw error @@ -1209,6 +1237,7 @@ export default class GoTrueClient { await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`) } + if (this.throwOnError) throw error return { data: { user: null }, error } } @@ -1268,7 +1297,10 @@ export default class GoTrueClient { jwt: session.access_token, xform: _userResponse, }) - if (userError) throw userError + if (userError) { + if (this.throwOnError) throw userError + throw userError + } session.user = data.user as User await this._saveSession(session) await this._notifyAllSubscribers('USER_UPDATED', session) @@ -1276,6 +1308,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null }, error } } @@ -1334,6 +1367,7 @@ export default class GoTrueClient { currentSession.refresh_token ) if (error) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error: error } } @@ -1361,6 +1395,7 @@ export default class GoTrueClient { return { data: { user: session.user, session }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { session: null, user: null }, error } } @@ -1402,6 +1437,7 @@ export default class GoTrueClient { const { session, error } = await this._callRefreshToken(currentSession.refresh_token) if (error) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error: error } } @@ -1413,6 +1449,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { user: null, session: null }, error } } @@ -1547,6 +1584,7 @@ export default class GoTrueClient { return { data: { session, redirectType: params.type }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { session: null, redirectType: null }, error } } @@ -1609,6 +1647,7 @@ export default class GoTrueClient { (error.status === 404 || error.status === 401 || error.status === 403) ) ) { + if (this.throwOnError) throw error return { error } } } @@ -1717,6 +1756,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } @@ -1742,6 +1782,7 @@ export default class GoTrueClient { return { data: { identities: data.user.identities ?? [] }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -1778,6 +1819,7 @@ export default class GoTrueClient { return { data: { provider: credentials.provider, url: data?.url }, error: null } } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { provider: credentials.provider, url: null }, error } } throw error @@ -1812,6 +1854,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -1858,6 +1901,7 @@ export default class GoTrueClient { this._debug(debugName, 'error', error) if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: { session: null, user: null }, error } } throw error @@ -2373,6 +2417,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -2405,6 +2450,7 @@ export default class GoTrueClient { }) if (error) { + if (this.throwOnError) throw error return { data: null, error } } @@ -2416,6 +2462,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -2445,6 +2492,7 @@ export default class GoTrueClient { } ) if (error) { + if (this.throwOnError) throw error return { data: null, error } } @@ -2458,6 +2506,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -2490,6 +2539,7 @@ export default class GoTrueClient { }) } catch (error) { if (isAuthError(error)) { + if (this.throwOnError) throw error return { data: null, error } } throw error @@ -2510,6 +2560,7 @@ export default class GoTrueClient { factorId: params.factorId, }) if (challengeError) { + if (this.throwOnError) throw challengeError return { data: null, error: challengeError } } @@ -2530,6 +2581,7 @@ export default class GoTrueClient { error: userError, } = await this.getUser() if (userError) { + if (this.throwOnError) throw userError return { data: null, error: userError } } @@ -2562,6 +2614,7 @@ export default class GoTrueClient { error: sessionError, } = result if (sessionError) { + if (this.throwOnError) throw sessionError return { data: null, error: sessionError } } if (!session) { diff --git a/src/lib/types.ts b/src/lib/types.ts index 31b117ff..500e53a9 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -86,6 +86,11 @@ export type GoTrueClientOptions = { * @experimental */ hasCustomAuthorizationHeader?: boolean + /** + * If there is an error with the query, throwOnError will reject the promise by + * throwing the error instead of returning it as part of a successful response. + */ + throwOnError?: boolean } export type WeakPasswordReasons = 'length' | 'characters' | 'pwned' | (string & {}) diff --git a/test/GoTrueClient.test.ts b/test/GoTrueClient.test.ts index 7d426513..8f7065ef 100644 --- a/test/GoTrueClient.test.ts +++ b/test/GoTrueClient.test.ts @@ -1080,3 +1080,37 @@ describe('GoTrueClient with storageisServer = true', () => { expect(store.getItem('test-storage-key')).toEqual(JSON.stringify(newSession)) }) }) + +describe('GoTrueClient with throwOnError option', () => { + const store = memoryLocalStorageAdapter() + const client = new GoTrueClient({ + url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + storageKey: 'test-storage-key', + autoRefreshToken: false, + persistSession: true, + storage: { + ...store + }, + throwOnError: true, // test that the client throws errors + }) + + test('signUp() should throw an error when throwOnError is true', async () => { + const { email, password } = mockUserCredentials() + + await expect(client.signUp({ email, password: '' })).rejects.toThrow() + }) + + test('signInWithPassword() should throw an error when throwOnError is true', async () => { + const { email, password } = mockUserCredentials() + + await expect(client.signInWithPassword({ email, password: '' })).rejects.toThrow() + }) + + test('updateUser() should throw an error when throwOnError is true', async () => { + const { email, password } = mockUserCredentials() + + await client.signUp({ email, password }) + + await expect(client.updateUser({ email: 'invalid-email' })).rejects.toThrow() + }) +})