@joouis/msal-react-utility
Version:
The utility package for @azure/msal-react.
199 lines (189 loc) • 5.82 kB
JavaScript
// 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
};