UNPKG

@joouis/msal-react-utility

Version:

The utility package for @azure/msal-react.

310 lines (294 loc) 10.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { RequestInProgressError: () => RequestInProgressError, TokenType: () => TokenType, getResponseData: () => getResponseData, msalConfig: () => msalConfig, sleep: () => sleep, useAutoSetActiveAccount: () => useAutoSetActiveAccount, useEventCallback: () => useEventCallback, useFetchWithStatus: () => useFetchWithStatus, useFetchWithToken: () => useFetchWithToken, useGetToken: () => useGetToken }); module.exports = __toCommonJS(src_exports); // src/hooks/useAutoSetActiveAccount.ts var import_react = __toESM(require("react"), 1); var import_msal_react = require("@azure/msal-react"); // src/globalConfig.ts var MSALConfigSingleton = class _MSALConfigSingleton { static instance; config = {}; constructor() { } static getInstance() { if (!_MSALConfigSingleton.instance) { _MSALConfigSingleton.instance = new _MSALConfigSingleton(); } return _MSALConfigSingleton.instance; } setDefaultSilentRequest(config) { this.config.defaultSilentRequest = config; } getDefaultSilentRequest() { return this.config.defaultSilentRequest; } setDefaultTenantId(tenantId) { this.config.defaultTenantId = tenantId; } getDefaultTenantId() { return this.config.defaultTenantId; } reset() { this.config = {}; } }; var msalConfig = MSALConfigSingleton.getInstance(); // src/hooks/useAutoSetActiveAccount.ts var useAutoSetActiveAccount = () => { const { instance, accounts } = (0, import_msal_react.useMsal)(); import_react.default.useEffect(() => { const activeAccount = instance.getActiveAccount(); const defaultTenantId = msalConfig.getDefaultTenantId(); if (defaultTenantId && activeAccount?.tenantId !== defaultTenantId) { const validAccount = accounts.find((a) => a.tenantId === defaultTenantId); if (validAccount) { instance.setActiveAccount(validAccount); } } else if (!activeAccount && accounts[0]) { instance.setActiveAccount(accounts[0]); } }, [instance, accounts]); }; // src/hooks/useEventCallback.ts var import_react2 = require("react"); function useEventCallback(handler) { const handlerRef = (0, import_react2.useRef)(handler); (0, import_react2.useLayoutEffect)(() => { handlerRef.current = handler; }); return (0, import_react2.useCallback)((...args) => { const handle = handlerRef.current; return handle(...args); }, []); } // src/hooks/useFetchWithStatus.ts var import_react5 = __toESM(require("react"), 1); // src/hooks/useFetchWithToken.ts var import_react4 = require("react"); // src/hooks/useGetToken.ts var import_react3 = require("react"); var import_msal_react2 = require("@azure/msal-react"); var import_msal_browser = require("@azure/msal-browser"); // src/utilities/sleep.ts var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // src/utilities/parseJwtToken.ts var parseJwtToken = (token) => { if (!token) { throw new Error("Token is empty"); } const base64Url = token.split(".")[1]; if (!base64Url) { throw new Error("Invalid token format"); } const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); const jsonPayload = decodeURIComponent( atob(base64).split("").map((c) => `%${("00" + c.charCodeAt(0).toString(16)).slice(-2)}`).join("") ); return JSON.parse(jsonPayload); }; // 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) => { useAutoSetActiveAccount(); const { instance, inProgress } = (0, import_msal_react2.useMsal)(); const inProgressRef = (0, import_react3.useRef)(inProgress); (0, import_react3.useEffect)(() => { inProgressRef.current = inProgress; }, [inProgress]); const getToken = useEventCallback(async (opts) => { const { tokenType = "access" /* access */, requestConfigs } = opts || {}; while (inProgressRef.current !== import_msal_browser.InteractionStatus.None) { await sleep(100); } const configs = { scopes: ["User.Read"], prompt: "select_account", ...msalConfig.getDefaultSilentRequest(), ...defaultRequestConfigs, ...requestConfigs }; try { const activeAccount = instance.getActiveAccount(); 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 = parseJwtToken(resp.idToken).exp; if (idTokenExp && 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 import_msal_browser.InteractionRequiredAuthError) { await instance.acquireTokenRedirect(configs); } else if (error instanceof import_msal_browser.BrowserAuthError && error.errorCode === import_msal_browser.BrowserAuthErrorCodes.interactionInProgress) { await sleep(12e4); } else { throw error; } } }); return getToken; }; // src/hooks/useFetchWithToken.ts var useFetchWithToken = (tokenRequestConfigs) => { const getToken = useGetToken(tokenRequestConfigs); return (0, import_react4.useCallback)( 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] = import_react5.default.useState(false); const isLoadingRef = import_react5.default.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 }; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { RequestInProgressError, TokenType, getResponseData, msalConfig, sleep, useAutoSetActiveAccount, useEventCallback, useFetchWithStatus, useFetchWithToken, useGetToken });