Skip to content

Conversation

@DanielRivers
Copy link
Member

Explain your changes

Adds support to call the token helpers synchronously then the store is not promise based

Checklist

🛟 If you need help, consider asking for advice over in the Kinde community.

@DanielRivers DanielRivers requested a review from a team as a code owner October 22, 2025 23:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Adds synchronous counterparts for many token utilities and re-exports them; marks stores with an asyncStore flag; converts memory and local storage implementations to synchronous APIs; broadens Awaitable<T> to T | Promise<T>; updates tests to use sync variants and to model token absence by removal; exchangeAuthCode now errors on persist failure.

Changes

Cohort / File(s) Summary
Main exports & tests
lib/main.ts, lib/main.test.ts
Exported new *Sync variants for token utilities and updated tests to expect them.
Token utils — barrel
lib/utils/token/index.ts
Re-exported sync variants alongside async helpers (e.g., getClaimSync, getClaimsSync, getDecodedTokenSync, getRawTokenSync, getFlagSync, getFlagsSync, getPermissionSync, getPermissionsSync, getRolesSync, getUserProfileSync, getUserOrganizationsSync, getCurrentOrganizationSync).
Token utils — decoding / raw token
lib/utils/token/getDecodedToken.ts, lib/utils/token/getDecodedToken.test.ts, lib/utils/token/getRawToken.ts, lib/utils/token/getRawToken.test.ts
Exported JWTDecoded, added _decodeTokenCore, added getDecodedTokenSync and getRawTokenSync; sync helpers validate storage is synchronous and throw if active storage is async-only; tests added/adjusted for sync behavior and errors.
Token utils — claims / claim
lib/utils/token/getClaims.ts, lib/utils/token/getClaim.ts, lib/utils/token/getClaim.test.ts
Added getClaimsSync and getClaimSync; extracted _getClaimCore to centralize claim formatting; tests added for sync variants.
Token utils — organization / Hasura
lib/utils/token/getCurrentOrganization.ts, lib/utils/token/getCurrentOrganization.test.ts, lib/utils/token/getCurrentOrganizationHasura.test.ts
Introduced _getCurrentOrganizationCore and getCurrentOrganizationSync; tests added for sync extraction (standard and Hasura header path).
Token utils — flags / flags list
lib/utils/token/getFlag.ts, lib/utils/token/getFlag.test.ts, lib/utils/token/getFlags.ts, lib/utils/token/getFlags.test.ts
Added internal types and core helpers _getFlagCore / _getFlagsCore; added getFlagSync and getFlagsSync (sync disallows forceApi); tests extended for sync and guard behavior.
Token utils — permission(s)
lib/utils/token/getPermission.ts, lib/utils/token/getPermission.test.ts, lib/utils/token/getPermissions.ts, lib/utils/token/getPermissions.test.ts
Added _getPermissionCore / _getPermissionsCore; added getPermissionSync and getPermissionsSync with forceApi validation; tests added for sync variants.
Token utils — roles
lib/utils/token/getRoles.ts, lib/utils/token/getRoles.test.ts
Added _getRolesCore and getRolesSync (sync disallows forceApi); tests added for sync extraction.
Token utils — user profile & orgs
lib/utils/token/getUserProfile.ts, lib/utils/token/getUserProfile.test.ts, lib/utils/token/getUserOrganizations.ts, lib/utils/token/getUserOrganizations.test.ts, lib/utils/token/getUserOrganizationsHasura.test.ts
Added _getUserProfileCore and _getUserOrganizationsCore; added getUserProfileSync and getUserOrganizationsSync; async implementations now delegate to core helpers; tests added for sync paths.
Session types & manager
lib/sessionManager/types.ts
Changed Awaitable<T> to `T
Storage implementations — async stores
lib/sessionManager/stores/chromeStore.ts, lib/sessionManager/stores/expoSecureStore.ts
Added public asyncStore = true property to mark async-only stores.
Storage implementations — sync stores
lib/sessionManager/stores/localStorage.ts, lib/sessionManager/stores/memory.ts, lib/sessionManager/stores/localStorage.test.ts
Added asyncStore = false; converted destroySession, setSessionItem, getSessionItem, removeSessionItem from async Promise signatures to synchronous returns; increased test wait time for mixed listeners.
Misc — activity tracking & exchange flow
lib/utils/activityTracking.test.ts, lib/utils/exchangeAuthCode.ts, lib/utils/exchangeAuthCode.test.ts
Tests updated to assert direct values (removed .resolves usage); exchangeAuthCode now awaits secureStore.setItems and returns failure on persist error; added test for persist failure.
Other tests / fixtures
lib/utils/token/getEntitlements.test.ts, various token tests
Added entitlements fixture/test for empty plans/entitlements; many tests now simulate no-token by calling storage.removeSessionItem(...) instead of setSessionItem(..., null); tests import and exercise new sync helpers.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant TokenUtils
  participant SessionStore
  rect rgb(221,238,255)
    Note over TokenUtils,SessionStore: synchronous helpers (new)
    Caller->>TokenUtils: getDecodedTokenSync(tokenType)
    TokenUtils->>SessionStore: getSessionItem(tokenKey) [sync]
    alt no storage or no token
      SessionStore-->>TokenUtils: null
      TokenUtils-->>Caller: null
    else token string
      SessionStore-->>TokenUtils: tokenString
      TokenUtils->>TokenUtils: _decodeTokenCore(tokenString)
      TokenUtils-->>Caller: decodedToken
    end
  end
Loading
sequenceDiagram
  participant Caller
  participant TokenUtils
  participant SessionStore
  rect rgb(255,245,235)
    Note over TokenUtils,SessionStore: existing async helpers
    Caller->>TokenUtils: await getDecodedToken(tokenType)
    TokenUtils->>SessionStore: await getItems(tokenKey) [async]
    alt no storage or no token
      SessionStore-->>TokenUtils: null
      TokenUtils-->>Caller: null
    else token string
      SessionStore-->>TokenUtils: tokenString
      TokenUtils->>TokenUtils: _decodeTokenCore(tokenString)
      TokenUtils-->>Caller: decodedToken
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: add support for sync helpers" directly and accurately reflects the primary change in the changeset. The entire PR focuses on introducing synchronous variants (with the "Sync" suffix) of existing token helper functions such as getClaimSync, getClaimsSync, getCurrentOrganizationSync, getDecodedTokenSync, getRawTokenSync, getFlagSync, and others. The title is concise, clear, avoids vague terminology, and clearly communicates the main objective without requiring readers to examine file lists or commit details.
Description Check ✅ Passed The pull request description states that it "Adds support to call the token helpers synchronously when the store is not promise based," which directly describes the changeset. The description explains both what is being added (synchronous support for token helpers) and the condition under which they apply (when stores are not promise-based). This aligns with the structural changes throughout the PR, including the new asyncStore property added to storage classes and the numerous Sync function variants. While the description is brief and contains a minor typo, it is clearly related to the changeset and conveys meaningful information about the changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sync-helpers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
lib/utils/token/getUserProfile.ts (1)

12-38: Return type claims & T but T is never included in the object.

_core builds only UserProfile fields; consumers seeing (UserProfile & T) will assume extra fields exist. Either drop the generic or actually merge T from the token.

Option A (simplest): return only UserProfile.

-const _getUserProfileCore = <T>(
-  idToken:
-    | (T & {
-        sub: string;
-        given_name: string;
-        family_name: string;
-        email: string;
-        picture: string;
-      })
-    | null,
-): (UserProfile & T) | null => {
+const _getUserProfileCore = (
+  idToken:
+    | {
+        sub?: string;
+        given_name?: string;
+        family_name?: string;
+        email?: string;
+        picture?: string;
+      }
+    | null,
+): UserProfile | null => {
   if (!idToken) {
     return null;
   }
-  const { sub } = idToken;
-  if (!sub) {
+  const { sub } = idToken;
+  if (!sub) {
     console.error("No sub in idToken");
     return null;
   }
   return {
-    id: idToken.sub,
-    givenName: idToken.given_name,
-    familyName: idToken.family_name,
-    email: idToken.email,
-    picture: idToken.picture,
-  } as UserProfile & T;
+    id: idToken.sub,
+    givenName: idToken.given_name,
+    familyName: idToken.family_name,
+    email: idToken.email,
+    picture: idToken.picture,
+  } as UserProfile;
 };
lib/utils/token/getFlag.ts (2)

25-32: API lookup uses name instead of key; returns null erroneously.

AccountFeatureFlagsResult entries are keyed by key (see getFlags.ts usage). Matching on name likely never hits.

-    const flag = data.feature_flags.find((flag) => flag.name === name);
+    const flag = data.feature_flags.find((flag) => flag.key === name);

Add a unit test for the forceApi path to prevent regressions.


17-20: JSDoc return type is outdated.

The return type comment excludes boolean/object and mentions string[]. Update to reflect generic T | null.

- * @returns { Promise<string | number | string[] | null> }
+ * @returns { Promise<T | null> }
🧹 Nitpick comments (15)
lib/utils/token/getDecodedToken.test.ts (1)

53-65: Consider testing the async-only store error case.

The tests cover the happy path and null case well. However, consider adding a test that verifies getDecodedTokenSync throws the expected error when called with an async-only store (e.g., ChromeStore or ExpoSecureStore).

Example test to add:

it("throws error when storage is async-only", () => {
  const asyncStorage = new ChromeStore();
  setActiveStorage(asyncStorage);
  expect(() => getDecodedTokenSync("accessToken")).toThrow(
    "Active storage is async-only. Use the async helpers."
  );
});
lib/utils/token/getRawToken.test.ts (1)

60-72: Consider testing the async-only store error case.

The tests cover the happy path and null case well. However, consider adding a test that verifies getRawTokenSync returns null (based on line 26 of getRawToken.ts) when called with an async-only store, for consistency with the sync API behavior.

Example test to add:

it("returns null when storage is async-only", () => {
  const asyncStorage = new ChromeStore();
  setActiveStorage(asyncStorage);
  expect(getRawTokenSync("accessToken")).toBe(null);
});
lib/utils/token/getUserProfile.test.ts (1)

86-117: LGTM! Core scenarios covered.

The synchronous tests validate null handling and basic profile mapping. The test coverage is appropriate for the sync variant given that the core logic is shared via _getUserProfileCore.

Optionally, you could add a test for the "no sub" scenario (like the async version at lines 57-83) to ensure the sync path also handles missing subject identifiers correctly.

lib/utils/token/getUserOrganizationsHasura.test.ts (1)

49-53: Reset store state between tests.

Call destroySession in beforeEach to avoid leaking tokens from prior tests.

 describe("getUserOrganizationsSync - Hasura", () => {
   beforeEach(() => {
-    setActiveStorage(storage);
+    storage.destroySession();
+    setActiveStorage(storage);
   });
lib/utils/token/getPermission.test.ts (2)

18-26: Prefer removing the token over storing null.

Setting null is stored as a value in MemoryStorage; remove the item to represent “no token”.

-  storage.setSessionItem(StorageKeys.accessToken, null);
+  storage.removeSessionItem(StorageKeys.accessToken);

8-10: Unnecessary mock for this file.

The sync tests don’t call the account API; consider dropping this mock to reduce noise.

-vi.mock("./accountApi/callAccountApi", () => ({
-  callAccountApi: vi.fn(),
-}));
+// No API calls in sync tests; mock not required here.
lib/utils/token/getRoles.test.ts (1)

364-384: Harden the sync suite and cover error path.

  • Reset storage per test to avoid state coupling.
  • Remove the token instead of storing null.
  • Add a test asserting getRolesSync({ forceApi: true }) throws.
 describe("getRolesSync", () => {
   beforeEach(() => {
-    setActiveStorage(storage);
+    storage.destroySession();
+    setActiveStorage(storage);
   });

   it("returns [] when no token", () => {
-    storage.setSessionItem(StorageKeys.accessToken, null);
+    storage.removeSessionItem(StorageKeys.accessToken);
     expect(getRolesSync()).toStrictEqual([]);
   });

+  it("throws when forceApi is true", () => {
+    expect(() => getRolesSync({ forceApi: true })).toThrow(
+      "forceApi cannot be used in sync mode",
+    );
+  });
lib/utils/token/getCurrentOrganizationHasura.test.ts (2)

37-40: Reset store state between tests.

Add destroySession in beforeEach to guarantee isolation.

 describe("getCurrentOrganizationSync - Hasura", () => {
   beforeEach(() => {
-    setActiveStorage(storage);
+    storage.destroySession();
+    setActiveStorage(storage);
   });

41-51: Add a no-token sync case for parity.

Mirror the async “no token” scenario to assert null in sync mode.

   it("with access", () => {
     // existing test...
   });
+
+  it("when no token", () => {
+    storage.removeSessionItem(StorageKeys.accessToken);
+    expect(getCurrentOrganizationSync()).toBeNull();
+  });
lib/utils/token/getUserProfile.ts (1)

28-29: Library logging footprint.

console.error in core can be noisy; consider warn/debug or remove.

lib/utils/token/getRoles.ts (1)

15-18: Reuse existing JWTDecoded type; avoid local alias drift.

JWTDecoded already models both roles and x-hasura-roles; type _getRolesCore with it to prevent divergence. Based on learnings.

-import { getDecodedTokenSync } from "./getDecodedToken";
+import { getDecodedTokenSync } from "./getDecodedToken";
+import type { JWTDecoded } from "./getDecodedToken";
@@
-type TokenWithRoles = {
-  roles?: Role[];
-  "x-hasura-roles"?: Role[];
-} | null;
-
-const _getRolesCore = (token: TokenWithRoles): Role[] => {
+const _getRolesCore = (token: JWTDecoded | null): Role[] => {
   if (!token) {
     return [];
   }
   if (!token.roles && !token["x-hasura-roles"]) {
     console.warn(
       "No roles found in token, ensure roles have been included in the token customisation within the application settings",
     );
     return [];
   }
   return (token.roles || token["x-hasura-roles"]) as Role[];
 };
lib/utils/token/getFlags.ts (1)

47-62: Core extraction is solid; consider dedup with getFlag.

_getFlagsCore mirrors logic in getFlag._getFlagCore. Consider a single shared extractor to avoid drift.

lib/utils/token/getFlag.ts (1)

50-63: Core extractor is fine; optional dedup with getFlags.

Consider sharing a single flag-extraction helper with getFlags to reduce duplication.

lib/utils/token/getUserOrganizations.ts (1)

1-31: LGTM! Excellent refactoring.

The extraction of _getUserOrganizationsCore is a clean refactoring that eliminates code duplication between the async and sync variants while maintaining the same behavior for both paths.

lib/utils/token/index.ts (1)

11-33: LGTM! Well-organized exports.

The reorganization to group async and sync variants together improves readability and maintainability. All synchronous helper variants are properly exported.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 246d2a3 and 259da46.

📒 Files selected for processing (35)
  • lib/main.test.ts (1 hunks)
  • lib/main.ts (1 hunks)
  • lib/sessionManager/stores/chromeStore.ts (1 hunks)
  • lib/sessionManager/stores/expoSecureStore.ts (1 hunks)
  • lib/sessionManager/stores/localStorage.test.ts (1 hunks)
  • lib/sessionManager/stores/localStorage.ts (5 hunks)
  • lib/sessionManager/stores/memory.ts (4 hunks)
  • lib/sessionManager/types.ts (6 hunks)
  • lib/utils/activityTracking.test.ts (1 hunks)
  • lib/utils/token/getClaim.test.ts (2 hunks)
  • lib/utils/token/getClaim.ts (2 hunks)
  • lib/utils/token/getClaims.ts (2 hunks)
  • lib/utils/token/getCurrentOrganization.test.ts (3 hunks)
  • lib/utils/token/getCurrentOrganization.ts (2 hunks)
  • lib/utils/token/getCurrentOrganizationHasura.test.ts (2 hunks)
  • lib/utils/token/getDecodedToken.test.ts (2 hunks)
  • lib/utils/token/getDecodedToken.ts (1 hunks)
  • lib/utils/token/getFlag.test.ts (2 hunks)
  • lib/utils/token/getFlag.ts (2 hunks)
  • lib/utils/token/getFlags.test.ts (2 hunks)
  • lib/utils/token/getFlags.ts (2 hunks)
  • lib/utils/token/getPermission.test.ts (1 hunks)
  • lib/utils/token/getPermission.ts (3 hunks)
  • lib/utils/token/getPermissions.test.ts (2 hunks)
  • lib/utils/token/getPermissions.ts (3 hunks)
  • lib/utils/token/getRawToken.test.ts (2 hunks)
  • lib/utils/token/getRawToken.ts (1 hunks)
  • lib/utils/token/getRoles.test.ts (2 hunks)
  • lib/utils/token/getRoles.ts (3 hunks)
  • lib/utils/token/getUserOrganizations.test.ts (3 hunks)
  • lib/utils/token/getUserOrganizations.ts (1 hunks)
  • lib/utils/token/getUserOrganizationsHasura.test.ts (2 hunks)
  • lib/utils/token/getUserProfile.test.ts (2 hunks)
  • lib/utils/token/getUserProfile.ts (3 hunks)
  • lib/utils/token/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-02-24T16:16:36.437Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#86
File: lib/utils/token/getRoles.ts:8-23
Timestamp: 2025-02-24T16:16:36.437Z
Learning: The getDecodedToken function's return type (JWTDecoded) already includes type definitions for both traditional roles (roles) and Hasura roles (x-hasura-roles) through its union of JWTExtra and JWTExtraHasura interfaces.

Applied to files:

  • lib/utils/token/getClaims.ts
  • lib/utils/token/getCurrentOrganization.ts
  • lib/utils/token/getRoles.ts
  • lib/utils/token/getUserOrganizations.ts
  • lib/utils/token/getDecodedToken.ts
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getCurrentOrganization.test.ts
  • lib/utils/token/getDecodedToken.test.ts
  • lib/utils/token/getClaim.test.ts
  • lib/utils/token/getFlag.test.ts
  • lib/utils/token/getRawToken.test.ts
📚 Learning: 2024-10-25T14:24:05.260Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#16
File: lib/sessionManager/types.ts:75-80
Timestamp: 2024-10-25T14:24:05.260Z
Learning: All storage classes (`MemoryStorage`, `LocalStorage`, `ChromeStore`, `ExpoSecureStore`) extend `SessionBase`, inheriting the `setItems` method, so they do not need to implement `setItems` explicitly.

Applied to files:

  • lib/sessionManager/stores/expoSecureStore.ts
🧬 Code graph analysis (25)
lib/utils/token/getClaims.ts (1)
lib/utils/token/getDecodedToken.ts (2)
  • JWTDecoded (45-45)
  • getDecodedTokenSync (76-91)
lib/utils/token/getCurrentOrganization.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getDecodedToken.test.ts (3)
lib/utils/token/getDecodedToken.ts (1)
  • getDecodedTokenSync (76-91)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getPermission.test.ts (4)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getPermissionSync (25-25)
lib/utils/token/getPermission.ts (1)
  • getPermissionSync (50-60)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getRawToken.ts (1)
lib/utils/token/index.ts (1)
  • getActiveStorage (61-63)
lib/utils/token/getClaim.test.ts (2)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getClaimSync (11-11)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserOrganizationsHasura.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getUserOrganizationsSync (31-31)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserOrganizations.ts (1)
  • getUserOrganizationsSync (13-16)
lib/utils/token/getCurrentOrganization.ts (1)
lib/utils/token/getDecodedToken.ts (2)
  • getDecodedTokenSync (76-91)
  • JWTDecoded (45-45)
lib/utils/token/getClaim.ts (1)
lib/utils/token/getClaims.ts (1)
  • getClaimsSync (15-19)
lib/utils/token/getPermissions.ts (2)
lib/utils/token/getDecodedToken.ts (2)
  • JWTDecoded (45-45)
  • getDecodedTokenSync (76-91)
lib/types.ts (1)
  • GetPermissionsOptions (273-273)
lib/utils/token/getFlag.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getFlagSync (18-18)
lib/utils/token/getFlag.ts (1)
  • getFlagSync (38-48)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getFlag.ts (2)
lib/types.ts (1)
  • GetFeatureFlagsOptions (275-275)
lib/utils/token/getDecodedToken.ts (1)
  • getDecodedTokenSync (76-91)
lib/utils/token/getCurrentOrganizationHasura.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
lib/utils/token/getFlags.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getFlagsSync (20-20)
lib/utils/token/getFlags.ts (1)
  • getFlagsSync (36-45)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserProfile.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getUserProfileSync (22-22)
lib/utils/token/getUserProfile.ts (1)
  • getUserProfileSync (53-62)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getRoles.ts (2)
lib/types.ts (1)
  • GetRolesOptions (274-274)
lib/utils/token/getDecodedToken.ts (1)
  • getDecodedTokenSync (76-91)
lib/utils/token/getRawToken.test.ts (4)
lib/utils/token/index.ts (2)
  • getRawTokenSync (16-16)
  • setActiveStorage (49-55)
lib/utils/token/getRawToken.ts (1)
  • getRawTokenSync (24-42)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserOrganizations.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getUserOrganizationsSync (31-31)
lib/utils/token/getUserOrganizations.ts (1)
  • getUserOrganizationsSync (13-16)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserOrganizations.ts (1)
lib/utils/token/getDecodedToken.ts (3)
  • getDecodedToken (61-74)
  • getDecodedTokenSync (76-91)
  • JWTDecoded (45-45)
lib/utils/token/getUserProfile.ts (1)
lib/utils/token/getClaims.ts (2)
  • getClaims (9-13)
  • getClaimsSync (15-19)
lib/utils/token/getRoles.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getRolesSync (33-33)
lib/utils/token/getRoles.ts (1)
  • getRolesSync (57-64)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getPermission.ts (1)
lib/utils/token/getDecodedToken.ts (2)
  • JWTDecoded (45-45)
  • getDecodedTokenSync (76-91)
lib/utils/token/getPermissions.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getPermissionsSync (28-28)
lib/utils/token/getPermissions.ts (1)
  • getPermissionsSync (58-67)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getDecodedToken.ts (1)
lib/utils/token/index.ts (1)
  • getActiveStorage (61-63)
lib/utils/token/getFlags.ts (2)
lib/types.ts (1)
  • GetFeatureFlagsOptions (275-275)
lib/utils/token/getDecodedToken.ts (2)
  • getDecodedTokenSync (76-91)
  • JWTDecoded (45-45)
🔇 Additional comments (38)
lib/utils/activityTracking.test.ts (1)

216-226: LGTM! Test assertions simplified.

The test assertions have been refactored from the .resolves matcher pattern to directly awaiting promises and checking their resolved values. Both approaches are valid, and the new pattern is more straightforward.

lib/utils/token/getDecodedToken.ts (2)

47-59: LGTM! Good refactoring to extract common logic.

The _decodeTokenCore helper eliminates duplication between the async and sync variants while maintaining clear error handling.


76-91: LGTM! Sync implementation follows correct pattern.

The synchronous variant properly:

  • Validates storage existence
  • Checks asyncStore flag and throws a clear error for async-only stores
  • Calls getSessionItem synchronously (without await)
  • Delegates decoding to the shared _decodeTokenCore helper

The type cast on line 88 to string | null is acceptable given that token storage always contains string values.

lib/utils/token/getClaims.ts (1)

15-19: LGTM! Sync variant follows established pattern.

The getClaimsSync function correctly delegates to getDecodedTokenSync and maintains type parameter consistency with the async variant.

lib/sessionManager/stores/expoSecureStore.ts (1)

22-22: LGTM! Correctly marks store as async-only.

Setting asyncStore = true is appropriate for ExpoSecureStore since all its storage methods (getSessionItem, setSessionItem, removeSessionItem, destroySession) are asynchronous.

lib/sessionManager/stores/localStorage.test.ts (1)

291-291: LGTM! Reduces potential test flakiness.

Doubling the wait time from 10ms to 20ms provides additional buffer for the asynchronous listener (which has a 5ms delay on line 281) to complete before assertions run. This should improve test reliability.

lib/sessionManager/stores/chromeStore.ts (1)

25-25: LGTM! Correctly marks store as async-only.

Setting asyncStore = true is appropriate for ChromeStore since all its storage methods (getSessionItem, setSessionItem, removeSessionItem, destroySession) are asynchronous.

lib/utils/token/getCurrentOrganization.ts (1)

1-29: LGTM! Clean refactoring with shared core logic.

The extraction of _getCurrentOrganizationCore properly centralizes the organization code retrieval logic, and both async and sync variants correctly delegate to it. The synchronous variant follows the established pattern by calling getDecodedTokenSync.

lib/utils/token/getFlags.test.ts (1)

341-372: LGTM! Adequate test coverage for synchronous variant.

The test suite covers the essential scenarios: no token returns null, single flag extraction works, and forceApi correctly throws an error. The lighter coverage compared to the async tests is reasonable since the core extraction logic is shared via _getFlagsCore.

lib/utils/token/getClaim.test.ts (1)

32-51: LGTM! Essential scenarios covered.

The synchronous tests appropriately verify null handling and basic claim retrieval. Since the core logic is shared with the async variant via _getClaimCore, this coverage is sufficient.

lib/utils/token/getPermissions.test.ts (1)

164-191: LGTM! Comprehensive sync test coverage.

The test suite properly validates the synchronous behavior: returning empty results when no token is present, correctly extracting org code and permissions from the token, and throwing an error when forceApi is used in sync mode.

lib/utils/token/getFlag.test.ts (1)

224-266: LGTM! Key scenarios validated.

The test suite covers the critical paths for the synchronous API: null token handling, missing flags, successful flag retrieval, and the expected error when forceApi is used in sync mode.

lib/utils/token/getClaim.ts (2)

4-15: LGTM! Well-designed core helper.

The _getClaimCore function properly centralizes the claim extraction logic with correct null handling and type preservation. The type parameters T and V maintain flexibility for callers.


34-43: LGTM! Synchronous variant follows the pattern.

The getClaimSync function correctly delegates to getClaimsSync for synchronous claim retrieval and uses the shared _getClaimCore for formatting.

lib/utils/token/getUserOrganizationsHasura.test.ts (1)

54-60: LGTM on Hasura claim usage.

Correct bracket notation for x-hasura-org-codes and expected array result.

lib/utils/token/getPermission.test.ts (2)

13-16: Good isolation.

destroySession + setActiveStorage ensures clean sync test setup.


41-45: LGTM: sync-only option guard.

forceApi error path is properly tested for the sync helper.

lib/utils/token/getRoles.ts (2)

20-31: Core extractor looks good.

Clear, side-effect-limited roles resolution with sensible warnings.


57-63: Test the sync error branch.

Add a unit test asserting getRolesSync({ forceApi: true }) throws.

You can append this to getRoles.test.ts “getRolesSync” block:

 it("throws when forceApi is true", () => {
   expect(() => getRolesSync({ forceApi: true })).toThrow(
     "forceApi cannot be used in sync mode",
   );
 });
lib/utils/token/getUserProfile.ts (1)

53-62: Remove generics from both async and sync versions, or keep both as-is—the proposed asymmetric change is incorrect.

The review's proposed diff has two critical issues:

  1. Asymmetric API: It removes <T> from getUserProfileSync only, leaving getUserProfile (async) unchanged. Both functions share identical generic patterns through _getUserProfileCore and should be consistent.

  2. Incorrect return type: The diff claims the return type becomes UserProfile | null, but without an explicit T, TypeScript infers it from idToken. Since getClaimsSync returns the intersection of those claim fields, the actual return would be UserProfile & { sub, given_name, family_name, email, picture } | null—not the simpler type shown.

Test coverage shows generics are unused (no calls pass type arguments), suggesting either intentional API design for extensibility or dead code. If dead code, remove <T> from both functions. Otherwise, keep both unchanged.

Likely an incorrect or invalid review comment.

lib/utils/token/getPermission.ts (3)

2-2: Import update looks good.

Directly importing getDecodedTokenSync and JWTDecoded avoids unnecessary index indirection.


47-48: Good reuse of shared core.

Async path now funnels through _getPermissionCore; behavior remains consistent.


50-60: Sync guard and flow LGTM.

Throwing on forceApi in sync mode is consistent with other sync helpers; sync path correctly reuses the core.

lib/utils/token/getPermissions.ts (4)

18-34: Core helper handles Hasura/standard claims properly.

Nice normalization of permissions and orgCode across token variants.


55-56: Async path parity via core helper looks good.


58-67: Sync guard and reuse LGTM.

Throws on forceApi and reuses _getPermissionsCore. Consistent with other sync helpers.


6-11: The review comment is incorrect. The typing is correct as written.

The callAccountApiPaginated function returns Promise<T["data"]>, which uses indexed access to extract the data property from T. When called with AccountPermissionsResult, it returns Promise<AccountPermissionsResult["data"]>, which resolves to { org_code: string; permissions: [...] }.

At line 48, accessing data.org_code is valid because the variable data has the unwrapped type with org_code at the top level. The type definition and usage align correctly—there is no mismatch between nested and unwrapped shapes. The code is type-safe as-is.

Likely an incorrect or invalid review comment.

lib/utils/token/getFlags.ts (3)

2-2: Import addition is appropriate.

Direct import of getDecodedTokenSync and JWTDecoded is consistent with the new sync pattern.


33-34: Async path parity via _getFlagsCore.

Good consolidation; keeps behavior aligned.


36-45: Sync guard and flow LGTM.

Error on forceApi is consistent; sync path reuses core.

lib/utils/token/getFlag.ts (1)

38-48: Sync guard and reuse LGTM.

The sync path mirrors async logic and blocks forceApi as expected.

lib/main.ts (1)

29-70: All Sync helpers are properly re-exported; imports will not break.

The verification confirms that every Sync helper exported in lib/main.ts (lines 29-70) is correctly re-exported from lib/utils/token/index.ts. All 11 Sync functions are present and accessible. The public API expansion is consistent and complete.

lib/utils/token/getUserOrganizations.test.ts (2)

3-7: LGTM!

The import additions and test setup changes are appropriate. The change on line 18 to use removeItems instead of setting to null aligns better with the synchronous test path behavior.

Also applies to: 18-18


67-74: LGTM!

The test correctly validates the multiple organization codes scenario for the synchronous variant.

lib/sessionManager/types.ts (1)

47-47: LGTM! Good addition for runtime sync/async detection.

The asyncStore property enables callers to determine whether a storage implementation operates synchronously or asynchronously, which is essential for the sync helper variants.

Also applies to: 122-122

lib/sessionManager/stores/memory.ts (1)

13-13: LGTM! Clean conversion to synchronous storage.

The MemoryStorage class has been consistently converted to synchronous operations with asyncStore = false correctly indicating the synchronous behavior. All method signatures and implementations are aligned.

Also applies to: 20-23, 31-49, 56-74, 81-90

lib/main.test.ts (1)

86-109: LGTM! Complete export coverage.

All synchronous helper variants are properly included in the expected exports list, ensuring the public API surface is correctly validated.

lib/sessionManager/stores/localStorage.ts (1)

13-13: LGTM! Clean conversion to synchronous storage.

The LocalStorage class has been consistently converted to synchronous operations with asyncStore = false correctly indicating the synchronous behavior. The change from Promise.all to forEach in destroySession is appropriate for the synchronous flow.

Also applies to: 27-33, 41-64, 71-88, 95-112

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/utils/token/getUserProfile.ts (2)

12-38: Remove misleading generic parameter and unsafe cast.

The generic T parameter suggests that additional properties from T would be preserved in the return value, but the implementation only returns UserProfile fields (lines 31-37). The cast on line 37 is unsafe—it claims the returned object includes properties from T that aren't actually present.

Since neither caller uses the generic parameter meaningfully, simplify by removing it.

Apply this diff to simplify the core helper:

-const _getUserProfileCore = <T>(
+const _getUserProfileCore = (
   idToken:
-    | (T & {
+    | ({
         sub: string;
         given_name: string;
         family_name: string;
         email: string;
         picture: string;
       })
     | null,
-): (UserProfile & T) | null => {
+): UserProfile | null => {
   if (!idToken) {
     return null;
   }
   const { sub } = idToken;
   if (!sub) {
     console.error("No sub in idToken");
     return null;
   }
   return {
     id: idToken.sub,
     givenName: idToken.given_name,
     familyName: idToken.family_name,
     email: idToken.email,
     picture: idToken.picture,
-  } as UserProfile & T;
+  };
 };

1-62: Fix Prettier formatting issues before merging.

The pipeline detected Prettier formatting issues. Run prettier --write lib/utils/token/getUserProfile.ts to fix them.

#!/bin/bash
# Run Prettier to fix formatting issues
prettier --write lib/utils/token/getUserProfile.ts
🧹 Nitpick comments (2)
lib/utils/exchangeAuthCode.ts (1)

190-202: Excellent error handling improvement for token persistence.

The try/catch block properly handles storage failures and returns a clear error instead of silently continuing. This prevents the function from returning success when tokens couldn't be persisted.

Optional enhancement for consistency:

Consider calling clearTempStore() before returning the error to clean up temporary authentication values (state, nonce, codeVerifier). While these values become stale after the authorization code is consumed, cleaning them maintains consistency with the error path at line 173.

    } catch (error) {
      console.error("Failed to persist tokens to secure storage:", error);
+     await clearTempStore();
      return {
        success: false,
        error: `Failed to persist tokens: ${error}`,
      };
    }
lib/utils/token/getCurrentOrganization.test.ts (1)

34-54: Consider consolidating token cleanup in beforeEach for better isolation.

The synchronous test coverage looks solid and correctly exercises the sync path. I notice the previous review feedback has been addressed (no console.log, variable correctly named orgCode).

However, the suggestion to consolidate token cleanup into beforeEach is still relevant. This would improve test isolation and reduce duplication:

 describe("getCurrentOrganizationSync", () => {
   beforeEach(() => {
     setActiveStorage(storage);
+    storage.removeSessionItem(StorageKeys.idToken);
+    storage.removeSessionItem(StorageKeys.accessToken);
   });
   it("when no token", () => {
-    storage.removeSessionItem(StorageKeys.idToken);
-    storage.removeSessionItem(StorageKeys.accessToken);
     const orgCode = getCurrentOrganizationSync();
-
     expect(orgCode).toStrictEqual(null);
   });

   it("with access", () => {
     storage.setSessionItem(
       StorageKeys.accessToken,
       createMockAccessToken({ org_code: "org_123456" }),
     );
     const orgCode = getCurrentOrganizationSync();
     expect(orgCode).toStrictEqual(null);
   });
 });

The same pattern could optionally be applied to the async describe block at lines 12-32.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 259da46 and 597b90b.

📒 Files selected for processing (6)
  • lib/utils/exchangeAuthCode.ts (1 hunks)
  • lib/utils/token/getCurrentOrganization.test.ts (3 hunks)
  • lib/utils/token/getPermission.ts (3 hunks)
  • lib/utils/token/getRawToken.ts (1 hunks)
  • lib/utils/token/getUserOrganizations.test.ts (3 hunks)
  • lib/utils/token/getUserProfile.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/utils/token/getRawToken.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-10T23:29:36.293Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/exchangeAuthCode.ts:51-51
Timestamp: 2024-11-10T23:29:36.293Z
Learning: In `lib/utils/exchangeAuthCode.ts`, the `exchangeAuthCode` function intentionally uses `getInsecureStorage()` to store temporary authentication flow values locally between sessions. This is necessary for storing data needed for the return trip. The `getInsecureStorage()` function returns the secure storage if no active insecure storage is defined.

Applied to files:

  • lib/utils/exchangeAuthCode.ts
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getCurrentOrganization.test.ts
🧬 Code graph analysis (5)
lib/utils/exchangeAuthCode.ts (2)
lib/main.ts (1)
  • StorageKeys (84-84)
lib/sessionManager/index.ts (1)
  • StorageKeys (51-51)
lib/utils/token/getCurrentOrganization.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserOrganizations.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getUserOrganizationsSync (31-31)
lib/utils/token/getUserOrganizations.ts (1)
  • getUserOrganizationsSync (13-16)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getPermission.ts (2)
lib/utils/token/getDecodedToken.ts (2)
  • JWTDecoded (45-45)
  • getDecodedTokenSync (76-91)
lib/types.ts (1)
  • GetPermissionOptions (272-272)
lib/utils/token/getUserProfile.ts (1)
lib/utils/token/getClaims.ts (2)
  • getClaims (9-13)
  • getClaimsSync (15-19)
🪛 GitHub Actions: Build and test
lib/utils/token/getUserProfile.ts

[warning] 1-1: Prettier formatting issues detected in this file. Run 'prettier --write' to fix.

🔇 Additional comments (7)
lib/utils/token/getCurrentOrganization.test.ts (2)

3-7: LGTM! Clean import addition.

The import of getCurrentOrganizationSync is structured correctly and aligns with the new synchronous test coverage.


17-18: Good fix: properly awaiting async storage operations.

Awaiting the removeSessionItem calls ensures the tokens are fully removed before the test assertion runs, improving test reliability.

lib/utils/token/getPermission.ts (3)

47-48: LGTM: Clean refactor to use shared core helper.

The async path correctly delegates to _getPermissionCore after retrieving the token. This keeps the sync and async implementations consistent.


51-61: LGTM: Sync API correctly guards against async operations.

The implementation properly prevents forceApi usage in sync mode with a clear error message, then delegates to the same core helper as the async path. This maintains consistency across both APIs.


12-30: Hasura claims handling verified and type-safe; no changes needed.

The critical issue from the previous review has been successfully addressed. The _getPermissionCore helper now correctly handles both standard (permissions, org_code) and Hasura-specific (x-hasura-permissions, x-hasura-org-code) claims. TypeScript compilation confirms that the property accesses are type-safe—no type assertions are required. The code is correct as-is.

lib/utils/token/getUserProfile.ts (2)

1-2: LGTM!

The import of getClaimsSync is correctly added to support the new synchronous API.


40-42: Remove unnecessary parentheses from return type.

The parentheses around UserProfile on lines 40-41 are unnecessary. The return type should be Promise<UserProfile | null>.

Apply this diff:

-export const getUserProfile = async (): Promise<
-  (UserProfile) | null
-> => {
+export const getUserProfile = async (): Promise<UserProfile | null> => {

Likely an incorrect or invalid review comment.

@codecov
Copy link

codecov bot commented Oct 23, 2025

Codecov Report

❌ Patch coverage is 99.23077% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/sessionManager/stores/chromeStore.ts 0.00% 1 Missing ⚠️
lib/sessionManager/stores/expoSecureStore.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/utils/token/getUserProfile.ts (1)

12-38: Remove unnecessary generic T and fix return type.

The generic parameter T and its inclusion in the return type (UserProfile & T) | null is misleading. The implementation (lines 31-37) only extracts and returns the five UserProfile fields—it doesn't preserve or spread any additional properties from T. The cast as UserProfile & T on line 37 is incorrect.

Since both public functions call this helper without needing the generic and only expect UserProfile | null, simplify the core helper to be non-generic.

Apply this diff:

-const _getUserProfileCore = <T>(
+const _getUserProfileCore = (
   idToken:
-    | (T & {
+    | ({
         sub: string;
         given_name: string;
         family_name: string;
         email: string;
         picture: string;
       })
     | null,
-): (UserProfile & T) | null => {
+): UserProfile | null => {
   if (!idToken) {
     return null;
   }
   const { sub } = idToken;
   if (!sub) {
     console.error("No sub in idToken");
     return null;
   }
   return {
     id: idToken.sub,
     givenName: idToken.given_name,
     familyName: idToken.family_name,
     email: idToken.email,
     picture: idToken.picture,
-  } as UserProfile & T;
+  };
 };
lib/utils/token/getCurrentOrganizationHasura.test.ts (1)

16-21: Remove StorageKeys.idToken and replace with StorageKeys.accessToken (line 17).

getCurrentOrganization() reads the access token by default (via getDecodedToken() which defaults to StorageKeys.accessToken). The "when no token" test should remove the access token to properly test the no-token scenario, not the ID token. The "with access" test confirms this by setting StorageKeys.accessToken (lines 25-26).

🧹 Nitpick comments (1)
lib/utils/token/getFlag.test.ts (1)

224-266: Expand test coverage for getFlagSync to match getFlag.

The sync test suite has good structure but incomplete coverage compared to the async version. Consider adding these test cases to ensure the sync variant has equivalent coverage:

  • when name missing (see lines 38-55 for reference)
  • boolean false (see lines 74-89)
  • string (see lines 91-106)
  • integer (see lines 108-123)
  • no existing flag (see lines 125-140)

All of these scenarios are independent of async/API behavior and should work identically in sync mode.

Example implementation for the missing test cases:

it("when name missing", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({
      feature_flags: {
        test: { v: true, t: "b" },
      },
    }),
  );
  // @ts-expect-error - we want to test the case where no flag name is provided
  const flagValue = getFlagSync();
  expect(flagValue).toStrictEqual(null);
});

it("boolean false", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({
      feature_flags: {
        test: { v: false, t: "b" },
      },
    }),
  );
  const flagValue = getFlagSync<boolean>("test");
  expect(flagValue).toStrictEqual(false);
});

it("string", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({
      feature_flags: {
        test: { v: "hello", t: "s" },
      },
    }),
  );
  const flagValue = getFlagSync<string>("test");
  expect(flagValue).toStrictEqual("hello");
});

it("integer", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({
      feature_flags: {
        test: { v: 5, t: "i" },
      },
    }),
  );
  const flagValue = getFlagSync<number>("test");
  expect(flagValue).toStrictEqual(5);
});

it("no existing flag", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({
      feature_flags: {
        test: { v: 5, t: "i" },
      },
    }),
  );
  const flagValue = getFlagSync<number>("noexist");
  expect(flagValue).toStrictEqual(null);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 597b90b and f241065.

📒 Files selected for processing (8)
  • lib/utils/token/getCurrentOrganizationHasura.test.ts (3 hunks)
  • lib/utils/token/getFlag.test.ts (3 hunks)
  • lib/utils/token/getPermission.test.ts (3 hunks)
  • lib/utils/token/getPermissions.test.ts (3 hunks)
  • lib/utils/token/getRawToken.ts (1 hunks)
  • lib/utils/token/getUserOrganizations.test.ts (3 hunks)
  • lib/utils/token/getUserProfile.test.ts (3 hunks)
  • lib/utils/token/getUserProfile.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/utils/token/getPermissions.test.ts
  • lib/utils/token/getUserOrganizations.test.ts
  • lib/utils/token/getUserProfile.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getFlag.test.ts
  • lib/utils/token/getRawToken.ts
  • lib/utils/token/getPermission.test.ts
  • lib/utils/token/getCurrentOrganizationHasura.test.ts
🧬 Code graph analysis (5)
lib/utils/token/getFlag.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getFlagSync (18-18)
lib/utils/token/getFlag.ts (1)
  • getFlagSync (38-48)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getRawToken.ts (1)
lib/utils/token/index.ts (1)
  • getActiveStorage (61-63)
lib/utils/token/getPermission.test.ts (4)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getPermissionSync (25-25)
lib/utils/token/getPermission.ts (1)
  • getPermissionSync (51-61)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getUserProfile.ts (1)
lib/utils/token/getClaims.ts (2)
  • getClaims (9-13)
  • getClaimsSync (15-19)
lib/utils/token/getCurrentOrganizationHasura.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
🔇 Additional comments (8)
lib/utils/token/getRawToken.ts (1)

24-46: LGTM! Past review concern has been properly addressed.

The implementation correctly throws an error when activeStorage.asyncStore is true (lines 33-35), which resolves the inconsistency flagged in the previous review. The function now follows the established error-handling pattern used by getDecodedTokenSync and other sync helpers, preventing silent failures when async-only storage is encountered.

The overall implementation is solid:

  • Appropriate null handling for missing storage and tokens
  • Correct synchronous token retrieval (no await)
  • Consistent error messaging across sync helpers
lib/utils/token/getUserProfile.ts (2)

40-49: LGTM!

The async implementation correctly awaits getClaims and forwards to the core helper. The non-generic signature is appropriate.


51-60: LGTM!

The synchronous variant correctly mirrors the async implementation using getClaimsSync. The pattern is consistent with other sync helpers added in this PR.

lib/utils/token/getFlag.test.ts (1)

3-3: LGTM: Import updated for sync variant.

The addition of getFlagSync to the imports is correct and necessary for testing the new synchronous API.

lib/utils/token/getPermission.test.ts (3)

28-39: LGTM!

The test correctly verifies that getPermissionSync reads permission status and organization code from the token.


41-45: LGTM!

Correctly verifies that getPermissionSync throws an error when forceApi is used, since synchronous mode cannot make API calls.


8-10: The mock is necessary but tests don't exercise the API code path.

The callAccountApi mock is correctly defined and necessary—the implementation imports and uses it when forceApi option is true in getPermission(). However, the current tests never set forceApi, so they don't exercise the code path that calls the API. The mock prevents real API calls (which is good), but there's no test coverage for the forceApi behavior. This may be intentional, but consider adding a test case for getPermission() with forceApi: true to verify that code path.

lib/utils/token/getCurrentOrganizationHasura.test.ts (1)

3-7: LGTM!

The import of getCurrentOrganizationSync aligns with the PR's objective to add synchronous helper support.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/utils/token/getRawToken.test.ts (1)

60-82: Good test coverage for the sync API.

The test suite covers the essential scenarios for getRawTokenSync: no storage, successful retrieval, and async storage error handling. The tests follow existing patterns well.

Consider adding these optional test cases for completeness:

  • Sync storage with no token set (similar to the async test at lines 17-20)
  • idToken retrieval with sync API (currently only accessToken is tested)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f241065 and c79b540.

📒 Files selected for processing (4)
  • lib/utils/exchangeAuthCode.test.ts (1 hunks)
  • lib/utils/token/getDecodedToken.test.ts (2 hunks)
  • lib/utils/token/getRawToken.test.ts (2 hunks)
  • lib/utils/token/getRoles.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/utils/token/getDecodedToken.test.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getRawToken.test.ts
  • lib/utils/exchangeAuthCode.test.ts
📚 Learning: 2024-11-10T23:29:36.293Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/exchangeAuthCode.ts:51-51
Timestamp: 2024-11-10T23:29:36.293Z
Learning: In `lib/utils/exchangeAuthCode.ts`, the `exchangeAuthCode` function intentionally uses `getInsecureStorage()` to store temporary authentication flow values locally between sessions. This is necessary for storing data needed for the return trip. The `getInsecureStorage()` function returns the secure storage if no active insecure storage is defined.

Applied to files:

  • lib/utils/exchangeAuthCode.test.ts
🧬 Code graph analysis (3)
lib/utils/token/getRawToken.test.ts (4)
lib/utils/token/index.ts (3)
  • clearActiveStorage (76-78)
  • getRawTokenSync (16-16)
  • setActiveStorage (49-55)
lib/utils/token/getRawToken.ts (1)
  • getRawTokenSync (24-46)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/exchangeAuthCode.test.ts (1)
lib/utils/exchangeAuthCode.ts (1)
  • exchangeAuthCode (67-241)
lib/utils/token/getRoles.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getRolesSync (33-33)
lib/utils/token/getRoles.ts (1)
  • getRolesSync (57-64)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
🔇 Additional comments (5)
lib/utils/token/getRoles.test.ts (2)

3-3: LGTM: Import correctly updated for sync API.

The addition of getRolesSync to the imports aligns with the PR's objective of adding synchronous counterparts for token utilities.


364-391: Verify behavioral inconsistency between async and sync versions.

The test coverage for getRolesSync is well-structured and covers key scenarios. However, there's a notable behavioral difference:

  • Async version (Line 24): getRoles() throws "Authentication token not found." when no token exists.
  • Sync version (Line 371): getRolesSync() returns [] when no token exists.

Additionally, the sync test suite lacks an afterEach cleanup block (present in the async suite at lines 17-20), which could lead to test pollution if storage state persists across tests.

Consider adding cleanup to prevent state leakage:

 describe("getRolesSync", () => {
   beforeEach(() => {
     setActiveStorage(storage);
   });
+
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });

Please verify that the behavioral difference (throw vs. return []) is intentional and aligns with the expected user experience for sync vs. async APIs.

lib/utils/token/getRawToken.test.ts (2)

2-4: LGTM - Import additions support the new sync API tests.

The addition of getRawTokenSync and clearActiveStorage imports are necessary and correctly placed for the new test suite.


74-81: Error message verification complete — no issues found.

The error message in the test matches the implementation exactly. The test correctly verifies that using async storage with the sync API throws the expected error.

lib/utils/exchangeAuthCode.test.ts (1)

580-620: Confirm whether temporary storage cleanup on persistence failure is intentional.

The verification reveals that when secureStore.setItems() fails at line 196, the catch block returns immediately without calling clearTempStore(). This means state and codeVerifier remain in storage after a persistence failure.

The test correctly validates the error path, but does not verify the state of temporary storage post-failure. Clarify:

  1. Is leaving temporary storage intact intentional (to support retry scenarios)?
  2. Or should clearTempStore() be called in the catch block before returning?

If temporary storage should be cleared, the implementation needs a fix and the test should verify that cleanup occurs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/utils/token/getEntitlements.test.ts (1)

33-47: Consider fixing the naming convention.

The variable names use "Plansand" which should be "PlansAnd" (capital A) to follow camelCase convention properly.

Apply this diff to improve naming consistency:

-const mockEntitlementsAPINoPlansandEntitlementsResponse = {
+const mockEntitlementsAPINoPlansAndEntitlementsResponse = {
   data: {
     org_code: "org_0195ac80a14e",
   },
   metadata: {
     has_more: false,
     next_page_starting_after: "entitlement_0195ac80a14e8d71f42b98e75d3c61ad",
   },
 };
 
-const expectedResponseNoPlansandEntitlements = {
+const expectedResponseNoPlansAndEntitlements = {
   orgCode: "org_0195ac80a14e",
   plans: [],
   entitlements: [],
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c79b540 and fe187c3.

📒 Files selected for processing (3)
  • lib/utils/token/getDecodedToken.test.ts (2 hunks)
  • lib/utils/token/getEntitlements.test.ts (2 hunks)
  • lib/utils/token/getPermission.test.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/utils/token/getDecodedToken.test.ts
  • lib/utils/token/getPermission.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
lib/utils/token/getEntitlements.test.ts (1)
lib/utils/token/getEntitlements.ts (1)
  • getEntitlements (18-41)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/utils/token/getPermissions.test.ts (1)

24-32: Fix token removal: change StorageKeys.idToken to StorageKeys.accessToken on line 25.

The getPermissions() function defaults to reading from StorageKeys.accessToken (via getDecodedToken), not idToken. The test at line 25 removes the wrong token. The sync test at line 170 correctly removes accessToken.

- await storage.removeSessionItem(StorageKeys.idToken);
+ await storage.removeSessionItem(StorageKeys.accessToken);
🧹 Nitpick comments (5)
lib/utils/token/getRoles.test.ts (1)

364-391: Consider expanding test coverage for edge cases.

The sync test suite has minimal coverage (3 tests) compared to the async version (~20+ tests). Consider adding tests for:

  • When getDecodedTokenSync() returns null (similar to async test at line 345)
  • When token has x-hasura-roles instead of roles (similar to async tests at lines 283, 310)
  • When token has roles: undefined or other edge cases

This would ensure parity in robustness between sync and async variants.

lib/utils/token/getClaim.test.ts (1)

32-51: LGTM! Synchronous test suite correctly mirrors async tests.

The new getClaimSync test suite properly tests the synchronous API by:

  • Correctly omitting await keywords for synchronous operations
  • Mirroring the async test cases for consistency
  • Covering both no-token and claim-retrieval scenarios

The test structure is sound and should validate the synchronous helper functionality effectively.

Optional: Consider reducing duplication.

The sync and async test suites have significant code duplication. While this is typical for testing both variants, you could optionally refactor to use a shared test factory or helper function if this pattern repeats across other test files. However, the current approach is clear and maintainable as-is.

lib/utils/token/getFlags.test.ts (1)

341-372: Expand test coverage for sync variant.

The sync test suite currently covers only 3 scenarios (no token, single boolean flag, forceApi error), while the async suite covers 11+ scenarios. Although getFlagsSync shares core logic via _getFlagsCore, the sync code path should be independently verified.

Consider adding sync tests for:

  • No flags in token (null feature_flags)
  • Empty flags object (feature_flags: {})
  • Different flag types: string, integer, object
  • Multiple flags with mixed types
  • Hasura-style flags (x-hasura-feature-flags)
  • Preference of feature_flags over x-hasura-feature-flags

Example test to add:

+  it("when no flags in token", () => {
+    storage.setSessionItem(
+      StorageKeys.accessToken,
+      createMockAccessToken({
+        feature_flags: null,
+      }),
+    );
+    const flags = getFlagsSync();
+    expect(flags).toStrictEqual(null);
+  });
+
+  it("single string flag", () => {
+    storage.setSessionItem(
+      StorageKeys.accessToken,
+      createMockAccessToken({
+        feature_flags: {
+          theme: { v: "dark", t: "string" },
+        },
+      }),
+    );
+    const flags = getFlagsSync();
+    expect(flags).toStrictEqual([
+      { key: "theme", value: "dark", type: "string" },
+    ]);
+  });
lib/utils/token/getPermission.test.ts (1)

59-59: Optional cleanup: Remove redundant idToken clearing.

Lines 59 and 72 remove idToken, but permission functions only read accessToken (as correctly removed on lines 60 and 73). The idToken removal is harmless but unnecessary.

Apply this diff if you'd like to simplify:

   it("when no token", async () => {
-    storage.removeSessionItem(StorageKeys.idToken);
     storage.removeSessionItem(StorageKeys.accessToken);
   it("when no token with enum", async () => {
-    storage.removeSessionItem(StorageKeys.idToken);
     storage.removeSessionItem(StorageKeys.accessToken);

Also applies to: 72-72

lib/utils/token/getPermissions.test.ts (1)

164-191: Consider adding typed permissions test for completeness.

While the current sync test coverage validates essential scenarios, consider adding a test for typed permissions (similar to lines 47-58 in the async suite) to ensure type parameter handling works correctly in sync mode:

it("with typed permissions", () => {
  storage.setSessionItem(
    StorageKeys.accessToken,
    createMockAccessToken({ permissions: ["canEdit"] }),
  );
  const result = getPermissionsSync<PermissionEnum>();
  expect(result.permissions).toStrictEqual([PermissionEnum.canEdit]);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe187c3 and 6d9ea20.

📒 Files selected for processing (10)
  • lib/utils/token/getClaim.test.ts (3 hunks)
  • lib/utils/token/getEntitlements.test.ts (2 hunks)
  • lib/utils/token/getFlagHasura.test.ts (1 hunks)
  • lib/utils/token/getFlags.test.ts (3 hunks)
  • lib/utils/token/getPermission.test.ts (4 hunks)
  • lib/utils/token/getPermissions.test.ts (3 hunks)
  • lib/utils/token/getPermissionsHasura.test.ts (1 hunks)
  • lib/utils/token/getRoles.test.ts (2 hunks)
  • lib/utils/token/has/has.test.ts (1 hunks)
  • lib/utils/token/has/hasRoles.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/utils/token/getEntitlements.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getClaim.test.ts
  • lib/utils/token/has/has.test.ts
  • lib/utils/token/has/hasRoles.test.ts
  • lib/utils/token/getFlagHasura.test.ts
  • lib/utils/token/getPermissionsHasura.test.ts
  • lib/utils/token/getPermission.test.ts
🧬 Code graph analysis (6)
lib/utils/token/getClaim.test.ts (2)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getClaimSync (11-11)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/has/has.test.ts (1)
lib/main.ts (1)
  • StorageKeys (84-84)
lib/utils/token/getPermissions.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getPermissionsSync (28-28)
lib/utils/token/getPermissions.ts (1)
  • getPermissionsSync (58-67)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getFlags.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getFlagsSync (20-20)
lib/utils/token/getFlags.ts (1)
  • getFlagsSync (36-45)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getPermission.test.ts (5)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/index.ts (3)
  • setActiveStorage (49-55)
  • getPermissionSync (25-25)
  • getPermission (24-24)
lib/utils/token/getPermission.ts (2)
  • getPermissionSync (51-61)
  • getPermission (37-49)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/accountApi/callAccountApi.ts (1)
  • callAccountApi (8-43)
lib/utils/token/getRoles.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getRolesSync (33-33)
lib/utils/token/getRoles.ts (1)
  • getRolesSync (57-64)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
🔇 Additional comments (17)
lib/utils/token/has/has.test.ts (1)

22-32: LGTM! Improved token-absence semantics.

Using removeSessionItem instead of setting the token to null is semantically clearer and aligns with token-absence patterns used throughout the codebase.

lib/utils/token/has/hasRoles.test.ts (1)

23-23: LGTM! More explicit test setup for token absence.

Using removeSessionItem instead of setting the token to null is semantically clearer and better represents the "no token" scenario. This aligns with the sync helper patterns introduced in this PR.

lib/utils/token/getRoles.test.ts (2)

3-3: LGTM!

The import correctly adds getRolesSync alongside the existing getRoles import.


369-372: Verify behavioral difference: async throws, sync returns empty array.

The async version getRoles() throws "Authentication token not found" when no token is present (line 24), but the sync version getRolesSync() returns an empty array. Ensure this behavioral divergence is intentional and aligns with the design goals for synchronous APIs.

lib/utils/token/getClaim.test.ts (2)

2-2: LGTM! Import addition is correct.

The addition of getClaimSync to the imports aligns with the PR objective to add synchronous helper functions.


14-14: Good semantic improvement.

Using removeSessionItem is more explicit and semantically correct than setting the token to null. This improves code clarity.

lib/utils/token/getFlags.test.ts (2)

3-3: LGTM: Import addition is correct.

The addition of getFlagsSync to the imports is necessary and properly structured for the new synchronous test suite.


21-21: LGTM: Clearer token removal semantics.

Using removeSessionItem explicitly removes the token rather than setting it to null, which better reflects the intent of testing the "no token" scenario.

lib/utils/token/getFlagHasura.test.ts (2)

14-14: LGTM: Semantically clearer token removal.

Using removeSessionItem is more explicit and appropriate than setting the token to null.


1-104: Now let me search for more specific information about the test file structure and whether sync tests for Hasura scenarios should exist:Let me run a more targeted script to see the full structure of both test files:

Add sync test coverage for Hasura feature flag scenarios in getFlagHasura.test.ts.

Sync tests for getFlagSync exist in lib/utils/token/getFlag.test.ts, but the Hasura-specific test file (getFlagHasura.test.ts) lacks corresponding sync test coverage. Since this file tests Hasura-specific flag scenarios (e.g., x-hasura-feature-flags), add a describe("getFlagSync - Hasura", ...) block that mirrors the async test scenarios to ensure the synchronous path is validated for all Hasura flag types and edge cases.

lib/utils/token/getPermission.test.ts (2)

14-48: LGTM! Comprehensive coverage of sync permission API.

The test suite properly validates:

  • Fallback behavior when no token exists
  • Permission reading from stored access tokens
  • Error handling for the unsupported forceApi option in sync mode

133-158: LGTM! Proper validation of API-based permission fetching.

The test correctly verifies:

  • The forceApi path invokes callAccountApi
  • Permission keys with special characters are properly URL-encoded (space → %20, slash → %2F)
  • The API response is returned as expected
lib/utils/token/getPermissions.test.ts (5)

1-3: LGTM: Import changes align with sync helper additions.

The addition of getPermissionsSync to the imports is consistent with the PR's objective to add synchronous counterparts for token utilities.


164-167: LGTM: Appropriate setup for sync tests.

The beforeEach setup correctly initializes storage without mock fetch configuration, which is appropriate since sync operations cannot make API calls.


169-175: LGTM: Correctly tests empty state for sync mode.

The test appropriately uses synchronous removeSessionItem (without await) and validates the expected empty state structure. This correctly removes accessToken, which is the token containing permissions.


177-184: LGTM: Validates synchronous permission retrieval.

The test correctly uses synchronous storage operations and validates that permissions are properly extracted from the access token in sync mode.


186-190: LGTM: Critical constraint validation for sync mode.

This test is essential for ensuring that API-based operations (forceApi) are properly rejected in synchronous mode, preventing runtime issues from attempting async operations in a sync context.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/utils/token/getCurrentOrganizationHasura.test.ts (1)

16-21: Fix token key mismatch in "when no token" test.

The async test removes StorageKeys.idToken (line 17), but both "with access" tests set StorageKeys.accessToken (lines 25, 50), and the sync "when no token" test removes StorageKeys.accessToken (line 43). Since getCurrentOrganization extracts the org code from the access token's Hasura claim, this test should remove the access token for consistency.

Apply this diff:

   it("when no token", async () => {
-    await storage.removeSessionItem(StorageKeys.idToken);
+    await storage.removeSessionItem(StorageKeys.accessToken);
     const idToken = await getCurrentOrganization();
🧹 Nitpick comments (1)
lib/utils/token/getRawToken.test.ts (1)

60-88: LGTM: Comprehensive test coverage for sync helper.

The test suite properly covers the key scenarios:

  • Absence of active storage
  • Token not set (modeling absence by not setting, not by setting null—aligns with the PR's approach)
  • Successful token retrieval
  • Error handling for async-only storage

Optional: Consider adding explicit test for default parameter.

The default tokenType parameter (which defaults to "accessToken") is implicitly tested in the error case at line 84, but a happy-path test calling getRawTokenSync() without arguments would make the coverage more explicit:

it("returns accessToken by default when no tokenType specified", () => {
  const storage = new MemoryStorage();
  setActiveStorage(storage);
  const mockedToken = createMockAccessToken({ unique: "default" });
  storage.setSessionItem(StorageKeys.accessToken, mockedToken);
  expect(getRawTokenSync()).toBe(mockedToken);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9ea20 and a0d361c.

📒 Files selected for processing (4)
  • lib/utils/token/getCurrentOrganizationHasura.test.ts (3 hunks)
  • lib/utils/token/getPermission.test.ts (4 hunks)
  • lib/utils/token/getRawToken.test.ts (2 hunks)
  • lib/utils/token/getRoles.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/utils/token/getRoles.test.ts
  • lib/utils/token/getPermission.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getRawToken.test.ts
  • lib/utils/token/getCurrentOrganizationHasura.test.ts
🧬 Code graph analysis (2)
lib/utils/token/getRawToken.test.ts (4)
lib/utils/token/index.ts (3)
  • clearActiveStorage (76-78)
  • getRawTokenSync (16-16)
  • setActiveStorage (49-55)
lib/utils/token/getRawToken.ts (1)
  • getRawTokenSync (24-46)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getCurrentOrganizationHasura.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
🔇 Additional comments (3)
lib/utils/token/getCurrentOrganizationHasura.test.ts (2)

3-7: LGTM: Imports correctly updated for sync test support.

The addition of getCurrentOrganizationSync and setActiveStorage to the imports is appropriate for the new synchronous test cases.


37-59: Well-structured sync test coverage.

The synchronous test block includes both "when no token" and "with access" scenarios, properly mirroring the async test structure and addressing the previous review feedback. The tests correctly use synchronous operations without await and validate both null return for missing tokens and correct Hasura org code extraction.

lib/utils/token/getRawToken.test.ts (1)

2-2: LGTM: Import updates are correct.

The new imports getRawTokenSync and clearActiveStorage are properly used in the test suite below and necessary for testing the synchronous API surface.

Also applies to: 4-4

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/utils/token/getCurrentOrganizationHasura.test.ts (1)

16-21: Incorrect storage key removed in async "when no token" test.

Line 17 removes StorageKeys.idToken, but getCurrentOrganization() reads the Hasura org code from the access token's x-hasura-org-code claim. This is inconsistent with:

  • Line 24, which sets StorageKeys.accessToken in the "with access" test
  • Line 43, which correctly removes StorageKeys.accessToken in the sync variant's "when no token" test

Apply this diff to fix the inconsistency:

  it("when no token", async () => {
-   await storage.removeSessionItem(StorageKeys.idToken);
+   await storage.removeSessionItem(StorageKeys.accessToken);
    const idToken = await getCurrentOrganization();

    expect(idToken).toStrictEqual(null);
  });
🧹 Nitpick comments (4)
lib/utils/token/getRoles.test.ts (2)

385-390: Remove unnecessary token removal.

Line 386 removes the token before testing that forceApi throws in sync mode. Since getRolesSync should throw immediately when forceApi: true is passed (before checking for a token), this removal is unnecessary.

Apply this diff:

 it("can't forceApi in sync request", () => {
-  storage.removeSessionItem(StorageKeys.accessToken);
   expect(() => getRolesSync({ forceApi: true })).toThrow(
     "forceApi cannot be used in sync mode",
   );
 });

364-391: Consider adding afterEach for pattern consistency.

The async test suite includes an afterEach block (lines 17-20) for cleanup, but the sync test suite omits it. While the sync tests don't use mocks that require cleanup, adding a minimal afterEach would maintain pattern consistency across the test file.

Example:

afterEach(() => {
  vi.restoreAllMocks();
});
lib/utils/token/getPermission.test.ts (2)

50-52: Add storage.destroySession() for consistent test cleanup.

The sync tests (line 11) call storage.destroySession() in beforeEach, but the async tests do not. While each test explicitly sets or removes tokens, omitting cleanup could lead to state bleeding between tests.

Apply this diff to add cleanup:

 describe("getPermission", () => {
   beforeEach(() => {
+    storage.destroySession();
     setActiveStorage(storage);
   });

54-55: Consider removing only the relevant token.

These tests remove both idToken and accessToken, but getPermission only reads from accessToken. Removing idToken is unnecessary (though harmless).

Apply this diff to remove only the relevant token:

   it("when no token", async () => {
-    storage.removeSessionItem(StorageKeys.idToken);
     storage.removeSessionItem(StorageKeys.accessToken);
   it("when no token with enum", async () => {
-    storage.removeSessionItem(StorageKeys.idToken);
     storage.removeSessionItem(StorageKeys.accessToken);

Also applies to: 67-68

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0d361c and 48b25f6.

📒 Files selected for processing (4)
  • lib/utils/token/getCurrentOrganizationHasura.test.ts (3 hunks)
  • lib/utils/token/getPermission.test.ts (4 hunks)
  • lib/utils/token/getRawToken.test.ts (2 hunks)
  • lib/utils/token/getRoles.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/utils/token/getRawToken.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-19T20:31:59.197Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#11
File: lib/utils/token/index.test.ts:59-62
Timestamp: 2024-11-19T20:31:59.197Z
Learning: In `lib/utils/token/index.test.ts`, the test "getInsecureStorage returns active storage when no insecure storage is set" is intentional. It verifies that when insecure storage is not set, `getInsecureStorage()` returns the active storage as a fallback, preferring secure storage but allowing for insecure storage when needed by consumers of the library.

Applied to files:

  • lib/utils/token/getPermission.test.ts
  • lib/utils/token/getCurrentOrganizationHasura.test.ts
🧬 Code graph analysis (3)
lib/utils/token/getPermission.test.ts (5)
lib/sessionManager/stores/memory.ts (1)
  • MemoryStorage (9-91)
lib/utils/token/index.ts (3)
  • setActiveStorage (49-55)
  • getPermissionSync (25-25)
  • getPermission (24-24)
lib/utils/token/getPermission.ts (2)
  • getPermissionSync (51-61)
  • getPermission (37-49)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/accountApi/callAccountApi.ts (1)
  • callAccountApi (8-43)
lib/utils/token/getCurrentOrganizationHasura.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getCurrentOrganizationSync (14-14)
lib/utils/token/getCurrentOrganization.ts (1)
  • getCurrentOrganizationSync (17-20)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
lib/utils/token/getRoles.test.ts (3)
lib/utils/token/index.ts (2)
  • setActiveStorage (49-55)
  • getRolesSync (33-33)
lib/utils/token/getRoles.ts (1)
  • getRolesSync (57-64)
lib/utils/token/testUtils/index.ts (1)
  • createMockAccessToken (23-44)
🔇 Additional comments (6)
lib/utils/token/getCurrentOrganizationHasura.test.ts (1)

37-59: LGTM! Sync test coverage correctly implemented.

The synchronous test block properly mirrors the async tests with both "when no token" and "with access" scenarios. The sync operations correctly avoid await, and the test structure aligns with patterns established in other token utility tests.

lib/utils/token/getRoles.test.ts (1)

3-3: Import addition looks good.

The addition of getRolesSync to the imports is appropriate for the new synchronous test suite.

lib/utils/token/getPermission.test.ts (4)

1-5: LGTM!

The imports are properly updated to support the new synchronous tests and API mocking functionality.


9-43: LGTM!

The synchronous test suite is well-structured and comprehensive. It properly covers the no-token case, successful token read, and the forceApi error path. The use of removeSessionItem and destroySession ensures clean test state.


124-126: LGTM!

The mock setup for callAccountApi is properly configured and will be hoisted by Vitest. This enables the forceApi tests to verify API interaction without actual network calls.


128-153: Excellent test coverage for API encoding!

This test properly verifies that permission keys with special characters (spaces, slashes) are URL-encoded before being passed to the account API. The test choice of "view reports/advanced" is particularly good as it exercises multiple encoding scenarios.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48b25f6 and f2d8e30.

📒 Files selected for processing (2)
  • lib/utils/token/getRoles.test.ts (2 hunks)
  • lib/utils/token/getRoles.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/utils/token/getRoles.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-24T16:16:36.437Z
Learnt from: DanielRivers
PR: kinde-oss/js-utils#86
File: lib/utils/token/getRoles.ts:8-23
Timestamp: 2025-02-24T16:16:36.437Z
Learning: The getDecodedToken function's return type (JWTDecoded) already includes type definitions for both traditional roles (roles) and Hasura roles (x-hasura-roles) through its union of JWTExtra and JWTExtraHasura interfaces.

Applied to files:

  • lib/utils/token/getRoles.ts
🧬 Code graph analysis (1)
lib/utils/token/getRoles.ts (2)
lib/types.ts (1)
  • GetRolesOptions (274-274)
lib/utils/token/getDecodedToken.ts (1)
  • getDecodedTokenSync (76-91)
🔇 Additional comments (3)
lib/utils/token/getRoles.ts (3)

2-2: LGTM!

The import is necessary for the synchronous implementation and is correctly placed.


15-31: Well-structured core extraction logic.

The TokenWithRoles type and _getRolesCore function provide a clean abstraction for extracting roles from either the standard roles field or Hasura's x-hasura-roles field. The null handling and warning message are appropriate.

The cast on line 30 is safe because lines 24-29 guarantee at least one roles field is truthy.


53-54: LGTM!

Good refactoring to delegate role extraction to the shared _getRolesCore function, enabling code reuse between async and sync implementations.

@DanielRivers DanielRivers merged commit 0f8d81c into main Oct 24, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants