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 SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
|
||||
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 acceptByType = {
|
||||
|
|
@ -664,6 +666,8 @@ const isNodeDCSessionLogoutEvent = (value) => (
|
|||
|
||||
let nodeDCSessionSyncCleanup = null;
|
||||
let nodeDCLogoutHandling = false;
|
||||
let nodeDCSessionWatchTimer = null;
|
||||
let nodeDCSessionWatchInFlight = false;
|
||||
|
||||
const handleNodeDCSessionLogout = async () => {
|
||||
if (nodeDCLogoutHandling) return;
|
||||
|
|
@ -759,7 +763,8 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
|||
let storageKey = "";
|
||||
try {
|
||||
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;
|
||||
}
|
||||
sessionStorage.setItem(storageKey, String(Date.now()));
|
||||
|
|
@ -786,6 +791,7 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
|||
if (payload.type !== "nodedc:bim-share-promoted") return;
|
||||
done = true;
|
||||
cleanup();
|
||||
if (payload.promoted === false) return;
|
||||
window.location.replace(payload.nextPath || getCurrentReturnPath());
|
||||
};
|
||||
|
||||
|
|
@ -798,6 +804,89 @@ const scheduleShareSessionPromotion = (shareToken) => {
|
|||
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 = `
|
||||
<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>
|
||||
|
|
@ -5309,6 +5398,7 @@ const applyViewerState = (state) => {
|
|||
const initViewer = async () => {
|
||||
await initializeRuntimeMode();
|
||||
setupNodeDCSessionSync();
|
||||
setupNodeDCSessionWatcher();
|
||||
|
||||
const preloadHidden = document.getElementById("preload-hidden");
|
||||
if (preloadHidden) {
|
||||
|
|
|
|||
|
|
@ -929,9 +929,10 @@ const buildSharePromotionUrls = (token) => {
|
|||
const sharePath = `/share/${encodeURIComponent(safeToken)}`;
|
||||
const guestPath = `${sharePath}?${SHARE_AUTH_GUEST_PARAM}=1`;
|
||||
const bridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}`;
|
||||
const guestBridgePath = `/auth/share-promoted?next=${encodeURIComponent(sharePath)}&status=guest`;
|
||||
return {
|
||||
promoteUrl: buildLauncherLaunchUrl(sharePath),
|
||||
silentPromoteUrl: buildLauncherLaunchUrl(bridgePath),
|
||||
silentPromoteUrl: buildLauncherOptionalLaunchUrl(bridgePath, guestBridgePath),
|
||||
optionalPromoteUrl: buildLauncherOptionalLaunchUrl(sharePath, guestPath)
|
||||
};
|
||||
};
|
||||
|
|
@ -1781,6 +1782,7 @@ const handleLauncherHandoff = async (req, res, url) => {
|
|||
const handleSharePromoted = (res, url) => {
|
||||
const nextPath = sanitizeReturnTo(url.searchParams.get("next") || "/");
|
||||
const nextPathJson = JSON.stringify(nextPath);
|
||||
const promoted = url.searchParams.get("status") !== "guest";
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
|
|
@ -1797,6 +1799,7 @@ const handleSharePromoted = (res, url) => {
|
|||
const payload = {
|
||||
type: "nodedc:bim-share-promoted",
|
||||
nextPath,
|
||||
promoted: ${promoted ? "true" : "false"},
|
||||
createdAt: Date.now()
|
||||
};
|
||||
if (window.parent && window.parent !== window) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue