@the-portland-company/shell 0.36.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-chrome.cjs +201 -94
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.js +26 -3
- package/dist/app-chrome.js.map +1 -1
- package/dist/{chunk-QX2UNWSE.js → chunk-LIGKGXFT.js} +97 -95
- package/dist/chunk-LIGKGXFT.js.map +1 -0
- package/dist/chunk-LL6LSJAH.js +92 -0
- package/dist/chunk-LL6LSJAH.js.map +1 -0
- package/dist/logout-signal.cjs +99 -0
- package/dist/logout-signal.cjs.map +1 -0
- package/dist/logout-signal.d.cts +56 -0
- package/dist/logout-signal.d.ts +56 -0
- package/dist/logout-signal.js +3 -0
- package/dist/logout-signal.js.map +1 -0
- package/dist/native.cjs +95 -93
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +1 -1
- package/package.json +6 -1
- package/dist/chunk-QX2UNWSE.js.map +0 -1
package/dist/app-chrome.cjs
CHANGED
|
@@ -2154,116 +2154,118 @@ function NavTopBar({
|
|
|
2154
2154
|
}
|
|
2155
2155
|
) })
|
|
2156
2156
|
] }),
|
|
2157
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 w-72 rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
|
|
2157
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 flex max-h-[calc(100vh-5rem)] w-72 flex-col overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
|
|
2158
2158
|
/* @__PURE__ */ jsxRuntime.jsx(TopBarPanelArrow, {}),
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2183
|
-
|
|
2184
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2185
|
-
|
|
2186
|
-
"
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2193
|
-
|
|
2194
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2195
|
-
|
|
2159
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: [
|
|
2160
|
+
userMenu?.myProfileHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2161
|
+
"a",
|
|
2162
|
+
{
|
|
2163
|
+
href: safeHref(userMenu.myProfileHref),
|
|
2164
|
+
className: "flex w-full items-center gap-2.5 px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
|
|
2165
|
+
children: [
|
|
2166
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProfileIcon, {}),
|
|
2167
|
+
"My Profile"
|
|
2168
|
+
]
|
|
2169
|
+
}
|
|
2170
|
+
) : null,
|
|
2171
|
+
userMenu?.organizationSettingsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2172
|
+
"a",
|
|
2173
|
+
{
|
|
2174
|
+
href: safeHref(userMenu.organizationSettingsHref),
|
|
2175
|
+
className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
|
|
2176
|
+
children: [
|
|
2177
|
+
/* @__PURE__ */ jsxRuntime.jsx(BriefcaseIcon, {}),
|
|
2178
|
+
"Organization Settings"
|
|
2179
|
+
]
|
|
2180
|
+
}
|
|
2181
|
+
) : null,
|
|
2182
|
+
userMenu?.roles && userMenu.roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
2183
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Roles & Organizations" }),
|
|
2184
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1.5", children: userMenu.roles.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-wrap items-center gap-1.5 text-sm", children: [
|
|
2185
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-md bg-[color:var(--panel)] px-2 py-0.5 text-xs font-bold uppercase tracking-wide text-[color:var(--foreground)]", children: r.role }),
|
|
2186
|
+
r.orgName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[color:var(--muted)]", children: [
|
|
2187
|
+
"in ",
|
|
2188
|
+
r.orgName
|
|
2189
|
+
] }) : null
|
|
2190
|
+
] }, `${r.role}-${i}`)) })
|
|
2191
|
+
] }) : null,
|
|
2192
|
+
userMenu?.accounts && userMenu.accounts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
2193
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Accounts" }),
|
|
2194
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: userMenu.accounts.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2.5", children: [
|
|
2195
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2196
|
+
"button",
|
|
2197
|
+
{
|
|
2198
|
+
type: "button",
|
|
2199
|
+
onClick: () => {
|
|
2200
|
+
if (!a.active) userMenu.onSwitchAccount?.(a.id);
|
|
2201
|
+
},
|
|
2202
|
+
className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
|
|
2203
|
+
children: [
|
|
2204
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-[11px] font-bold text-white", children: a.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: a.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : (a.name ?? a.email ?? "?").slice(0, 2).toUpperCase() }),
|
|
2205
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col leading-tight", children: [
|
|
2206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2207
|
+
"span",
|
|
2208
|
+
{
|
|
2209
|
+
className: [
|
|
2210
|
+
"truncate text-sm font-semibold",
|
|
2211
|
+
a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
|
|
2212
|
+
].join(" "),
|
|
2213
|
+
children: a.name ?? a.email ?? a.id.slice(0, 8)
|
|
2214
|
+
}
|
|
2215
|
+
),
|
|
2216
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
|
|
2217
|
+
] })
|
|
2218
|
+
]
|
|
2219
|
+
}
|
|
2220
|
+
),
|
|
2221
|
+
!a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2222
|
+
"button",
|
|
2223
|
+
{
|
|
2224
|
+
type: "button",
|
|
2225
|
+
"aria-label": `Remove ${a.name ?? a.email ?? "account"}`,
|
|
2226
|
+
onClick: () => userMenu.onRemoveAccount?.(a.id),
|
|
2227
|
+
className: "shrink-0 rounded p-1 text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2228
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {})
|
|
2229
|
+
}
|
|
2230
|
+
) : null
|
|
2231
|
+
] }, a.id)) }),
|
|
2232
|
+
userMenu.addAccountHref || userMenu.onAddAccount ? userMenu.addAccountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2233
|
+
"a",
|
|
2196
2234
|
{
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (!a.active) userMenu.onSwitchAccount?.(a.id);
|
|
2200
|
-
},
|
|
2201
|
-
className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
|
|
2235
|
+
href: safeHref(userMenu.addAccountHref),
|
|
2236
|
+
className: "mt-1.5 flex items-center gap-2.5 rounded-lg px-1 py-1.5 text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2202
2237
|
children: [
|
|
2203
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2204
|
-
|
|
2205
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2206
|
-
"span",
|
|
2207
|
-
{
|
|
2208
|
-
className: [
|
|
2209
|
-
"truncate text-sm font-semibold",
|
|
2210
|
-
a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
|
|
2211
|
-
].join(" "),
|
|
2212
|
-
children: a.name ?? a.email ?? a.id.slice(0, 8)
|
|
2213
|
-
}
|
|
2214
|
-
),
|
|
2215
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
|
|
2216
|
-
] })
|
|
2238
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2239
|
+
"Add Account"
|
|
2217
2240
|
]
|
|
2218
2241
|
}
|
|
2219
|
-
)
|
|
2220
|
-
!a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2242
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2221
2243
|
"button",
|
|
2222
2244
|
{
|
|
2223
2245
|
type: "button",
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2246
|
+
onClick: userMenu.onAddAccount,
|
|
2247
|
+
className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2248
|
+
children: [
|
|
2249
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2250
|
+
"Add Account"
|
|
2251
|
+
]
|
|
2228
2252
|
}
|
|
2229
2253
|
) : null
|
|
2230
|
-
] }
|
|
2231
|
-
userMenu
|
|
2232
|
-
|
|
2254
|
+
] }) : null,
|
|
2255
|
+
userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2256
|
+
NavThemeControl,
|
|
2233
2257
|
{
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
children: [
|
|
2237
|
-
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2238
|
-
"Add Account"
|
|
2239
|
-
]
|
|
2258
|
+
value: userMenu.theme.value,
|
|
2259
|
+
onChange: userMenu.theme.onChange
|
|
2240
2260
|
}
|
|
2241
|
-
) :
|
|
2242
|
-
|
|
2243
|
-
{
|
|
2244
|
-
type: "button",
|
|
2245
|
-
onClick: userMenu.onAddAccount,
|
|
2246
|
-
className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2247
|
-
children: [
|
|
2248
|
-
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2249
|
-
"Add Account"
|
|
2250
|
-
]
|
|
2251
|
-
}
|
|
2252
|
-
) : null
|
|
2253
|
-
] }) : null,
|
|
2254
|
-
userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2255
|
-
NavThemeControl,
|
|
2256
|
-
{
|
|
2257
|
-
value: userMenu.theme.value,
|
|
2258
|
-
onChange: userMenu.theme.onChange
|
|
2259
|
-
}
|
|
2260
|
-
) }) : null,
|
|
2261
|
+
) }) : null
|
|
2262
|
+
] }),
|
|
2261
2263
|
onSignOut ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2262
2264
|
"button",
|
|
2263
2265
|
{
|
|
2264
2266
|
type: "button",
|
|
2265
2267
|
onClick: onSignOut,
|
|
2266
|
-
className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
|
|
2268
|
+
className: "flex w-full shrink-0 items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
|
|
2267
2269
|
children: [
|
|
2268
2270
|
/* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {}),
|
|
2269
2271
|
"Sign out"
|
|
@@ -2602,6 +2604,89 @@ function resolveIdentity(input) {
|
|
|
2602
2604
|
loaded: input.loaded ?? false
|
|
2603
2605
|
};
|
|
2604
2606
|
}
|
|
2607
|
+
|
|
2608
|
+
// src/logout-signal.ts
|
|
2609
|
+
var LOGOUT_COOKIE = "politogy_logout_at";
|
|
2610
|
+
var LOGOUT_CHANNEL = "politogy-logout";
|
|
2611
|
+
var POLL_MS = 1e3;
|
|
2612
|
+
function parseTimestamp(value) {
|
|
2613
|
+
if (!value) return null;
|
|
2614
|
+
const n = Number(value);
|
|
2615
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
2616
|
+
}
|
|
2617
|
+
function isSessionRevoked(logoutCookieValue, authTime) {
|
|
2618
|
+
const loggedOutAt = parseTimestamp(logoutCookieValue);
|
|
2619
|
+
if (loggedOutAt === null) return false;
|
|
2620
|
+
if (authTime == null || !Number.isFinite(authTime) || authTime <= 0) return true;
|
|
2621
|
+
const authMs = authTime < 1e12 ? authTime * 1e3 : authTime;
|
|
2622
|
+
return authMs < loggedOutAt;
|
|
2623
|
+
}
|
|
2624
|
+
function readLogoutCookie() {
|
|
2625
|
+
if (typeof document === "undefined") return null;
|
|
2626
|
+
try {
|
|
2627
|
+
const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${LOGOUT_COOKIE}=([^;]+)`));
|
|
2628
|
+
return parseTimestamp(m ? decodeURIComponent(m[1]) : null);
|
|
2629
|
+
} catch {
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
function broadcastLogout(at = Date.now()) {
|
|
2634
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2635
|
+
const host = window.location.hostname;
|
|
2636
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
2637
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
2638
|
+
document.cookie = `${LOGOUT_COOKIE}=${at}; Path=/; Max-Age=2592000; SameSite=Lax${domainAttr}${secure}`;
|
|
2639
|
+
try {
|
|
2640
|
+
new BroadcastChannel(LOGOUT_CHANNEL).postMessage(at);
|
|
2641
|
+
} catch {
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
function watchLogout(onLogout, options = {}) {
|
|
2645
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
2646
|
+
return () => {
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
const { authTime = null, intervalMs = POLL_MS } = options;
|
|
2650
|
+
const baseline = readLogoutCookie() ?? 0;
|
|
2651
|
+
let done = false;
|
|
2652
|
+
const fire = (at) => {
|
|
2653
|
+
if (done) return;
|
|
2654
|
+
if (at <= baseline) return;
|
|
2655
|
+
if (authTime != null && !isSessionRevoked(String(at), authTime)) return;
|
|
2656
|
+
done = true;
|
|
2657
|
+
cleanup();
|
|
2658
|
+
onLogout();
|
|
2659
|
+
};
|
|
2660
|
+
const check = () => {
|
|
2661
|
+
const at = readLogoutCookie();
|
|
2662
|
+
if (at !== null) fire(at);
|
|
2663
|
+
};
|
|
2664
|
+
let channel = null;
|
|
2665
|
+
try {
|
|
2666
|
+
channel = new BroadcastChannel(LOGOUT_CHANNEL);
|
|
2667
|
+
channel.onmessage = (event) => {
|
|
2668
|
+
const at = typeof event.data === "number" ? event.data : readLogoutCookie();
|
|
2669
|
+
if (at !== null) fire(at);
|
|
2670
|
+
};
|
|
2671
|
+
} catch {
|
|
2672
|
+
channel = null;
|
|
2673
|
+
}
|
|
2674
|
+
const timer = window.setInterval(check, intervalMs);
|
|
2675
|
+
window.addEventListener("visibilitychange", check);
|
|
2676
|
+
window.addEventListener("focus", check);
|
|
2677
|
+
window.addEventListener("pageshow", check);
|
|
2678
|
+
function cleanup() {
|
|
2679
|
+
window.clearInterval(timer);
|
|
2680
|
+
window.removeEventListener("visibilitychange", check);
|
|
2681
|
+
window.removeEventListener("focus", check);
|
|
2682
|
+
window.removeEventListener("pageshow", check);
|
|
2683
|
+
try {
|
|
2684
|
+
channel?.close();
|
|
2685
|
+
} catch {
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return cleanup;
|
|
2689
|
+
}
|
|
2605
2690
|
var CORE_APP = "vrm";
|
|
2606
2691
|
var POLITOGY_APPS = {
|
|
2607
2692
|
vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
|
|
@@ -2678,6 +2763,28 @@ function FrameBody({
|
|
|
2678
2763
|
}) {
|
|
2679
2764
|
const meta = POLITOGY_APPS[app];
|
|
2680
2765
|
const chrome = useChromeState();
|
|
2766
|
+
const signOutRef = react.useRef(onSignOut);
|
|
2767
|
+
signOutRef.current = onSignOut;
|
|
2768
|
+
const signingOutRef = react.useRef(false);
|
|
2769
|
+
const handleSignOut = react.useCallback(() => {
|
|
2770
|
+
signingOutRef.current = true;
|
|
2771
|
+
broadcastLogout();
|
|
2772
|
+
signOutRef.current();
|
|
2773
|
+
}, []);
|
|
2774
|
+
react.useEffect(
|
|
2775
|
+
() => (
|
|
2776
|
+
// A tab that learns of a sign-out elsewhere ends its OWN session properly
|
|
2777
|
+
// (clear cookie, RP-initiated logout at the IdP) rather than merely
|
|
2778
|
+
// navigating away — otherwise its session cookie would outlive the
|
|
2779
|
+
// sign-out and a later visit would silently resume it. No re-broadcast:
|
|
2780
|
+
// the signal that woke us is already on record for everyone.
|
|
2781
|
+
watchLogout(() => {
|
|
2782
|
+
if (signingOutRef.current) return;
|
|
2783
|
+
signOutRef.current();
|
|
2784
|
+
})
|
|
2785
|
+
),
|
|
2786
|
+
[]
|
|
2787
|
+
);
|
|
2681
2788
|
const core = app === CORE_APP ? data?.core : void 0;
|
|
2682
2789
|
const identity = useChromeIdentity({ userName: session.userName, email: session.email });
|
|
2683
2790
|
const derivedUserMenu = useChromeUserMenu({
|
|
@@ -2760,7 +2867,7 @@ function FrameBody({
|
|
|
2760
2867
|
role: session.role
|
|
2761
2868
|
},
|
|
2762
2869
|
userMenu,
|
|
2763
|
-
onSignOut,
|
|
2870
|
+
onSignOut: handleSignOut,
|
|
2764
2871
|
// The action cluster is IDENTICAL everywhere — core included. Page
|
|
2765
2872
|
// actions sit BEFORE it, never inside it, so the Alerts bell keeps its
|
|
2766
2873
|
// required position: farthest right, immediately before My Account.
|