@mundogamernetwork/shared-ui 1.16.4 → 1.16.6

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.
@@ -60,9 +60,35 @@ const handleToggleOnline = () => {
60
60
  <template>
61
61
  <div ref="refDiv" :class="{ 'ui-config-user': true, 'signed-in': signedIn }">
62
62
  <div ref="wrapper" class="ui-config-user__content">
63
- <MgLoginRegisterMenu v-if="!signedIn" :dropdown="false" />
64
-
65
- <div v-else class="signed-in-menu">
63
+ <!--
64
+ This MUST render a different root tag than MgLoginRegisterMenu's
65
+ own root (a <div>) a plain :key difference is NOT enough here.
66
+ signedIn always starts false and flips true only after the async
67
+ /auth/user check resolves (SSR never has the session, so every
68
+ load — not just a race — renders guest first, then the real
69
+ state client-side). When both branches are <div>s, whatever
70
+ code path handles that guest→signed-in swap (traced to Vue's
71
+ hydration-mismatch recovery, not the plain runtime v-if/v-else
72
+ patch — a distinct :key on each branch demonstrably did NOT
73
+ stop it) reuses the existing DOM node instead of replacing it:
74
+ the node keeps MgLoginRegisterMenu's own class
75
+ ("mg-login-register") and scope attribute while only its
76
+ children get patched to the signed-in markup. Result: the
77
+ signed-in content renders styled by the GUEST component's CSS
78
+ (340px guest width instead of 240px, and .username/
79
+ .btn-primary-outline/.status-toggle never match their real
80
+ ".signed-in-menu" ancestor selector, so they fall through to
81
+ unstyled/inherited color instead of var(--active) — text goes
82
+ near-invisible on any host whose global default text color is
83
+ dark, e.g. jobs-frontend's Bootstrap default). A genuinely
84
+ different tag (section, not div) forces a real replace — this
85
+ was verified against a live reproduction, not just reasoned
86
+ about; do not "simplify" this back to <div> without re-testing
87
+ the actual guest→signed-in transition, not just a hard reload.
88
+ -->
89
+ <MgLoginRegisterMenu v-if="!signedIn" key="guest" :dropdown="false" />
90
+
91
+ <section v-else key="signed-in" class="signed-in-menu">
66
92
  <div class="row info-row ps-0">
67
93
  <div class="col-auto px-3">
68
94
  <div v-if="!user?.avatar_url" class="no-avatar-wrapper">
@@ -130,7 +156,7 @@ const handleToggleOnline = () => {
130
156
  {{ $t("account_menu.buttons.logout") }}
131
157
  </button>
132
158
  </div>
133
- </div>
159
+ </section>
134
160
  </div>
135
161
  </div>
136
162
  </template>
@@ -1,41 +1,56 @@
1
- import { default as AuthService } from "../services/authService";
2
-
3
1
  export function useLogout() {
4
2
  const authStore = useAuthStore();
5
3
  const runtimeConfig = useRuntimeConfig();
6
4
  const accountsBaseUrl = runtimeConfig.public.mgSharedUi?.accountsBaseUrl || runtimeConfig.public.accountsBaseUrl;
5
+ // Same host login uses (see MgLoginRegisterMenu's goLogin) — the platform's
6
+ // own API, not accountsBaseUrl. That's where OAuthLogoutController's real
7
+ // /logout route lives.
8
+ const apiBase = (
9
+ (runtimeConfig.public.mgSharedUi?.apiBaseURL as string) ||
10
+ (runtimeConfig.public.apiBaseURL as string) ||
11
+ (import.meta.env.VITE_API_BASE_URL as string) ||
12
+ ""
13
+ ).replace(/\/api\/v1\/?$/, "");
7
14
 
8
15
  const performLogout = async (redirectTo?: string) => {
9
- try {
10
- await AuthService.logout();
11
- } catch {
12
- // continue with cleanup even if server call fails
13
- }
14
-
15
- // Clear auth state
16
+ // Clear client-visible auth state immediately so the UI doesn't sit in
17
+ // a signed-in state while the real server-side logout below completes.
16
18
  authStore.clearUser();
17
19
  authStore.$reset();
18
-
19
- // Clear cookies client-side
20
- if (typeof document !== "undefined") {
21
- const cookiesToClear = ["oauth_token", "browser_id", "mundo_gamer_network_session", "XSRF-TOKEN"];
22
- cookiesToClear.forEach((name) => {
23
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
24
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
25
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${window.location.hostname};`;
26
- });
27
- }
28
-
29
- // Clear localStorage auth data
30
20
  if (typeof localStorage !== "undefined") {
31
21
  ["accessToken", "userData", "userAbility"].forEach((key) => {
32
22
  localStorage.removeItem(key);
33
23
  });
34
24
  }
35
25
 
36
- // Redirect
37
26
  const target = redirectTo || `${accountsBaseUrl}/login`;
38
- window.location.href = target;
27
+
28
+ if (typeof document === "undefined" || !apiBase) {
29
+ if (typeof window !== "undefined") window.location.href = target;
30
+ return;
31
+ }
32
+
33
+ // The real session is an httpOnly `oauth_token` cookie set by the
34
+ // platform's own API — document.cookie can never read or clear an
35
+ // httpOnly cookie, on any domain. Only a real server response can clear
36
+ // it (a Set-Cookie header the browser actually honors), which means a
37
+ // genuine top-level POST navigation to that API's own /logout route,
38
+ // not an axios/fetch call whose response body a script merely inspects.
39
+ // POST is required (the route isn't registered for GET) and CSRF is
40
+ // already exempted server-side for exactly this cross-origin case.
41
+ const form = document.createElement("form");
42
+ form.method = "POST";
43
+ form.action = `${apiBase}/logout`;
44
+ form.style.display = "none";
45
+
46
+ const redirectField = document.createElement("input");
47
+ redirectField.type = "hidden";
48
+ redirectField.name = "redirect_to";
49
+ redirectField.value = target;
50
+ form.appendChild(redirectField);
51
+
52
+ document.body.appendChild(form);
53
+ form.submit();
39
54
  };
40
55
 
41
56
  return { performLogout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.16.4",
3
+ "version": "1.16.6",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",