@startsimpli/auth 0.4.14 → 0.4.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/auth",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "Shared authentication package for StartSimpli Next.js apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -8,6 +8,7 @@ import {
8
8
  createContext,
9
9
  useContext,
10
10
  useEffect,
11
+ useRef,
11
12
  useState,
12
13
  useCallback,
13
14
  type ReactNode,
@@ -108,43 +109,49 @@ export function AuthProvider({
108
109
  };
109
110
  }, [authClient, initialSession]);
110
111
 
111
- // Session expiration handler covers both AuthClient timer and authFetch 401
112
- useEffect(() => {
113
- // Capture the consumer's onSessionExpired before we overwrite it below.
114
- const consumerCallback = config.onSessionExpired;
115
- const loginPath = config.loginPath;
116
- const callbackParam = config.callbackParam ?? 'callbackUrl';
112
+ // Keep refs current on every render so the stable handleExpired closure
113
+ // below always reads the latest config values without being in deps.
114
+ const loginPathRef = useRef(config.loginPath);
115
+ const callbackParamRef = useRef(config.callbackParam ?? 'callbackUrl');
116
+ const onSessionExpiredRef = useRef(config.onSessionExpired);
117
+ loginPathRef.current = config.loginPath;
118
+ callbackParamRef.current = config.callbackParam ?? 'callbackUrl';
119
+ onSessionExpiredRef.current = config.onSessionExpired;
117
120
 
121
+ // Session expiration handler — covers both AuthClient timer and authFetch 401.
122
+ // Depends only on authClient (a useState singleton that never changes after
123
+ // mount). config intentionally omitted: it's a new object literal on every
124
+ // render from the caller, so including it caused destroy() to fire and wipe
125
+ // the session on every re-render.
126
+ useEffect(() => {
118
127
  const handleExpired = () => {
119
128
  setState({
120
129
  session: null,
121
130
  isLoading: false,
122
131
  isAuthenticated: false,
123
132
  });
124
- consumerCallback?.();
133
+ onSessionExpiredRef.current?.();
125
134
 
126
- // Redirect to login if configured. Done after state reset + consumer
127
- // callback so any cleanup runs first. window.location avoids pulling
128
- // a router dep into the shared package — works in any framework.
129
- if (loginPath && typeof window !== 'undefined') {
135
+ if (loginPathRef.current && typeof window !== 'undefined') {
130
136
  const here = window.location.pathname + window.location.search;
131
- const isOnLogin = window.location.pathname.startsWith(loginPath);
137
+ const isOnLogin = window.location.pathname.startsWith(loginPathRef.current);
132
138
  if (!isOnLogin) {
133
139
  const callback = encodeURIComponent(here);
134
- window.location.href = `${loginPath}?${callbackParam}=${callback}`;
140
+ window.location.href = `${loginPathRef.current}?${callbackParamRef.current}=${callback}`;
135
141
  }
136
142
  }
137
143
  };
138
144
 
145
+ // Wire AuthClient's internal expiry calls and the functional API (FetchWrapper).
139
146
  config.onSessionExpired = handleExpired;
140
- // Wire up the functional API's session expiration callback
141
147
  setOnSessionExpired(handleExpired);
142
148
 
143
149
  return () => {
144
150
  setOnSessionExpired(null);
145
151
  authClient.destroy();
146
152
  };
147
- }, [authClient, config]);
153
+ // eslint-disable-next-line react-hooks/exhaustive-deps
154
+ }, [authClient]);
148
155
 
149
156
  const login = useCallback(
150
157
  async (email: string, password: string) => {