@witsauth/react-client 1.3.6 → 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,15 +1,20 @@
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;
5
5
  private storage;
6
+ private refreshPromise;
6
7
  constructor(config: OAuth2Config, storage?: TokenStorage);
7
8
  log(level: 'error' | 'warn' | 'info' | 'debug', ...args: any[]): void;
8
- startAuthorization(): Promise<void>;
9
+ startAuthorization(options?: AuthorizeOptions): Promise<void>;
9
10
  exchangeCodeForTokens(code: string, state: string): Promise<TokenInfo>;
10
11
  refreshAccessToken(): Promise<TokenInfo>;
11
12
  private saveTokenInfo;
12
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>;
13
18
  getExpiresAt(): number | null;
14
19
  isAuthenticated(): boolean;
15
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,17 +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;
103
+ refreshPromise = null;
77
104
  constructor(e, t) {
78
- 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);
79
106
  }
80
107
  log(e, ...t) {
81
108
  let n = {
@@ -87,29 +114,30 @@ var f = class {
87
114
  }, r = n[this.config.logLevel || "warn"] || 0, i = n[e] || 0;
88
115
  i > 0 && r >= i && console[e](...t);
89
116
  }
90
- async startAuthorization() {
91
- let e = l(), t = await d(e), n = u(), r = this.config.nonce || u();
92
- this.storage.setItem(c.CODE_VERIFIER, e), this.storage.setItem(c.STATE, n), this.storage.setItem(c.NONCE, r);
93
- 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({
94
121
  client_id: this.config.clientId,
95
122
  redirect_uri: this.config.redirectUri,
96
123
  response_type: this.config.responseType || "code",
97
124
  code_challenge_method: this.config.codeChallengeMethod || "S256",
98
- code_challenge: t,
99
- state: n,
100
- nonce: r
125
+ code_challenge: n,
126
+ state: r,
127
+ nonce: i
101
128
  });
102
129
  if (this.config.theme) {
103
130
  let e = typeof this.config.theme == "function" ? this.config.theme() : this.config.theme;
104
- e && i.append("theme", e);
131
+ e && a.append("theme", e);
105
132
  }
106
- this.config.scope && i.append("scope", this.config.scope), this.config.audience && i.append("audience", this.config.audience);
107
- 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()}`;
108
136
  if (typeof window > "u" || !window.location) throw Error("startAuthorization requires a browser environment to redirect the user.");
109
- window.location.href = a;
137
+ window.location.href = c;
110
138
  }
111
139
  async exchangeCodeForTokens(e, t) {
112
- 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);
113
141
  if (t !== n) throw Error("Invalid state parameter. Possible CSRF attack.");
114
142
  if (!r) throw Error("Code verifier missing from storage.");
115
143
  let i = new URLSearchParams({
@@ -135,36 +163,76 @@ var f = class {
135
163
  return this.saveTokenInfo(s), this.storage.clearTemporary(), s;
136
164
  }
137
165
  async refreshAccessToken() {
138
- let e = this.storage.getItem(c.REFRESH_TOKEN);
139
- if (!e) throw Error("No refresh token available");
140
- let t = new URLSearchParams({
141
- grant_type: "refresh_token",
142
- client_id: this.config.clientId,
143
- refresh_token: e
144
- }), n = await fetch(this.config.tokenEndpoint, {
145
- method: "POST",
146
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
147
- body: t.toString()
148
- });
149
- if (!n.ok) throw this.storage.clearTokens(), Error("Failed to refresh token");
150
- let r = await n.json(), i = {
151
- accessToken: r.access_token,
152
- refreshToken: r.refresh_token || e,
153
- tokenType: r.token_type || "Bearer",
154
- expiresAt: Date.now() + r.expires_in * 1e3,
155
- scope: r.scope,
156
- idToken: r.id_token
157
- };
158
- return this.saveTokenInfo(i), i;
166
+ return this.refreshPromise ||= (async () => {
167
+ let e = this.storage.getItem(l.REFRESH_TOKEN);
168
+ if (!e) throw Error("No refresh token available");
169
+ let t = new URLSearchParams({
170
+ grant_type: "refresh_token",
171
+ client_id: this.config.clientId,
172
+ refresh_token: e
173
+ }), n;
174
+ try {
175
+ n = await fetch(this.config.tokenEndpoint, {
176
+ method: "POST",
177
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
178
+ body: t.toString()
179
+ });
180
+ } catch (e) {
181
+ throw this.log("error", "Token refresh network request failed:", e), e;
182
+ }
183
+ if (!n.ok) {
184
+ let e = {};
185
+ try {
186
+ e = await n.json();
187
+ } catch {
188
+ try {
189
+ e = { error: await n.text() };
190
+ } catch {
191
+ e = {};
192
+ }
193
+ }
194
+ this.log("error", "Token refresh failed with status:", n.status, e), (n.status === 400 || n.status === 401) && this.storage.clearTokens();
195
+ let t = e.error_description || e.error || n.statusText || "Failed to refresh token";
196
+ throw Error(`Failed to refresh token (${n.status}): ${t}`);
197
+ }
198
+ let r = await n.json(), i = {
199
+ accessToken: r.access_token,
200
+ refreshToken: r.refresh_token || e,
201
+ tokenType: r.token_type || "Bearer",
202
+ expiresAt: Date.now() + r.expires_in * 1e3,
203
+ scope: r.scope,
204
+ idToken: r.id_token
205
+ };
206
+ return this.saveTokenInfo(i), i;
207
+ })().finally(() => {
208
+ this.refreshPromise = null;
209
+ }), this.refreshPromise;
159
210
  }
160
211
  saveTokenInfo(e) {
161
- 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);
162
213
  }
163
214
  getAccessToken() {
164
- 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
+ }
165
233
  }
166
234
  getExpiresAt() {
167
- let e = this.storage.getItem(c.TOKEN_EXPIRES_AT);
235
+ let e = this.storage.getItem(l.TOKEN_EXPIRES_AT);
168
236
  if (!e) return null;
169
237
  let t = Number(e);
170
238
  return Number.isFinite(t) ? t : null;
@@ -174,9 +242,9 @@ var f = class {
174
242
  return e !== null && Date.now() < e - 3e4 && !!this.getAccessToken();
175
243
  }
176
244
  async navigateToAccountManagement() {
177
- let e = this.storage.getItem(c.REFRESH_TOKEN);
245
+ let e = this.storage.getItem(l.REFRESH_TOKEN);
178
246
  if (!e) throw Error("No refresh token available to access account management.");
179
- 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.");
180
248
  let t = document.createElement("form");
181
249
  t.method = "POST";
182
250
  let n = this.config.authorizationEndpoint.replace(/\/authorize\/?$/, "");
@@ -194,96 +262,109 @@ var f = class {
194
262
  r("refreshToken", e), r("clientId", this.config.clientId), r("redirectUri", this.config.redirectUri), document.body.appendChild(t), t.submit();
195
263
  }
196
264
  async logout() {
197
- let e = this.getAccessToken();
198
- if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint && e) try {
199
- await fetch(this.config.revokeEndpoint, {
200
- method: "POST",
201
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
202
- body: new URLSearchParams({ token: e }).toString()
203
- });
204
- } catch (e) {
205
- 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
+ }
206
287
  }
207
288
  }
208
- }, m = e(null), h = ({ config: e, children: t }) => {
209
- let [i, o] = a({
289
+ }, _ = e(null), v = ({ config: e, children: n }) => {
290
+ let [a, s] = o({
210
291
  isAuthenticated: !1,
211
292
  isLoading: !0
212
- }), c = r(() => new p(e), [e]);
213
- n(() => {
293
+ }), l = i(() => new g(e), [e]);
294
+ r(() => {
214
295
  (async () => {
215
296
  try {
216
- if (c.isAuthenticated()) o({
297
+ if (l.isAuthenticated()) s({
217
298
  isAuthenticated: !0,
218
299
  isLoading: !1
219
300
  });
220
301
  else try {
221
- await c.refreshAccessToken(), o({
302
+ await l.refreshAccessToken(), s({
222
303
  isAuthenticated: !0,
223
304
  isLoading: !1
224
305
  });
225
306
  } catch {
226
- o({
307
+ s({
227
308
  isAuthenticated: !1,
228
309
  isLoading: !1
229
310
  });
230
311
  }
231
312
  } catch (e) {
232
- o({
313
+ s({
233
314
  isAuthenticated: !1,
234
315
  isLoading: !1,
235
316
  error: e instanceof Error ? e.message : String(e)
236
317
  });
237
318
  }
238
319
  })();
239
- }, [c]), n(() => {
240
- if (e.autoRefresh === !1 || !i.isAuthenticated) return;
320
+ }, [l]), r(() => {
321
+ if (e.autoRefresh === !1 || !a.isAuthenticated) return;
241
322
  let t = !1, n = setInterval(async () => {
242
323
  if (!t) try {
243
- let n = c.getExpiresAt();
324
+ let n = l.getExpiresAt();
244
325
  if (!n) return;
245
326
  let r = (e.refreshThreshold ?? 60) * 1e3;
246
327
  if (Date.now() >= n - r) {
247
328
  t = !0;
248
329
  try {
249
- let e = await c.refreshAccessToken();
250
- o((t) => ({
330
+ let e = await l.refreshAccessToken();
331
+ s((t) => ({
251
332
  ...t,
252
333
  tokenInfo: e
253
334
  }));
254
335
  } catch {
255
- u();
336
+ d();
256
337
  } finally {
257
338
  t = !1;
258
339
  }
259
340
  }
260
341
  } catch (e) {
261
- c.log("error", "Auto-refresh error:", e);
342
+ l.log("error", "Auto-refresh error:", e);
262
343
  }
263
344
  }, 1e4);
264
345
  return () => clearInterval(n);
265
346
  }, [
266
347
  e.autoRefresh,
267
348
  e.refreshThreshold,
268
- i.isAuthenticated,
269
- c
349
+ a.isAuthenticated,
350
+ l
270
351
  ]);
271
- let l = async () => {
272
- o((e) => ({
352
+ let u = t(async (e) => {
353
+ s((e) => ({
273
354
  ...e,
274
355
  isLoading: !0
275
- })), await c.startAuthorization();
276
- }, u = async () => {
277
- o((e) => ({
356
+ })), await l.startAuthorization(e);
357
+ }, [l]), d = async () => {
358
+ s((e) => ({
278
359
  ...e,
279
360
  isLoading: !0
280
361
  }));
281
362
  try {
282
- await c.logout();
363
+ await l.logout();
283
364
  } catch (e) {
284
- c.log("error", "Logout error:", e);
365
+ l.log("error", "Logout error:", e);
285
366
  } finally {
286
- if (o({
367
+ if (s({
287
368
  isAuthenticated: !1,
288
369
  isLoading: !1
289
370
  }), e.logoutEndpoint && typeof window < "u") {
@@ -296,14 +377,14 @@ var f = class {
296
377
  window.location.assign(t.toString());
297
378
  }
298
379
  }
299
- }, d = async (t, n) => {
300
- o((e) => ({
380
+ }, f = async (t, n) => {
381
+ s((e) => ({
301
382
  ...e,
302
383
  isLoading: !0
303
384
  }));
304
385
  try {
305
- let r = await c.exchangeCodeForTokens(t, n);
306
- return o({
386
+ let r = await l.exchangeCodeForTokens(t, n);
387
+ return s({
307
388
  isAuthenticated: !0,
308
389
  isLoading: !1,
309
390
  tokenInfo: r
@@ -311,58 +392,63 @@ var f = class {
311
392
  window.location.assign(e.redirectRoute);
312
393
  }, 0), r;
313
394
  } catch (e) {
314
- throw o({
395
+ throw s({
315
396
  isAuthenticated: !1,
316
397
  isLoading: !1,
317
398
  error: e instanceof Error ? e.message : String(e)
318
399
  }), e;
319
400
  }
320
- }, f = {
321
- ...i,
322
- login: l,
323
- logout: u,
324
- navigateToAccountManagement: () => c.navigateToAccountManagement(),
325
- handleCallback: d,
326
- 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
327
412
  };
328
- return /* @__PURE__ */ s(m.Provider, {
329
- value: f,
330
- children: t
413
+ return /* @__PURE__ */ c(_.Provider, {
414
+ value: y,
415
+ children: n
331
416
  });
332
- }, g = () => {
333
- let e = t(m);
417
+ }, y = () => {
418
+ let e = n(_);
334
419
  if (!e) throw Error("useAuth must be used within an AuthProvider");
335
420
  return e;
336
- }, _ = ({ onSuccess: e, onError: t, loadingComponent: r = /* @__PURE__ */ s("div", { children: "Processing authentication..." }) }) => {
337
- let { handleCallback: a, client: c } = g(), l = i(!1);
338
- 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(() => {
339
424
  l.current || (l.current = !0, (async () => {
340
- 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");
341
- if (s) {
342
- 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}`);
343
429
  t && t(e);
