From 613e79f12ef53e39accb60a05c7520650c7edb4c Mon Sep 17 00:00:00 2001 From: kascit Date: Wed, 13 May 2026 18:06:21 +0530 Subject: [PATCH 1/2] shell export --- static/js/core/shell.js | 142 ++++++------------ static/js/system/auth-integration.js | 213 +++++++++++++++++++-------- 2 files changed, 192 insertions(+), 163 deletions(-) diff --git a/static/js/core/shell.js b/static/js/core/shell.js index a241e73..b29da42 100644 --- a/static/js/core/shell.js +++ b/static/js/core/shell.js @@ -28,10 +28,18 @@ ensureDefaultPolicy(); window.__componentsJS = true; let _injected = false; +// Explicitly default all standard modules to True to prevent configuration-drop erasures const DEFAULT_SHELL_CONFIG = { ...SHELL_CONFIG_DEFAULTS, noCss: false, showNavbar: true, + showLanguage: true, + showAppsGrid: true, + showAccountButton: true, + showThemeToggle: true, + showMobileMenu: true, + enablePwa: false, + favicon: false, }; function getShellRuntimeConfig() { @@ -49,12 +57,10 @@ function getShellRuntimeConfig() { return merged; } -// Determines if relative paths work (primary domain context) function isSameOriginHost() { return window.location.origin === BASE_URL; } -// Authorizes execution across all controlled top-level and subdomains function isTrustedHost() { const host = window.location.hostname; return ( @@ -75,14 +81,13 @@ function maybeRegisterServiceWorker(config) { "load", () => { navigator.serviceWorker.register(swPath).catch(() => { - // Subdomains can opt out or provide their own /sw.js. + // Subdomains opt out or host custom instances }); }, { once: true }, ); } -// Helpers to inject CSS and favicon assets into foreign documents. function injectCSS(sameOrigin, config) { if (config.noCss) return; if (document.querySelector('link[data-shell-style="main"]')) return; @@ -121,8 +126,6 @@ function injectFavicons(sameOrigin, config) { ) .forEach((el) => el.remove()); - // For cross-origin subdomains, skip .ico files to avoid CORB errors. - // Browsers block opaque cross-origin .ico reads even with CORS headers. const iconLinks = [ { rel: "icon", @@ -138,7 +141,6 @@ function injectFavicons(sameOrigin, config) { }, ]; - // Only include .ico for same-origin (avoids CORB on subdomains) if (sameOrigin) { iconLinks.push({ rel: "shortcut icon", @@ -146,7 +148,6 @@ function injectFavicons(sameOrigin, config) { }); } - // Avoid cross-origin webmanifest warnings on subdomains. if (sameOrigin) { document .querySelectorAll('link[rel="manifest"]') @@ -206,7 +207,7 @@ function safeHref(value) { return parsed.href; } } catch { - // Ignore malformed URL and fall back to anchor. + // Ignore malformed strings } return "#"; } @@ -276,20 +277,16 @@ async function updateAppsGridForRole(shellRoot, role) { return freshVisible; } -// --- Mobile panel logic --- -function initMobilePanel(shellRoot) { - const panel = shellRoot.querySelector("[data-shell-mobile-panel]"); +// --- Mobile Panel Event Delegation --- +function initMobilePanel() { + const panel = document.querySelector("[data-shell-mobile-panel]"); if (!panel) return; - const toggle = shellRoot.querySelector("#shell-mobile-toggle"); - const backdrop = panel.querySelector("[data-shell-mobile-backdrop]"); const drawer = panel.querySelector("[data-shell-mobile-drawer]"); - const closeBtn = panel.querySelector("[data-shell-mobile-close]"); function openPanel() { panel.classList.remove("hidden"); - // Force DOM reflow to trigger slide animation cleanly - void panel.offsetWidth; + void panel.offsetWidth; // Trigger clean reflow layout execution if (drawer) drawer.style.transform = "translateX(0)"; document.body.style.overflow = "hidden"; } @@ -302,103 +299,52 @@ function initMobilePanel(shellRoot) { }, 300); } - // Prevent duplicate event binding if hydrated multiple times - if (toggle && !toggle.hasAttribute("data-shell-bound")) { - toggle.setAttribute("data-shell-bound", "true"); - toggle.addEventListener("click", openPanel); - } + // Pure single-listener routing blocks element detach loops + if (!document.__shellMobileDelegated) { + document.__shellMobileDelegated = true; + document.addEventListener("click", (e) => { + const toggleTarget = e.target.closest("#shell-mobile-toggle"); + if (toggleTarget) { + e.preventDefault(); + e.stopPropagation(); + openPanel(); + return; + } - if (backdrop && !backdrop.hasAttribute("data-shell-bound")) { - backdrop.setAttribute("data-shell-bound", "true"); - backdrop.addEventListener("click", closePanel); - } + const closeTarget = e.target.closest("[data-shell-mobile-close]"); + const backdropTarget = e.target.closest("[data-shell-mobile-backdrop]"); + if (closeTarget || backdropTarget) { + e.preventDefault(); + e.stopPropagation(); + closePanel(); + return; + } + }); - if (closeBtn && !closeBtn.hasAttribute("data-shell-bound")) { - closeBtn.setAttribute("data-shell-bound", "true"); - closeBtn.addEventListener("click", closePanel); + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && !panel.classList.contains("hidden")) { + closePanel(); + } + }); } - - document.addEventListener("keydown", (e) => { - if (e.key === "Escape" && !panel.classList.contains("hidden")) { - closePanel(); - } - }); } -// --- Mobile auth sync --- function syncMobileAuth(shellRoot, authStatus) { + // Redundant proxy check maintained strictly for edge consumers using local overrides const panel = shellRoot.querySelector("[data-shell-mobile-panel]"); if (!panel) return; const isAuthed = authStatus?.authenticated === true; const user = authStatus?.user; - - const guestAvatar = panel.querySelector('[data-auth="mobile-guest-avatar"]'); - const authedAvatar = panel.querySelector( - '[data-auth="mobile-authed-avatar"]', - ); const nameEl = panel.querySelector('[data-auth="mobile-name"]'); const emailEl = panel.querySelector('[data-auth="mobile-email"]'); - const loginBtn = panel.querySelector('[data-auth="mobile-login-btn"]'); - const logoutBtn = panel.querySelector('[data-auth="mobile-logout-btn"]'); - const accountBtn = panel.querySelector('[data-auth="mobile-account-btn"]'); if (isAuthed && user) { - if (guestAvatar) guestAvatar.classList.add("hidden"); - if (authedAvatar) { - authedAvatar.classList.remove("hidden"); - const img = authedAvatar.querySelector("img"); - if (img) { - const avatarUrl = user.avatar_url; - if (avatarUrl) { - img.src = avatarUrl; - } else { - img.removeAttribute("src"); - } - } - } if (nameEl) nameEl.textContent = user.name || "User"; if (emailEl) emailEl.textContent = user.email || ""; - if (loginBtn) loginBtn.classList.add("hidden"); - if (logoutBtn) logoutBtn.classList.remove("hidden"); - if (accountBtn) accountBtn.classList.remove("hidden"); } else { - if (guestAvatar) guestAvatar.classList.remove("hidden"); - if (authedAvatar) authedAvatar.classList.add("hidden"); if (nameEl) nameEl.textContent = "Guest"; if (emailEl) emailEl.textContent = "Not signed in"; - if (loginBtn) loginBtn.classList.remove("hidden"); - if (logoutBtn) logoutBtn.classList.add("hidden"); - if (accountBtn) accountBtn.classList.add("hidden"); - } - - if (logoutBtn) { - logoutBtn.onclick = (e) => { - e.preventDefault(); - const auth = window.AUTH; - if (auth && typeof auth.logout === "function") { - const result = auth.logout(); - if (result && typeof result.then === "function") { - result - .then(() => window.location.reload()) - .catch(() => window.location.reload()); - } else { - window.location.reload(); - } - } - }; - } - - if (loginBtn) { - loginBtn.onclick = (e) => { - e.preventDefault(); - const auth = window.AUTH; - if (auth && typeof auth.login === "function") { - auth.login(); - } else { - window.location.href = "https://auth.dhanur.me"; - } - }; } } @@ -421,15 +367,14 @@ function hydrate(shellRoot) { } applyChromeVisibility(shellRoot, config); - updateAppsGridForRole(shellRoot, "guest"); initResponsive(); initTheme(shellRoot); initDropdowns(shellRoot); - initMobilePanel(shellRoot); + initMobilePanel(); - initAuth(shellRoot, (authStatus) => { + initAuth(document, (authStatus) => { const role = authStatus?.role || "guest"; const access = checkAccess(config, authStatus); const contentSlot = @@ -459,7 +404,6 @@ async function bootstrapShell() { const config = getShellRuntimeConfig(); if (config.showNavbar === false) return; - // Enforce execution mapping strictly to authorized domain zones if (!isTrustedHost()) { console.warn("[shell.js] Execution blocked on unauthorized origin."); return; @@ -471,14 +415,12 @@ async function bootstrapShell() { const existingNavbar = document.querySelector(".navbar"); if (existingNavbar) { - // Subdomains bypass primary origin scripts, requiring shell hydration if (!sameOrigin) { hydrate(document.body); } return; } - // Inject underlying platform CSS assets cleanly if targeting missing navigation blocks if (!sameOrigin) { injectCSS(false, config); injectFavicons(false, config); diff --git a/static/js/system/auth-integration.js b/static/js/system/auth-integration.js index 1f8d177..c074e3f 100644 --- a/static/js/system/auth-integration.js +++ b/static/js/system/auth-integration.js @@ -1,6 +1,6 @@ /** * Authentication Integration - * Handles Auth client loading and UI state syncing. + * Handles Auth client loading and UI state syncing across root and subdomains. */ import { appendScriptOnce } from "../core/resource-loader.js"; @@ -22,30 +22,96 @@ function once(fn) { }; } -function showElements(...elements) { - elements.forEach((el) => el && el.classList.remove("hidden")); +// Robust unwrapping for single nodes, Arrays, or NodeLists +function showElements(...items) { + items.forEach((item) => { + if (!item) return; + if (item instanceof NodeList || Array.isArray(item)) { + item.forEach((el) => el && el.classList.remove("hidden")); + } else if (item.classList) { + item.classList.remove("hidden"); + } + }); } -function hideElements(...elements) { - elements.forEach((el) => el && el.classList.add("hidden")); +function hideElements(...items) { + items.forEach((item) => { + if (!item) return; + if (item instanceof NodeList || Array.isArray(item)) { + item.forEach((el) => el && el.classList.add("hidden")); + } else if (item.classList) { + item.classList.add("hidden"); + } + }); } -function setText(el, value) { - if (el) el.textContent = value; +function setText(item, value) { + if (!item) return; + if (item instanceof NodeList || Array.isArray(item)) { + item.forEach((el) => { + if (el) el.textContent = value; + }); + } else if (item.nodeType) { + item.textContent = value; + } } -function setSrc(el, value) { - if (el) el.src = value; +function setSrc(item, value) { + if (!item) return; + if (item instanceof NodeList || Array.isArray(item)) { + item.forEach((el) => { + if (!el) return; + if (value) { + el.src = value; + } else { + el.removeAttribute("src"); + } + }); + } else if (item.nodeType) { + if (value) { + item.src = value; + } else { + item.removeAttribute("src"); + } + } } -function bindAll(root, selector, eventName, handler, options) { - const elements = root.querySelectorAll(selector); - elements.forEach((el) => el.addEventListener(eventName, handler, options)); -} +function updateCreditsUI(root, credits) { + const rows = root.querySelectorAll( + '[data-auth="credits-row"], [data-auth="sidebar-credits-row"]', + ); + const balances = root.querySelectorAll( + '[data-auth="credits-balance"], [data-auth="sidebar-credits-balance"]', + ); + const resets = root.querySelectorAll( + '[data-auth="credits-reset"], [data-auth="sidebar-credits-reset"]', + ); + + if (!credits || rows.length === 0) { + hideElements(rows); + return; + } -// function updateCreditsUI(drawerElement, credits) { -function updateCreditsUI() { - // Update credit badges/counters if applicable + showElements(rows); + if (credits.unlimited || credits.balance === -1) { + setText(balances, "∞"); + setText(resets, "Admin"); + } else { + setText(balances, String(credits.balance ?? "—")); + if (credits.periodEnd) { + try { + const d = new Date(credits.periodEnd); + setText( + resets, + `resets ${d.toLocaleDateString(undefined, { month: "short", day: "numeric" })}`, + ); + } catch { + setText(resets, ""); + } + } else { + setText(resets, ""); + } + } } function injectAuthSDK(callback) { @@ -71,6 +137,7 @@ function injectAuthSDK(callback) { }, onError: () => { console.warn("[Auth] Could not load auth-client.js"); + done(); }, }); @@ -87,29 +154,42 @@ export function initAuth(drawerElement = document, onAuthResolved = null) { const auth = getAuthClient(); if (!auth) return; - // DOM references + // Comprehensive node mapping covering Desktop Navbars, Subdomain Panels, and Root Domain Sidebars const r = { - navGuestAvatar: drawerElement.querySelector('[data-auth="guest-avatar"]'), - navAuthedAvatar: drawerElement.querySelector( - '[data-auth="authed-avatar"]', + navGuestAvatar: drawerElement.querySelectorAll( + '[data-auth="nav-guest-avatar"], [data-auth="mobile-guest-avatar"], [data-auth="sidebar-guest-avatar"]', + ), + navAuthedAvatar: drawerElement.querySelectorAll( + '[data-auth="nav-authed-avatar"], [data-auth="mobile-authed-avatar"], [data-auth="sidebar-authed-avatar"]', + ), + navAvatarImg: drawerElement.querySelectorAll( + '[data-auth="nav-authed-avatar"] img, [data-auth="mobile-authed-avatar"] img, [data-auth="sidebar-authed-avatar"] img', + ), + navAuthedHeaderImg: drawerElement.querySelectorAll( + '[data-auth="nav-authed-header-avatar"], [data-auth="mobile-authed-header-avatar"]', + ), + navName: drawerElement.querySelectorAll( + '[data-auth="nav-name"], [data-auth="mobile-name"], [data-auth="sidebar-name"]', + ), + navEmail: drawerElement.querySelectorAll( + '[data-auth="nav-email"], [data-auth="mobile-email"], [data-auth="sidebar-email"]', + ), + navLoginItem: drawerElement.querySelectorAll( + '[data-auth="login-item"], [data-auth="mobile-login-btn"], [data-auth="sidebar-login-btn"]', ), - navAvatarImg: drawerElement.querySelector('[data-auth="avatar-img"]'), - navAuthedHeaderImg: drawerElement.querySelector( - '[data-auth="authed-header-img"]', + navAccountItem: drawerElement.querySelectorAll( + '[data-auth="account-item"], [data-auth="mobile-account-btn"], [data-auth="sidebar-account-btn"]', ), - navName: drawerElement.querySelector('[data-auth="user-name"]'), - navEmail: drawerElement.querySelector('[data-auth="user-email"]'), - navLoginItem: drawerElement.querySelector('[data-auth="nav-login-item"]'), - navAccountItem: drawerElement.querySelector( - '[data-auth="nav-account-item"]', + navLogoutItem: drawerElement.querySelectorAll( + '[data-auth="logout-item"], [data-auth="mobile-logout-btn"], [data-auth="sidebar-logout-btn"]', ), - navLogoutItem: drawerElement.querySelector( - '[data-auth="nav-logout-item"]', + navGuestHeader: drawerElement.querySelectorAll( + '[data-auth="nav-guest-header"], [data-auth="mobile-guest-header"]', ), - navGuestHeader: drawerElement.querySelector('[data-auth="guest-header"]'), - navAuthedHeader: drawerElement.querySelector( - '[data-auth="authed-header"]', + navAuthedHeader: drawerElement.querySelectorAll( + '[data-auth="nav-authed-header"], [data-auth="mobile-authed-header"]', ), + navRoleBadge: drawerElement.querySelectorAll('[data-auth="nav-role"]'), }; function updateUI(status) { @@ -131,12 +211,13 @@ export function initAuth(drawerElement = document, onAuthResolved = null) { setSrc(r.navAuthedHeaderImg, avatarUrl); setText(r.navName, userName); setText(r.navEmail, user.email || ""); + setText(r.navRoleBadge, status.role || "user"); try { if (avatarUrl) localStorage.setItem("dhanur_avatar_url_v1", avatarUrl); } catch { - // err + /* ignore */ } updateCreditsUI(drawerElement, status.credits || null); @@ -155,7 +236,6 @@ export function initAuth(drawerElement = document, onAuthResolved = null) { } } - // Initialize UI on SDK load if (typeof auth.onReady === "function") { auth.onReady((payload) => updateUI(payload?.status || payload || auth.status || null), @@ -164,48 +244,56 @@ export function initAuth(drawerElement = document, onAuthResolved = null) { updateUI(auth.status); } - // Listeners for SDK state changes document.addEventListener("authChanged", (e) => updateUI(e.detail)); document.addEventListener("creditsChanged", (e) => updateCreditsUI(drawerElement, e.detail), ); - // Click Bindings - bindAll( - drawerElement, - '[data-auth="login-btn"], [data-auth="sidebar-login-btn"]', - "click", - (e) => { - e.preventDefault(); - if (typeof auth.login === "function") auth.login(); - }, - ); + // Global Event Delegation for all authentication triggers + if (!document.__authClickDelegated) { + document.__authClickDelegated = true; + document.addEventListener("click", (e) => { + const loginTarget = e.target.closest( + '[data-auth="login-btn"], [data-auth="sidebar-login-btn"], [data-auth="mobile-login-btn"]', + ); + if (loginTarget) { + e.preventDefault(); + if (typeof auth.login === "function") auth.login(); + return; + } - bindAll( - drawerElement, - '[data-auth="logout-btn"], [data-auth="sidebar-logout-btn"]', - "click", - (e) => { - e.preventDefault(); - if (typeof auth.logout === "function") auth.logout(); - }, - ); + const logoutTarget = e.target.closest( + '[data-auth="logout-btn"], [data-auth="sidebar-logout-btn"], [data-auth="mobile-logout-btn"]', + ); + if (logoutTarget) { + e.preventDefault(); + if (typeof auth.logout === "function") auth.logout(); + return; + } + }); + } - // Intercept cross-origin messages from the OAuth popup + // Intercept cross-origin callbacks securely across domain boundaries window.addEventListener("message", async (event) => { - const trustedOrigins = ["https://auth.dhanur.me"]; - if (!trustedOrigins.includes(event.origin)) return; + const isTrustedOrigin = + event.origin === "https://auth.dhanur.me" || + event.origin === "https://dhanur.me" || + event.origin.endsWith(".dhanur.me") || + event.origin.startsWith("http://localhost:"); + + if (!isTrustedOrigin) return; if (!event.data || typeof event.data !== "object") return; - if (event.data.type === "auth-login-success") { - // Force the SDK to fetch the latest session set by the callback cookies + if ( + event.data.type === "auth-login-success" || + event.data.type === "auth-upgrade-success" + ) { if (auth && typeof auth.refresh === "function") { const freshStatus = await auth.refresh(); updateUI(freshStatus); } else { - // Fallback manual fetch if the SDK does not expose refresh() try { - const res = await fetch("https://auth.dhanur.me/api/session", { + const res = await fetch("https://auth.dhanur.me/api/status", { credentials: "include", }); if (res.ok) { @@ -223,7 +311,6 @@ export function initAuth(drawerElement = document, onAuthResolved = null) { } } - // Send confirmation back to popup so it safely terminates if (event.source) { event.source.postMessage({ type: "auth-ack-close" }, event.origin); } From f5d3e94ab7868f37ab7f3c34d9a688e294eebc76 Mon Sep 17 00:00:00 2001 From: kascit Date: Wed, 13 May 2026 18:38:53 +0530 Subject: [PATCH 2/2] shell export still --- scripts/generate-icons.js | 13 +++++++++++++ static/icons/site.webmanifest | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js index d02e13f..cba3a45 100644 --- a/scripts/generate-icons.js +++ b/scripts/generate-icons.js @@ -85,6 +85,8 @@ function renderIcon( `${size}x${size}`, "xc:none", "(", + "-density", + "1200", "-background", "none", src, @@ -122,6 +124,8 @@ function renderIcon( `${size}x${size}`, `xc:${bg}`, "(", + "-density", + "1200", "-background", "none", src, @@ -162,6 +166,8 @@ function renderIcon( "-draw", `roundrectangle 0,0,${max},${max},${radius},${radius}`, "(", + "-density", + "1200", "-background", "none", src, @@ -281,6 +287,7 @@ function main() { BASE_GLYPH_COLOR, ); + // Enforce true square boundary generation for all target maskables renderIcon( command, SRC_SVG, @@ -289,6 +296,7 @@ function main() { path.join(ICON_DIR, "icon-192x192-maskable.png"), PWA_MASKABLE_GLYPH_SCALE, BASE_GLYPH_COLOR, + "square", ); renderIcon( command, @@ -298,6 +306,7 @@ function main() { path.join(ICON_DIR, "icon-512x512-maskable.png"), PWA_MASKABLE_GLYPH_SCALE, BASE_GLYPH_COLOR, + "square", ); renderIcon( @@ -308,6 +317,7 @@ function main() { path.join(ICON_DIR, "icon-192x192-maskable-transparent.png"), PWA_MASKABLE_GLYPH_SCALE, BASE_GLYPH_COLOR, + "square", ); renderIcon( command, @@ -317,6 +327,7 @@ function main() { path.join(ICON_DIR, "icon-512x512-maskable-transparent.png"), PWA_MASKABLE_GLYPH_SCALE, BASE_GLYPH_COLOR, + "square", ); renderIcon( command, @@ -344,6 +355,7 @@ function main() { path.join(ICON_DIR, "icon-192x192-maskable-dark.png"), PWA_MASKABLE_GLYPH_SCALE, WHITE_BG, + "square", ); renderIcon( command, @@ -353,6 +365,7 @@ function main() { path.join(ICON_DIR, "icon-512x512-maskable-dark.png"), PWA_MASKABLE_GLYPH_SCALE, WHITE_BG, + "square", ); copyAlias( diff --git a/static/icons/site.webmanifest b/static/icons/site.webmanifest index 2b22b06..7d1a901 100644 --- a/static/icons/site.webmanifest +++ b/static/icons/site.webmanifest @@ -23,25 +23,25 @@ "related_applications": [], "icons": [ { - "src": "/icons/icon-192x192.png", + "src": "/icons/icon-192x192-maskable.png?v=1200", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/icons/icon-512x512.png", + "src": "/icons/icon-512x512-maskable.png?v=1200", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/icons/icon-192x192-maskable.png", + "src": "/icons/icon-192x192-maskable.png?v=1200", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/icons/icon-512x512-maskable.png", + "src": "/icons/icon-512x512-maskable.png?v=1200", "sizes": "512x512", "type": "image/png", "purpose": "maskable" @@ -71,7 +71,7 @@ "url": "/blog/?source=pwa", "icons": [ { - "src": "/icons/icon-192x192.png", + "src": "/icons/icon-192x192-maskable.png?v=1200", "sizes": "192x192", "type": "image/png", "purpose": "any" @@ -85,7 +85,7 @@ "url": "/about/?source=pwa", "icons": [ { - "src": "/icons/icon-192x192.png", + "src": "/icons/icon-192x192-maskable.png?v=1200", "sizes": "192x192", "type": "image/png", "purpose": "any"