@witsauth/react-client 1.0.0 → 1.1.0

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.
@@ -9,6 +9,7 @@ export declare class AuthClient {
9
9
  refreshAccessToken(): Promise<TokenInfo>;
10
10
  private saveTokenInfo;
11
11
  getAccessToken(): string | null;
12
+ getExpiresAt(): number | null;
12
13
  isAuthenticated(): boolean;
13
14
  logout(): void;
14
15
  }
package/dist/index.es.js CHANGED
@@ -144,9 +144,15 @@ var f = class {
144
144
  getAccessToken() {
145
145
  return this.storage.getItem(c.ACCESS_TOKEN);
146
146
  }
147
- isAuthenticated() {
147
+ getExpiresAt() {
148
148
  let e = this.storage.getItem(c.TOKEN_EXPIRES_AT);
149
- return e ? Date.now() < parseInt(e, 10) - 3e4 && !!this.getAccessToken() : !1;
149
+ if (!e) return null;
150
+ let t = Number(e);
151
+ return Number.isFinite(t) ? t : null;
152
+ }
153
+ isAuthenticated() {
154
+ let e = this.getExpiresAt();
155
+ return e !== null && Date.now() < e - 3e4 && !!this.getAccessToken();
150
156
  }
151
157
  logout() {
152
158
  this.storage.clearTokens(), this.storage.clearTemporary();
@@ -182,7 +188,38 @@ var f = class {
182
188
  });
183
189
  }
184
190
  })();
185
- }, [c]);
191
+ }, [c]), n(() => {
192
+ if (!e.autoRefresh || !i.isAuthenticated) return;
193
+ let t = !1, n = setInterval(async () => {
194
+ if (!t) try {
195
+ let n = c.getExpiresAt();
196
+ if (!n) return;
197
+ let r = (e.refreshThreshold ?? 60) * 1e3;
198
+ if (Date.now() >= n - r) {
199
+ t = !0;
200
+ try {
201
+ let e = await c.refreshAccessToken();
202
+ o((t) => ({
203
+ ...t,
204
+ tokenInfo: e
205
+ }));
206
+ } catch {
207
+ u();
208
+ } finally {
209
+ t = !1;
210
+ }
211
+ }
212
+ } catch (e) {
213
+ console.error("Auto-refresh error:", e);
214
+ }
215
+ }, 1e4);
216
+ return () => clearInterval(n);
217
+ }, [
218
+ e.autoRefresh,
219
+ e.refreshThreshold,
220
+ i.isAuthenticated,
221
+ c
222
+ ]);
186
223
  let l = async () => {
187
224
  o((e) => ({
188
225
  ...e,
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.react_jsx_runtime))})(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{memoryStorage=new Map;getStorage(){if(typeof window<`u`&&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()},get length(){return e.size},key:t=>Array.from(e.keys())[t]||null}}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}async startAuthorization(){let e=d(),t=await p(e),n=f(),r=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:`S256`,code_challenge:t,state:n,nonce:r});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)}isAuthenticated(){let e=this.storage.getItem(u.TOKEN_EXPIRES_AT);return e?Date.now()<parseInt(e,10)-3e4&&!!this.getAccessToken():!1}logout(){this.storage.clearTokens(),this.storage.clearTemporary()}},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,new m),[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]);let s=async()=>{a(e=>({...e,isLoading:!0})),await o.startAuthorization()},c=()=>{o.logout(),a({isAuthenticated:!1,isLoading:!1})},l=async(e,t)=>{a(e=>({...e,isLoading:!0}));try{let n=await o.exchangeCodeForTokens(e,t);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:n}),n}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u={...i,login:s,logout:c,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}=v(),o=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{o.current||(o.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`),o=t.get(`state`)||n.get(`state`),s=t.get(`error`)||n.get(`error`);if(s){let e=Error(`OAuth Error: ${s}`);r&&r(e);return}if(i&&o)try{await a(i,o),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}})())},[a,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.react_jsx_runtime))})(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{memoryStorage=new Map;getStorage(){if(typeof window<`u`&&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()},get length(){return e.size},key:t=>Array.from(e.keys())[t]||null}}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}async startAuthorization(){let e=d(),t=await p(e),n=f(),r=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:`S256`,code_challenge:t,state:n,nonce:r});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()}logout(){this.storage.clearTokens(),this.storage.clearTemporary()}},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,new m),[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||!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){console.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=()=>{o.logout(),a({isAuthenticated:!1,isLoading:!1})},l=async(e,t)=>{a(e=>({...e,isLoading:!0}));try{let n=await o.exchangeCodeForTokens(e,t);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:n}),n}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u={...i,login:s,logout:c,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}=v(),o=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{o.current||(o.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`),o=t.get(`state`)||n.get(`state`),s=t.get(`error`)||n.get(`error`);if(s){let e=Error(`OAuth Error: ${s}`);r&&r(e);return}if(i&&o)try{await a(i,o),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}})())},[a,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});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witsauth/react-client",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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",