@iqauth/sdk 2.0.2 → 2.0.4

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.
@@ -1,4 +1,4 @@
1
- export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-C8f6qVjD.mjs';
1
+ export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-CEMdUAwd.mjs';
2
2
  export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.mjs';
3
3
  export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
4
4
  import './types-Cxl3bQHt.mjs';
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-Cy2lbEXb.js';
1
+ export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-VRNzlNyG.js';
2
2
  export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.js';
3
3
  export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
4
4
  import './types-Cxl3bQHt.js';
package/dist/browser.js CHANGED
@@ -270,8 +270,9 @@ var SessionManager = class {
270
270
  this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
271
271
  this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
272
272
  this.useCookies = options.useCookies ?? true;
273
- this.proactiveRefresh = options.proactiveRefresh ?? true;
274
- this.tokenStore = options.tokenStore ?? (this.useCookies ? defaultCookieStore() : NO_OP_STORE);
273
+ this.serverManagedSession = options.serverManagedSession ?? false;
274
+ this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
275
+ this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
275
276
  this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
276
277
  this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
277
278
  throw new Error("global fetch is not available; pass fetchImpl");
@@ -315,6 +316,40 @@ var SessionManager = class {
315
316
  async bootstrap() {
316
317
  if (this.bootstrapped) return;
317
318
  this.bootstrapped = true;
319
+ if (this.serverManagedSession) {
320
+ try {
321
+ const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
322
+ method: "GET",
323
+ credentials: "include",
324
+ headers: { Accept: "application/json" }
325
+ });
326
+ if (!res.ok) {
327
+ this.setStatus("unauthenticated");
328
+ return;
329
+ }
330
+ const body = await res.json().catch(() => ({}));
331
+ const data = body?.data;
332
+ const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
333
+ if (!user) {
334
+ this.setStatus("unauthenticated");
335
+ return;
336
+ }
337
+ this.update({
338
+ status: "authenticated",
339
+ accessToken: null,
340
+ user,
341
+ claims: data?.claims ?? null,
342
+ tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
343
+ error: null,
344
+ version: this.snapshot.version + 1
345
+ });
346
+ this.broadcast("session:update");
347
+ return;
348
+ } catch {
349
+ this.setStatus("unauthenticated");
350
+ return;
351
+ }
352
+ }
318
353
  const stored = await Promise.resolve(this.tokenStore.read());
319
354
  if (!stored) {
320
355
  this.setStatus("unauthenticated");
package/dist/browser.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  setCookie,
13
13
  signIn,
14
14
  signOut
15
- } from "./chunk-YDO2RDWQ.mjs";
15
+ } from "./chunk-S3M2IXCE.mjs";
16
16
  import {
17
17
  encodePublishableKey,
18
18
  isPublishableKey,
@@ -3,6 +3,22 @@ import {
3
3
  } from "./chunk-5WFR6Y33.mjs";
4
4
 
5
5
  // src/server/handlers.ts
6
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
7
+ "TOKEN_REVOKED",
8
+ "SESSION_REVOKED",
9
+ "INVALID_GRANT",
10
+ "invalid_grant",
11
+ "USER_DEACTIVATED",
12
+ "USER_DISABLED",
13
+ "TENANT_SUSPENDED"
14
+ ]);
15
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
16
+ if (policy === "always") return true;
17
+ if (policy === "never") return false;
18
+ if (status === 410) return true;
19
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
20
+ return false;
21
+ }
6
22
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
7
23
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
8
24
  function resolve(config) {
@@ -30,7 +46,8 @@ function resolve(config) {
30
46
  throw new Error("global fetch is unavailable; pass fetchImpl");
31
47
  })),
32
48
  appId: parsed.appId,
33
- tenantId: parsed.tenantId
49
+ tenantId: parsed.tenantId,
50
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
34
51
  };
35
52
  }
36
53
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -126,7 +143,7 @@ async function handleRefresh(config, input) {
126
143
  return {
127
144
  status: 401,
128
145
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
129
- cookies: clearCookies(cfg)
146
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
130
147
  };
131
148
  }
