@niid/sdk 1.0.2 → 1.0.3

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.
@@ -4,12 +4,20 @@ export declare class OAuthFlow {
4
4
  private config;
5
5
  private apiClient;
6
6
  private stateStorageKey;
7
+ private pkceStorageKey;
7
8
  constructor(config: NIIDConfig);
8
9
  initiateLogin(): void;
9
10
  exchangeCodeForToken(code: string, state?: string): Promise<TokenResponse>;
10
11
  refreshAccessToken(refreshToken: string): Promise<TokenResponse>;
12
+ /**
13
+ * POST request with automatic retry on network/server errors
14
+ */
15
+ private postWithRetry;
11
16
  private saveState;
12
17
  private getState;
13
18
  private clearState;
19
+ private savePKCE;
20
+ private getPKCE;
21
+ private clearPKCE;
14
22
  }
15
23
  //# sourceMappingURL=OAuthFlow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OAuthFlow.d.ts","sourceRoot":"","sources":["../../src/core/OAuthFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGrD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,UAAU;IAqB9B,aAAa,IAAI,IAAI;IAef,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA+B1E,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAoBtE,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,UAAU;CASnB"}
1
+ {"version":3,"file":"OAuthFlow.d.ts","sourceRoot":"","sources":["../../src/core/OAuthFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA0BrD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAAS;gBAEnB,MAAM,EAAE,UAAU;IAuB9B,aAAa,IAAI,IAAI;IAoBf,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAgC1E,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IActE;;OAEG;YACW,aAAa;IA2B3B,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,SAAS;CASlB"}
@@ -5,6 +5,8 @@ export declare class TokenManager {
5
5
  private accessTokenKey;
6
6
  private refreshTokenKey;
7
7
  private refreshTimer;
8
+ private isRefreshing;
9
+ private refreshPromise;
8
10
  constructor(storageKey?: string);
9
11
  setTokens(tokenResponse: TokenResponse): void;
10
12
  getAccessToken(): string | null;
@@ -12,6 +14,15 @@ export declare class TokenManager {
12
14
  isTokenValid(token: string | null): boolean;
13
15
  isAccessTokenValid(): boolean;
14
16
  getTokenExpiration(token: string): number | null;
17
+ /**
18
+ * Execute a refresh with lock to prevent duplicate requests.
19
+ * If refresh is already in progress, wait for it to complete.
20
+ */
21
+ executeWithRefreshLock(refreshFn: () => Promise<void>): Promise<void>;
22
+ /**
23
+ * Check if a refresh is currently in progress
24
+ */
25
+ isRefreshInProgress(): boolean;
15
26
  scheduleTokenRefresh(accessToken: string, onRefresh?: () => void): void;
16
27
  clearRefreshTimer(): void;
17
28
  clearTokens(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"TokenManager.d.ts","sourceRoot":"","sources":["../../src/core/TokenManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,aAAa,EAAE,MAAM,UAAU,CAAC;AAErD,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAA8C;gBAEtD,UAAU,GAAE,MAAe;IAMvC,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAQ7C,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IAY3C,kBAAkB,IAAI,OAAO;IAK7B,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAShD,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAmBvE,iBAAiB,IAAI,IAAI;IAOzB,WAAW,IAAI,IAAI;CAKpB"}
1
+ {"version":3,"file":"TokenManager.d.ts","sourceRoot":"","sources":["../../src/core/TokenManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,aAAa,EAAE,MAAM,UAAU,CAAC;AAErD,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAA8C;IAGlE,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,cAAc,CAA8B;gBAExC,UAAU,GAAE,MAAe;IAMvC,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAQ7C,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IAY3C,kBAAkB,IAAI,OAAO;IAK7B,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAShD;;;OAGG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3E;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAoBvE,iBAAiB,IAAI,IAAI;IAOzB,WAAW,IAAI,IAAI;CAKpB"}
package/dist/index.js CHANGED
@@ -2512,7 +2512,27 @@ function validateConfig(config) {
2512
2512
  function generateState() {
2513
2513
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
2514
2514
  }
2515
- function buildAuthorizationUrl(ssoUrl, clientId, redirectUri, scope = "profile email", state) {
2515
+ function generatePKCE() {
2516
+ const array = new Uint8Array(32);
2517
+ if (typeof window !== "undefined" && window.crypto) {
2518
+ window.crypto.getRandomValues(array);
2519
+ } else {
2520
+ for (let i = 0; i < array.length; i++) {
2521
+ array[i] = Math.floor(Math.random() * 256);
2522
+ }
2523
+ }
2524
+ const verifier = base64UrlEncode(array);
2525
+ const challenge = verifier;
2526
+ return { verifier, challenge };
2527
+ }
2528
+ function base64UrlEncode(buffer) {
2529
+ let binary = "";
2530
+ for (let i = 0; i < buffer.length; i++) {
2531
+ binary += String.fromCharCode(buffer[i]);
2532
+ }
2533
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2534
+ }
2535
+ function buildAuthorizationUrl(ssoUrl, clientId, redirectUri, scope = "profile email", state, codeChallenge) {
2516
2536
  const params = new URLSearchParams({
2517
2537
  response_type: "code",
2518
2538
  client_id: clientId,
@@ -2522,6 +2542,10 @@ function buildAuthorizationUrl(ssoUrl, clientId, redirectUri, scope = "profile e
2522
2542
  if (state) {
2523
2543
  params.append("state", state);
2524
2544
  }
2545
+ if (codeChallenge) {
2546
+ params.append("code_challenge", codeChallenge);
2547
+ params.append("code_challenge_method", "plain");
2548
+ }
2525
2549
  return `${ssoUrl}/?${params.toString()}`;
2526
2550
  }
2527
2551
  class InvalidTokenError extends Error {
@@ -2624,6 +2648,8 @@ class StorageManager {
2624
2648
  class TokenManager {
2625
2649
  constructor(storageKey = "niid") {
2626
2650
  this.refreshTimer = null;
2651
+ this.isRefreshing = false;
2652
+ this.refreshPromise = null;
2627
2653
  this.storage = new StorageManager(storageKey);
2628
2654
  this.accessTokenKey = "access_token";
2629
2655
  this.refreshTokenKey = "refresh_token";
@@ -2663,6 +2689,27 @@ class TokenManager {
2663
2689
  return null;
2664
2690
  }
2665
2691
  }
2692
+ /**
2693
+ * Execute a refresh with lock to prevent duplicate requests.
2694
+ * If refresh is already in progress, wait for it to complete.
2695
+ */
2696
+ async executeWithRefreshLock(refreshFn) {
2697
+ if (this.isRefreshing && this.refreshPromise) {
2698
+ return this.refreshPromise;
2699
+ }
2700
+ this.isRefreshing = true;
2701
+ this.refreshPromise = refreshFn().finally(() => {
2702
+ this.isRefreshing = false;
2703
+ this.refreshPromise = null;
2704
+ });
2705
+ return this.refreshPromise;
2706
+ }
2707
+ /**
2708
+ * Check if a refresh is currently in progress
2709
+ */
2710
+ isRefreshInProgress() {
2711
+ return this.isRefreshing;
2712
+ }
2666
2713
  scheduleTokenRefresh(accessToken, onRefresh) {
2667
2714
  this.clearRefreshTimer();
2668
2715
  const expiration = this.getTokenExpiration(accessToken);
@@ -2690,6 +2737,18 @@ class TokenManager {
2690
2737
  this.clearRefreshTimer();
2691
2738
  }
2692
2739
  }
2740
+ const MAX_RETRIES = 3;
2741
+ const RETRY_DELAY_MS = 1e3;
2742
+ function delay(ms) {
2743
+ return new Promise((resolve) => setTimeout(resolve, ms));
2744
+ }
2745
+ function isRetryableError(error) {
2746
+ if (!error.response) {
2747
+ return true;
2748
+ }
2749
+ const status = error.response.status;
2750
+ return status >= 500 && status < 600;
2751
+ }
2693
2752
  class OAuthFlow {
2694
2753
  constructor(config) {
2695
2754
  this.config = {
@@ -2702,27 +2761,32 @@ class OAuthFlow {
2702
2761
  storageKey: config.storageKey || "niid"
2703
2762
  };
2704
2763
  this.stateStorageKey = `${this.config.storageKey}_oauth_state_${this.config.clientId}`;
2764
+ this.pkceStorageKey = `${this.config.storageKey}_pkce_verifier_${this.config.clientId}`;
2705
2765
  this.apiClient = axios.create({
2706
2766
  baseURL: this.config.apiUrl,
2707
2767
  headers: {
2708
2768
  "Content-Type": "application/json"
2709
- }
2769
+ },
2770
+ timeout: 3e4
2771
+ // 30 second timeout
2710
2772
  });
2711
2773
  }
2712
2774
  initiateLogin() {
2713
2775
  const state = generateState();
2714
2776
  this.saveState(state);
2777
+ const { verifier, challenge } = generatePKCE();
2778
+ this.savePKCE(verifier);
2715
2779
  const authUrl = buildAuthorizationUrl(
2716
2780
  this.config.ssoUrl,
2717
2781
  this.config.clientId,
2718
2782
  this.config.redirectUri,
2719
2783
  this.config.scope,
2720
- state
2784
+ state,
2785
+ challenge
2721
2786
  );
2722
2787
  window.location.href = authUrl;
2723
2788
  }
2724
2789
  async exchangeCodeForToken(code, state) {
2725
- var _a, _b;
2726
2790
  if (state) {
2727
2791
  const savedState = this.getState();
2728
2792
  if (savedState) {
@@ -2741,16 +2805,14 @@ class OAuthFlow {
2741
2805
  if (this.config.clientSecret) {
2742
2806
  tokenData.client_secret = this.config.clientSecret;
2743
2807
  }
2744
- try {
2745
- const response = await this.apiClient.post("/oauth/token", tokenData);
2746
- return response.data;
2747
- } catch (error) {
2748
- const errorMessage = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.detail) || error.message || "Failed to exchange authorization code";
2749
- throw new Error(errorMessage);
2808
+ const pkceVerifier = this.getPKCE();
2809
+ if (pkceVerifier) {
2810
+ tokenData.code_verifier = pkceVerifier;
2811
+ this.clearPKCE();
2750
2812
  }
2813
+ return this.postWithRetry("/oauth/token", tokenData);
2751
2814
  }
2752
2815
  async refreshAccessToken(refreshToken) {
2753
- var _a, _b;
2754
2816
  const tokenData = {
2755
2817
  grant_type: "refresh_token",
2756
2818
  refresh_token: refreshToken,
@@ -2759,13 +2821,32 @@ class OAuthFlow {
2759
2821
  if (this.config.clientSecret) {
2760
2822
  tokenData.client_secret = this.config.clientSecret;
2761
2823
  }
2762
- try {
2763
- const response = await this.apiClient.post("/oauth/token", tokenData);
2764
- return response.data;
2765
- } catch (error) {
2766
- const errorMessage = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.detail) || error.message || "Failed to refresh access token";
2767
- throw new Error(errorMessage);
2824
+ return this.postWithRetry("/oauth/token", tokenData);
2825
+ }
2826
+ /**
2827
+ * POST request with automatic retry on network/server errors
2828
+ */
2829
+ async postWithRetry(url, data) {
2830
+ var _a, _b;
2831
+ let lastError = null;
2832
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
2833
+ try {
2834
+ const response = await this.apiClient.post(url, data);
2835
+ return response.data;
2836
+ } catch (error) {
2837
+ lastError = error;
2838
+ if (error.isAxiosError && isRetryableError(error)) {
2839
+ if (attempt < MAX_RETRIES) {
2840
+ console.warn(`[NIID SDK] Request failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`);
2841
+ await delay(RETRY_DELAY_MS * attempt);
2842
+ continue;
2843
+ }
2844
+ }
2845
+ const errorMessage = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.detail) || error.message || "Request failed";
2846
+ throw new Error(errorMessage);
2847
+ }
2768
2848
  }
2849
+ throw lastError || new Error("Request failed after retries");
2769
2850
  }
2770
2851
  saveState(state) {
2771
2852
  if (typeof window !== "undefined") {
@@ -2796,6 +2877,33 @@ class OAuthFlow {
2796
2877
  }
2797
2878
  }
2798
2879
  }
2880
+ savePKCE(verifier) {
2881
+ if (typeof window !== "undefined") {
2882
+ try {
2883
+ localStorage.setItem(this.pkceStorageKey, verifier);
2884
+ } catch (error) {
2885
+ console.warn("[NIID SDK] Failed to save PKCE verifier:", error);
2886
+ }
2887
+ }
2888
+ }
2889
+ getPKCE() {
2890
+ if (typeof window !== "undefined") {
2891
+ try {
2892
+ return localStorage.getItem(this.pkceStorageKey);
2893
+ } catch (error) {
2894
+ return null;
2895
+ }
2896
+ }
2897
+ return null;
2898
+ }
2899
+ clearPKCE() {
2900
+ if (typeof window !== "undefined") {
2901
+ try {
2902
+ localStorage.removeItem(this.pkceStorageKey);
2903
+ } catch (error) {
2904
+ }
2905
+ }
2906
+ }
2799
2907
  }
2800
2908
  class NIIDClient {
2801
2909
  constructor(config) {