Skip to content

fix(journey-client): create JourneyLoginFailure step, handle Login Failure case#574

Open
vatsalparikh wants to merge 4 commits intomainfrom
sdks-4796
Open

fix(journey-client): create JourneyLoginFailure step, handle Login Failure case#574
vatsalparikh wants to merge 4 commits intomainfrom
sdks-4796

Conversation

@vatsalparikh
Copy link
Copy Markdown
Contributor

@vatsalparikh vatsalparikh commented Apr 20, 2026

JIRA Ticket

https://pingidentity.atlassian.net/browse/SDKS-4796

Description

While working on the Journey Client migration, I noticed that when AM returns a failure response (e.g., unauthorized / invalid credentials), the Journey Client was returning a generic “no data” error and the LoginFailure path in createJourneyObject() was effectively never reached. This PR closes that gap by ensuring LoginFailure is triggered and a JourneyLoginFailure is returned when the server provides a failure payload with a code.

What changed

  • start() / next() now map RTK Query error responses that include a code into createJourneyObject(), which returns JourneyLoginFailure (hitting the previously-unreached LoginFailure branch).
  • Added unit coverage for the LoginFailure mapping.
  • Updated the journey-app e2e consumer to avoid throwing on LoginFailure and instead renders the LoginFailure error message.
  • Added a changeset for @forgerock/journey-client.

Edit

Ryan shared his closed PR that was trying to fix this issue: #541. Will use this as a reference.

How to test

  • Build and start the e2e/journey-app
  • Go to http://localhost:5829/?clientId=tenant and enter incorrect credentials
  • You should see 'Login Failure' with the change in this PR. When you remove the error handling logic from journey.api.ts file, you should see unknown node in console, which means login failure is not handled as a step.

Summary by CodeRabbit

  • Bug Fixes

    • Authentication failures that include step-like responses are now reported as login failures instead of generic errors.
    • Login form submit failures update only the error UI, avoiding a full form re-render.
  • Tests

    • Expanded tests for authentication error translation, response handling, and redirect behavior.
  • Chores

    • Minor public typings and helper exports added to improve result handling and diagnostics.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 20, 2026

🦋 Changeset detected

Latest commit: dcf367f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@forgerock/journey-client Patch
@forgerock/davinci-client Patch
@forgerock/device-client Patch
@forgerock/oidc-client Patch
@forgerock/protect Patch
@forgerock/sdk-types Patch
@forgerock/sdk-utilities Patch
@forgerock/iframe-manager Patch
@forgerock/sdk-logger Patch
@forgerock/sdk-oidc Patch
@forgerock/sdk-request-middleware Patch
@forgerock/storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes mapping of RTK Query results into a single JourneyResult via resolveJourneyResult, updates start/next to return that result, adds tests for treating 401 responses with Step-shaped payloads as LoginFailure, minor import/metadata tweaks, and an e2e change to render only the error UI on LoginFailure.

Changes

Core Journey Result Mapping

Layer / File(s) Summary
Types / Exports
packages/journey-client/src/types.ts
Re-exports WellknownResponse and callbackType from @forgerock/sdk-types.
Data Shape / Helpers
packages/journey-client/src/lib/journey.utils.ts
Adds JourneyResult union, STEP_LIKE_KEYS, type guards, and exported resolveJourneyResult(data, error); createJourneyObject becomes an exported function and preserves step-type selection logic.
Core Client Integration
packages/journey-client/src/lib/client.store.ts
start and next now destructure { data, error } from dispatched endpoint results and return resolveJourneyResult(data, error) directly (removes prior manual no_response_data branch).
Release Metadata
.changeset/whole-mangos-find.md
Adds a patch changeset documenting LoginFailure mapping behavior.
Header / Imports
packages/journey-client/src/lib/journey.api.ts
Minor import ordering and copyright year update.

Tests and Mocks

Layer / File(s) Summary
Test Environment / Imports
packages/journey-client/src/lib/client.store.test.ts
Switches to Node Vitest environment and imports types from local ../types.js.
Mocking / Helpers
packages/journey-client/src/lib/client.store.test.ts
Extends setupMockFetch to accept authenticateStatus; /authenticate mock now rejects when journeyResponse is null and returns JSON Response with supplied status for Step payloads.
Behavioral Tests
packages/journey-client/src/lib/client.store.test.ts, packages/journey-client/src/lib/journey.utils.test.ts
Adds tests asserting 401 /authenticate with Step payload maps to LoginFailure (includes payload and getter accessors). Adds resolveJourneyResult unit tests covering no-data, request-failed, SerializedError, and Step-shaped error payloads. Updates expected GenericError kinds/messages.
Node Shims for Tests
packages/journey-client/src/lib/client.store.test.ts
Adds window.location getter shim to allow spying in Node Vitest.

E2E UI

Layer / File(s) Summary
Type Imports
e2e/journey-app/main.ts
Reorders type-only imports (JourneyClient, RequestMiddleware) inside existing import block (type-only).
UI Error Handling
e2e/journey-app/main.ts
On LoginFailure in the submit flow, replaces renderForm() with renderError() so only the error UI is updated.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (caller)
  participant JC as JourneyClient.start/next
  participant Store as Store.dispatch
  participant Backend as Remote API

  Client->>JC: call start/next(params)
  JC->>Store: dispatch request action
  Store->>Backend: network request (/authenticate)
  Backend-->>Store: Response (data OR error with error.data)
  Store-->>JC: { data, error }
  JC->>JC: resolveJourneyResult(data, error) -> createJourneyObject if step-shaped
  JC-->>Client: JourneyResult (Step | LoginSuccess | LoginFailure | GenericError)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ryanbas21
  • ancheetah
  • cerebrl

Poem

🐇 I hopped through code, a curious sight,
Found errors wearing step-shaped light.
I nudged them outward, gave them voice,
Now failures surface — a sensible choice.
Tests clap paws; the journey’s right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: creating JourneyLoginFailure and handling the previously-unreachable LoginFailure case in the journey-client.
Description check ✅ Passed The description includes a JIRA ticket link and comprehensive details about the problem, changes, and testing approach, meeting the repository's template requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sdks-4796

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 20, 2026

View your CI Pipeline Execution ↗ for commit dcf367f

Command Status Duration Result
nx run-many -t build --no-agents ✅ Succeeded <1s View ↗
nx affected -t build lint test typecheck e2e-ci ✅ Succeeded 2m 42s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-07 00:44:35 UTC

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
packages/journey-client/src/lib/client.store.test.ts (1)

265-277: Minor: globalThis.window is assigned but never cleaned up, and the initial defineProperty is redundant.

Two small points on the new shim:

  1. (globalThis as unknown as { window?: unknown }).window = {} leaks across tests within this file (no matching cleanup in afterEach). It happens to be benign today because this is the only test using window, but a future test run in any other order could observe a stray window global. Consider restoring it alongside locationSpy.mockRestore().
  2. The Object.defineProperty(window, 'location', { get: () => ({ assign: vi.fn() }) }) on lines 268–273 is immediately overridden by vi.spyOn(window, 'location', 'get').mockReturnValue(...) on line 274 — the inner assign: vi.fn() is never invoked, and the spread ...window.location on line 275 just reads the spied getter. Keeping only the defineProperty (so the property exists as a getter for spyOn to replace) with a minimal value, or dropping one of the two, would make the intent clearer.
♻️ Suggested cleanup
-    // Node test environment doesn't provide `window`, so create a minimal shim
-    // with a real `location` getter so we can keep using vi.spyOn(..., 'get').
-    (globalThis as unknown as { window?: unknown }).window = {};
-    Object.defineProperty(window, 'location', {
-      configurable: true,
-      get: () => ({
-        assign: vi.fn(),
-      }),
-    });
-    const locationSpy = vi.spyOn(window, 'location', 'get').mockReturnValue({
-      ...window.location,
-      assign: assignMock,
-    });
+    // Node test environment doesn't provide `window`, so create a minimal shim
+    // with a real `location` getter so we can keep using vi.spyOn(..., 'get').
+    (globalThis as unknown as { window: unknown }).window = {};
+    Object.defineProperty(window, 'location', {
+      configurable: true,
+      get: () => ({ assign: assignMock }),
+    });
+    const locationSpy = vi.spyOn(window, 'location', 'get');
     ...
     locationSpy.mockRestore();
+    delete (globalThis as unknown as { window?: unknown }).window;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.test.ts` around lines 265 - 277,
The test creates a global window shim and defines a getter for window.location
but never cleans up; modify the test to (1) make the minimal defineProperty for
window.location (so vi.spyOn can replace the getter) and remove the redundant
inner assign fn, and (2) restore the original state in afterEach by calling
locationSpy.mockRestore() and deleting or restoring globalThis.window (or saving
and reassigning the original window) so the global isn't leaked; reference the
locationSpy, assignMock and the temporary globalThis.window assignment when
updating the test.
packages/journey-client/src/lib/client.store.ts (1)

159-200: Consider extracting the error-mapping logic into a shared helper.

The start and next methods now contain near-identical blocks (lines 159–175 and 182–200) that unwrap { data, error }, map error.data with a defined code to createJourneyObject, and fall back to a GenericError with only the message string differing. Extracting a small helper (e.g., mapJourneyResult(result, fallbackMessage)) would remove the duplication and keep the two methods symmetric going forward.

Also, the errorData as Step | undefined cast is unchecked — if the server ever returns a non-object body (e.g., a string or HTML), errorStep?.code would still pass the optional-chain but could be against a non-Step shape. A narrow typeof errorData === 'object' && errorData !== null guard before the cast would be safer.

♻️ Proposed refactor
+    const mapResult = (
+      { data, error }: { data?: Step; error?: unknown },
+      fallbackMessage: string,
+    ): JourneyResult => {
+      if (data) {
+        return createJourneyObject(data);
+      }
+      const errorData = (error as FetchBaseQueryError | undefined)?.data;
+      if (typeof errorData === 'object' && errorData !== null) {
+        const errorStep = errorData as Step;
+        if (errorStep.code !== undefined) {
+          return createJourneyObject(errorStep);
+        }
+      }
+      return {
+        error: 'no_response_data',
+        message: fallbackMessage,
+        type: 'unknown_error',
+      };
+    };

Then start/next reduce to a single return mapResult(await store.dispatch(...), '...') call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.ts` around lines 159 - 200,
Extract the duplicated error-unwrapping logic used in start and next into a
shared helper (e.g., mapJourneyResult or mapJourneyResponse) that accepts the
dispatch result and a fallback message; the helper should check for data and
return createJourneyObject(data) when present, then safely inspect error data by
first guarding typeof errorData === 'object' && errorData !== null before
treating it as a Step and returning createJourneyObject(errorStep) if
errorStep.code is defined, and finally return a GenericError object with the
provided fallbackMessage if neither path yields a Journey object—then replace
the duplicated blocks in start and next with a single call to this helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/src/lib/client.store.test.ts`:
- Around line 265-277: The test creates a global window shim and defines a
getter for window.location but never cleans up; modify the test to (1) make the
minimal defineProperty for window.location (so vi.spyOn can replace the getter)
and remove the redundant inner assign fn, and (2) restore the original state in
afterEach by calling locationSpy.mockRestore() and deleting or restoring
globalThis.window (or saving and reassigning the original window) so the global
isn't leaked; reference the locationSpy, assignMock and the temporary
globalThis.window assignment when updating the test.

In `@packages/journey-client/src/lib/client.store.ts`:
- Around line 159-200: Extract the duplicated error-unwrapping logic used in
start and next into a shared helper (e.g., mapJourneyResult or
mapJourneyResponse) that accepts the dispatch result and a fallback message; the
helper should check for data and return createJourneyObject(data) when present,
then safely inspect error data by first guarding typeof errorData === 'object'
&& errorData !== null before treating it as a Step and returning
createJourneyObject(errorStep) if errorStep.code is defined, and finally return
a GenericError object with the provided fallbackMessage if neither path yields a
Journey object—then replace the duplicated blocks in start and next with a
single call to this helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3033ff09-5ae0-426f-9ba5-70f1baf0c83b

📥 Commits

Reviewing files that changed from the base of the PR and between 9088443 and bec0866.

📒 Files selected for processing (4)
  • .changeset/journey-loginfailure-on-error-code.md
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
💤 Files with no reviewable changes (1)
  • e2e/journey-app/main.ts

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 20, 2026

Open in StackBlitz

@forgerock/davinci-client

pnpm add https://pkg.pr.new/@forgerock/davinci-client@574

@forgerock/device-client

pnpm add https://pkg.pr.new/@forgerock/device-client@574

@forgerock/journey-client

pnpm add https://pkg.pr.new/@forgerock/journey-client@574

@forgerock/oidc-client

pnpm add https://pkg.pr.new/@forgerock/oidc-client@574

@forgerock/protect

pnpm add https://pkg.pr.new/@forgerock/protect@574

@forgerock/sdk-types

pnpm add https://pkg.pr.new/@forgerock/sdk-types@574

@forgerock/sdk-utilities

pnpm add https://pkg.pr.new/@forgerock/sdk-utilities@574

@forgerock/iframe-manager

pnpm add https://pkg.pr.new/@forgerock/iframe-manager@574

@forgerock/sdk-logger

pnpm add https://pkg.pr.new/@forgerock/sdk-logger@574

@forgerock/sdk-oidc

pnpm add https://pkg.pr.new/@forgerock/sdk-oidc@574

@forgerock/sdk-request-middleware

pnpm add https://pkg.pr.new/@forgerock/sdk-request-middleware@574

@forgerock/storage

pnpm add https://pkg.pr.new/@forgerock/storage@574

commit: dcf367f

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 17.83%. Comparing base (5d6747a) to head (dcf367f).
⚠️ Report is 94 commits behind head on main.

❌ Your project status has failed because the head coverage (17.83%) is below the target coverage (40.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #574       +/-   ##
===========================================
- Coverage   70.90%   17.83%   -53.08%     
===========================================
  Files          53      150       +97     
  Lines        2021    24255    +22234     
  Branches      377     1163      +786     
===========================================
+ Hits         1433     4325     +2892     
- Misses        588    19930    +19342     
Files with missing lines Coverage Δ
packages/journey-client/src/lib/client.store.ts 82.87% <100.00%> (+9.49%) ⬆️
packages/journey-client/src/lib/journey.utils.ts 91.93% <100.00%> (+15.26%) ⬆️
packages/journey-client/src/types.ts 100.00% <100.00%> (+96.66%) ⬆️

... and 105 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

Deployed 520f9f3 to https://ForgeRock.github.io/ping-javascript-sdk/pr-574/520f9f3c3ff7c3004f1f42cfc0397d889328bb29 branch gh-pages in ForgeRock/ping-javascript-sdk

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

📦 Bundle Size Analysis

📦 Bundle Size Analysis

🚨 Significant Changes

🔻 @forgerock/device-client - 0.0 KB (-10.0 KB, -100.0%)
🔻 @forgerock/journey-client - 0.0 KB (-91.9 KB, -100.0%)

📊 Minor Changes

📈 @forgerock/journey-client - 92.4 KB (+0.5 KB)

➖ No Changes

@forgerock/device-client - 10.0 KB
@forgerock/davinci-client - 48.9 KB
@forgerock/oidc-client - 25.2 KB
@forgerock/sdk-utilities - 11.2 KB
@forgerock/sdk-types - 7.9 KB
@forgerock/protect - 144.6 KB
@forgerock/storage - 1.5 KB
@forgerock/sdk-oidc - 4.8 KB
@forgerock/sdk-request-middleware - 4.5 KB
@forgerock/sdk-logger - 1.6 KB
@forgerock/iframe-manager - 2.4 KB


14 packages analyzed • Baseline from latest main build

Legend

🆕 New package
🔺 Size increased
🔻 Size decreased
➖ No change

ℹ️ How bundle sizes are calculated
  • Current Size: Total gzipped size of all files in the package's dist directory
  • Baseline: Comparison against the latest build from the main branch
  • Files included: All build outputs except source maps and TypeScript build cache
  • Exclusions: .map, .tsbuildinfo, and .d.ts.map files

🔄 Updated automatically on each push to this PR

Copy link
Copy Markdown
Contributor

@SteinGabriel SteinGabriel left a comment

Choose a reason for hiding this comment

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

Minor findings.

Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.test.ts Outdated
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
packages/journey-client/src/lib/journey.api.ts (1)

139-202: Extract the duplicated login-failure mapping into a helper.

The blocks at lines 139-155 and 186-202 are identical. Consider extracting into a small helper to reduce drift risk when the set of failure codes or the shape handling evolves.

♻️ Proposed refactor
+function mapLoginFailureResponse(
+  response: QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>,
+): QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> {
+  if ('error' in response) {
+    const errorData = (response.error as FetchBaseQueryError | undefined)?.data as
+      | Step
+      | undefined;
+    if (errorData?.code && LOGIN_FAILURE_CODES.includes(errorData.code)) {
+      return { data: errorData };
+    }
+  }
+  return response as QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta>;
+}

Then use return mapLoginFailureResponse(response); in both start and next.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.api.ts` around lines 139 - 202,
Extract the duplicated login-failure mapping into a small helper (e.g.
mapLoginFailureResponse) that accepts the endpoint response and returns a
QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> when the
response body is a Step with a code in LOGIN_FAILURE_CODES, or undefined/falsey
otherwise; move the logic that inspects ('error' in response), casts to
(response.error as FetchBaseQueryError)?.data as Step, checks data.code and
LOGIN_FAILURE_CODES into this helper, then replace the duplicated blocks in both
the start and next builder handlers with a single call like: return
mapLoginFailureResponse(response) ?? (continue with existing return).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/src/lib/journey.api.ts`:
- Around line 139-202: Extract the duplicated login-failure mapping into a small
helper (e.g. mapLoginFailureResponse) that accepts the endpoint response and
returns a QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> when
the response body is a Step with a code in LOGIN_FAILURE_CODES, or
undefined/falsey otherwise; move the logic that inspects ('error' in response),
casts to (response.error as FetchBaseQueryError)?.data as Step, checks data.code
and LOGIN_FAILURE_CODES into this helper, then replace the duplicated blocks in
both the start and next builder handlers with a single call like: return
mapLoginFailureResponse(response) ?? (continue with existing return).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d2a0334-1d00-4e63-85af-4145e953a6d4

📥 Commits

Reviewing files that changed from the base of the PR and between bec0866 and 16dd372.

📒 Files selected for processing (4)
  • .changeset/polite-horses-dig.md
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/journey-client/src/lib/client.store.ts
  • .changeset/polite-horses-dig.md

Copy link
Copy Markdown
Contributor

@SteinGabriel SteinGabriel left a comment

Choose a reason for hiding this comment

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

Changes look great! Everything worked as expected when I tested it locally. Just need to update a stale copyright date.

Comment thread packages/journey-client/src/lib/journey.api.ts
Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

Left some alternative thoughts on this solution.

Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment on lines +139 to +143
/**
* If the endpoint returned an HTTP error whose body is an AM Step with a
* login-failure code, treat it as successful data so callers receive the
* Step via the `data` path (keeps downstream logic simpler).
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm a bit worried about tightly coupling our understanding of "true errors" to validation or authentication errors by relying solely on the error code. The logic we use in the original SDK was much more "liberal". It essentially treated all errors as FRLoginFailure: https://github.com/ForgeRock/forgerock-javascript-sdk/blob/develop/packages/javascript-sdk/src/fr-auth/index.ts#L76.

Rather than doing all of this logic here, we could grab error from this destructure: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/client.store.ts#L174, and pass it to createJourneyObject here: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/client.store.ts#L183. Finally, just handle the logic here: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/journey.utils.ts#L28.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interesting, yeah, earlier logic in forgerock sdk was simply treating every error as FRLoginFailure so I tried to think about what constitutes as login failure and used 4xx codes.

Yeah, error codes can feel too restrictive but that's the only reliable data point about login failures. I think it just depends on what we want to classify as login failure. If we want to include 500 error from server as login failure as well, then yes, this is tightly coupled.

I looked at Ryan's closed PR and updated the logic of determining a login failure. If data contains any Step like properties then we treat it as login failure. Open to suggestions on whether you think this is the best way to determine a login failure.

Thanks for the suggestion on moving logic to journey utils. client.store file is now clean and all logic is handled by journey.utils file.

Thanks!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see Vatsal may have already implemented Justin's suggestion above but I've proposed another improvement below. We can handle the error either inside or outside of createJourneyObject, but I think it is important to also standardize the type narrowing of RTK errors throughout our SDK, checking for a Fetch error or Serialized error. After we know what type of error it is then we can return a LoginFailure or GenericError.

Copy link
Copy Markdown

@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 (3)
packages/journey-client/src/lib/journey.utils.ts (1)

49-50: Nit: consider hoisting resolveStep above createJourneyObject to drop the eslint-disable.

Declaring resolveStep before its only caller removes the need for // eslint-disable-next-line @typescript-eslint/no-use-before-define`` and reads linearly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.ts` around lines 49 - 50, Hoist
the helper function resolveStep so it is declared before its only caller
createJourneyObject; move the resolveStep function definition above
createJourneyObject, remove the now-unnecessary // eslint-disable-next-line
`@typescript-eslint/no-use-before-define` comment, and run lint to verify no
use-before-define warning remains. Ensure resolveStep and createJourneyObject
names are unchanged to preserve references.
packages/journey-client/src/lib/client.store.test.ts (1)