132
149
  const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
@@ -136,16 +153,23 @@ async function handleRefresh(config, input) {
136
153
  });
137
154
  const json = await res.json().catch(() => ({}));
138
155
  if (!res.ok || !json.success || !json.data?.accessToken) {
156
+ const status = res.status || 401;
157
+ const errorCode = json.error?.code || "TOKEN_INVALID";
158
+ const shouldClear = shouldClearCookiesOnFailure(
159
+ cfg.clearCookiesOnRefreshFailure,
160
+ status,
161
+ errorCode
162
+ );
139
163
  return {
140
- status: res.status || 401,
164
+ status,
141
165
  body: {
142
166
  success: false,
143
167
  error: {
144
- code: json.error?.code || "TOKEN_INVALID",
168
+ code: errorCode,
145
169
  message: json.error?.message || "Refresh failed"
146
170
  }
147
171
  },
148
- cookies: clearCookies(cfg)
172
+ cookies: shouldClear ? clearCookies(cfg) : []
149
173
  };
150
174
  }
151
175
  const cookies = [
@@ -137,8 +137,9 @@ var SessionManager = class {
137
137
  this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
138
138
  this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
139
139
  this.useCookies = options.useCookies ?? true;
140
- this.proactiveRefresh = options.proactiveRefresh ?? true;
141
- this.tokenStore = options.tokenStore ?? (this.useCookies ? defaultCookieStore() : NO_OP_STORE);
140
+ this.serverManagedSession = options.serverManagedSession ?? false;
141
+ this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
142
+ this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
142
143
  this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
143
144
  this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
144
145
  throw new Error("global fetch is not available; pass fetchImpl");
@@ -182,6 +183,40 @@ var SessionManager = class {
182
183
  async bootstrap() {
183
184
  if (this.bootstrapped) return;
184
185
  this.bootstrapped = true;
186
+ if (this.serverManagedSession) {
187
+ try {
188
+ const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
189
+ method: "GET",
190
+ credentials: "include",
191
+ headers: { Accept: "application/json" }
192
+ });
193
+ if (!res.ok) {
194
+ this.setStatus("unauthenticated");
195
+ return;
196
+ }
197
+ const body = await res.json().catch(() => ({}));
198
+ const data = body?.data;
199
+ const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
200
+ if (!user) {
201
+ this.setStatus("unauthenticated");
202
+ return;
203
+ }
204
+ this.update({
205
+ status: "authenticated",
206
+ accessToken: null,
207
+ user,
208
+ claims: data?.claims ?? null,
209
+ tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
210
+ error: null,
211
+ version: this.snapshot.version + 1
212
+ });
213
+ this.broadcast("session:update");
214
+ return;
215
+ } catch {
216
+ this.setStatus("unauthenticated");
217
+ return;
218
+ }
219
+ }
185
220
  const stored = await Promise.resolve(this.tokenStore.read());
186
221
  if (!stored) {
187
222
  this.setStatus("unauthenticated");
package/dist/express.js CHANGED
@@ -1980,6 +1980,22 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
1980
1980
  }
1981
1981
 
1982
1982
  // src/server/handlers.ts
1983
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
1984
+ "TOKEN_REVOKED",
1985
+ "SESSION_REVOKED",
1986
+ "INVALID_GRANT",
1987
+ "invalid_grant",
1988
+ "USER_DEACTIVATED",
1989
+ "USER_DISABLED",
1990
+ "TENANT_SUSPENDED"
1991
+ ]);
1992
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
1993
+ if (policy === "always") return true;
1994
+ if (policy === "never") return false;
1995
+ if (status === 410) return true;
1996
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
1997
+ return false;
1998
+ }
1983
1999
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
1984
2000
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
1985
2001
  function resolve(config) {
@@ -2007,7 +2023,8 @@ function resolve(config) {
2007
2023
  throw new Error("global fetch is unavailable; pass fetchImpl");
2008
2024
  })),
2009
2025
  appId: parsed.appId,
2010
- tenantId: parsed.tenantId
2026
+ tenantId: parsed.tenantId,
2027
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
2011
2028
  };
