Sync BIM controls with live Launcher session
This commit is contained in:
parent
ffa60e4a22
commit
168b535953
|
|
@ -186,6 +186,8 @@ const COMMENTS_API = "/api/comments";
|
||||||
const UPLOAD_VERSIONS_API = "/api/uploads/versions";
|
const UPLOAD_VERSIONS_API = "/api/uploads/versions";
|
||||||
const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
|
const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
|
||||||
const SHARE_PROMOTION_TIMEOUT_MS = 4500;
|
const SHARE_PROMOTION_TIMEOUT_MS = 4500;
|
||||||
|
const SHARE_PROMOTION_RETRY_MS = 15000;
|
||||||
|
const SESSION_STATUS_WATCH_INTERVAL_MS = 2500;
|
||||||
const NODEDC_SESSION_EVENT_TYPE = "nodedc:session:logout";
|
const NODEDC_SESSION_EVENT_TYPE = "nodedc:session:logout";
|
||||||
|
|
||||||
const acceptByType = {
|
const acceptByType = {
|
||||||
|
|
@ -664,6 +666,8 @@ const isNodeDCSessionLogoutEvent = (value) => (
|
||||||
|
|
||||||
let nodeDCSessionSyncCleanup = null;
|
let nodeDCSessionSyncCleanup = null;
|
||||||
let nodeDCLogoutHandling = false;
|
let nodeDCLogoutHandling = false;
|
||||||
|
let nodeDCSessionWatchTimer = null;
|
||||||
|
let nodeDCSessionWatchInFlight = false;
|
||||||
|
|
||||||
const handleNodeDCSessionLogout = async () => {
|
const handleNodeDCSessionLogout = async () => {
|
||||||
if (nodeDCLogoutHandling) return;
|
if (nodeDCLogoutHandling) return;
|
||||||
|
|
@ -759,7 +763,8 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
||||||
let storageKey = "";
|
let storageKey = "";
|
||||||
try {
|
try {
|
||||||
storageKey = `bimdc-share-promote:${shareToken}`;
|
storageKey = `bimdc-share-promote:${shareToken}`;
|
||||||
if (sessionStorage.getItem(storageKey)) {
|
const lastAttempt = Number(sessionStorage.getItem(storageKey) || 0);
|
||||||
|
if (Number.isFinite(lastAttempt) && Date.now() - lastAttempt < SHARE_PROMOTION_RETRY_MS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sessionStorage.setItem(storageKey, String(Date.now()));
|
sessionStorage.setItem(storageKey, String(Date.now()));
|
||||||
|
|
@ -786,6 +791,7 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
||||||
if (payload.type !== "nodedc:bim-share-promoted") return;
|
if (payload.type !== "nodedc:bim-share-promoted") return;
|
||||||
done = true;
|
done = true;
|
||||||
cleanup();
|
cleanup();
|
||||||
|
if (payload.promoted === false) return;
|
||||||
window.location.replace(payload.nextPath || getCurrentReturnPath());
|
window.location.replace(payload.nextPath || getCurrentReturnPath());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -798,6 +804,89 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applyLiveSessionPayload = async (sessionPayload, { reason = "watch" } = {}) => {
|
||||||
|
const wasAuthenticated = runtimeSessionInfo.authenticated === true;
|
||||||
|
const isAuthenticated = sessionPayload?.authenticated === true;
|
||||||
|
|
||||||
|
runtimeSessionInfo = {
|
||||||
|
...runtimeSessionInfo,
|
||||||
|
...(sessionPayload || {}),
|
||||||
|
authenticated: isAuthenticated,
|
||||||
|
canShare: sessionPayload?.canShare === true,
|
||||||
|
user: sessionPayload?.user || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
if (wasAuthenticated) {
|
||||||
|
await handleNodeDCSessionLogout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const shareToken = getShareTokenFromPath();
|
||||||
|
if (shareToken && viewerMode === "guest") {
|
||||||
|
scheduleShareSessionPromotion(shareToken);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getShareTokenFromPath() && viewerMode === "guest") {
|
||||||
|
viewerMode = "standard";
|
||||||
|
}
|
||||||
|
applyRuntimeMode();
|
||||||
|
updatePanelsVisibility();
|
||||||
|
|
||||||
|
if (reason === "focus" || reason === "visibility") {
|
||||||
|
loadProjectsList?.().catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshNodeDCSessionStatus = async (options = {}) => {
|
||||||
|
if (nodeDCSessionWatchInFlight || nodeDCLogoutHandling) return;
|
||||||
|
nodeDCSessionWatchInFlight = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(AUTH_SESSION_API, {
|
||||||
|
credentials: "include",
|
||||||
|
cache: "no-store",
|
||||||
|
headers: { "Accept": "application/json" },
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
await applyLiveSessionPayload(await response.json(), options);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// A transient network failure must not log the user out client-side.
|
||||||
|
} finally {
|
||||||
|
nodeDCSessionWatchInFlight = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupNodeDCSessionWatcher = () => {
|
||||||
|
if (nodeDCSessionWatchTimer || typeof window === "undefined") return;
|
||||||
|
|
||||||
|
nodeDCSessionWatchTimer = window.setInterval(() => {
|
||||||
|
void refreshNodeDCSessionStatus({ reason: "interval" });
|
||||||
|
}, SESSION_STATUS_WATCH_INTERVAL_MS);
|
||||||
|
|
||||||
|
window.addEventListener("focus", () => {
|
||||||
|
void refreshNodeDCSessionStatus({ reason: "focus" });
|
||||||
|
const shareToken = getShareTokenFromPath();
|
||||||
|
if (shareToken && viewerMode === "guest") {
|
||||||
|
scheduleShareSessionPromotion(shareToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("pageshow", () => {
|
||||||
|
void refreshNodeDCSessionStatus({ reason: "pageshow" });
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", () => {
|
||||||
|
if (document.visibilityState !== "visible") return;
|
||||||
|
void refreshNodeDCSessionStatus({ reason: "visibility" });
|
||||||
|
const shareToken = getShareTokenFromPath();
|
||||||
|
if (shareToken && viewerMode === "guest") {
|
||||||
|
scheduleShareSessionPromotion(shareToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const COMMENT_TARGET_ICON = `
|
const COMMENT_TARGET_ICON = `
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path d="M6.5 4.75h11A3.25 3.25 0 0 1 20.75 8v6.25a3.25 3.25 0 0 1-3.25 3.25h-5.26l-5.29 3.82V17.5H6.5a3.25 3.25 0 0 1-3.25-3.25V8A3.25 3.25 0 0 1 6.5 4.75Z"></path>
|
<path d="M6.5 4.75h11A3.25 3.25 0 0 1 20.75 8v6.25a3.25 3.25 0 0 1-3.25 3.25h-5.26l-5.29 3.82V17.5H6.5a3.25 3.25 0 0 1-3.25-3.25V8A3.25 3.25 0 0 1 6.5 4.75Z"></path>
|
||||||
|
|
@ -5309,6 +5398,7 @@ const applyViewerState = (state) => {
|
||||||
const initViewer = async () => {
|
const initViewer = async () => {
|
||||||
await initializeRuntimeMode();
|
await initializeRuntimeMode();
|
||||||
setupNodeDCSessionSync();
|
setupNodeDCSessionSync();
|
||||||
|
setupNodeDCSessionWatcher();
|
||||||
|
|
||||||
const preloadHidden = document.getElementById("preload-hidden");
|
const preloadHidden = document.getElementById("preload-hidden");
|
||||||
if (preloadHidden) {
|
if (preloadHidden) {
|
||||||
|
|
|
||||||
|
|
@ -929,9 +929,10 @@ const buildSharePromotionUrls = (token) => {
|
||||||
const sharePath = `/share/${encodeURIComponent(safeToken)}`;
|
const sharePath = `/share/${encodeURIComponent(safeToken)}`;
|
||||||
const guestPath = `${sharePath}?${SHARE_AUTH_GUEST_PARAM}=1`;
|
const guestPath = `${sharePath}?${SHARE_AUTH_GUEST_PARAM}=1`;
|
||||||
const bridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}`;
|
const bridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}`;
|
||||||
|
const guestBridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}&status=guest`;
|
||||||
return {
|
return {
|
||||||
promoteUrl: buildLauncherLaunchUrl(sharePath),
|
promoteUrl: buildLauncherLaunchUrl(sharePath),
|
||||||
silentPromoteUrl: buildLauncherLaunchUrl(bridgePath),
|
silentPromoteUrl: buildLauncherOptionalLaunchUrl(bridgePath, guestBridgePath),
|
||||||
optionalPromoteUrl: buildLauncherOptionalLaunchUrl(sharePath, guestPath)
|
optionalPromoteUrl: buildLauncherOptionalLaunchUrl(sharePath, guestPath)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -1781,6 +1782,7 @@ const handleLauncherHandoff = async (req, res, url) => {
|
||||||
const handleSharePromoted = (res, url) => {
|
const handleSharePromoted = (res, url) => {
|
||||||
const nextPath = sanitizeReturnTo(url.searchParams.get("next") || "/");
|
const nextPath = sanitizeReturnTo(url.searchParams.get("next") || "/");
|
||||||
const nextPathJson = JSON.stringify(nextPath);
|
const nextPathJson = JSON.stringify(nextPath);
|
||||||
|
const promoted = url.searchParams.get("status") !== "guest";
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||||
res.setHeader("Cache-Control", "no-store");
|
res.setHeader("Cache-Control", "no-store");
|
||||||
|
|
@ -1797,6 +1799,7 @@ const handleSharePromoted = (res, url) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: "nodedc:bim-share-promoted",
|
type: "nodedc:bim-share-promoted",
|
||||||
nextPath,
|
nextPath,
|
||||||
|
promoted: ${promoted ? "true" : "false"},
|
||||||
createdAt: Date.now()
|
createdAt: Date.now()
|
||||||
};
|
};
|
||||||
if (window.parent && window.parent !== window) {
|
if (window.parent && window.parent !== window) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue