embed-dossier-mstr-react
Version:
This is a React component that allows you to embed a MicroStrategy Dossier in your React application.
497 lines (486 loc) • 14.1 kB
JavaScript
// src/hooks/useCreateDashboard.ts
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
// src/hooks/useLoadMstrSDK.ts
import { useEffect, useState } from "react";
var useLoadMstrSDK = ({ serverUrlLibrary }) => {
const [isSdkLoaded, setIsSdkLoaded] = useState(false);
const [isSdkError, setIsSdkError] = useState({
isError: false,
message: ""
});
useEffect(() => {
const loadSdk = () => {
if (!window.microstrategy) {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = serverUrlLibrary + "/javascript/embeddinglib.js";
script.onload = () => {
setIsSdkLoaded(true);
};
script.onerror = () => {
setIsSdkError({
isError: true,
message: "Error loading the SDK"
});
};
document.head.appendChild(script);
}
};
loadSdk();
}, [serverUrlLibrary]);
return { isSdkLoaded, isSdkError };
};
// src/hooks/useCreateDashboard.ts
var useCreateDashboard = ({
serverUrlLibrary,
config
}) => {
const containerRef = useRef(null);
const [dashboard, setDashboard] = useState2(null);
const [isDashboardError, setIsDashboardError] = useState2(false);
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect2(() => {
if (!isSdkLoaded)
return;
async function createDashboard() {
try {
const dossier = await window.microstrategy.dossier.create({
...config,
placeholder: containerRef.current
});
setDashboard(dossier);
} catch (error) {
console.error(error);
setIsDashboardError(true);
}
}
createDashboard();
}, [isSdkLoaded]);
return {
dashboard,
containerRef,
isSdkLoaded,
isDashboardError
};
};
// src/utils.ts
var getInfoFromUrl = (url) => {
const urlParts = url.split("/").filter((part) => part !== "");
const serverUrl = urlParts[0] + "//" + urlParts[1];
const serverUrlLibrary = urlParts[0] + "//" + urlParts[1] + "/" + urlParts[2];
const libraryName = urlParts[2];
const projectId = urlParts[3];
const dossierId = urlParts[4];
return { serverUrl, serverUrlLibrary, libraryName, projectId, dossierId };
};
var getAuthToken = async ({
serverUrlLibrary
}) => {
const options = {
method: "GET",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" }
};
let mstrToken = "";
try {
const response = await fetch(`${serverUrlLibrary}/api/auth/token`, options);
if (response.ok) {
const data = await response.json();
mstrToken = data.token;
return mstrToken;
}
return response.json().then((json) => console.log(json));
} catch (error) {
console.error(error);
}
};
var getServerUrl = (url) => {
return url.split("/app/")[0];
};
// src/components/DashboardEmbed.tsx
import cn from "classnames";
import { jsx } from "react/jsx-runtime";
var DashboardEmbed = ({
dossierUrl,
className,
style,
config
}) => {
let serverUrlLibrary = "";
try {
const urlInfo = getInfoFromUrl(dossierUrl);
serverUrlLibrary = urlInfo.serverUrlLibrary;
} catch (error) {
console.error("Failed to parse dossier URL:", error);
}
const { containerRef } = useCreateDashboard({
serverUrlLibrary,
config: {
url: dossierUrl,
...config
}
});
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn(className), style });
};
// src/hooks/useCreateLibraryPage.ts
import { useEffect as useEffect3, useState as useState3, useRef as useRef2 } from "react";
var useCreateLibraryPage = ({
serverUrlLibrary,
config
}) => {
const libraryPageRef = useRef2(null);
const [libraryPage, setLibraryPage] = useState3(null);
const [libraryPageState, setLibraryPageState] = useState3({
isLoading: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
const prevConfigRef = useRef2(config);
useEffect3(() => {
if (!isSdkLoaded)
return;
if (!libraryPage && !libraryPageState.isLoading) {
createLibraryPage();
}
}, [isSdkLoaded]);
useEffect3(() => {
const configChanged = JSON.stringify(config) !== JSON.stringify(prevConfigRef.current);
if (libraryPage && configChanged) {
createLibraryPage();
}
prevConfigRef.current = config;
}, [config]);
const createLibraryPage = async () => {
setLibraryPageState((prev) => ({
...prev,
isLoading: true,
error: null
}));
try {
const embeddingContext = await window.microstrategy.embeddingContexts.embedLibraryPage({
placeholder: libraryPageRef.current,
serverUrl: serverUrlLibrary,
errorHandler: (error) => {
console.error("Library page error:", error);
setLibraryPageState((prev) => ({
...prev,
error: error.message || "Failed to load library page"
}));
},
sessionErrorHandler: (error) => {
console.error("Session error:", error);
setLibraryPageState((prev) => ({
...prev,
error: "Session expired. Please refresh the page."
}));
},
...config
});
setLibraryPage(embeddingContext);
} catch (error) {
setLibraryPageState((prev) => ({
...prev,
error: error instanceof Error ? error.message : "Failed to create library page"
}));
} finally {
setLibraryPageState((prev) => ({ ...prev, isLoading: false }));
}
};
return {
libraryPage,
libraryPageRef,
isSdkLoaded,
isLoading: libraryPageState.isLoading,
error: libraryPageState.error
};
};
// src/components/LibraryPageEmbed.tsx
import cn2 from "classnames";
import { jsx as jsx2 } from "react/jsx-runtime";
var LibraryPageEmbed = ({
serverUrlLibrary,
config,
className,
style
}) => {
const { libraryPageRef } = useCreateLibraryPage({
serverUrlLibrary,
config
});
return /* @__PURE__ */ jsx2("div", { ref: libraryPageRef, className: cn2(className), style });
};
// src/hooks/useCreateBotConsumptionPage.ts
import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
var useCreateBotConsumptionPage = ({
serverUrlLibrary,
projectId,
objectId,
config
}) => {
const botConsumptionPageRef = useRef3(null);
const [botConsumptionPage, setBotConsumptionPage] = useState4(null);
const [isBotConsumptionPageError, setIsBotConsumptionPageError] = useState4(false);
const { isSdkLoaded, isSdkError } = useLoadMstrSDK({ serverUrlLibrary });
useEffect4(() => {
if (!isSdkLoaded)
return;
async function createBotConsumptionPage() {
try {
const botConsumptionPage2 = await window.microstrategy.embeddingContexts.embedBotConsumptionPage({
...config,
placeholder: botConsumptionPageRef.current,
serverUrl: serverUrlLibrary,
projectId,
objectId
});
setBotConsumptionPage(botConsumptionPage2);
} catch (error) {
console.error(error);
setIsBotConsumptionPageError(true);
}
}
createBotConsumptionPage();
}, [isSdkLoaded]);
return {
botConsumptionPage,
botConsumptionPageRef,
isBotConsumptionPageError,
isSdkError,
isSdkLoaded
};
};
// src/components/BotConsumptionPage.tsx
import cn3 from "classnames";
import { jsx as jsx3 } from "react/jsx-runtime";
var BotConsumptionPage = ({
serverUrlLibrary,
projectId,
objectId,
config,
className,
style
}) => {
const { botConsumptionPageRef } = useCreateBotConsumptionPage({
serverUrlLibrary,
projectId,
objectId,
config
});
return /* @__PURE__ */ jsx3(
"div",
{
ref: botConsumptionPageRef,
className: cn3(className),
style
}
);
};
// src/hooks/useCreateDashboardWithAuth.ts
import { useEffect as useEffect5, useState as useState5 } from "react";
var useCreateDashboardWithAuth = ({
serverUrlLibrary,
placeholder,
config,
loginMode,
username,
password
}) => {
const [dashboard, setDashboard] = useState5(null);
const [authState, setAuthState] = useState5({
isAuthenticated: false,
isAuthenticating: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect5(() => {
if (!isSdkLoaded || !placeholder)
return;
let mounted = true;
const initializeDashboard = async () => {
if (!mounted)
return;
setAuthState((prev) => ({
...prev,
isAuthenticating: true,
error: null
}));
try {
switch (loginMode) {
case "standard":
case "ldap": {
if (!username || !password) {
throw new Error(
"Username and password are required for standard/LDAP authentication"
);
}
const standardOptions = {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({
loginMode: 1,
username,
password
})
};
await fetch(`${serverUrlLibrary}/api/auth/login`, standardOptions);
break;
}
case "guest": {
const guestOptions = {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginMode: 8 })
};
await fetch(`${serverUrlLibrary}/api/auth/login`, guestOptions);
break;
}
case "saml":
try {
await window.microstrategy.auth.samlLogin(serverUrlLibrary);
} catch (e) {
if (e instanceof Error && e.message.includes("Failed to open a new tab")) {
throw new Error(
"Please enable popups to be directed to the SAML login site"
);
}
throw e;
}
break;
case "oidc":
try {
await window.microstrategy.auth.oidcLogin(serverUrlLibrary);
} catch (e) {
if (e instanceof Error && e.message.includes("Failed to open a new tab")) {
throw new Error(
"Please enable popups to be directed to the OIDC login site"
);
}
throw e;
}
break;
}
const dossier = await window.microstrategy.dossier.create({
...config,
placeholder,
enableCustomAuthentication: true,
customAuthenticationType: window.microstrategy.dossier.CustomAuthenticationType.AUTH_TOKEN,
getLoginToken: async () => {
const options = {
method: "GET",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" }
};
const response = await fetch(
`${serverUrlLibrary}/api/auth/token`,
options
);
return response.ok ? response.headers.get("x-mstr-authtoken") : null;
}
});
if (mounted) {
setDashboard(dossier);
setAuthState((prev) => ({ ...prev, isAuthenticated: true }));
}
} catch (error) {
if (mounted) {
console.error("Dashboard auth error:", error);
setAuthState((prev) => ({
...prev,
error: error instanceof Error ? error.message : "Failed to initialize dashboard",
isAuthenticated: false
}));
}
} finally {
if (mounted) {
setAuthState((prev) => ({ ...prev, isAuthenticating: false }));
}
}
};
initializeDashboard();
return () => {
mounted = false;
};
}, [
isSdkLoaded,
placeholder,
loginMode,
serverUrlLibrary,
config,
username,
password
]);
return {
dashboard,
...authState
};
};
// src/components/DashboardEmbedWithAuth.tsx
import cn4 from "classnames";
import { useRef as useRef4 } from "react";
import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
var DashboardEmbedWithAuth = ({
dossierUrl,
className,
style,
config,
loginMode,
username,
password,
loadingComponent = /* @__PURE__ */ jsx4("div", { children: "Loading..." }),
errorComponent = (error) => /* @__PURE__ */ jsxs("div", { className: "text-center text-red-500 p-4 max-w-md", children: [
/* @__PURE__ */ jsx4("h3", { className: "text-lg font-semibold mb-2", children: "Authentication Error" }),
/* @__PURE__ */ jsx4("p", { children: error })
] })
}) => {
const { serverUrlLibrary } = getInfoFromUrl(dossierUrl);
const divRef = useRef4(null);
const { isAuthenticating, error } = useCreateDashboardWithAuth({
serverUrlLibrary,
placeholder: divRef.current,
config: {
url: dossierUrl,
enableResponsive: true,
enableCustomAuthentication: true,
containerHeight: "600px",
containerWidth: "100%",
...config
},
loginMode,
username,
password
});
if (isAuthenticating) {
return /* @__PURE__ */ jsx4(Fragment, { children: loadingComponent });
}
if (error) {
return /* @__PURE__ */ jsx4(Fragment, { children: typeof errorComponent === "function" ? errorComponent(error) : errorComponent });
}
return /* @__PURE__ */ jsx4(
"div",
{
ref: divRef,
className: cn4("w-full h-[600px]", className),
style
}
);
};
export {
BotConsumptionPage,
DashboardEmbed,
DashboardEmbedWithAuth,
LibraryPageEmbed,
getAuthToken,
getInfoFromUrl,
getServerUrl,
useCreateBotConsumptionPage,
useCreateDashboard,
useCreateDashboardWithAuth,
useCreateLibraryPage,
useLoadMstrSDK
};