2012
2029
  }
2013
2030
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -2093,7 +2110,7 @@ async function handleRefresh(config, input) {
2093
2110
  return {
2094
2111
  status: 401,
2095
2112
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
2096
- cookies: clearCookies(cfg)
2113
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
2097
2114
  };
2098
2115
  }
2099
2116
  const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
@@ -2103,16 +2120,23 @@ async function handleRefresh(config, input) {
2103
2120
  });
2104
2121
  const json = await res.json().catch(() => ({}));
2105
2122
  if (!res.ok || !json.success || !json.data?.accessToken) {
2123
+ const status = res.status || 401;
2124
+ const errorCode = json.error?.code || "TOKEN_INVALID";
2125
+ const shouldClear = shouldClearCookiesOnFailure(
2126
+ cfg.clearCookiesOnRefreshFailure,
2127
+ status,
2128
+ errorCode
2129
+ );
2106
2130
  return {
2107
- status: res.status || 401,
2131
+ status,
2108
2132
  body: {
2109
2133
  success: false,
2110
2134
  error: {
2111
- code: json.error?.code || "TOKEN_INVALID",
2135
+ code: errorCode,
2112
2136
  message: json.error?.message || "Refresh failed"
2113
2137
  }
2114
2138
  },
2115
- cookies: clearCookies(cfg)
2139
+ cookies: shouldClear ? clearCookies(cfg) : []
2116
2140
  };
2117
2141
  }
2118
2142
  const cookies = [
package/dist/express.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  handleCallback,
7
7
  handleRefresh,
8
8
  handleSignout
9
- } from "./chunk-5HF3OBNO.mjs";
9
+ } from "./chunk-JQRTY5MY.mjs";
10
10
  import {
11
11
  parsePublishableKey
12
12
  } from "./chunk-5WFR6Y33.mjs";
package/dist/fastify.js CHANGED
@@ -1781,6 +1781,22 @@ function parsePublishableKey(raw) {
1781
1781
  }
1782
1782
 
1783
1783
  // src/server/handlers.ts
1784
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
1785
+ "TOKEN_REVOKED",
1786
+ "SESSION_REVOKED",
1787
+ "INVALID_GRANT",
1788
+ "invalid_grant",
1789
+ "USER_DEACTIVATED",
1790
+ "USER_DISABLED",
1791
+ "TENANT_SUSPENDED"
1792
+ ]);
1793
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
1794
+ if (policy === "always") return true;
1795
+ if (policy === "never") return false;
1796
+ if (status === 410) return true;
1797
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
1798
+ return false;
1799
+ }
1784
1800
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
1785
1801
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
1786
1802
  function resolve(config) {
@@ -1808,7 +1824,8 @@ function resolve(config) {
1808
1824
  throw new Error("global fetch is unavailable; pass fetchImpl");
1809
1825
  })),
1810
1826
  appId: parsed.appId,
1811
- tenantId: parsed.tenantId
1827
+ tenantId: parsed.tenantId,
1828
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
1812
1829
  };
1813
1830
  }
1814
1831
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -1904,7 +1921,7 @@ async function handleRefresh(config, input) {
1904
1921
  return {
1905
1922
  status: 401,
1906
1923
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
1907
- cookies: clearCookies(cfg)
1924
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
1908
1925
  };
1909
1926
  }
1910
1927
  const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
@@ -1914,16 +1931,23 @@ async function handleRefresh(config, input) {
1914
1931
  });
1915
1932
  const json = await res.json().catch(() => ({}));
1916
1933
  if (!res.ok || !json.success || !json.data?.accessToken) {
1934
+ const status = res.status || 401;
1935
+ const errorCode = json.error?.code || "TOKEN_INVALID";
1936
+ const shouldClear = shouldClearCookiesOnFailure(
1937
+ cfg.clearCookiesOnRefreshFailure,
1938
+ status,
1939
+ errorCode
1940
+ );
1917
1941
  return {
1918
- status: res.status || 401,
1942
+ status,
1919
1943
  body: {
1920
1944
  success: false,
1921
1945
  error: {
1922
- code: json.error?.code || "TOKEN_INVALID",
1946
+ code: errorCode,
1923
1947
  message: json.error?.message || "Refresh failed"
1924
1948
  }
1925
1949
  },
1926
- cookies: clearCookies(cfg)
1950
+ cookies: shouldClear ? clearCookies(cfg) : []
1927
1951
  };
1928
1952
  }
1929
1953
  const cookies = [
package/dist/fastify.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  handleRefresh,
4
4
  handleSignout,
5
5
  serializeCookie
6
- } from "./chunk-5HF3OBNO.mjs";
6
+ } from "./chunk-JQRTY5MY.mjs";
7
7
  import {
8
8
  parsePublishableKey
9
9
  } from "./chunk-5WFR6Y33.mjs";
package/dist/hono.js CHANGED
@@ -1780,6 +1780,22 @@ function parsePublishableKey(raw) {
1780
1780
  }
1781
1781
 
1782
1782
  // src/server/handlers.ts
1783
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
1784
+ "TOKEN_REVOKED",
1785
+ "SESSION_REVOKED",
1786
+ "INVALID_GRANT",
1787
+ "invalid_grant",
1788
+ "USER_DEACTIVATED",
1789
+ "USER_DISABLED",
1790
+ "TENANT_SUSPENDED"
1791
+ ]);
1792
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
1793
+ if (policy === "always") return true;
1794
+ if (policy === "never") return false;
1795
+ if (status === 410) return true;
1796
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
1797
+ return false;
1798
+ }
1783
1799
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
1784
1800
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
1785
1801
  function resolve(config) {
@@ -1807,7 +1823,8 @@ function resolve(config) {
1807
1823
  throw new Error("global fetch is unavailable; pass fetchImpl");
1808
1824
  })),
1809
1825
  appId: parsed.appId,
1810
- tenantId: parsed.tenantId
1826
+ tenantId: parsed.tenantId,
1827
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
1811
1828
  };
1812
1829
  }
1813
1830
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -1903,7 +1920,7 @@ async function handleRefresh(config, input) {
1903
1920
  return {
1904
1921
  status: 401,
1905
1922
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
1906
- cookies: clearCookies(cfg)
1923
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
1907
1924
  };
1908
1925
  }
1909
1926
  const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
@@ -1913,16 +1930,23 @@ async function handleRefresh(config, input) {
1913
1930
  });
1914
1931
  const json = await res.json().catch(() => ({}));
1915
1932
  if (!res.ok || !json.success || !json.data?.accessToken) {
1933
+ const status = res.status || 401;
1934
+ const errorCode = json.error?.code || "TOKEN_INVALID";
1935
+ const shouldClear = shouldClearCookiesOnFailure(
1936
+ cfg.clearCookiesOnRefreshFailure,
1937
+ status,
1938
+ errorCode
1939
+ );
1916
1940
  return {
1917
- status: res.status || 401,
1941
+ status,
1918
1942
  body: {
1919
1943
  success: false,
1920
1944
  error: {
1921
- code: json.error?.code || "TOKEN_INVALID",
1945
+ code: errorCode,
1922
1946
  message: json.error?.message || "Refresh failed"
1923
1947
  }
1924
1948
  },
1925
- cookies: clearCookies(cfg)
1949
+ cookies: shouldClear ? clearCookies(cfg) : []
1926
1950
  };
1927
1951
  }
1928
1952
  const cookies = [
package/dist/hono.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  handleRefresh,
4
4
  handleSignout,
5
5
  serializeCookie
6
- } from "./chunk-5HF3OBNO.mjs";
6
+ } from "./chunk-JQRTY5MY.mjs";
7
7
  import {
8
8
  parsePublishableKey
9
9
  } from "./chunk-5WFR6Y33.mjs";
package/dist/next.js CHANGED
@@ -66,6 +66,22 @@ function parsePublishableKey(raw) {
66
66
  }
67
67
 
68
68
  // src/server/handlers.ts
69
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
70
+ "TOKEN_REVOKED",
71
+ "SESSION_REVOKED",
72
+ "INVALID_GRANT",
73
+ "invalid_grant",
74
+ "USER_DEACTIVATED",
75
+ "USER_DISABLED",
76
+ "TENANT_SUSPENDED"
77
+ ]);
78
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
79
+ if (policy === "always") return true;
80
+ if (policy === "never") return false;
81
+ if (status === 410) return true;
82
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
83
+ return false;
84
+ }
69
85
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
70
86
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
71
87
  function resolve(config) {
@@ -93,7 +109,8 @@ function resolve(config) {
93
109
  throw new Error("global fetch is unavailable; pass fetchImpl");
94
110
  })),
95
111
  appId: parsed.appId,
96
- tenantId: parsed.tenantId
112
+ tenantId: parsed.tenantId,
113
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
97
114
  };
98
115
  }
99
116
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -189,7 +206,7 @@ async function handleRefresh(config, input) {
189
206
  return {
190
207
  status: 401,
191
208
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
192
- cookies: clearCookies(cfg)
209
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
193
210
  };
194
211
  }
195
212
  const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
@@ -199,16 +216,23 @@ async function handleRefresh(config, input) {
199
216
  });
200
217
  const json = await res.json().catch(() => ({}));
201
218
  if (!res.ok || !json.success || !json.data?.accessToken) {
219
+ const status = res.status || 401;
220
+ const errorCode = json.error?.code || "TOKEN_INVALID";
221
+ const shouldClear = shouldClearCookiesOnFailure(
222
+ cfg.clearCookiesOnRefreshFailure,
223
+ status,
224
+ errorCode
225
+ );
202
226
  return {
203
- status: res.status || 401,
227
+ status,
204
228
  body: {
205
229
  success: false,
206
230
  error: {
207
- code: json.error?.code || "TOKEN_INVALID",
231
+ code: errorCode,
208
232
  message: json.error?.message || "Refresh failed"
209
233
  }
210
234
  },
211
- cookies: clearCookies(cfg)
235
+ cookies: shouldClear ? clearCookies(cfg) : []
212
236
  };
213
237
  }
214
238
  const cookies = [
package/dist/next.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  handleRefresh,
4
4
  handleSignout,
5
5
  serializeCookie
6
- } from "./chunk-5HF3OBNO.mjs";
6
+ } from "./chunk-JQRTY5MY.mjs";
7
7
  import {
8
8
  parsePublishableKey
9
9
  } from "./chunk-5WFR6Y33.mjs";
package/dist/react.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-C8f6qVjD.mjs';
4
+ import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-CEMdUAwd.mjs';
5
5
  import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.mjs';
6
6
  import './publishableKey-B5DIK81A.mjs';
7
7
 
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-Cy2lbEXb.js';
4
+ import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-VRNzlNyG.js';
5
5
  import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.js';
6
6
  import './publishableKey-B5DIK81A.js';
7
7
 
package/dist/react.js CHANGED
@@ -215,8 +215,9 @@ var SessionManager = class {
215
215
  this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
216
216
  this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
217
217
  this.useCookies = options.useCookies ?? true;
218
- this.proactiveRefresh = options.proactiveRefresh ?? true;
219
- this.tokenStore = options.tokenStore ?? (this.useCookies ? defaultCookieStore() : NO_OP_STORE);
218
+ this.serverManagedSession = options.serverManagedSession ?? false;
219
+ this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
220
+ this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
220
221
  this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
221
222
  this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
222
223
  throw new Error("global fetch is not available; pass fetchImpl");
@@ -260,6 +261,40 @@ var SessionManager = class {
260
261
  async bootstrap() {
261
262
  if (this.bootstrapped) return;
262
263
  this.bootstrapped = true;
264
+ if (this.serverManagedSession) {
265
+ try {
266
+ const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
267
+ method: "GET",
268
+ credentials: "include",
269
+ headers: { Accept: "application/json" }
270
+ });
271
+ if (!res.ok) {
272
+ this.setStatus("unauthenticated");
273
+ return;
274
+ }
275
+ const body = await res.json().catch(() => ({}));
276
+ const data = body?.data;
277
+ const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
278
+ if (!user) {
279
+ this.setStatus("unauthenticated");
280
+ return;
281
+ }
282
+ this.update({
283
+ status: "authenticated",
284
+ accessToken: null,
285
+ user,
286
+ claims: data?.claims ?? null,
287
+ tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
288
+ error: null,
289
+ version: this.snapshot.version + 1
290
+ });
291
+ this.broadcast("session:update");
292
+ return;
293
+ } catch {
294
+ this.setStatus("unauthenticated");
295
+ return;
296
+ }
297
+ }
263
298
  const stored = await Promise.resolve(this.tokenStore.read());
264
299
  if (!stored) {
265
300
  this.setStatus("unauthenticated");
package/dist/react.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  redirectToSignIn,
5
5
  signIn,
6
6
  signOut
7
- } from "./chunk-YDO2RDWQ.mjs";
7
+ } from "./chunk-S3M2IXCE.mjs";
8
8
  import "./chunk-5WFR6Y33.mjs";
9
9
  import "./chunk-6I6RM4MN.mjs";
10
10
  import "./chunk-Y6FXYEAI.mjs";
@@ -63,14 +63,33 @@ interface IQAuthHelperConfig {
63
63
  logoutPath?: string;
64
64
  /** Optional fetch implementation override. */
65
65
  fetchImpl?: typeof fetch;
66
+ /**
67
+ * Policy for clearing the access + refresh cookies when `/refresh` fails.
68
+ *
69
+ * - `"terminal-only"` (default, recommended): only clear cookies when the
70
+ * issuer indicates the session is unrecoverable
71
+ * (`TOKEN_REVOKED`, `SESSION_REVOKED`, `INVALID_GRANT`,
72
+ * `USER_DEACTIVATED`, or HTTP 410 Gone). Transient failures
73
+ * (`TOKEN_INVALID` from a rotated-out token, `TOKEN_EXPIRED`, network
74
+ * errors, 5xx) leave cookies intact so the next legitimate request can
75
+ * either succeed against a still-valid access cookie or be redirected
76
+ * cleanly to sign-in by the middleware. Fixes the multi-tab /
77
+ * proactive-refresh race that previously silently signed users out.
78
+ * - `"always"`: pre-2.0.3 behavior — wipe both cookies on any non-2xx.
79
+ * Use only if you have an external reason to depend on the old semantics.
80
+ * - `"never"`: leave cookies untouched on every failure path. Suitable for
81
+ * apps that manage cookie lifecycle entirely outside the SDK helpers.
82
+ */
83
+ clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
66
84
  }
67
- interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl">> {
85
+ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure">> {
68
86
  secretKey?: string;
69
87
  cookieDomain?: string;
70
88
  issuer: string;
71
89
  fetchImpl: typeof fetch;
72
90
  appId: string;
73
91
  tenantId: string;
92
+ clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
74
93
  }
75
94
  /**
76
95
  * Serialize a cookie directive to a Set-Cookie header value. Adapters that
@@ -84,7 +103,15 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
84
103
  codeVerifier?: string;
85
104
  redirectUri?: string;
86
105
  }): Promise<HandlerResponse>;
87
- /** POST /api/iqauth/refresh — rotate refresh + access cookies. */
106
+ /** POST /api/iqauth/refresh — rotate refresh + access cookies.
107
+ *
108
+ * Cookie-clearing policy is governed by `config.clearCookiesOnRefreshFailure`
109
+ * (default `"terminal-only"`). Prior to 2.0.3 this helper wiped both cookies
110
+ * on any non-2xx, which converted survivable refresh-token races (multi-tab,
111
+ * proactive-refresh timer, React StrictMode double-mount) into silent forced
112
+ * sign-outs. The default behavior now preserves cookies on transient failures
113
+ * and only clears them when the issuer signals the session is truly dead.
114
+ */
88
115
  declare function handleRefresh(config: IQAuthHelperConfig, input: {
89
116
  refreshToken?: string;
90
117
  }): Promise<HandlerResponse>;