344
430
  return;
345
431
  }
346
- if (i && o) try {
347
- await a(i, o), e && e();
432
+ if (a && s) try {
433
+ await i(a, s), e && e();
348
434
  } catch (e) {
349
435
  t && t(e instanceof Error ? e : Error(String(e)));
350
436
  }
351
- else if (!i) {
352
- let e = c.config;
437
+ else if (!a) {
438
+ let e = o.config;
353
439
  window.location.assign(e.redirectRoute || "/");
354
440
  }
355
441
  })());
356
442
  }, [
357
- a,
358
- c,
443
+ i,
444
+ o,
359
445
  e,
360
446
  t
361
- ]), /* @__PURE__ */ s(o, { children: r });
447
+ ]), /* @__PURE__ */ c(s, { children: n });
362
448
  };
363
449
  //#endregion
364
450
  //#region src/axios/interceptor.ts
365
- function v(e, t) {
451
+ function x(e, t) {
366
452
  e.interceptors.request.use((e) => {
367
453
  let n = t.getAccessToken();
368
454
  return n && e.headers && (e.headers.Authorization = `Bearer ${n}`), e;
@@ -381,4 +467,4 @@ function v(e, t) {
381
467
  });
382
468
  }
383
469
  //#endregion
384
- 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;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(){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=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()});if(!n.ok)throw this.storage.clearTokens(),Error(`Failed to refresh token`);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}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.6",
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
  }