Sync BIM login state and stabilize zoom

This commit is contained in:
CODEX 2026-06-23 19:28:47 +03:00
parent ce34ecdd4d
commit 249d46c7c7
2 changed files with 65 additions and 2 deletions

View File

@ -447,9 +447,10 @@ const configureCameraControl = () => {
if (!control) return; if (!control) return;
activeZoomSpeed = normalizeZoomSpeed(activeZoomSpeed); activeZoomSpeed = normalizeZoomSpeed(activeZoomSpeed);
configureCameraPanMap(control); configureCameraPanMap(control);
control.followPointer = true; // Keep wheel dolly speed independent from whether the pointer is over model geometry.
control.followPointer = false;
control.doublePickFlyTo = false; control.doublePickFlyTo = false;
control.smartPivot = true; control.smartPivot = false;
control.zoomOnMouseWheel = true; control.zoomOnMouseWheel = true;
control.keyboardDollyRate = CAMERA_CONTROL_SETTINGS.keyboardDollyRate; control.keyboardDollyRate = CAMERA_CONTROL_SETTINGS.keyboardDollyRate;
control.mouseWheelDollyRate = activeZoomSpeed; control.mouseWheelDollyRate = activeZoomSpeed;
@ -684,6 +685,8 @@ let nodeDCLogoutHandling = false;
let nodeDCLoginHandling = false; let nodeDCLoginHandling = false;
let nodeDCSessionWatchTimer = null; let nodeDCSessionWatchTimer = null;
let nodeDCSessionWatchInFlight = false; let nodeDCSessionWatchInFlight = false;
let nodeDCLoginSyncPrimed = false;
let nodeDCLoginSyncEventId = "";
const handleNodeDCSessionLogout = async () => { const handleNodeDCSessionLogout = async () => {
if (nodeDCLogoutHandling) return; if (nodeDCLogoutHandling) return;
@ -844,9 +847,24 @@ const scheduleShareSessionPromotion = (shareToken) => {
document.body.appendChild(iframe); document.body.appendChild(iframe);
}; };
const getLoginSyncEventId = (sessionPayload) => (
typeof sessionPayload?.loginSyncEventId === "string" ? sessionPayload.loginSyncEventId : ""
);
const applyLiveSessionPayload = async (sessionPayload, { reason = "watch" } = {}) => { const applyLiveSessionPayload = async (sessionPayload, { reason = "watch" } = {}) => {
const wasAuthenticated = runtimeSessionInfo.authenticated === true; const wasAuthenticated = runtimeSessionInfo.authenticated === true;
const isAuthenticated = sessionPayload?.authenticated === true; const isAuthenticated = sessionPayload?.authenticated === true;
const loginSyncEventId = getLoginSyncEventId(sessionPayload);
const hasNewLoginSyncEvent =
nodeDCLoginSyncPrimed &&
!isAuthenticated &&
!!loginSyncEventId &&
loginSyncEventId !== nodeDCLoginSyncEventId;
nodeDCLoginSyncPrimed = true;
if (loginSyncEventId) {
nodeDCLoginSyncEventId = loginSyncEventId;
}
runtimeSessionInfo = { runtimeSessionInfo = {
...runtimeSessionInfo, ...runtimeSessionInfo,
@ -861,6 +879,10 @@ const applyLiveSessionPayload = async (sessionPayload, { reason = "watch" } = {}
await handleNodeDCSessionLogout(); await handleNodeDCSessionLogout();
return; return;
} }
if (hasNewLoginSyncEvent) {
handleNodeDCSessionLogin();
return;
}
const shareToken = getShareTokenFromPath(); const shareToken = getShareTokenFromPath();
if (shareToken && viewerMode === "guest") { if (shareToken && viewerMode === "guest") {
scheduleShareSessionPromotion(shareToken); scheduleShareSessionPromotion(shareToken);

View File

@ -56,6 +56,7 @@ const BIM_SESSION_COOKIE = process.env.NODEDC_BIM_SESSION_COOKIE || "nodedc_bim_
const BIM_HANDOFF_COOKIE = process.env.NODEDC_BIM_HANDOFF_COOKIE || "nodedc_bim_handoff"; const BIM_HANDOFF_COOKIE = process.env.NODEDC_BIM_HANDOFF_COOKIE || "nodedc_bim_handoff";
const BIM_SESSION_TTL_MS = Number(process.env.NODEDC_BIM_SESSION_TTL_MS || 12 * 60 * 60 * 1000); const BIM_SESSION_TTL_MS = Number(process.env.NODEDC_BIM_SESSION_TTL_MS || 12 * 60 * 60 * 1000);
const BIM_HANDOFF_TTL_SECONDS = Number(process.env.NODEDC_BIM_HANDOFF_TTL_SECONDS || 60); const BIM_HANDOFF_TTL_SECONDS = Number(process.env.NODEDC_BIM_HANDOFF_TTL_SECONDS || 60);
const BIM_LOGIN_SYNC_TTL_MS = Number(process.env.NODEDC_BIM_LOGIN_SYNC_TTL_MS || 60 * 1000);
const BIM_COOKIE_SECURE = parseBooleanEnv(process.env.NODEDC_BIM_COOKIE_SECURE, false); const BIM_COOKIE_SECURE = parseBooleanEnv(process.env.NODEDC_BIM_COOKIE_SECURE, false);
const BIM_COOKIE_SAMESITE = process.env.NODEDC_BIM_COOKIE_SAMESITE || (BIM_COOKIE_SECURE ? "None" : "Lax"); const BIM_COOKIE_SAMESITE = process.env.NODEDC_BIM_COOKIE_SAMESITE || (BIM_COOKIE_SECURE ? "None" : "Lax");
const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest"; const SHARE_AUTH_GUEST_PARAM = "ndc_bim_guest";
@ -64,6 +65,7 @@ const CORS_ALLOWED_ORIGINS = String(process.env.NODEDC_BIM_ALLOWED_ORIGINS || ""
.map((origin) => origin.trim()) .map((origin) => origin.trim())
.filter(Boolean); .filter(Boolean);
const bimSessions = new Map(); const bimSessions = new Map();
let latestBimLoginSyncEvent = null;
const MIME_TYPES = { const MIME_TYPES = {
".html": "text/html", ".html": "text/html",
@ -721,6 +723,27 @@ const parseJSONBody = async (req) => {
const randomBase64Url = (bytes = 24) => crypto.randomBytes(bytes).toString("base64url"); const randomBase64Url = (bytes = 24) => crypto.randomBytes(bytes).toString("base64url");
const markBimLoginSyncEvent = () => {
const ttlMs = Number.isFinite(BIM_LOGIN_SYNC_TTL_MS) ? Math.max(1000, BIM_LOGIN_SYNC_TTL_MS) : 60 * 1000;
latestBimLoginSyncEvent = {
id: randomBase64Url(16),
createdAt: nowIso(),
expiresAt: Date.now() + ttlMs
};
return latestBimLoginSyncEvent;
};
const getActiveBimLoginSyncEvent = () => {
if (!latestBimLoginSyncEvent) {
return null;
}
if (latestBimLoginSyncEvent.expiresAt <= Date.now()) {
latestBimLoginSyncEvent = null;
return null;
}
return latestBimLoginSyncEvent;
};
const sanitizeReturnTo = (value, fallback = "/") => { const sanitizeReturnTo = (value, fallback = "/") => {
if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//")) { if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//")) {
return fallback; return fallback;
@ -1749,16 +1772,30 @@ const handleGetUploadVersions = async (req, res, searchParams) => {
const handleAuthSession = async (req, res) => { const handleAuthSession = async (req, res) => {
const session = await getValidatedBimSession(req, res); const session = await getValidatedBimSession(req, res);
const loginSyncEvent = getActiveBimLoginSyncEvent();
res.setHeader("Cache-Control", "no-store");
sendJSON(res, 200, { sendJSON(res, 200, {
ok: true, ok: true,
authRequired: BIM_AUTH_REQUIRED, authRequired: BIM_AUTH_REQUIRED,
authenticated: !!session, authenticated: !!session,
canShare: !BIM_AUTH_REQUIRED || !!session, canShare: !BIM_AUTH_REQUIRED || !!session,
user: session?.user || null, user: session?.user || null,
loginSyncEventId: loginSyncEvent?.id || null,
loginSyncEventCreatedAt: loginSyncEvent?.createdAt || null,
loginUrl: session ? null : buildLauncherLoginUrl(req, "/") loginUrl: session ? null : buildLauncherLoginUrl(req, "/")
}); });
}; };
const handleLoginSync = (res) => {
const event = markBimLoginSyncEvent();
setCors(res);
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Cache-Control", "no-store");
res.setHeader("X-NODEDC-Session-Event", event.id);
res.end("ok");
};
const handleLauncherHandoff = async (req, res, url) => { const handleLauncherHandoff = async (req, res, url) => {
const token = url.searchParams.get("token") || ""; const token = url.searchParams.get("token") || "";
const nextPath = sanitizeReturnTo(url.searchParams.get("next_path") || url.searchParams.get("returnTo") || "/"); const nextPath = sanitizeReturnTo(url.searchParams.get("next_path") || url.searchParams.get("returnTo") || "/");
@ -3435,6 +3472,10 @@ const requestHandler = async (req, res) => {
return handleSharePromoted(res, url); return handleSharePromoted(res, url);
} }
if (req.method === "GET" && url.pathname === "/auth/login-sync") {
return handleLoginSync(res);
}
if (req.method === "GET" && url.pathname === "/auth/logout") { if (req.method === "GET" && url.pathname === "/auth/logout") {
return handleLogout(req, res); return handleLogout(req, res);
} }