UNPKG

@joouis/msal-react-utility

Version:

The utility package for @azure/msal-react.

199 lines (189 loc) 5.82 kB
// src/hooks/useEventCallback.ts import { useCallback, useLayoutEffect, useRef } from "react"; function useEventCallback(handler) { const handlerRef = useRef(handler); useLayoutEffect(() => { handlerRef.current = handler; }); return useCallback((...args) => { const handle = handlerRef.current; return handle(...args); }, []); } // src/hooks/useFetchWithStatus.ts import React from "react"; // src/hooks/useFetchWithToken.ts import { useCallback as useCallback2 } from "react"; // src/hooks/useGetToken.ts import { useRef as useRef2, useEffect } from "react"; import { useMsal } from "@azure/msal-react"; import { InteractionRequiredAuthError, BrowserAuthError, BrowserAuthErrorCodes, InteractionStatus } from "@azure/msal-browser"; // src/utilities/sleep.ts var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // src/inteface.ts var TokenType = /* @__PURE__ */ ((TokenType2) => { TokenType2["id"] = "id"; TokenType2["access"] = "access"; return TokenType2; })(TokenType || {}); var RequestInProgressError = class extends Error { constructor() { super("Request is in progress!"); } }; // src/hooks/useGetToken.ts var useGetToken = (defaultRequestConfigs) => { const { instance, inProgress, accounts } = useMsal(); const inProgressRef = useRef2(inProgress); const account = accounts[0]; useEffect(() => { inProgressRef.current = inProgress; }, [inProgress]); const getToken = useEventCallback(async (opts) => { const { tokenType = "access" /* access */, requestConfigs } = opts || {}; while (inProgressRef.current !== InteractionStatus.None) { await sleep(100); } const configs = { scopes: ["User.Read"], prompt: "select_account", ...defaultRequestConfigs, ...requestConfigs }; try { const activeAccount = instance.getActiveAccount() || account; if (!activeAccount) { await instance.loginRedirect(); } const resp = await instance.acquireTokenSilent({ account: activeAccount, ...configs }); if (tokenType === "access") { return resp.accessToken; } if (!resp.idToken) { throw new Error("ID token is not available"); } const idTokenExp = resp.idTokenClaims.exp; if (resp.fromCache && idTokenExp * 1e3 - Date.now() < 2 * 60 * 1e3) { return await getToken({ tokenType: "id" /* id */, requestConfigs: { ...configs, forceRefresh: true } }); } return resp.idToken; } catch (error) { console.error(`[getToken] ${error}`); if (error instanceof InteractionRequiredAuthError) { await instance.acquireTokenRedirect(configs); } else if (error instanceof BrowserAuthError && error.errorCode === BrowserAuthErrorCodes.interactionInProgress) { await sleep(12e4); } else { throw error; } } }); return getToken; }; // src/hooks/useFetchWithToken.ts var useFetchWithToken = (tokenRequestConfigs) => { const getToken = useGetToken(tokenRequestConfigs); return useCallback2( async (input, init, getTokenOpts) => { try { const token = await getToken(getTokenOpts); if (!token) { throw new Error("Failed to fetch token"); } return fetch(input, { ...init, headers: { Authorization: `Bearer ${token}`, // User can override token ...init?.headers } }); } catch (error) { console.error(`[useFetchWithToken] ${error}`); const { name, message } = error; return new Response(name, { status: 401, statusText: message }); } }, [getToken] ); }; // src/utilities/getResponseData.ts var getResponseData = async (response) => { let data; const contentType = response.headers.get("Content-Type") || ""; if (contentType.includes("application/json")) { data = await response.json(); } else if (contentType.includes("text/")) { data = await response.text(); } else if (contentType.includes("application/octet-stream")) { data = await response.arrayBuffer(); } else if (contentType.includes("application/xml") || contentType.includes("text/xml")) { data = await response.text(); } else if (contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/") || contentType.includes("application/pdf")) { data = await response.blob(); } else { try { data = await response.json(); } catch (e) { data = await response.text(); } } return data; }; // src/hooks/useFetchWithStatus.ts var useFetchWithStatus = (input, init) => { const [isLoading, setIsLoading] = React.useState(false); const isLoadingRef = React.useRef(false); const fetchWithToken = useFetchWithToken(); const _fetch = useEventCallback(async (payload, getTokenOpts) => { if (isLoadingRef.current) { throw new RequestInProgressError(); } setIsLoading(true); isLoadingRef.current = true; const requestInit = !init && !payload ? void 0 : { ...init, ...payload }; try { const response = await fetchWithToken(input, requestInit, getTokenOpts); if (!response.ok) { throw new Error(`Failed to fetch data: ${response.statusText}`, { cause: response }); } const data = await getResponseData(response); return data; } catch (error) { throw error; } finally { setIsLoading(false); isLoadingRef.current = false; } }); return { isLoading, _fetch }; }; export { RequestInProgressError, TokenType, getResponseData, sleep, useEventCallback, useFetchWithStatus, useFetchWithToken, useGetToken };