Created
May 10, 2023 19:55
-
-
Save phenixita/259defd0161dea8c81d14f2ceb4e76d1 to your computer and use it in GitHub Desktop.
msal with ExponentialBackoff (jitter included by default)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useCallback } from "react"; | |
import { InteractionType, PopupRequest } from "@azure/msal-browser"; | |
import { useMsal, useMsalAuthentication } from "@azure/msal-react"; | |
import { | |
ExponentialBackoff, | |
retry, | |
handleAll, | |
circuitBreaker, | |
ConsecutiveBreaker, | |
wrap, | |
} from "cockatiel"; | |
/** | |
* Custom hook to call a web API using bearer token obtained from MSAL | |
* @param {PopupRequest} msalRequest | |
* @returns | |
*/ | |
const useFetchWithMsal = (msalRequest) => { | |
const { instance } = useMsal(); | |
const [isLoading, setIsLoading] = useState(false); | |
const [error, setError] = useState(null); | |
const [data, setData] = useState(null); | |
const { result, error: msalError } = useMsalAuthentication( | |
InteractionType.Popup, | |
{ | |
...msalRequest, | |
account: instance.getActiveAccount(), | |
redirectUri: "/redirect", | |
} | |
); | |
const internalFetchToJson = async (endpoint, options) => { | |
console.log("internalFetchToJson"); | |
return await (await fetch(endpoint, options)).json(); | |
}; | |
const resilientFecth = async (endpoint, options) => { | |
const exponentialBackoffRetry = retry(handleAll, { | |
backoff: new ExponentialBackoff({ initialDelay: 200, maxDelay: 10000 }), | |
shouldRetry: (e) => e.status !== 404, | |
}); | |
return await exponentialBackoffRetry.execute(() => | |
internalFetchToJson(endpoint, options) | |
); | |
}; | |
/** | |
* Execute a fetch request with the given options | |
* @param {string} method: GET, POST, PUT, DELETE | |
* @param {String} endpoint: The endpoint to call | |
* @param {Object} data: The data to send to the endpoint, if any | |
* @returns JSON response | |
*/ | |
const execute = async (method, endpoint, data = null) => { | |
if (msalError) { | |
setError(msalError); | |
return; | |
} | |
if (result) { | |
let response = null; | |
try { | |
const headers = new Headers(); | |
const bearer = `Bearer ${result.accessToken}`; | |
headers.append("Authorization", bearer); | |
if (data) headers.append("Content-Type", "application/json"); | |
let options = { | |
method: method, | |
headers: headers, | |
body: data ? JSON.stringify(data) : null, | |
}; | |
setIsLoading(true); | |
response = await resilientFecth(endpoint, options); | |
setData(response); | |
setIsLoading(false); | |
return response; | |
} catch (e) { | |
setError(e); | |
setIsLoading(false); | |
throw e; | |
} | |
} | |
}; | |
return { | |
isLoading, | |
error, | |
data, | |
execute: useCallback(execute, [result, msalError]), // to avoid infinite calls when inside a `useEffect` | |
}; | |
}; | |
export default useFetchWithMsal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment