authscape 1.0.710 → 1.0.714

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/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
 
3
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
@@ -30,6 +29,50 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
30
29
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
31
30
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
32
31
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
32
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
33
+ // ---- optional: import your cookie util if not global ----
34
+ // import { setCookie } from "cookies-next";
35
+ // import { apiService } from "@/services/api"; // wherever yours lives
36
+
37
+ // Decorate a user object with role/permission helpers (idempotent)
38
+ function ensureUserHelpers(u) {
39
+ if (!u || _typeof(u) !== "object") return u;
40
+
41
+ // Avoid redefining on every call
42
+ if (typeof u.hasRole === "function" && typeof u.hasRoleId === "function" && typeof u.hasPermission === "function") {
43
+ return u;
44
+ }
45
+ var rolesArr = Array.isArray(u.roles) ? u.roles : [];
46
+ var permsArr = Array.isArray(u.permissions) ? u.permissions : [];
47
+
48
+ // defineProperty keeps them non-enumerable
49
+ Object.defineProperty(u, "hasRole", {
50
+ value: function hasRole(name) {
51
+ if (!name) return false;
52
+ return rolesArr.some(function (r) {
53
+ return (r === null || r === void 0 ? void 0 : r.name) === name;
54
+ });
55
+ },
56
+ writable: false
57
+ });
58
+ Object.defineProperty(u, "hasRoleId", {
59
+ value: function hasRoleId(id) {
60
+ if (id === undefined || id === null) return false;
61
+ return rolesArr.some(function (r) {
62
+ return (r === null || r === void 0 ? void 0 : r.id) === id;
63
+ });
64
+ },
65
+ writable: false
66
+ });
67
+ Object.defineProperty(u, "hasPermission", {
68
+ value: function hasPermission(name) {
69
+ if (!name) return false;
70
+ return permsArr.includes(name);
71
+ },
72
+ writable: false
73
+ });
74
+ return u;
75
+ }
33
76
  function AuthScapeApp(_ref) {
34
77
  var Component = _ref.Component,
35
78
  layout = _ref.layout,
@@ -60,95 +103,103 @@ function AuthScapeApp(_ref) {
60
103
  var queryCodeUsed = (0, _react.useRef)(null);
61
104
  var ga4React = (0, _react.useRef)(null);
62
105
  var searchParams = (0, _navigation.useSearchParams)();
63
- var queryCode = searchParams.get('code');
106
+ var queryCode = searchParams.get("code");
64
107
  var pathname = (0, _navigation.usePathname)();
65
108
 
66
- // ---------- PKCE Sign-in ----------
109
+ // ----- PKCE Sign-in (browser-only) -----
67
110
  var signInValidator = /*#__PURE__*/function () {
68
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(queryCode) {
69
- var codeVerifier, headers, queryString, response, domainHost, redirectUri;
111
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(codeFromQuery) {
112
+ var codeVerifier, headers, body, response, domainHost, redirectUri;
70
113
  return _regeneratorRuntime().wrap(function _callee$(_context) {
71
114
  while (1) switch (_context.prev = _context.next) {
72
115
  case 0:
73
- if (!(queryCodeUsed.current === queryCode)) {
116
+ if (!(queryCodeUsed.current === codeFromQuery)) {
74
117
  _context.next = 2;
75
118
  break;
76
119
  }
77
120
  return _context.abrupt("return");
78
121
  case 2:
79
- queryCodeUsed.current = queryCode;
122
+ queryCodeUsed.current = codeFromQuery;
123
+ if (!(typeof window === "undefined")) {
124
+ _context.next = 5;
125
+ break;
126
+ }
127
+ return _context.abrupt("return");
128
+ case 5:
80
129
  codeVerifier = window.localStorage.getItem("verifier");
81
- if (!(!queryCode || !codeVerifier)) {
82
- _context.next = 6;
130
+ if (!(!codeFromQuery || !codeVerifier)) {
131
+ _context.next = 8;
83
132
  break;
84
133
  }
85
134
  return _context.abrupt("return");
86
- case 6:
135
+ case 8:
87
136
  headers = {
88
- 'Content-Type': 'application/x-www-form-urlencoded'
137
+ "Content-Type": "application/x-www-form-urlencoded"
89
138
  };
90
- queryString = _queryString["default"].stringify({
91
- code: queryCode,
139
+ body = _queryString["default"].stringify({
140
+ code: codeFromQuery,
92
141
  grant_type: "authorization_code",
93
142
  redirect_uri: window.location.origin + "/signin-oidc",
94
143
  client_id: process.env.client_id,
95
144
  client_secret: process.env.client_secret,
96
145
  code_verifier: codeVerifier
97
146
  });
98
- _context.prev = 8;
99
- _context.next = 11;
100
- return _axios["default"].post(process.env.authorityUri + '/connect/token', queryString, {
147
+ _context.prev = 10;
148
+ _context.next = 13;
149
+ return _axios["default"].post(process.env.authorityUri + "/connect/token", body, {
101
150
  headers: headers
102
151
  });
103
- case 11:
152
+ case 13:
104
153
  response = _context.sent;
105
- domainHost = window.location.hostname.split('.').slice(-2).join('.');
154
+ domainHost = window.location.hostname.split(".").slice(-2).join(".");
106
155
  window.localStorage.removeItem("verifier");
107
- _context.next = 16;
108
- return setCookie('access_token', response.data.access_token, {
109
- maxAge: 60 * 60 * 24 * 365,
110
- path: '/',
111
- domain: domainHost,
112
- secure: true
113
- });
114
- case 16:
156
+
157
+ // NOTE: replace setCookie below with your implementation if different
115
158
  _context.next = 18;
116
- return setCookie('expires_in', response.data.expires_in, {
159
+ return setCookie("access_token", response.data.access_token, {
117
160
  maxAge: 60 * 60 * 24 * 365,
118
- path: '/',
161
+ path: "/",
119
162
  domain: domainHost,
120
163
  secure: true
121
164
  });
122
165
  case 18:
123
166
  _context.next = 20;
124
- return setCookie('refresh_token', response.data.refresh_token, {
167
+ return setCookie("expires_in", response.data.expires_in, {
125
168
  maxAge: 60 * 60 * 24 * 365,
126
- path: '/',
169
+ path: "/",
127
170
  domain: domainHost,
128
171
  secure: true
129
172
  });
130
173
  case 20:
131
- redirectUri = localStorage.getItem("redirectUri");
132
- localStorage.clear();
174
+ _context.next = 22;
175
+ return setCookie("refresh_token", response.data.refresh_token, {
176
+ maxAge: 60 * 60 * 24 * 365,
177
+ path: "/",
178
+ domain: domainHost,
179
+ secure: true
180
+ });
181
+ case 22:
182
+ redirectUri = window.localStorage.getItem("redirectUri");
183
+ window.localStorage.clear();
133
184
  window.location.href = redirectUri || "/";
134
- _context.next = 28;
185
+ _context.next = 30;
135
186
  break;
136
- case 25:
137
- _context.prev = 25;
138
- _context.t0 = _context["catch"](8);
187
+ case 27:
188
+ _context.prev = 27;
189
+ _context.t0 = _context["catch"](10);
139
190
  console.error("PKCE sign-in failed", _context.t0);
140
- case 28:
191
+ case 30:
141
192
  case "end":
142
193
  return _context.stop();
143
194
  }
144
- }, _callee, null, [[8, 25]]);
195
+ }, _callee, null, [[10, 27]]);
145
196
  }));
146
197
  return function signInValidator(_x) {
147
198
  return _ref2.apply(this, arguments);
148
199
  };
149
200
  }();
150
201
 
151
- // ---------- GA + Clarity ----------
202
+ // ----- GA + Clarity -----
152
203
  function initGA(_x2) {
153
204
  return _initGA.apply(this, arguments);
154
205
  }
@@ -157,7 +208,7 @@ function AuthScapeApp(_ref) {
157
208
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
158
209
  while (1) switch (_context2.prev = _context2.next) {
159
210
  case 0:
160
- if (!(!_ga4React["default"].isInitialized() && G && typeof window !== "undefined")) {
211
+ if (!(typeof window !== "undefined" && !_ga4React["default"].isInitialized() && G)) {
161
212
  _context2.next = 10;
162
213
  break;
163
214
  }
@@ -183,13 +234,13 @@ function AuthScapeApp(_ref) {
183
234
  return _initGA.apply(this, arguments);
184
235
  }
185
236
  var logEvent = function logEvent(category, action, label) {
186
- if (ga4React.current) {
187
- ga4React.current.event(action, label, category);
188
- }
237
+ if (ga4React.current) ga4React.current.event(action, label, category);
238
+ // your DB analytics can go here if desired
189
239
  };
190
240
  var databaseDrivenPageView = function databaseDrivenPageView(pathName) {
191
241
  var _signedInUser$current, _signedInUser$current2, _signedInUser$current3;
192
242
  if (process.env.enableDatabaseAnalytics !== "true") return;
243
+ if (typeof window === "undefined") return;
193
244
  if (pathName === "/signin-oidc") return;
194
245
  var host = window.location.protocol + "//" + window.location.host;
195
246
  apiService().post("/Analytics/PageView", {
@@ -201,16 +252,23 @@ function AuthScapeApp(_ref) {
201
252
  });
202
253
  };
203
254
 
204
- // ---------- Auth + Tracking Init ----------
255
+ // ----- Auth init (runs once) -----
205
256
  (0, _react.useEffect)(function () {
206
257
  if (queryCode) {
207
258
  signInValidator(queryCode);
208
- } else if (!loadingAuth.current) {
259
+ return;
260
+ }
261
+ if (!loadingAuth.current) {
209
262
  loadingAuth.current = true;
210
263
  if (enableAuth) {
211
264
  apiService().GetCurrentUser().then(function (usr) {
212
- signedInUser.current = usr;
213
- setSignedInUserState(usr);
265
+ signedInUser.current = ensureUserHelpers(usr);
266
+ setSignedInUserState(signedInUser.current);
267
+ setFrontEndLoadedState(true);
268
+ })["catch"](function () {
269
+ // no user / anonymous
270
+ signedInUser.current = null;
271
+ setSignedInUserState(null);
214
272
  setFrontEndLoadedState(true);
215
273
  });
216
274
  } else {
@@ -219,9 +277,9 @@ function AuthScapeApp(_ref) {
219
277
  }
220
278
  }, [queryCode, enableAuth]);
221
279
 
222
- // ---------- Analytics Init ----------
280
+ // ----- Analytics init -----
223
281
  (0, _react.useEffect)(function () {
224
- if (!frontEndLoadedState) return;
282
+ if (!frontEndLoadedState || typeof window === "undefined") return;
225
283
  if (pageProps.googleAnalytics4Code) {
226
284
  initGA(pageProps.googleAnalytics4Code);
227
285
  } else if (process.env.googleAnalytics4) {
@@ -233,44 +291,50 @@ function AuthScapeApp(_ref) {
233
291
  _reactMicrosoftClarity.clarity.init(process.env.microsoftClarityTrackingCode);
234
292
  }
235
293
  databaseDrivenPageView(window.location.pathname);
236
- _router["default"].events.on('routeChangeComplete', function (url) {
294
+ var handler = function handler(url) {
237
295
  var _ga4React$current;
238
296
  (_ga4React$current = ga4React.current) === null || _ga4React$current === void 0 || _ga4React$current.pageview(url);
239
297
  databaseDrivenPageView(url);
240
- });
241
- }, [frontEndLoadedState]);
298
+ };
299
+ _router["default"].events.on("routeChangeComplete", handler);
300
+ return function () {
301
+ return _router["default"].events.off("routeChangeComplete", handler);
302
+ };
303
+ }, [frontEndLoadedState, pageProps.googleAnalytics4Code, pageProps.microsoftClarityCode]);
242
304
 
243
- // ---------- Enforce Login ----------
305
+ // ----- Enforce login (client) -----
244
306
  (0, _react.useEffect)(function () {
245
307
  if (enforceLoggedIn && pathname !== "/signin-oidc" && frontEndLoadedState && !signedInUserState) {
246
308
  (0, _authscape.authService)().login();
247
309
  }
248
310
  }, [signedInUserState, enforceLoggedIn, frontEndLoadedState, pathname]);
249
- var GetSignedInUser = function GetSignedInUser() {
250
- return signedInUser.current;
251
- };
252
- var useStore = (0, _zustand.create)(function (set) {
311
+
312
+ // Stable getter for current user (with helpers)
313
+ var currentUser = (0, _react.useMemo)(function () {
314
+ return ensureUserHelpers(signedInUser.current);
315
+ }, [signedInUserState]);
316
+ var useStore = (0, _zustand.create)(function () {
253
317
  return store;
254
318
  });
255
319
 
256
- // ---------- Render ----------
320
+ // ----- Render (SSR-safe; always output page so <title> is visible) -----
257
321
  var pageContent = layout ? layout({
258
322
  children: /*#__PURE__*/_react["default"].createElement(Component, _extends({}, pageProps, {
259
- currentUser: GetSignedInUser(),
323
+ currentUser: currentUser,
260
324
  loadedUser: frontEndLoadedState,
261
325
  setIsLoading: setIsLoadingShow,
262
326
  logEvent: logEvent,
263
327
  store: useStore,
264
328
  toast: _reactToastify.toast
265
329
  })),
266
- currentUser: GetSignedInUser(),
330
+ currentUser: currentUser,
267
331
  setIsLoading: setIsLoadingShow,
268
332
  logEvent: logEvent,
269
333
  toast: _reactToastify.toast,
270
334
  store: useStore,
271
335
  pageProps: pageProps
272
336
  }) : /*#__PURE__*/_react["default"].createElement(Component, _extends({}, pageProps, {
273
- currentUser: GetSignedInUser(),
337
+ currentUser: currentUser,
274
338
  loadedUser: frontEndLoadedState,
275
339
  setIsLoading: setIsLoadingShow,
276
340
  logEvent: logEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "authscape",
3
- "version": "1.0.710",
3
+ "version": "1.0.714",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,15 +1,61 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import { ToastContainer, toast } from 'react-toastify';
3
- import { ThemeProvider } from '@mui/material/styles';
1
+ import React, { useState, useRef, useEffect, useMemo } from "react";
2
+ import { ToastContainer, toast } from "react-toastify";
3
+ import { ThemeProvider } from "@mui/material/styles";
4
4
  import Head from "next/head";
5
- import { useSearchParams, usePathname } from 'next/navigation';
6
- import axios from 'axios';
5
+ import { useSearchParams, usePathname } from "next/navigation";
6
+ import axios from "axios";
7
7
  import querystring from "query-string";
8
- import Router from 'next/router';
9
- import GA4React from 'ga-4-react';
10
- import { create } from 'zustand';
11
- import { clarity } from 'react-microsoft-clarity';
12
- import { authService } from 'authscape';
8
+ import Router from "next/router";
9
+ import GA4React from "ga-4-react";
10
+ import { create } from "zustand";
11
+ import { clarity } from "react-microsoft-clarity";
12
+ import { authService } from "authscape";
13
+
14
+ // ---- optional: import your cookie util if not global ----
15
+ // import { setCookie } from "cookies-next";
16
+ // import { apiService } from "@/services/api"; // wherever yours lives
17
+
18
+ // Decorate a user object with role/permission helpers (idempotent)
19
+ function ensureUserHelpers(u) {
20
+ if (!u || typeof u !== "object") return u;
21
+
22
+ // Avoid redefining on every call
23
+ if (typeof u.hasRole === "function" &&
24
+ typeof u.hasRoleId === "function" &&
25
+ typeof u.hasPermission === "function") {
26
+ return u;
27
+ }
28
+
29
+ const rolesArr = Array.isArray(u.roles) ? u.roles : [];
30
+ const permsArr = Array.isArray(u.permissions) ? u.permissions : [];
31
+
32
+ // defineProperty keeps them non-enumerable
33
+ Object.defineProperty(u, "hasRole", {
34
+ value: function hasRole(name) {
35
+ if (!name) return false;
36
+ return rolesArr.some(r => r?.name === name);
37
+ },
38
+ writable: false
39
+ });
40
+
41
+ Object.defineProperty(u, "hasRoleId", {
42
+ value: function hasRoleId(id) {
43
+ if (id === undefined || id === null) return false;
44
+ return rolesArr.some(r => r?.id === id);
45
+ },
46
+ writable: false
47
+ });
48
+
49
+ Object.defineProperty(u, "hasPermission", {
50
+ value: function hasPermission(name) {
51
+ if (!name) return false;
52
+ return permsArr.includes(name);
53
+ },
54
+ writable: false
55
+ });
56
+
57
+ return u;
58
+ }
13
59
 
14
60
  export function AuthScapeApp({
15
61
  Component,
@@ -19,7 +65,7 @@ export function AuthScapeApp({
19
65
  muiTheme = {},
20
66
  store = {},
21
67
  enforceLoggedIn = false,
22
- enableAuth = true
68
+ enableAuth = true,
23
69
  }) {
24
70
  const [frontEndLoadedState, setFrontEndLoadedState] = useState(false);
25
71
  const [isLoadingShow, setIsLoadingShow] = useState(false);
@@ -31,64 +77,72 @@ export function AuthScapeApp({
31
77
  const ga4React = useRef(null);
32
78
 
33
79
  const searchParams = useSearchParams();
34
- const queryCode = searchParams.get('code');
80
+ const queryCode = searchParams.get("code");
35
81
  const pathname = usePathname();
36
82
 
37
- // ---------- PKCE Sign-in ----------
38
- const signInValidator = async (queryCode) => {
39
- if (queryCodeUsed.current === queryCode) return;
40
- queryCodeUsed.current = queryCode;
83
+ // ----- PKCE Sign-in (browser-only) -----
84
+ const signInValidator = async (codeFromQuery) => {
85
+ if (queryCodeUsed.current === codeFromQuery) return;
86
+ queryCodeUsed.current = codeFromQuery;
87
+
88
+ if (typeof window === "undefined") return;
41
89
 
42
90
  const codeVerifier = window.localStorage.getItem("verifier");
43
- if (!queryCode || !codeVerifier) return;
91
+ if (!codeFromQuery || !codeVerifier) return;
44
92
 
45
- const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
93
+ const headers = { "Content-Type": "application/x-www-form-urlencoded" };
46
94
 
47
- const queryString = querystring.stringify({
48
- code: queryCode,
95
+ const body = querystring.stringify({
96
+ code: codeFromQuery,
49
97
  grant_type: "authorization_code",
50
98
  redirect_uri: window.location.origin + "/signin-oidc",
51
99
  client_id: process.env.client_id,
52
100
  client_secret: process.env.client_secret,
53
- code_verifier: codeVerifier
101
+ code_verifier: codeVerifier,
54
102
  });
55
103
 
56
104
  try {
57
- const response = await axios.post(process.env.authorityUri + '/connect/token', queryString, { headers });
58
- const domainHost = window.location.hostname.split('.').slice(-2).join('.');
105
+ const response = await axios.post(
106
+ process.env.authorityUri + "/connect/token",
107
+ body,
108
+ { headers }
109
+ );
110
+
111
+ const domainHost = window.location.hostname.split(".").slice(-2).join(".");
59
112
 
60
113
  window.localStorage.removeItem("verifier");
61
114
 
62
- await setCookie('access_token', response.data.access_token, {
115
+ // NOTE: replace setCookie below with your implementation if different
116
+ await setCookie("access_token", response.data.access_token, {
63
117
  maxAge: 60 * 60 * 24 * 365,
64
- path: '/',
118
+ path: "/",
65
119
  domain: domainHost,
66
- secure: true
120
+ secure: true,
67
121
  });
68
- await setCookie('expires_in', response.data.expires_in, {
122
+ await setCookie("expires_in", response.data.expires_in, {
69
123
  maxAge: 60 * 60 * 24 * 365,
70
- path: '/',
124
+ path: "/",
71
125
  domain: domainHost,
72
- secure: true
126
+ secure: true,
73
127
  });
74
- await setCookie('refresh_token', response.data.refresh_token, {
128
+ await setCookie("refresh_token", response.data.refresh_token, {
75
129
  maxAge: 60 * 60 * 24 * 365,
76
- path: '/',
130
+ path: "/",
77
131
  domain: domainHost,
78
- secure: true
132
+ secure: true,
79
133
  });
80
134
 
81
- const redirectUri = localStorage.getItem("redirectUri");
82
- localStorage.clear();
135
+ const redirectUri = window.localStorage.getItem("redirectUri");
136
+ window.localStorage.clear();
83
137
  window.location.href = redirectUri || "/";
84
138
  } catch (exp) {
85
139
  console.error("PKCE sign-in failed", exp);
86
140
  }
87
141
  };
88
142
 
89
- // ---------- GA + Clarity ----------
143
+ // ----- GA + Clarity -----
90
144
  async function initGA(G) {
91
- if (!GA4React.isInitialized() && G && typeof window !== "undefined") {
145
+ if (typeof window !== "undefined" && !GA4React.isInitialized() && G) {
92
146
  ga4React.current = new GA4React(G, { debug_mode: !process.env.production });
93
147
  try {
94
148
  await ga4React.current.initialize();
@@ -99,13 +153,13 @@ export function AuthScapeApp({
99
153
  }
100
154
 
101
155
  const logEvent = (category, action, label) => {
102
- if (ga4React.current) {
103
- ga4React.current.event(action, label, category);
104
- }
156
+ if (ga4React.current) ga4React.current.event(action, label, category);
157
+ // your DB analytics can go here if desired
105
158
  };
106
159
 
107
160
  const databaseDrivenPageView = (pathName) => {
108
161
  if (process.env.enableDatabaseAnalytics !== "true") return;
162
+ if (typeof window === "undefined") return;
109
163
  if (pathName === "/signin-oidc") return;
110
164
 
111
165
  const host = window.location.protocol + "//" + window.location.host;
@@ -115,20 +169,29 @@ export function AuthScapeApp({
115
169
  locationId: signedInUser.current?.locationId,
116
170
  companyId: signedInUser.current?.companyId,
117
171
  uri: pathName,
118
- host
172
+ host,
119
173
  });
120
174
  };
121
175
 
122
- // ---------- Auth + Tracking Init ----------
176
+ // ----- Auth init (runs once) -----
123
177
  useEffect(() => {
124
178
  if (queryCode) {
125
179
  signInValidator(queryCode);
126
- } else if (!loadingAuth.current) {
180
+ return;
181
+ }
182
+
183
+ if (!loadingAuth.current) {
127
184
  loadingAuth.current = true;
185
+
128
186
  if (enableAuth) {
129
187
  apiService().GetCurrentUser().then((usr) => {
130
- signedInUser.current = usr;
131
- setSignedInUserState(usr);
188
+ signedInUser.current = ensureUserHelpers(usr);
189
+ setSignedInUserState(signedInUser.current);
190
+ setFrontEndLoadedState(true);
191
+ }).catch(() => {
192
+ // no user / anonymous
193
+ signedInUser.current = null;
194
+ setSignedInUserState(null);
132
195
  setFrontEndLoadedState(true);
133
196
  });
134
197
  } else {
@@ -137,9 +200,9 @@ export function AuthScapeApp({
137
200
  }
138
201
  }, [queryCode, enableAuth]);
139
202
 
140
- // ---------- Analytics Init ----------
203
+ // ----- Analytics init -----
141
204
  useEffect(() => {
142
- if (!frontEndLoadedState) return;
205
+ if (!frontEndLoadedState || typeof window === "undefined") return;
143
206
 
144
207
  if (pageProps.googleAnalytics4Code) {
145
208
  initGA(pageProps.googleAnalytics4Code);
@@ -155,40 +218,71 @@ export function AuthScapeApp({
155
218
 
156
219
  databaseDrivenPageView(window.location.pathname);
157
220
 
158
- Router.events.on('routeChangeComplete', (url) => {
221
+ const handler = (url) => {
159
222
  ga4React.current?.pageview(url);
160
223
  databaseDrivenPageView(url);
161
- });
162
- }, [frontEndLoadedState]);
224
+ };
225
+ Router.events.on("routeChangeComplete", handler);
226
+ return () => Router.events.off("routeChangeComplete", handler);
227
+ }, [frontEndLoadedState, pageProps.googleAnalytics4Code, pageProps.microsoftClarityCode]);
163
228
 
164
- // ---------- Enforce Login ----------
229
+ // ----- Enforce login (client) -----
165
230
  useEffect(() => {
166
- if (enforceLoggedIn && pathname !== "/signin-oidc" && frontEndLoadedState && !signedInUserState) {
231
+ if (
232
+ enforceLoggedIn &&
233
+ pathname !== "/signin-oidc" &&
234
+ frontEndLoadedState &&
235
+ !signedInUserState
236
+ ) {
167
237
  authService().login();
168
238
  }
169
239
  }, [signedInUserState, enforceLoggedIn, frontEndLoadedState, pathname]);
170
240
 
171
- const GetSignedInUser = () => signedInUser.current;
241
+ // Stable getter for current user (with helpers)
242
+ const currentUser = useMemo(() => ensureUserHelpers(signedInUser.current), [signedInUserState]);
172
243
 
173
- const useStore = create((set) => (store));
244
+ const useStore = create(() => store);
174
245
 
175
- // ---------- Render ----------
246
+ // ----- Render (SSR-safe; always output page so <title> is visible) -----
176
247
  const pageContent = layout
177
248
  ? layout({
178
- children: <Component {...pageProps} currentUser={GetSignedInUser()} loadedUser={frontEndLoadedState} setIsLoading={setIsLoadingShow} logEvent={logEvent} store={useStore} toast={toast} />,
179
- currentUser: GetSignedInUser(),
249
+ children: (
250
+ <Component
251
+ {...pageProps}
252
+ currentUser={currentUser}
253
+ loadedUser={frontEndLoadedState}
254
+ setIsLoading={setIsLoadingShow}
255
+ logEvent={logEvent}
256
+ store={useStore}
257
+ toast={toast}
258
+ />
259
+ ),
260
+ currentUser,
180
261
  setIsLoading: setIsLoadingShow,
181
262
  logEvent,
182
263
  toast,
183
264
  store: useStore,
184
- pageProps
265
+ pageProps,
185
266
  })
186
- : <Component {...pageProps} currentUser={GetSignedInUser()} loadedUser={frontEndLoadedState} setIsLoading={setIsLoadingShow} logEvent={logEvent} store={useStore} toast={toast} />;
267
+ : (
268
+ <Component
269
+ {...pageProps}
270
+ currentUser={currentUser}
271
+ loadedUser={frontEndLoadedState}
272
+ setIsLoading={setIsLoadingShow}
273
+ logEvent={logEvent}
274
+ store={useStore}
275
+ toast={toast}
276
+ />
277
+ );
187
278
 
188
279
  return (
189
280
  <>
190
281
  <Head>
191
- <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
282
+ <meta
283
+ name="viewport"
284
+ content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86"
285
+ />
192
286
  </Head>
193
287
 
194
288
  <ThemeProvider theme={muiTheme}>