388-399: Consider aligning the test name with the new assertion.

The test is named start_NoDataFromServer_ReturnsGenericError and previously validated error === 'no_response_data', but now asserts error === 'request_failed'. Since setupMockFetch(null) actually causes /authenticate to reject (i.e., the RTK Query error path with status: 'FETCH_ERROR'), this test is really covering the "fetch failed" case rather than the "server returned nothing" case. Renaming to something like start_FetchRejects_ReturnsRequestFailedError would better describe the scenario, and a separate start_ServerReturnsEmptyBody_ReturnsNoResponseData could cover the truly empty-response case via createJourneyObject(undefined, undefined) (already covered in journey.utils.test.ts).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.test.ts` around lines 388 - 399,
Rename the test to reflect that the fetch call rejects rather than the server
returning an empty body: update the test title from
start_NoDataFromServer_ReturnsGenericError to something like
start_FetchRejects_ReturnsRequestFailedError, leaving the test body unchanged
(it uses setupMockFetch(null), invokes journey({ config: mockConfig }) and
client.start(), and asserts via isGenericError(result) and result.error ===
'request_failed'); this makes it clear the scenario exercised is the RTK Query
fetch-error path rather than a no-response-data case.
packages/journey-client/src/lib/journey.utils.test.ts (1)

15-93: Solid coverage for the new createJourneyObject(step, error) signature.

Good branch coverage across (step with authId), (step with successUrl), (no step + no error → no_response_data), (no step + error with status → request_failed), (non-Step-like error.datarequest_failed), and (Step-like error.dataLoginFailure).

Consider adding one more case to lock in behavior for a 5xx (e.g., status: 500) that returns a Step-like error.data (i.e., a body containing one of STEP_LIKE_KEYS like code) — this currently maps to LoginFailure regardless of HTTP status. If that's the intended behavior, a test asserting it prevents future regressions; if the intent is login-specific (401/403) as discussed in earlier review rounds, the utility’s logic should be revisited.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.test.ts` around lines 15 - 93,
Add a test covering the case where error.status is 5xx but error.data is
Step-like to lock in current behavior (or to indicate needed change);
specifically update the createJourneyObject tests to include a case that calls
createJourneyObject(undefined, { status: 500, data: { code: 500, message:
'Server Err' } }) and assert whether it returns a JourneyLoginFailure (check
type === StepType.LoginFailure and payload equals the Step-like data, plus
validate getters like getCode/getMessage) to document/lock the handling of
STEP_LIKE_KEYS by createJourneyObject.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 20-34: The STEP_LIKE_KEYS heuristic is too permissive and causes
non-auth errors to be treated as a Step; tighten the check in resolveStep (and
the downstream createJourneyObject path) by requiring a more specific
login-shaped signature before casting error.data to Step — for example, require
either (a) an HTTP auth status (status or statusCode equal to 401 or 403) OR (b)
a login-specific field combination like authId or successUrl present, rather
than any single key from STEP_LIKE_KEYS; update STEP_LIKE_KEYS/use in
resolveStep to check these conditions and only cast to Step when they pass so
createJourneyObject only produces JourneyLoginFailure for true AM/login failure
shapes.

---

Nitpick comments:
In `@packages/journey-client/src/lib/client.store.test.ts`:
- Around line 388-399: Rename the test to reflect that the fetch call rejects
rather than the server returning an empty body: update the test title from
start_NoDataFromServer_ReturnsGenericError to something like
start_FetchRejects_ReturnsRequestFailedError, leaving the test body unchanged
(it uses setupMockFetch(null), invokes journey({ config: mockConfig }) and
client.start(), and asserts via isGenericError(result) and result.error ===
'request_failed'); this makes it clear the scenario exercised is the RTK Query
fetch-error path rather than a no-response-data case.

In `@packages/journey-client/src/lib/journey.utils.test.ts`:
- Around line 15-93: Add a test covering the case where error.status is 5xx but
error.data is Step-like to lock in current behavior (or to indicate needed
change); specifically update the createJourneyObject tests to include a case
that calls createJourneyObject(undefined, { status: 500, data: { code: 500,
message: 'Server Err' } }) and assert whether it returns a JourneyLoginFailure
(check type === StepType.LoginFailure and payload equals the Step-like data,
plus validate getters like getCode/getMessage) to document/lock the handling of
STEP_LIKE_KEYS by createJourneyObject.

In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 49-50: Hoist the helper function resolveStep so it is declared
before its only caller createJourneyObject; move the resolveStep function
definition above createJourneyObject, remove the now-unnecessary //
eslint-disable-next-line `@typescript-eslint/no-use-before-define` comment, and
run lint to verify no use-before-define warning remains. Ensure resolveStep and
createJourneyObject names are unchanged to preserve references.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23eb68ce-90c9-4575-adf7-55f70f46f8d7

