From e43259fee66176af86997de85831491235cbf623 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 28 Feb 2023 17:05:20 -0500 Subject: [PATCH] Handle fetch top-level errors and retry network errors --- README.md | 15 +++++++ package.json | 1 + src/config.js | 3 ++ src/useFirebaseUser.js | 94 ++++++++++++++++++++++++------------------ yarn.lock | 5 +++ 5 files changed, 77 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index e1f9c338..206e609d 100644 --- a/README.md +++ b/README.md @@ -560,6 +560,21 @@ Error handler that will be called if there's an unexpected error while refreshin This library will **not** throw when it cannot refresh an ID token. Instead, it will provide an unauthenticated user to the app. See [#366](https://github.com/gladly-team/next-firebase-auth/issues/366) and [#174](https://github.com/gladly-team/next-firebase-auth/issues/174) for additional background. +#### fetchRetryConfig + +`Object` (optional) + +Configuration options for the [fetch-retry](https://www.npmjs.com/package/fetch-retry) module. Allows to customize retry behavior of calls to `loginAPIEndpoint` and `logoutAPIEndpoint`. + +Example: + +```js +{ + retries: 5, + retryDelay: 800, +} +``` + ## Types ### AuthAction diff --git a/package.json b/package.json index 8f02ce35..17d48460 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "dependencies": { "@babel/runtime": "^7.18.9", "cookies": "^0.8.0", + "fetch-retry": "^5.0.4", "hoist-non-react-statics": "^3.3.2" }, "files": [ diff --git a/src/config.js b/src/config.js index e3bf1ffd..8ce5b122 100644 --- a/src/config.js +++ b/src/config.js @@ -71,6 +71,9 @@ const defaultConfig = { secure: true, signed: true, }, + // Config for fetch-retry module, to customize the retry behavior of + // API login and logout requests. + fetchRetryConfig: undefined, } const validateConfig = (mergedConfig) => { diff --git a/src/useFirebaseUser.js b/src/useFirebaseUser.js index 2b3db032..d3c36ddb 100644 --- a/src/useFirebaseUser.js +++ b/src/useFirebaseUser.js @@ -1,17 +1,21 @@ import { useEffect, useState } from 'react' import { getApp } from 'firebase/app' import { getAuth, getIdTokenResult, onIdTokenChanged } from 'firebase/auth' +import makeFetchRetry from 'fetch-retry' import { getConfig } from 'src/config' import createAuthUser from 'src/createAuthUser' import { filterStandardClaims } from 'src/claims' import logDebug from 'src/logDebug' +const fetchRetry = makeFetchRetry(fetch) + const defaultTokenChangedHandler = async (authUser) => { const { loginAPIEndpoint, logoutAPIEndpoint, onLoginRequestError, onLogoutRequestError, + fetchRetryConfig, } = getConfig() let response // If the user is authed, call login to set a cookie. @@ -20,29 +24,33 @@ const defaultTokenChangedHandler = async (authUser) => { // place we use this logic. logDebug('[withAuthUser] Calling the login endpoint.') const userToken = await authUser.getIdToken() - response = await fetch(loginAPIEndpoint, { - method: 'POST', - headers: { - Authorization: userToken, - }, - credentials: 'include', - }) - if (!response.ok) { - const responseJSON = await response.json() - logDebug( - `[withAuthUser] The call to the login endpoint failed with status ${ - response.status - } and response: ${JSON.stringify(responseJSON)}` - ) + try { + response = await fetchRetry(loginAPIEndpoint, { + method: 'POST', + headers: { + Authorization: userToken, + }, + credentials: 'include', + ...fetchRetryConfig, + }) + if (!response.ok) { + const responseJSON = await response.json() + logDebug( + `[withAuthUser] The call to the login endpoint failed with status ${ + response.status + } and response: ${JSON.stringify(responseJSON)}` + ) - // If the developer provided a handler for login errors, - // call it and don't throw. - // https://github.com/gladly-team/next-firebase-auth/issues/367 - const err = new Error( - `Received ${ - response.status - } response from login API endpoint: ${JSON.stringify(responseJSON)}` - ) + // If the developer provided a handler for login errors, + // call it and don't throw. + // https://github.com/gladly-team/next-firebase-auth/issues/367 + throw new Error( + `Received ${ + response.status + } response from login API endpoint: ${JSON.stringify(responseJSON)}` + ) + } + } catch (err) { if (onLoginRequestError) { await onLoginRequestError(err) } else { @@ -52,26 +60,30 @@ const defaultTokenChangedHandler = async (authUser) => { } else { // If the user is not authed, call logout to unset the cookie. logDebug('[withAuthUser] Calling the logout endpoint.') - response = await fetch(logoutAPIEndpoint, { - method: 'POST', - credentials: 'include', - }) - if (!response.ok) { - const responseJSON = await response.json() - logDebug( - `[withAuthUser] The call to the logout endpoint failed with status ${ - response.status - } and response: ${JSON.stringify(responseJSON)}` - ) + try { + response = await fetchRetry(logoutAPIEndpoint, { + method: 'POST', + credentials: 'include', + ...fetchRetryConfig, + }) + if (!response.ok) { + const responseJSON = await response.json() + logDebug( + `[withAuthUser] The call to the logout endpoint failed with status ${ + response.status + } and response: ${JSON.stringify(responseJSON)}` + ) - // If the developer provided a handler for logout errors, - // call it and don't throw. - // https://github.com/gladly-team/next-firebase-auth/issues/367 - const err = new Error( - `Received ${ - response.status - } response from logout API endpoint: ${JSON.stringify(responseJSON)}` - ) + // If the developer provided a handler for logout errors, + // call it and don't throw. + // https://github.com/gladly-team/next-firebase-auth/issues/367 + throw new Error( + `Received ${ + response.status + } response from logout API endpoint: ${JSON.stringify(responseJSON)}` + ) + } + } catch (err) { if (onLogoutRequestError) { await onLogoutRequestError(err) } else { diff --git a/yarn.lock b/yarn.lock index 3a633ec1..b12c5b95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3999,6 +3999,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fetch-retry@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.4.tgz#06e8e4533030bf6faa00ffbb9450cb9264c23c12" + integrity sha512-LXcdgpdcVedccGg0AZqg+S8lX/FCdwXD92WNZ5k5qsb0irRhSFsBOpcJt7oevyqT2/C2nEE0zSFNdBEpj3YOSw== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"