embed-dossier-mstr-react
Version:
A production-ready React library for embedding MicroStrategy Dossiers, Reports, and Bot Consumption pages with full TypeScript support.
919 lines (903 loc) • 26.6 kB
JavaScript
// src/constants/eventType.ts
var EVENT_TYPE = {
ON_COMPONENT_SELECTION_CHANGED: "onComponentSelectionChanged",
ON_DOSSIER_AUTHORING_CLOSED: "onDossierAuthoringClosed",
ON_DOSSIER_AUTHORING_SAVED: "onDossierAuthoringSaved",
ON_DOSSIER_INSTANCE_CHANGED: "onDossierInstanceChanged",
ON_DOSSIER_INSTANCE_ID_CHANGE: "onDossierInstanceIDChange",
ON_ERROR: "onError",
ON_FILTER_UPDATED: "onFilterUpdated",
ON_GRAPHICS_SELECTED: "onGraphicsSelected",
ON_LAYOUT_CHANGED: "onLayoutChanged",
ON_LIBRARY_ITEM_SELECTED: "onLibraryItemSelected",
ON_LIBRARY_ITEM_SELECTION_CLEARED: "onLibraryItemSelectionCleared",
ON_LIBRARY_MENU_SELECTED: "onLibraryMenuSelected",
ON_PAGE_LOADED: "onPageLoaded",
ON_PAGE_RENDER_FINISHED: "onPageRenderFinished",
ON_PAGE_SWITCHED: "onPageSwitched",
ON_PANEL_SWITCHED: "onPanelSwitched",
ON_PROMPT_ANSWERED: "onPromptAnswered",
ON_PROMPT_LOADED: "onPromptLoaded",
ON_SESSION_ERROR: "onSessionError",
ON_VISUALIZATION_RESIZED: "onVisualizationResized",
ON_VIZ_ELEMENT_CHANGED: "onVisualizationElementsChanged",
ON_VIZ_SELECTION_CHANGED: "onVizSelectionChanged"
};
// 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(() => {
if (window.microstrategy) {
setIsSdkLoaded(true);
return;
}
if (!serverUrlLibrary) {
setIsSdkError({
isError: true,
message: "serverUrlLibrary is required to load the MicroStrategy SDK"
});
return;
}
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: `Failed to load MicroStrategy SDK from ${serverUrlLibrary}`
});
};
document.head.appendChild(script);
return () => {
script.remove();
};
}, [serverUrlLibrary]);
return { isSdkLoaded, isSdkError };
};
// src/hooks/useCreateDashboard.ts
var useCreateDashboard = ({
serverUrlLibrary,
config
}) => {
const containerRef = useRef(null);
const [dashboard, setDashboard] = useState2(null);
const dashboardRef = useRef(null);
const [isDashboardError, setIsDashboardError] = useState2(false);
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect2(() => {
if (!isSdkLoaded)
return;
let mounted = true;
async function createDashboard() {
try {
const dossier = await window.microstrategy.dossier.create({
...config,
placeholder: containerRef.current
});
if (mounted) {
dashboardRef.current = dossier;
setDashboard(dossier);
}
} catch (error) {
if (mounted) {
console.error("Failed to create dashboard:", error);
setIsDashboardError(true);
}
}
}
createDashboard();
return () => {
mounted = false;
if (dashboardRef.current) {
try {
dashboardRef.current.close();
} catch {
}
dashboardRef.current = null;
}
};
}, [isSdkLoaded]);
return {
dashboard,
containerRef,
isSdkLoaded,
isDashboardError
};
};
// src/utils.ts
var getInfoFromUrl = (url) => {
if (!url || typeof url !== "string") {
throw new Error("A valid MicroStrategy dossier URL is required");
}
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(
`Invalid URL format: "${url}". Expected format: https://{host}/{libraryName}/app/{projectId}/{dossierId}`
);
}
const pathSegments = parsed.pathname.split("/").filter(Boolean);
if (pathSegments.length < 4 || pathSegments[1] !== "app") {
throw new Error(
`Invalid MicroStrategy URL path: "${parsed.pathname}". Expected format: /{libraryName}/app/{projectId}/{dossierId}`
);
}
const serverUrl = parsed.origin;
const libraryName = pathSegments[0];
const serverUrlLibrary = `${serverUrl}/${libraryName}`;
const projectId = pathSegments[2];
const dossierId = pathSegments[3];
return { serverUrl, serverUrlLibrary, libraryName, projectId, dossierId };
};
var getServerUrl = (url) => {
const appIndex = url.indexOf("/app/");
if (appIndex === -1) {
throw new Error(
`URL does not contain "/app/" segment: "${url}". Expected format: https://{host}/{libraryName}/app/{projectId}/{dossierId}`
);
}
return url.substring(0, appIndex);
};
var getAuthToken = async ({
serverUrlLibrary
}) => {
const options = {
method: "GET",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" }
};
try {
const response = await fetch(`${serverUrlLibrary}/api/auth/token`, options);
if (response.ok) {
const data = await response.json();
return data.token;
}
return void 0;
} catch (error) {
console.error("Failed to get auth token:", error);
return void 0;
}
};
// src/components/DashboardEmbed.tsx
import { clsx } from "clsx";
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: clsx(className), style });
};
// src/hooks/useCreateDashboardWithAuth.ts
import { useEffect as useEffect3, useState as useState3 } from "react";
var useCreateDashboardWithAuth = ({
serverUrlLibrary,
placeholder,
config,
loginMode,
username,
password
}) => {
const [dashboard, setDashboard] = useState3(null);
const [authState, setAuthState] = useState3({
isAuthenticated: false,
isAuthenticating: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect3(() => {
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 { clsx as clsx2 } from "clsx";
import { useRef as useRef2 } from "react";
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
var DashboardEmbedWithAuth = ({
dossierUrl,
className,
style,
config,
loginMode,
username,
password,
loadingComponent = /* @__PURE__ */ jsx2("div", { children: "Loading..." }),
errorComponent = (error) => /* @__PURE__ */ jsxs(
"div",
{
style: {
textAlign: "center",
color: "#ef4444",
padding: "16px",
maxWidth: "448px"
},
children: [
/* @__PURE__ */ jsx2("h3", { style: { fontSize: "18px", fontWeight: 600, marginBottom: "8px" }, children: "Authentication Error" }),
/* @__PURE__ */ jsx2("p", { children: error })
]
}
)
}) => {
const { serverUrlLibrary } = getInfoFromUrl(dossierUrl);
const divRef = useRef2(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__ */ jsx2(Fragment, { children: loadingComponent });
}
if (error) {
return /* @__PURE__ */ jsx2(Fragment, { children: typeof errorComponent === "function" ? errorComponent(error) : errorComponent });
}
return /* @__PURE__ */ jsx2(
"div",
{
ref: divRef,
className: clsx2(className),
style: { width: "100%", height: "600px", ...style }
}
);
};
// src/hooks/useCreateLibraryPage.ts
import { useEffect as useEffect4, useState as useState4, useRef as useRef3 } from "react";
var useCreateLibraryPage = ({
serverUrlLibrary,
config
}) => {
const libraryPageRef = useRef3(null);
const [libraryPage, setLibraryPage] = useState4(null);
const [libraryPageState, setLibraryPageState] = useState4({
isLoading: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
const prevConfigRef = useRef3(config);
useEffect4(() => {
if (!isSdkLoaded)
return;
if (!libraryPage && !libraryPageState.isLoading) {
createLibraryPage();
}
}, [isSdkLoaded]);
useEffect4(() => {
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 { clsx as clsx3 } from "clsx";
import { jsx as jsx3 } from "react/jsx-runtime";
var LibraryPageEmbed = ({
serverUrlLibrary,
config,
className,
style
}) => {
const { libraryPageRef } = useCreateLibraryPage({
serverUrlLibrary,
config
});
return /* @__PURE__ */ jsx3("div", { ref: libraryPageRef, className: clsx3(className), style });
};
// src/hooks/useCreateLibraryPageWithAuth.ts
import { useEffect as useEffect5, useState as useState5 } from "react";
var useCreateLibraryPageWithAuth = ({
serverUrlLibrary,
placeholder,
config,
loginMode,
username,
password
}) => {
const [libraryPage, setLibraryPage] = useState5(null);
const [authState, setAuthState] = useState5({
isAuthenticated: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect5(() => {
if (!isSdkLoaded || !placeholder)
return;
let mounted = true;
const initializeLibraryPage = async () => {
if (!mounted)
return;
setAuthState((prev) => ({
...prev,
error: null
}));
try {
switch (loginMode) {
case "standard":
case "ldap": {
if (!username || !password) {
throw new Error(
"Username and password are required for standard/LDAP authentication"
);
}
await fetch(`${serverUrlLibrary}/api/auth/login`, {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginMode: 1, username, password })
});
break;
}
case "guest": {
await fetch(`${serverUrlLibrary}/api/auth/login`, {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginMode: 8 })
});
break;
}
case "saml":
await window.microstrategy.auth.samlLogin(serverUrlLibrary);
break;
case "oidc":
await window.microstrategy.auth.oidcLogin(serverUrlLibrary);
break;
}
const page = await window.microstrategy.embeddingContexts.embedLibraryPage({
...config,
placeholder,
serverUrl: serverUrlLibrary,
enableCustomAuthentication: true,
customAuthenticationType: window.microstrategy.dossier.CustomAuthenticationType.AUTH_TOKEN,
getLoginToken: async () => {
const response = await fetch(
`${serverUrlLibrary}/api/auth/token`,
{
method: "GET",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" }
}
);
return response.ok ? response.headers.get("x-mstr-authtoken") : null;
}
});
if (mounted) {
setLibraryPage(page);
setAuthState((prev) => ({ ...prev, isAuthenticated: true }));
}
} catch (error) {
if (mounted) {
console.error("Library page auth error:", error);
setAuthState((prev) => ({
...prev,
error: error instanceof Error ? error.message : "Failed to initialize library page",
isAuthenticated: false
}));
}
} finally {
if (mounted) {
setAuthState((prev) => ({ ...prev }));
}
}
};
initializeLibraryPage();
return () => {
mounted = false;
};
}, [
isSdkLoaded,
placeholder,
loginMode,
serverUrlLibrary,
config,
username,
password
]);
return {
libraryPage,
...authState
};
};
// src/components/LibraryPageEmbedWithAuth.tsx
import { clsx as clsx4 } from "clsx";
import { useRef as useRef4 } from "react";
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
var LibraryPageEmbedWithAuth = ({
serverUrlLibrary,
config,
loginMode,
username,
password,
className,
style,
loadingComponent = /* @__PURE__ */ jsx4("div", { children: "Loading..." }),
errorComponent = (error) => /* @__PURE__ */ jsx4("div", { children: error })
}) => {
const containerRef = useRef4(null);
const { libraryPage, isAuthenticated, error } = useCreateLibraryPageWithAuth({
serverUrlLibrary,
placeholder: containerRef.current,
config,
loginMode,
username,
password
});
if (error) {
return /* @__PURE__ */ jsx4(Fragment2, { children: errorComponent(error) });
}
if (!isAuthenticated || !libraryPage) {
return /* @__PURE__ */ jsx4(Fragment2, { children: loadingComponent });
}
return /* @__PURE__ */ jsx4(
"div",
{
ref: containerRef,
className: clsx4(className),
style: { width: "100%", height: "600px", ...style }
}
);
};
// src/hooks/useCreateBotConsumptionPage.ts
import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
var useCreateBotConsumptionPage = ({
serverUrlLibrary,
projectId,
objectId,
config
}) => {
const botConsumptionPageRef = useRef5(null);
const [botConsumptionPage, setBotConsumptionPage] = useState6(null);
const [isBotConsumptionPageError, setIsBotConsumptionPageError] = useState6(false);
const { isSdkLoaded, isSdkError } = useLoadMstrSDK({ serverUrlLibrary });
useEffect6(() => {
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 { clsx as clsx5 } from "clsx";
import { jsx as jsx5 } from "react/jsx-runtime";
var BotConsumptionPage = ({
serverUrlLibrary,
projectId,
objectId,
config,
className,
style
}) => {
const { botConsumptionPageRef } = useCreateBotConsumptionPage({
serverUrlLibrary,
projectId,
objectId,
config
});
return /* @__PURE__ */ jsx5(
"div",
{
ref: botConsumptionPageRef,
className: clsx5(className),
style
}
);
};
// src/hooks/useCreateBotConsumptionPageWithAuth.ts
import { useEffect as useEffect7, useState as useState7 } from "react";
var useCreateBotConsumptionPageWithAuth = ({
serverUrlLibrary,
placeholder,
projectId,
objectId,
config,
loginMode,
username,
password
}) => {
const [botConsumptionPage, setBotConsumptionPage] = useState7(null);
const [authState, setAuthState] = useState7({
isAuthenticated: false,
isAuthenticating: false,
error: null
});
const { isSdkLoaded } = useLoadMstrSDK({ serverUrlLibrary });
useEffect7(() => {
if (!isSdkLoaded || !placeholder)
return;
let mounted = true;
const initializeBotConsumptionPage = 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"
);
}
await fetch(`${serverUrlLibrary}/api/auth/login`, {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginMode: 1, username, password })
});
break;
}
case "guest": {
await fetch(`${serverUrlLibrary}/api/auth/login`, {
method: "POST",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginMode: 8 })
});
break;
}
case "saml":
await window.microstrategy.auth.samlLogin(serverUrlLibrary);
break;
case "oidc":
await window.microstrategy.auth.oidcLogin(serverUrlLibrary);
break;
}
const botPage = await window.microstrategy.embeddingContexts.embedBotConsumptionPage({
...config,
placeholder,
serverUrl: serverUrlLibrary,
projectId,
objectId,
enableCustomAuthentication: true,
customAuthenticationType: window.microstrategy.dossier.CustomAuthenticationType.AUTH_TOKEN,
getLoginToken: async () => {
const response = await fetch(
`${serverUrlLibrary}/api/auth/token`,
{
method: "GET",
credentials: "include",
mode: "cors",
headers: { "content-type": "application/json" }
}
);
return response.ok ? response.headers.get("x-mstr-authtoken") : null;
}
});
if (mounted) {
setBotConsumptionPage(botPage);
setAuthState((prev) => ({ ...prev, isAuthenticated: true }));
}
} catch (error) {
if (mounted) {
console.error("Bot consumption page auth error:", error);
setAuthState((prev) => ({
...prev,
error: error instanceof Error ? error.message : "Failed to initialize bot consumption page",
isAuthenticated: false
}));
}
} finally {
if (mounted) {
setAuthState((prev) => ({ ...prev, isAuthenticating: false }));
}
}
};
initializeBotConsumptionPage();
return () => {
mounted = false;
};
}, [
isSdkLoaded,
placeholder,
loginMode,
serverUrlLibrary,
projectId,
objectId,
config,
username,
password
]);
return {
botConsumptionPage,
...authState
};
};
// src/components/BotConsumptionPageWithAuth.tsx
import { clsx as clsx6 } from "clsx";
import { useRef as useRef6 } from "react";
import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
var BotConsumptionPageWithAuth = ({
serverUrlLibrary,
projectId,
objectId,
config,
loginMode,
username,
password,
className,
style,
loadingComponent = /* @__PURE__ */ jsx6("div", { children: "Loading..." }),
errorComponent = (error) => /* @__PURE__ */ jsx6("div", { children: error })
}) => {
const containerRef = useRef6(null);
const { isAuthenticated, isAuthenticating, error } = useCreateBotConsumptionPageWithAuth({
serverUrlLibrary,
placeholder: containerRef.current,
projectId,
objectId,
config,
loginMode,
username,
password
});
if (isAuthenticating) {
return /* @__PURE__ */ jsx6(Fragment3, { children: loadingComponent });
}
if (error) {
return /* @__PURE__ */ jsx6(Fragment3, { children: errorComponent(error) });
}
if (!isAuthenticated) {
return null;
}
return /* @__PURE__ */ jsx6(
"div",
{
ref: containerRef,
className: clsx6(className),
style: { width: "100%", height: "600px", ...style }
}
);
};
export {
BotConsumptionPage,
BotConsumptionPageWithAuth,
DashboardEmbed,
DashboardEmbedWithAuth,
EVENT_TYPE,
LibraryPageEmbed,
LibraryPageEmbedWithAuth,
getAuthToken,
getInfoFromUrl,
getServerUrl,
useCreateBotConsumptionPage,
useCreateBotConsumptionPageWithAuth,
useCreateDashboard,
useCreateDashboardWithAuth,
useCreateLibraryPage,
useCreateLibraryPageWithAuth,
useLoadMstrSDK
};