📥 Commits

Reviewing files that changed from the base of the PR and between 16dd372 and a24550c.

📒 Files selected for processing (7)
  • .changeset/whole-mangos-find.md
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
  • packages/journey-client/src/lib/journey.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • .changeset/whole-mangos-find.md
  • packages/journey-client/src/lib/journey.api.ts

Comment thread packages/journey-client/src/lib/journey.utils.ts Outdated
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
packages/journey-client/src/lib/journey.utils.ts (1)

20-34: ⚠️ Potential issue | 🟠 Major

Tighten the Step-like error heuristic before routing to LoginFailure.

STEP_LIKE_KEYS still includes very common error fields like code, status, detail, and ok, so unrelated API failures can be misclassified as JourneyLoginFailure. That makes the new error-path handling broader than the login-specific behavior this PR is trying to add.

🔧 Suggested tightening
-const STEP_LIKE_KEYS = [
-  'authId',
-  'callbacks',
-  'code',
-  'description',
-  'detail',
-  'header',
-  'ok',
-  'realm',
-  'reason',
-  'stage',
-  'status',
-  'successUrl',
-  'tokenId',
-] as const;
+const LOGIN_FAILURE_STATUSES = new Set([401, 403]);
+const STEP_LIKE_KEYS = ['authId', 'callbacks', 'successUrl', 'tokenId'] as const;
@@
-  if (data && typeof data === 'object' && STEP_LIKE_KEYS.some((key) => key in data)) {
+  if (
+    data &&
+    typeof data === 'object' &&
+    ((typeof errorObj?.status === 'number' && LOGIN_FAILURE_STATUSES.has(errorObj.status)) ||
+      STEP_LIKE_KEYS.some((key) => key in data))
+  ) {

Also applies to: 80-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.ts` around lines 20 - 34, The
STEP_LIKE_KEYS array is too permissive and causes unrelated API errors to be
treated as login failures; restrict the heuristic by removing generic fields
('code', 'status', 'detail', 'ok', 'description', 'reason') and only keep
login-specific identifiers (e.g., 'authId', 'callbacks', 'header', 'realm',
'tokenId', 'successUrl', 'stage') so only objects with explicit login-related
shape route to JourneyLoginFailure; update the STEP_LIKE_KEYS constant and any
other usage sites (the other occurrence referenced around the same file) to use
the tightened list so the LoginFailure path is only triggered for genuine
login-shaped payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 20-34: The STEP_LIKE_KEYS array is too permissive and causes
unrelated API errors to be treated as login failures; restrict the heuristic by
removing generic fields ('code', 'status', 'detail', 'ok', 'description',
'reason') and only keep login-specific identifiers (e.g., 'authId', 'callbacks',
'header', 'realm', 'tokenId', 'successUrl', 'stage') so only objects with
explicit login-related shape route to JourneyLoginFailure; update the
STEP_LIKE_KEYS constant and any other usage sites (the other occurrence
referenced around the same file) to use the tightened list so the LoginFailure
path is only triggered for genuine login-shaped payloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ce5ba34-661e-4c44-9881-10433079f8b2

📥 Commits

Reviewing files that changed from the base of the PR and between a24550c and b109b87.

📒 Files selected for processing (6)
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
  • packages/journey-client/src/lib/journey.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/journey-app/main.ts

Copy link
Copy Markdown
Collaborator

@ancheetah ancheetah left a comment

Choose a reason for hiding this comment

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

Left some comments about type imports and an alternative to how we could do the error handling.

Comment thread packages/journey-client/src/lib/client.store.test.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.ts Outdated
@vatsalparikh vatsalparikh requested a review from cerebrl May 4, 2026 18:48
@vatsalparikh vatsalparikh force-pushed the sdks-4796 branch 3 times, most recently from 14a5f3d to 8649b37 Compare May 5, 2026 02:50
Comment thread packages/journey-client/src/types.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.ts
@vatsalparikh vatsalparikh force-pushed the sdks-4796 branch 3 times, most recently from c00f12b to f3f5efa Compare May 5, 2026 23:08
@vatsalparikh vatsalparikh requested a review from ancheetah May 5, 2026 23:31
@vatsalparikh vatsalparikh force-pushed the sdks-4796 branch 3 times, most recently from 7cfc7c2 to cca2340 Compare May 6, 2026 17:17
Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

I'd like to really think about this. If we are going to increase the complexity of how we handle the Journey responses, I'd start moving towards the pattern we have in DaVinci Client, rather than inventing a new pattern here.

DaVinci uses the concept of handling the response at the RTK API layer, rather than the store layer.

}
return createJourneyObject(data);
const { data, error } = await store.dispatch(journeyApi.endpoints.start.initiate(options));
return resolveJourneyResult(data, error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than having this new function swallow up createJourneyObject, I'd rather flatten them out. If the main concern here is evaluating the error in a more nuanced manner, then let's do that, but without it absorbing the original function.

For example:

// pseudocode
const { data, error } = await store.dispatch(journeyApi.endpoints.start.initiate(options));
let resolvedError;
if (error) {
  resolvedError = resolveJourneyError(error);
  if (resolveError.catosrophic) {
    return { /* GenericError */ };
  }
}
return createJourneyObject(data || resolvedError):

Copy link
Copy Markdown
Contributor Author

@vatsalparikh vatsalparikh May 7, 2026

Choose a reason for hiding this comment

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

If we called createJourneyObject with createJourneyObject(data || resolvedError), then we're going back to the initial implementation where I was error handling in journey.api.ts file, which is

* If the endpoint returned an HTTP error whose body is an AM Step with a

At the time, you mentioned that "Rather than doing all of this logic here, we could grab error from this destructure: main/packages/journey-client/src/lib/client.store.ts#L174, and pass it to createJourneyObject here: main/packages/journey-client/src/lib/client.store.ts#L183. Finally, just handle the logic here: main/packages/journey-client/src/lib/journey.utils.ts#L28."

And I feel like error handling with resolveJourneyResult handles all errors in one place. If we split like your suggestion, we'll handle some errors in journey.api.ts file, some errors in client.store.ts file and some errors in journey.utils.ts file.

Would be great to discuss further on this over a call to reach a consensus.

Comment on lines +65 to +85
function isStepLike(value: unknown): boolean {
return (
typeof value === 'object' &&
value !== null &&
[
'authId',
'callbacks',
'code',
'description',
'detail',
'header',
'ok',
'realm',
'reason',
'stage',
'status',
'successUrl',
'tokenId',
].some((key) => key in value)
);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how I feel about this kind of "meta-analysis" on the object. In the legacy SDK, it was much, much more simple, and I'm not sure there's any additional value this provides to offset the cost of complexity.

In the legacy SDK, an object was essentially a Step if it had an authId, a LoginSuccess if it didn't have an authId, but the fetch was .ok, and an LoginFailure if it was neither. You can see that codified here: https://github.com/ForgeRock/forgerock-javascript-sdk/blob/develop/packages/javascript-sdk/src/fr-auth/index.ts#L65

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can treat every failure as login failure then without a concept of Generic Error. I think this goes back to my earlier comment of, what is too tight of a coupling? What can help us recognize that a step is login failure and not generic error?

#574 (comment)

I am open to returning every failure as login failure, but we have a pattern of generic error in journey client. We are able to simply fallback to login failure because we don't have a concept of generic error in legacy sdk.

We can just check whether error.data exists and is an object instead of scanning the properties, and then any non-object returns GenericError. If error.data is an object we always return login failure, otherwise return generic error, we can simplify it down to this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've removed the isStepLike check and if error.data is an object it is treated as login failure step. I think we still need GenericError, but would be great to discuss this further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants