@witsauth/react-client 1.3.7 → 1.3.9

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
- import { OAuth2Config, TokenInfo } from '../models/types';
1
+ import { AuthorizeOptions, OAuth2Config, TokenInfo, UserInfo } from '../models/types';
2
2
  import { TokenStorage } from './storage';
3
3
  export declare class AuthClient {
4
4
  private config;
@@ -6,11 +6,15 @@ export declare class AuthClient {
6
6
  private refreshPromise;
7
7
  constructor(config: OAuth2Config, storage?: TokenStorage);
8
8
  log(level: 'error' | 'warn' | 'info' | 'debug', ...args: any[]): void;
9
- startAuthorization(): Promise<void>;
9
+ startAuthorization(options?: AuthorizeOptions): Promise<void>;
10
10
  exchangeCodeForTokens(code: string, state: string): Promise<TokenInfo>;
11
11
  refreshAccessToken(): Promise<TokenInfo>;
12
12
  private saveTokenInfo;
13
13
  getAccessToken(): string | null;
14
+ getIdToken(): string | null;
15
+ getRefreshToken(): string | null;
16
+ getUserProfile<T extends UserInfo = UserInfo>(): T | null;
17
+ getToken(): Promise<string | null>;
14
18
  getExpiresAt(): number | null;
15
19
  isAuthenticated(): boolean;
16
20
  navigateToAccountManagement(): Promise<void>;
@@ -11,3 +11,4 @@ export declare function generateState(): string;
11
11
  * Creates a SHA-256 code challenge from the code verifier
12
12
  */
13
13
  export declare function createCodeChallenge(verifier: string): Promise<string>;
14
+ export declare function decodeJwtPayload<T = Record<string, any>>(token: string | null | undefined): T | null;
@@ -3,7 +3,7 @@ export declare class TokenStorage {
3
3
  private storageType?;
4
4
  private customStorage?;
5
5
  private memoryStorage;
6
- constructor(storageType?: "localStorage" | "sessionStorage" | "custom" | undefined, customStorage?: OAuth2Storage | undefined);
6
+ constructor(storageType?: 'localStorage' | 'sessionStorage' | 'custom' | undefined, customStorage?: OAuth2Storage | undefined);
7
7
  private getStorage;
8
8
  setItem(key: string, value: string): void;
9
9
  getItem(key: string): string | null;
package/dist/index.es.js CHANGED
@@ -1,33 +1,59 @@
1
- import { createContext as e, useContext as t, useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
2
- import { Fragment as o, jsx as s } from "react/jsx-runtime";
1
+ import { createContext as e, useCallback as t, useContext as n, useEffect as r, useMemo as i, useRef as a, useState as o } from "react";
2
+ import { Fragment as s, jsx as c } from "react/jsx-runtime";
3
3
  //#region src/models/types.ts
4
- var c = {
4
+ var l = {
5
5
  ACCESS_TOKEN: "witsauth_access_token",
6
6
  REFRESH_TOKEN: "witsauth_refresh_token",
7
7
  TOKEN_EXPIRES_AT: "witsauth_expires_at",
8
8
  TOKEN_TYPE: "witsauth_token_type",
9
9
  SCOPE: "witsauth_scope",
10
+ ID_TOKEN: "witsauth_id_token",
10
11
  CODE_VERIFIER: "witsauth_code_verifier",
11
12
  STATE: "witsauth_state",
12
13
  NONCE: "witsauth_nonce"
13
14
  };
14
15
  //#endregion
15
16
  //#region src/core/pkce.ts
16
- function l() {
17
+ function u() {
17
18
  let e = /* @__PURE__ */ new Uint8Array(32);
18
19
  return crypto.getRandomValues(e), btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
19
20
  }
20
- function u() {
21
+ function d() {
21
22
  let e = /* @__PURE__ */ new Uint8Array(32);
22
23
  return crypto.getRandomValues(e), btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
23
24
  }
24
- async function d(e) {
25
+ async function f(e) {
25
26
  let t = new TextEncoder().encode(e), n = await crypto.subtle.digest("SHA-256", t), r = new Uint8Array(n);
26
27
  return btoa(String.fromCharCode(...r)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
27
28
  }
29
+ function p(e) {
30
+ let t = e.replace(/-/g, "+").replace(/_/g, "/"), n = t + "=".repeat((4 - t.length % 4) % 4);
31
+ if (typeof atob == "function") {
32
+ let e = atob(n), t = new Uint8Array(e.length);
33
+ for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n);
34
+ if (typeof TextDecoder < "u") return new TextDecoder().decode(t);
35
+ let r = "";
36
+ for (let e = 0; e < t.length; e++) r += String.fromCharCode(t[e]);
37
+ return decodeURIComponent(escape(r));
38
+ }
39
+ let r = typeof globalThis < "u" ? globalThis : void 0;
40
+ if (r?.Buffer) return r.Buffer.from(n, "base64").toString("utf8");
41
+ throw Error("Unable to decode base64url string.");
42
+ }
43
+ function m(e) {
44
+ if (!e || typeof e != "string") return null;
45
+ let t = e.split(".");
46
+ if (t.length < 2) return null;
47
+ try {
48
+ let e = p(t[1]);
49
+ return JSON.parse(e);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
28
54
  //#endregion
29
55
  //#region src/core/storage.ts
30
- var f = class {
56
+ var h = class {
31
57
  storageType;
32
58
  customStorage;
33
59
  memoryStorage = /* @__PURE__ */ new Map();
@@ -36,10 +62,10 @@ var f = class {
36
62
  }
37
63
  getStorage() {
38
64
  if (this.storageType === "custom" && this.customStorage) return this.customStorage;
39
- if (typeof window < "u") {
65
+ if (typeof window < "u") try {
40
66
  if (this.storageType === "sessionStorage" && window.sessionStorage) return window.sessionStorage;
41
67
  if ((!this.storageType || this.storageType === "localStorage") && window.localStorage) return window.localStorage;
42
- }
68
+ } catch {}
43
69
  let e = this.memoryStorage;
44
70
  return {
45
71
  getItem: (t) => e.has(t) ? e.get(t) : null,
@@ -65,18 +91,18 @@ var f = class {
65
91
  }
66
92
  clearTokens() {
67
93
  let e = this.getStorage();
68
- e.removeItem(c.ACCESS_TOKEN), e.removeItem(c.REFRESH_TOKEN), e.removeItem(c.TOKEN_EXPIRES_AT), e.removeItem(c.TOKEN_TYPE), e.removeItem(c.SCOPE);
94
+ e.removeItem(l.ACCESS_TOKEN), e.removeItem(l.REFRESH_TOKEN), e.removeItem(l.TOKEN_EXPIRES_AT), e.removeItem(l.TOKEN_TYPE), e.removeItem(l.SCOPE), e.removeItem(l.ID_TOKEN);
69
95
  }
70
96
  clearTemporary() {
71
97
  let e = this.getStorage();
72
- e.removeItem(c.CODE_VERIFIER), e.removeItem(c.STATE), e.removeItem(c.NONCE);
98
+ e.removeItem(l.CODE_VERIFIER), e.removeItem(l.STATE), e.removeItem(l.NONCE);
73
99
  }
74
- }, p = class {
100
+ }, g = class {
75
101
  config;
76
102
  storage;
77
103
  refreshPromise = null;
78
104
  constructor(e, t) {
79
- this.config = e, this.storage = t || new f(e.storage, e.customStorage);
105
+ this.config = e, this.storage = t || new h(e.storage, e.customStorage);
80
106
  }
81
107
  log(e, ...t) {
82
108
  let n = {
@@ -88,29 +114,30 @@ var f = class {
88
114
  }, r = n[this.config.logLevel || "warn"] || 0, i = n[e] || 0;
89
115
  i > 0 && r >= i && console[e](...t);
90
116
  }
91
- async startAuthorization() {
92
- let e = l(), t = await d(e), n = u(), r = this.config.nonce || u();
93
- this.storage.setItem(c.CODE_VERIFIER, e), this.storage.setItem(c.STATE, n), this.storage.setItem(c.NONCE, r);
94
- let i = new URLSearchParams({
117
+ async startAuthorization(e) {
118
+ let t = u(), n = await f(t), r = d(), i = this.config.nonce || d();
119
+ this.storage.setItem(l.CODE_VERIFIER, t), this.storage.setItem(l.STATE, r), this.storage.setItem(l.NONCE, i);
120
+ let a = new URLSearchParams({
95
121
  client_id: this.config.clientId,
96
122
  redirect_uri: this.config.redirectUri,
97
123
  response_type: this.config.responseType || "code",
98
124
  code_challenge_method: this.config.codeChallengeMethod || "S256",
99
- code_challenge: t,
100
- state: n,
101
- nonce: r
125
+ code_challenge: n,
126
+ state: r,
127
+ nonce: i
102
128
  });
103
129
  if (this.config.theme) {
104
130
  let e = typeof this.config.theme == "function" ? this.config.theme() : this.config.theme;
105
- e && i.append("theme", e);
131
+ e && a.append("theme", e);
106
132
  }
107
- this.config.scope && i.append("scope", this.config.scope), this.config.audience && i.append("audience", this.config.audience);
108
- let a = `${this.config.authorizationEndpoint}?${i.toString()}`;
133
+ let o = e?.scope ?? this.config.scope, s = e?.audience ?? this.config.audience;
134
+ o && a.append("scope", o), s && a.append("audience", s), e?.prompt && a.append("prompt", e.prompt);
135
+ let c = `${this.config.authorizationEndpoint}?${a.toString()}`;
109
136
  if (typeof window > "u" || !window.location) throw Error("startAuthorization requires a browser environment to redirect the user.");
110
- window.location.href = a;
137
+ window.location.href = c;
111
138
  }
112
139
  async exchangeCodeForTokens(e, t) {
113
- let n = this.storage.getItem(c.STATE), r = this.storage.getItem(c.CODE_VERIFIER);
140
+ let n = this.storage.getItem(l.STATE), r = this.storage.getItem(l.CODE_VERIFIER);
114
141
  if (t !== n) throw Error("Invalid state parameter. Possible CSRF attack.");
115
142
  if (!r) throw Error("Code verifier missing from storage.");
116
143
  let i = new URLSearchParams({
@@ -137,7 +164,7 @@ var f = class {
137
164
  }
138
165
  async refreshAccessToken() {
139
166
  return this.refreshPromise ||= (async () => {
140
- let e = this.storage.getItem(c.REFRESH_TOKEN);
167
+ let e = this.storage.getItem(l.REFRESH_TOKEN);
141
168
  if (!e) throw Error("No refresh token available");
142
169
  let t = new URLSearchParams({
143
170
  grant_type: "refresh_token",
@@ -182,13 +209,30 @@ var f = class {
182
209
  }), this.refreshPromise;
183
210
  }
184
211
  saveTokenInfo(e) {
185
- this.storage.setItem(c.ACCESS_TOKEN, e.accessToken), this.storage.setItem(c.TOKEN_EXPIRES_AT, e.expiresAt.toString()), this.storage.setItem(c.TOKEN_TYPE, e.tokenType), e.refreshToken && this.storage.setItem(c.REFRESH_TOKEN, e.refreshToken), e.scope && this.storage.setItem(c.SCOPE, e.scope);
212
+ this.storage.setItem(l.ACCESS_TOKEN, e.accessToken), this.storage.setItem(l.TOKEN_EXPIRES_AT, e.expiresAt.toString()), this.storage.setItem(l.TOKEN_TYPE, e.tokenType), e.refreshToken && this.storage.setItem(l.REFRESH_TOKEN, e.refreshToken), e.scope && this.storage.setItem(l.SCOPE, e.scope), e.idToken && this.storage.setItem(l.ID_TOKEN, e.idToken);
186
213
  }
187
214
  getAccessToken() {
188
- return this.storage.getItem(c.ACCESS_TOKEN);
215
+ return this.storage.getItem(l.ACCESS_TOKEN);
216
+ }
217
+ getIdToken() {
218
+ return this.storage.getItem(l.ID_TOKEN);
219
+ }
220
+ getRefreshToken() {
221
+ return this.storage.getItem(l.REFRESH_TOKEN);
222
+ }
223
+ getUserProfile() {
224
+ return m(this.getIdToken() || this.getAccessToken());
225
+ }
226
+ async getToken() {
227
+ if (this.isAuthenticated()) return this.getAccessToken();
228
+ try {
229
+ return (await this.refreshAccessToken()).accessToken;
230
+ } catch {
231
+ return null;
232
+ }
189
233
  }
190
234
  getExpiresAt() {
191
- let e = this.storage.getItem(c.TOKEN_EXPIRES_AT);
235
+ let e = this.storage.getItem(l.TOKEN_EXPIRES_AT);
192
236
  if (!e) return null;
193
237
  let t = Number(e);
194
238
  return Number.isFinite(t) ? t : null;
@@ -198,9 +242,9 @@ var f = class {
198
242
  return e !== null && Date.now() < e - 3e4 && !!this.getAccessToken();
199
243
  }
200
244
  async navigateToAccountManagement() {
201
- let e = this.storage.getItem(c.REFRESH_TOKEN);
245
+ let e = this.storage.getItem(l.REFRESH_TOKEN);
202
246
  if (!e) throw Error("No refresh token available to access account management.");
203
- if (typeof window > "u" || !document) throw Error("navigateToAccountManagement requires a browser environment.");
247
+ if (typeof window > "u" || typeof document > "u" || !document) throw Error("navigateToAccountManagement requires a browser environment.");
204
248
  let t = document.createElement("form");
205
249
  t.method = "POST";
206
250
  let n = this.config.authorizationEndpoint.replace(/\/authorize\/?$/, "");
@@ -218,96 +262,109 @@ var f = class {
218
262
  r("refreshToken", e), r("clientId", this.config.clientId), r("redirectUri", this.config.redirectUri), document.body.appendChild(t), t.submit();
219
263
  }
220
264
  async logout() {
221
- let e = this.getAccessToken();
222
- if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint && e) try {
223
- await fetch(this.config.revokeEndpoint, {
224
- method: "POST",
225
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
226
- body: new URLSearchParams({ token: e }).toString()
227
- });
228
- } catch (e) {
229
- this.log("error", "Failed to revoke token during logout", e);
265
+ let e = this.getAccessToken(), t = this.storage.getItem(l.REFRESH_TOKEN);
266
+ if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint) {
267
+ let n = [...t ? [{
268
+ token: t,
269
+ token_type_hint: "refresh_token"
270
+ }] : [], ...e ? [{
271
+ token: e,
272
+ token_type_hint: "access_token"
273
+ }] : []];
274
+ for (let e of n) try {
275
+ await fetch(this.config.revokeEndpoint, {
276
+ method: "POST",
277
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
278
+ body: new URLSearchParams({
279
+ token: e.token,
280
+ token_type_hint: e.token_type_hint,
281
+ client_id: this.config.clientId
282
+ }).toString()
283
+ });
284
+ } catch (e) {
285
+ this.log("error", "Failed to revoke token during logout", e);
286
+ }
230
287
  }
231
288
  }
232
- }, m = e(null), h = ({ config: e, children: t }) => {
233
- let [i, o] = a({
289
+ }, _ = e(null), v = ({ config: e, children: n }) => {
290
+ let [a, s] = o({
234
291
  isAuthenticated: !1,
235
292
  isLoading: !0
236
- }), c = r(() => new p(e), [e]);
237
- n(() => {
293
+ }), l = i(() => new g(e), [e]);
294
+ r(() => {
238
295
  (async () => {
239
296
  try {
240
- if (c.isAuthenticated()) o({
297
+ if (l.isAuthenticated()) s({
241
298
  isAuthenticated: !0,
242
299
  isLoading: !1
243
300
  });
244
301
  else try {
245
- await c.refreshAccessToken(), o({
302
+ await l.refreshAccessToken(), s({
246
303
  isAuthenticated: !0,
247
304
  isLoading: !1
248
305
  });
249
306
  } catch {
250
- o({
307
+ s({
251
308
  isAuthenticated: !1,
252
309
  isLoading: !1
253
310
  });
254
311
  }
255
312
  } catch (e) {
256
- o({
313
+ s({
257
314
  isAuthenticated: !1,
258
315
  isLoading: !1,
259
316
  error: e instanceof Error ? e.message : String(e)
260
317
  });
261
318
  }
262
319
  })();
263
- }, [c]), n(() => {
264
- if (e.autoRefresh === !1 || !i.isAuthenticated) return;
320
+ }, [l]), r(() => {
321
+ if (e.autoRefresh === !1 || !a.isAuthenticated) return;
265
322
  let t = !1, n = setInterval(async () => {
266
323
  if (!t) try {
267
- let n = c.getExpiresAt();
324
+ let n = l.getExpiresAt();
268
325
  if (!n) return;
269
326
  let r = (e.refreshThreshold ?? 60) * 1e3;
270
327
  if (Date.now() >= n - r) {
271
328
  t = !0;
272
329
  try {
273
- let e = await c.refreshAccessToken();
274
- o((t) => ({
330
+ let e = await l.refreshAccessToken();
331
+ s((t) => ({
275
332
  ...t,
276
333
  tokenInfo: e
277
334
  }));
278
335
  } catch {
279
- u();
336
+ d();
280
337
  } finally {
281
338
  t = !1;
282
339
  }
283
340
  }
284
341
  } catch (e) {
285
- c.log("error", "Auto-refresh error:", e);
342
+ l.log("error", "Auto-refresh error:", e);
286
343
  }
287
344
  }, 1e4);
288
345
  return () => clearInterval(n);
289
346
  }, [
290
347
  e.autoRefresh,
291
348
  e.refreshThreshold,
292
- i.isAuthenticated,
293
- c
349
+ a.isAuthenticated,
350
+ l
294
351
  ]);
295
- let l = async () => {
296
- o((e) => ({
352
+ let u = t(async (e) => {
353
+ s((e) => ({
297
354
  ...e,
298
355
  isLoading: !0
299
- })), await c.startAuthorization();
300
- }, u = async () => {
301
- o((e) => ({
356
+ })), await l.startAuthorization(e);
357
+ }, [l]), d = async () => {
358
+ s((e) => ({
302
359
  ...e,
303
360
  isLoading: !0
304
361
  }));
305
362
  try {
306
- await c.logout();
363
+ await l.logout();
307
364
  } catch (e) {
308
- c.log("error", "Logout error:", e);
365
+ l.log("error", "Logout error:", e);
309
366
  } finally {
310
- if (o({
367
+ if (s({
311
368
  isAuthenticated: !1,
312
369
  isLoading: !1
313
370
  }), e.logoutEndpoint && typeof window < "u") {
@@ -320,14 +377,14 @@ var f = class {
320
377
  window.location.assign(t.toString());
321
378
  }
322
379
  }
323
- }, d = async (t, n) => {
324
- o((e) => ({
380
+ }, f = async (t, n) => {
381
+ s((e) => ({
325
382
  ...e,
326
383
  isLoading: !0
327
384
  }));
328
385
  try {
329
- let r = await c.exchangeCodeForTokens(t, n);
330
- return o({
386
+ let r = await l.exchangeCodeForTokens(t, n);
387
+ return s({
331
388
  isAuthenticated: !0,
332
389
  isLoading: !1,
333
390
  tokenInfo: r
@@ -335,58 +392,63 @@ var f = class {
335
392
  window.location.assign(e.redirectRoute);
336
393
  }, 0), r;
337
394
  } catch (e) {
338
- throw o({
395
+ throw s({
339
396
  isAuthenticated: !1,
340
397
  isLoading: !1,
341
398
  error: e instanceof Error ? e.message : String(e)
342
399
  }), e;
343
400
  }
344
- }, f = {
345
- ...i,
346
- login: l,
347
- logout: u,
348
- navigateToAccountManagement: () => c.navigateToAccountManagement(),
349
- handleCallback: d,
350
- client: c
401
+ }, p = t(async () => l.getToken(), [l]), m = a.isAuthenticated ? l.getAccessToken() : null, h = a.isAuthenticated ? l.getIdToken() : null, v = a.isAuthenticated ? l.getUserProfile() : null, y = {
402
+ ...a,
403
+ accessToken: m,
404
+ idToken: h,
405
+ user: v,
406
+ login: u,
407
+ logout: d,
408
+ getToken: p,
409
+ navigateToAccountManagement: () => l.navigateToAccountManagement(),
410
+ handleCallback: f,
411
+ client: l
351
412
  };
352
- return /* @__PURE__ */ s(m.Provider, {
353
- value: f,
354
- children: t
413
+ return /* @__PURE__ */ c(_.Provider, {
414
+ value: y,
415
+ children: n
355
416
  });
356
- }, g = () => {
357
- let e = t(m);
417
+ }, y = () => {
418
+ let e = n(_);
358
419
  if (!e) throw Error("useAuth must be used within an AuthProvider");
359
420
  return e;
360
- }, _ = ({ onSuccess: e, onError: t, loadingComponent: r = /* @__PURE__ */ s("div", { children: "Processing authentication..." }) }) => {
361
- let { handleCallback: a, client: c } = g(), l = i(!1);
362
- return n(() => {
421
+ }, b = ({ onSuccess: e, onError: t, loadingComponent: n = /* @__PURE__ */ c("div", { children: "Processing authentication..." }) }) => {
422
+ let { handleCallback: i, client: o } = y(), l = a(!1);
423
+ return r(() => {
363
424
  l.current || (l.current = !0, (async () => {
364
- let n = new URLSearchParams(window.location.search), r = new URLSearchParams(window.location.hash.substring(1)), i = n.get("code") || r.get("code"), o = n.get("state") || r.get("state"), s = n.get("error") || r.get("error");
365
- if (s) {
366
- let e = /* @__PURE__ */ Error(`OAuth Error: ${s}`);
425
+ if (typeof window > "u" || !window.location) return;
426
+ let n = new URLSearchParams(window.location.search), r = new URLSearchParams(window.location.hash.substring(1)), a = n.get("code") || r.get("code"), s = n.get("state") || r.get("state"), c = n.get("error") || r.get("error");
427
+ if (c) {
428
+ let e = /* @__PURE__ */ Error(`OAuth Error: ${c}`);
367
429
  t && t(e);
368
430
  return;
369
431
  }
370
- if (i && o) try {
371
- await a(i, o), e && e();
432
+ if (a && s) try {
433
+ await i(a, s), e && e();
372
434
  } catch (e) {
373
435
  t && t(e instanceof Error ? e : Error(String(e)));
374
436
  }
375
- else if (!i) {
376
- let e = c.config;
437
+ else if (!a) {
438
+ let e = o.config;
377
439
  window.location.assign(e.redirectRoute || "/");
378
440
  }
379
441
  })());
380
442
  }, [
381
- a,
382
- c,
443
+ i,
444
+ o,
383
445
  e,
384
446
  t
385
- ]), /* @__PURE__ */ s(o, { children: r });
447
+ ]), /* @__PURE__ */ c(s, { children: n });
386
448
  };
387
449
  //#endregion
388
450
  //#region src/axios/interceptor.ts
389
- function v(e, t) {
451
+ function x(e, t) {
390
452
  e.interceptors.request.use((e) => {
391
453
  let n = t.getAccessToken();
392
454
  return n && e.headers && (e.headers.Authorization = `Bearer ${n}`), e;
@@ -405,4 +467,4 @@ function v(e, t) {
405
467
  });
406
468
  }
407
469
  //#endregion
408
- export { _ as AuthCallback, p as AuthClient, h as AuthProvider, c as STORAGE_KEYS, f as TokenStorage, d as createCodeChallenge, l as generateCodeVerifier, u as generateState, v as setupAxiosInterceptors, g as useAuth };
470
+ export { b as AuthCallback, g as AuthClient, v as AuthProvider, l as STORAGE_KEYS, h as TokenStorage, f as createCodeChallenge, m as decodeJwtPayload, u as generateCodeVerifier, d as generateState, x as setupAxiosInterceptors, y as useAuth };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.WitsAuthReactClient={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={ACCESS_TOKEN:`witsauth_access_token`,REFRESH_TOKEN:`witsauth_refresh_token`,TOKEN_EXPIRES_AT:`witsauth_expires_at`,TOKEN_TYPE:`witsauth_token_type`,SCOPE:`witsauth_scope`,CODE_VERIFIER:`witsauth_code_verifier`,STATE:`witsauth_state`,NONCE:`witsauth_nonce`};function d(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function f(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}var m=class{storageType;customStorage;memoryStorage=new Map;constructor(e,t){this.storageType=e,this.customStorage=t}getStorage(){if(this.storageType===`custom`&&this.customStorage)return this.customStorage;if(typeof window<`u`){if(this.storageType===`sessionStorage`&&window.sessionStorage)return window.sessionStorage;if((!this.storageType||this.storageType===`localStorage`)&&window.localStorage)return window.localStorage}let e=this.memoryStorage;return{getItem:t=>e.has(t)?e.get(t):null,setItem:(t,n)=>{e.set(t,n)},removeItem:t=>{e.delete(t)},clear:()=>{e.clear()}}}setItem(e,t){this.getStorage().setItem(e,t)}getItem(e){return this.getStorage().getItem(e)}removeItem(e){this.getStorage().removeItem(e)}clearTokens(){let e=this.getStorage();e.removeItem(u.ACCESS_TOKEN),e.removeItem(u.REFRESH_TOKEN),e.removeItem(u.TOKEN_EXPIRES_AT),e.removeItem(u.TOKEN_TYPE),e.removeItem(u.SCOPE)}clearTemporary(){let e=this.getStorage();e.removeItem(u.CODE_VERIFIER),e.removeItem(u.STATE),e.removeItem(u.NONCE)}},h=class{config;storage;refreshPromise=null;constructor(e,t){this.config=e,this.storage=t||new m(e.storage,e.customStorage)}log(e,...t){let n={none:0,error:1,warn:2,info:3,debug:4},r=n[this.config.logLevel||`warn`]||0,i=n[e]||0;i>0&&r>=i&&console[e](...t)}async startAuthorization(){let e=d(),t=await p(e),n=f(),r=this.config.nonce||f();this.storage.setItem(u.CODE_VERIFIER,e),this.storage.setItem(u.STATE,n),this.storage.setItem(u.NONCE,r);let i=new URLSearchParams({client_id:this.config.clientId,redirect_uri:this.config.redirectUri,response_type:this.config.responseType||`code`,code_challenge_method:this.config.codeChallengeMethod||`S256`,code_challenge:t,state:n,nonce:r});if(this.config.theme){let e=typeof this.config.theme==`function`?this.config.theme():this.config.theme;e&&i.append(`theme`,e)}this.config.scope&&i.append(`scope`,this.config.scope),this.config.audience&&i.append(`audience`,this.config.audience);let a=`${this.config.authorizationEndpoint}?${i.toString()}`;if(typeof window>`u`||!window.location)throw Error(`startAuthorization requires a browser environment to redirect the user.`);window.location.href=a}async exchangeCodeForTokens(e,t){let n=this.storage.getItem(u.STATE),r=this.storage.getItem(u.CODE_VERIFIER);if(t!==n)throw Error(`Invalid state parameter. Possible CSRF attack.`);if(!r)throw Error(`Code verifier missing from storage.`);let i=new URLSearchParams({grant_type:`authorization_code`,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,code:e,code_verifier:r}),a=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i.toString()});if(!a.ok)throw Error(`Failed to exchange code for tokens`);let o=await a.json(),s={accessToken:o.access_token,refreshToken:o.refresh_token,tokenType:o.token_type||`Bearer`,expiresAt:Date.now()+o.expires_in*1e3,scope:o.scope,idToken:o.id_token};return this.saveTokenInfo(s),this.storage.clearTemporary(),s}async refreshAccessToken(){return this.refreshPromise||=(async()=>{let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available`);let t=new URLSearchParams({grant_type:`refresh_token`,client_id:this.config.clientId,refresh_token:e}),n;try{n=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()})}catch(e){throw this.log(`error`,`Token refresh network request failed:`,e),e}if(!n.ok){let e={};try{e=await n.json()}catch{try{e={error:await n.text()}}catch{e={}}}this.log(`error`,`Token refresh failed with status:`,n.status,e),(n.status===400||n.status===401)&&this.storage.clearTokens();let t=e.error_description||e.error||n.statusText||`Failed to refresh token`;throw Error(`Failed to refresh token (${n.status}): ${t}`)}let r=await n.json(),i={accessToken:r.access_token,refreshToken:r.refresh_token||e,tokenType:r.token_type||`Bearer`,expiresAt:Date.now()+r.expires_in*1e3,scope:r.scope,idToken:r.id_token};return this.saveTokenInfo(i),i})().finally(()=>{this.refreshPromise=null}),this.refreshPromise}saveTokenInfo(e){this.storage.setItem(u.ACCESS_TOKEN,e.accessToken),this.storage.setItem(u.TOKEN_EXPIRES_AT,e.expiresAt.toString()),this.storage.setItem(u.TOKEN_TYPE,e.tokenType),e.refreshToken&&this.storage.setItem(u.REFRESH_TOKEN,e.refreshToken),e.scope&&this.storage.setItem(u.SCOPE,e.scope)}getAccessToken(){return this.storage.getItem(u.ACCESS_TOKEN)}getExpiresAt(){let e=this.storage.getItem(u.TOKEN_EXPIRES_AT);if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}isAuthenticated(){let e=this.getExpiresAt();return e!==null&&Date.now()<e-3e4&&!!this.getAccessToken()}async navigateToAccountManagement(){let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available to access account management.`);if(typeof window>`u`||!document)throw Error(`navigateToAccountManagement requires a browser environment.`);let t=document.createElement(`form`);t.method=`POST`;let n=this.config.authorizationEndpoint.replace(/\/authorize\/?$/,``);t.action=`${n}/account/session`;let r=(e,n)=>{let r=document.createElement(`input`);r.type=`hidden`,r.name=e,r.value=n,t.appendChild(r)};try{let e=await(await fetch(`${n}/csrf-token`,{credentials:`include`})).json();e.csrfToken&&r(`_csrf`,e.csrfToken)}catch(e){this.log(`warn`,`Failed to fetch CSRF token for Account Management navigation`,e)}r(`refreshToken`,e),r(`clientId`,this.config.clientId),r(`redirectUri`,this.config.redirectUri),document.body.appendChild(t),t.submit()}async logout(){let e=this.getAccessToken();if(this.storage.clearTokens(),this.storage.clearTemporary(),this.config.revokeEndpoint&&e)try{await fetch(this.config.revokeEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({token:e}).toString()})}catch(e){this.log(`error`,`Failed to revoke token during logout`,e)}}},g=(0,t.createContext)(null),_=({config:e,children:r})=>{let[i,a]=(0,t.useState)({isAuthenticated:!1,isLoading:!0}),o=(0,t.useMemo)(()=>new h(e),[e]);(0,t.useEffect)(()=>{(async()=>{try{if(o.isAuthenticated())a({isAuthenticated:!0,isLoading:!1});else try{await o.refreshAccessToken(),a({isAuthenticated:!0,isLoading:!1})}catch{a({isAuthenticated:!1,isLoading:!1})}}catch(e){a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)})}})()},[o]),(0,t.useEffect)(()=>{if(e.autoRefresh===!1||!i.isAuthenticated)return;let t=!1,n=setInterval(async()=>{if(!t)try{let n=o.getExpiresAt();if(!n)return;let r=(e.refreshThreshold??60)*1e3;if(Date.now()>=n-r){t=!0;try{let e=await o.refreshAccessToken();a(t=>({...t,tokenInfo:e}))}catch{c()}finally{t=!1}}}catch(e){o.log(`error`,`Auto-refresh error:`,e)}},1e4);return()=>clearInterval(n)},[e.autoRefresh,e.refreshThreshold,i.isAuthenticated,o]);let s=async()=>{a(e=>({...e,isLoading:!0})),await o.startAuthorization()},c=async()=>{a(e=>({...e,isLoading:!0}));try{await o.logout()}catch(e){o.log(`error`,`Logout error:`,e)}finally{if(a({isAuthenticated:!1,isLoading:!1}),e.logoutEndpoint&&typeof window<`u`){let t=new URL(e.logoutEndpoint);t.searchParams.set(`client_id`,e.clientId);let n=e.redirectUri;e.logoutRedirectRoute&&(n=new URL(e.logoutRedirectRoute,window.location.origin).toString()),t.searchParams.set(`redirect_uri`,n),window.location.assign(t.toString())}else if(e.logoutRedirectRoute&&typeof window<`u`){let t=new URL(e.logoutRedirectRoute,window.location.origin);window.location.assign(t.toString())}}},l=async(t,n)=>{a(e=>({...e,isLoading:!0}));try{let r=await o.exchangeCodeForTokens(t,n);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:r}),e.redirectRoute&&typeof window<`u`&&setTimeout(()=>{window.location.assign(e.redirectRoute)},0),r}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u={...i,login:s,logout:c,navigateToAccountManagement:()=>o.navigateToAccountManagement(),handleCallback:l,client:o};return(0,n.jsx)(g.Provider,{value:u,children:r})},v=()=>{let e=(0,t.useContext)(g);if(!e)throw Error(`useAuth must be used within an AuthProvider`);return e},y=({onSuccess:e,onError:r,loadingComponent:i=(0,n.jsx)(`div`,{children:`Processing authentication...`})})=>{let{handleCallback:a,client:o}=v(),s=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{s.current||(s.current=!0,(async()=>{let t=new URLSearchParams(window.location.search),n=new URLSearchParams(window.location.hash.substring(1)),i=t.get(`code`)||n.get(`code`),s=t.get(`state`)||n.get(`state`),c=t.get(`error`)||n.get(`error`);if(c){let e=Error(`OAuth Error: ${c}`);r&&r(e);return}if(i&&s)try{await a(i,s),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}else if(!i){let e=o.config;window.location.assign(e.redirectRoute||`/`)}})())},[a,o,e,r]),(0,n.jsx)(n.Fragment,{children:i})};function b(e,t){e.interceptors.request.use(e=>{let n=t.getAccessToken();return n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async n=>{let r=n.config;if(n.response?.status===401&&r&&!r._retry){r._retry=!0;try{let n=await t.refreshAccessToken();return r.headers&&(r.headers.Authorization=`Bearer ${n.accessToken}`),e(r)}catch(e){return t.logout(),Promise.reject(e)}}return Promise.reject(n)})}e.AuthCallback=y,e.AuthClient=h,e.AuthProvider=_,e.STORAGE_KEYS=u,e.TokenStorage=m,e.createCodeChallenge=p,e.generateCodeVerifier=d,e.generateState=f,e.setupAxiosInterceptors=b,e.useAuth=v});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.WitsAuthReactClient={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={ACCESS_TOKEN:`witsauth_access_token`,REFRESH_TOKEN:`witsauth_refresh_token`,TOKEN_EXPIRES_AT:`witsauth_expires_at`,TOKEN_TYPE:`witsauth_token_type`,SCOPE:`witsauth_scope`,ID_TOKEN:`witsauth_id_token`,CODE_VERIFIER:`witsauth_code_verifier`,STATE:`witsauth_state`,NONCE:`witsauth_nonce`};function d(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function f(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function m(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=t+`=`.repeat((4-t.length%4)%4);if(typeof atob==`function`){let e=atob(n),t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);if(typeof TextDecoder<`u`)return new TextDecoder().decode(t);let r=``;for(let e=0;e<t.length;e++)r+=String.fromCharCode(t[e]);return decodeURIComponent(escape(r))}let r=typeof globalThis<`u`?globalThis:void 0;if(r?.Buffer)return r.Buffer.from(n,`base64`).toString(`utf8`);throw Error(`Unable to decode base64url string.`)}function h(e){if(!e||typeof e!=`string`)return null;let t=e.split(`.`);if(t.length<2)return null;try{let e=m(t[1]);return JSON.parse(e)}catch{return null}}var g=class{storageType;customStorage;memoryStorage=new Map;constructor(e,t){this.storageType=e,this.customStorage=t}getStorage(){if(this.storageType===`custom`&&this.customStorage)return this.customStorage;if(typeof window<`u`)try{if(this.storageType===`sessionStorage`&&window.sessionStorage)return window.sessionStorage;if((!this.storageType||this.storageType===`localStorage`)&&window.localStorage)return window.localStorage}catch{}let e=this.memoryStorage;return{getItem:t=>e.has(t)?e.get(t):null,setItem:(t,n)=>{e.set(t,n)},removeItem:t=>{e.delete(t)},clear:()=>{e.clear()}}}setItem(e,t){this.getStorage().setItem(e,t)}getItem(e){return this.getStorage().getItem(e)}removeItem(e){this.getStorage().removeItem(e)}clearTokens(){let e=this.getStorage();e.removeItem(u.ACCESS_TOKEN),e.removeItem(u.REFRESH_TOKEN),e.removeItem(u.TOKEN_EXPIRES_AT),e.removeItem(u.TOKEN_TYPE),e.removeItem(u.SCOPE),e.removeItem(u.ID_TOKEN)}clearTemporary(){let e=this.getStorage();e.removeItem(u.CODE_VERIFIER),e.removeItem(u.STATE),e.removeItem(u.NONCE)}},_=class{config;storage;refreshPromise=null;constructor(e,t){this.config=e,this.storage=t||new g(e.storage,e.customStorage)}log(e,...t){let n={none:0,error:1,warn:2,info:3,debug:4},r=n[this.config.logLevel||`warn`]||0,i=n[e]||0;i>0&&r>=i&&console[e](...t)}async startAuthorization(e){let t=d(),n=await p(t),r=f(),i=this.config.nonce||f();this.storage.setItem(u.CODE_VERIFIER,t),this.storage.setItem(u.STATE,r),this.storage.setItem(u.NONCE,i);let a=new URLSearchParams({client_id:this.config.clientId,redirect_uri:this.config.redirectUri,response_type:this.config.responseType||`code`,code_challenge_method:this.config.codeChallengeMethod||`S256`,code_challenge:n,state:r,nonce:i});if(this.config.theme){let e=typeof this.config.theme==`function`?this.config.theme():this.config.theme;e&&a.append(`theme`,e)}let o=e?.scope??this.config.scope,s=e?.audience??this.config.audience;o&&a.append(`scope`,o),s&&a.append(`audience`,s),e?.prompt&&a.append(`prompt`,e.prompt);let c=`${this.config.authorizationEndpoint}?${a.toString()}`;if(typeof window>`u`||!window.location)throw Error(`startAuthorization requires a browser environment to redirect the user.`);window.location.href=c}async exchangeCodeForTokens(e,t){let n=this.storage.getItem(u.STATE),r=this.storage.getItem(u.CODE_VERIFIER);if(t!==n)throw Error(`Invalid state parameter. Possible CSRF attack.`);if(!r)throw Error(`Code verifier missing from storage.`);let i=new URLSearchParams({grant_type:`authorization_code`,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,code:e,code_verifier:r}),a=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i.toString()});if(!a.ok)throw Error(`Failed to exchange code for tokens`);let o=await a.json(),s={accessToken:o.access_token,refreshToken:o.refresh_token,tokenType:o.token_type||`Bearer`,expiresAt:Date.now()+o.expires_in*1e3,scope:o.scope,idToken:o.id_token};return this.saveTokenInfo(s),this.storage.clearTemporary(),s}async refreshAccessToken(){return this.refreshPromise||=(async()=>{let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available`);let t=new URLSearchParams({grant_type:`refresh_token`,client_id:this.config.clientId,refresh_token:e}),n;try{n=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()})}catch(e){throw this.log(`error`,`Token refresh network request failed:`,e),e}if(!n.ok){let e={};try{e=await n.json()}catch{try{e={error:await n.text()}}catch{e={}}}this.log(`error`,`Token refresh failed with status:`,n.status,e),(n.status===400||n.status===401)&&this.storage.clearTokens();let t=e.error_description||e.error||n.statusText||`Failed to refresh token`;throw Error(`Failed to refresh token (${n.status}): ${t}`)}let r=await n.json(),i={accessToken:r.access_token,refreshToken:r.refresh_token||e,tokenType:r.token_type||`Bearer`,expiresAt:Date.now()+r.expires_in*1e3,scope:r.scope,idToken:r.id_token};return this.saveTokenInfo(i),i})().finally(()=>{this.refreshPromise=null}),this.refreshPromise}saveTokenInfo(e){this.storage.setItem(u.ACCESS_TOKEN,e.accessToken),this.storage.setItem(u.TOKEN_EXPIRES_AT,e.expiresAt.toString()),this.storage.setItem(u.TOKEN_TYPE,e.tokenType),e.refreshToken&&this.storage.setItem(u.REFRESH_TOKEN,e.refreshToken),e.scope&&this.storage.setItem(u.SCOPE,e.scope),e.idToken&&this.storage.setItem(u.ID_TOKEN,e.idToken)}getAccessToken(){return this.storage.getItem(u.ACCESS_TOKEN)}getIdToken(){return this.storage.getItem(u.ID_TOKEN)}getRefreshToken(){return this.storage.getItem(u.REFRESH_TOKEN)}getUserProfile(){return h(this.getIdToken()||this.getAccessToken())}async getToken(){if(this.isAuthenticated())return this.getAccessToken();try{return(await this.refreshAccessToken()).accessToken}catch{return null}}getExpiresAt(){let e=this.storage.getItem(u.TOKEN_EXPIRES_AT);if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}isAuthenticated(){let e=this.getExpiresAt();return e!==null&&Date.now()<e-3e4&&!!this.getAccessToken()}async navigateToAccountManagement(){let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available to access account management.`);if(typeof window>`u`||typeof document>`u`||!document)throw Error(`navigateToAccountManagement requires a browser environment.`);let t=document.createElement(`form`);t.method=`POST`;let n=this.config.authorizationEndpoint.replace(/\/authorize\/?$/,``);t.action=`${n}/account/session`;let r=(e,n)=>{let r=document.createElement(`input`);r.type=`hidden`,r.name=e,r.value=n,t.appendChild(r)};try{let e=await(await fetch(`${n}/csrf-token`,{credentials:`include`})).json();e.csrfToken&&r(`_csrf`,e.csrfToken)}catch(e){this.log(`warn`,`Failed to fetch CSRF token for Account Management navigation`,e)}r(`refreshToken`,e),r(`clientId`,this.config.clientId),r(`redirectUri`,this.config.redirectUri),document.body.appendChild(t),t.submit()}async logout(){let e=this.getAccessToken(),t=this.storage.getItem(u.REFRESH_TOKEN);if(this.storage.clearTokens(),this.storage.clearTemporary(),this.config.revokeEndpoint){let n=[...t?[{token:t,token_type_hint:`refresh_token`}]:[],...e?[{token:e,token_type_hint:`access_token`}]:[]];for(let e of n)try{await fetch(this.config.revokeEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({token:e.token,token_type_hint:e.token_type_hint,client_id:this.config.clientId}).toString()})}catch(e){this.log(`error`,`Failed to revoke token during logout`,e)}}}},v=(0,t.createContext)(null),y=({config:e,children:r})=>{let[i,a]=(0,t.useState)({isAuthenticated:!1,isLoading:!0}),o=(0,t.useMemo)(()=>new _(e),[e]);(0,t.useEffect)(()=>{(async()=>{try{if(o.isAuthenticated())a({isAuthenticated:!0,isLoading:!1});else try{await o.refreshAccessToken(),a({isAuthenticated:!0,isLoading:!1})}catch{a({isAuthenticated:!1,isLoading:!1})}}catch(e){a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)})}})()},[o]),(0,t.useEffect)(()=>{if(e.autoRefresh===!1||!i.isAuthenticated)return;let t=!1,n=setInterval(async()=>{if(!t)try{let n=o.getExpiresAt();if(!n)return;let r=(e.refreshThreshold??60)*1e3;if(Date.now()>=n-r){t=!0;try{let e=await o.refreshAccessToken();a(t=>({...t,tokenInfo:e}))}catch{c()}finally{t=!1}}}catch(e){o.log(`error`,`Auto-refresh error:`,e)}},1e4);return()=>clearInterval(n)},[e.autoRefresh,e.refreshThreshold,i.isAuthenticated,o]);let s=(0,t.useCallback)(async e=>{a(e=>({...e,isLoading:!0})),await o.startAuthorization(e)},[o]),c=async()=>{a(e=>({...e,isLoading:!0}));try{await o.logout()}catch(e){o.log(`error`,`Logout error:`,e)}finally{if(a({isAuthenticated:!1,isLoading:!1}),e.logoutEndpoint&&typeof window<`u`){let t=new URL(e.logoutEndpoint);t.searchParams.set(`client_id`,e.clientId);let n=e.redirectUri;e.logoutRedirectRoute&&(n=new URL(e.logoutRedirectRoute,window.location.origin).toString()),t.searchParams.set(`redirect_uri`,n),window.location.assign(t.toString())}else if(e.logoutRedirectRoute&&typeof window<`u`){let t=new URL(e.logoutRedirectRoute,window.location.origin);window.location.assign(t.toString())}}},l=async(t,n)=>{a(e=>({...e,isLoading:!0}));try{let r=await o.exchangeCodeForTokens(t,n);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:r}),e.redirectRoute&&typeof window<`u`&&setTimeout(()=>{window.location.assign(e.redirectRoute)},0),r}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u=(0,t.useCallback)(async()=>o.getToken(),[o]),d=i.isAuthenticated?o.getAccessToken():null,f=i.isAuthenticated?o.getIdToken():null,p=i.isAuthenticated?o.getUserProfile():null,m={...i,accessToken:d,idToken:f,user:p,login:s,logout:c,getToken:u,navigateToAccountManagement:()=>o.navigateToAccountManagement(),handleCallback:l,client:o};return(0,n.jsx)(v.Provider,{value:m,children:r})},b=()=>{let e=(0,t.useContext)(v);if(!e)throw Error(`useAuth must be used within an AuthProvider`);return e},x=({onSuccess:e,onError:r,loadingComponent:i=(0,n.jsx)(`div`,{children:`Processing authentication...`})})=>{let{handleCallback:a,client:o}=b(),s=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{s.current||(s.current=!0,(async()=>{if(typeof window>`u`||!window.location)return;let t=new URLSearchParams(window.location.search),n=new URLSearchParams(window.location.hash.substring(1)),i=t.get(`code`)||n.get(`code`),s=t.get(`state`)||n.get(`state`),c=t.get(`error`)||n.get(`error`);if(c){let e=Error(`OAuth Error: ${c}`);r&&r(e);return}if(i&&s)try{await a(i,s),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}else if(!i){let e=o.config;window.location.assign(e.redirectRoute||`/`)}})())},[a,o,e,r]),(0,n.jsx)(n.Fragment,{children:i})};function S(e,t){e.interceptors.request.use(e=>{let n=t.getAccessToken();return n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async n=>{let r=n.config;if(n.response?.status===401&&r&&!r._retry){r._retry=!0;try{let n=await t.refreshAccessToken();return r.headers&&(r.headers.Authorization=`Bearer ${n.accessToken}`),e(r)}catch(e){return t.logout(),Promise.reject(e)}}return Promise.reject(n)})}e.AuthCallback=x,e.AuthClient=_,e.AuthProvider=y,e.STORAGE_KEYS=u,e.TokenStorage=g,e.createCodeChallenge=p,e.decodeJwtPayload=h,e.generateCodeVerifier=d,e.generateState=f,e.setupAxiosInterceptors=S,e.useAuth=b});
@@ -44,15 +44,35 @@ export interface AuthState {
44
44
  export interface UserInfo {
45
45
  sub?: string;
46
46
  name?: string;
47
+ given_name?: string;
48
+ family_name?: string;
47
49
  email?: string;
50
+ email_verified?: boolean;
51
+ phone_number?: string;
52
+ phone_number_verified?: boolean;
53
+ roles?: string[];
54
+ permissions?: string[];
55
+ amr?: string[];
56
+ act?: {
57
+ sub: string;
58
+ ticket_ref?: string;
59
+ [key: string]: any;
60
+ };
48
61
  [key: string]: any;
49
62
  }
63
+ export type UserProfile = UserInfo;
64
+ export interface AuthorizeOptions {
65
+ prompt?: 'login' | 'consent' | 'select_account';
66
+ scope?: string;
67
+ audience?: string;
68
+ }
50
69
  export declare const STORAGE_KEYS: {
51
70
  ACCESS_TOKEN: string;
52
71
  REFRESH_TOKEN: string;
53
72
  TOKEN_EXPIRES_AT: string;
54
73
  TOKEN_TYPE: string;
55
74
  SCOPE: string;
75
+ ID_TOKEN: string;
56
76
  CODE_VERIFIER: string;
57
77
  STATE: string;
58
78
  NONCE: string;
@@ -1,4 +1,4 @@
1
- import { default as React } from 'react';
1
+ import React from 'react';
2
2
  interface AuthCallbackProps {
3
3
  onSuccess?: () => void;
4
4
  onError?: (error: Error) => void;
@@ -1,9 +1,13 @@
1
- import { default as React } from 'react';
2
- import { OAuth2Config, AuthState, TokenInfo } from '../models/types';
1
+ import React from 'react';
2
+ import { AuthorizeOptions, OAuth2Config, AuthState, TokenInfo, UserInfo } from '../models/types';
3
3
  import { AuthClient } from '../core/AuthClient';
4
- interface AuthContextValue extends AuthState {
5
- login: () => Promise<void>;
4
+ export interface AuthContextValue extends AuthState {
5
+ accessToken: string | null;
6
+ idToken: string | null;
7
+ user: UserInfo | null;
8
+ login: (options?: AuthorizeOptions) => Promise<void>;
6
9
  logout: () => Promise<void>;
10
+ getToken: () => Promise<string | null>;
7
11
  navigateToAccountManagement: () => Promise<void>;
8
12
  client: AuthClient;
9
13
  handleCallback: (code: string, state: string) => Promise<TokenInfo>;
@@ -13,4 +17,3 @@ export declare const AuthProvider: React.FC<{
13
17
  children: React.ReactNode;
14
18
  }>;
15
19
  export declare const useAuth: () => AuthContextValue;
16
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witsauth/react-client",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Official React client library for integrating Witsauth Single Sign-On (SSO) authentication or similar services into your React applications.",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.es.js",
@@ -42,16 +42,14 @@
42
42
  "@types/node": "^26.1.1",
43
43
  "@types/react": "^19.2.17",
44
44
  "@types/react-dom": "^19.2.3",
45
- "@typescript/typescript6": "^6.0.2",
46
45
  "jsdom": "^29.1.1",
47
46
  "typescript": "^7.0.2",
48
47
  "vite": "^8.1.5",
49
- "vite-plugin-dts": "^5.0.3",
50
48
  "vitest": "^4.1.10"
51
49
  },
52
50
  "scripts": {
53
51
  "dev": "vite",
54
- "build": "tsc && vite build",
52
+ "build": "tsc && vite build && tsc -p tsconfig.build.json",
55
53
  "test": "vitest run"
56
54
  }
57
55
  }