Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tender-signs-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@forgerock/journey-client': patch
---

export create-journey-step so it's easy to deserialize a step after it's been serialized
5 changes: 4 additions & 1 deletion e2e/journey-app/services/delete-webauthn-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ export async function deleteWebAuthnDevice(config: JourneyClientConfig): Promise
return 'No credential id found. Register a WebAuthn device first.';
}

const wellknown = config.serverConfig.wellknown;
const wellknown = 'wellknown' in config.serverConfig ? config.serverConfig.wellknown : undefined;
if (!wellknown) {
throw new Error('serverConfig.wellknown is required to delete a WebAuthn device.');
}
const baseUrl = getBaseUrlFromWellknown(wellknown);
const realmUrlPath = getRealmUrlPathFromWellknown(wellknown);
const userId = await getUserIdFromSession(baseUrl, realmUrlPath);
Expand Down
16 changes: 8 additions & 8 deletions packages/davinci-client/api-report/davinci-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "data" | "fulfilledTimeStamp"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -379,14 +379,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "error"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -407,14 +407,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "data" | "fulfilledTimeStamp"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -431,14 +431,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "error"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand Down
16 changes: 8 additions & 8 deletions packages/davinci-client/api-report/davinci-client.types.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "data" | "fulfilledTimeStamp"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -379,14 +379,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "error"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -407,14 +407,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "data" | "fulfilledTimeStamp"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand All @@ -431,14 +431,14 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
} & Omit<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
}, "error"> & Required<Pick<{
requestId: string;
data?: unknown;
error?: FetchBaseQueryError | SerializedError | undefined;
error?: SerializedError | FetchBaseQueryError | undefined;
endpointName: string;
startedTimeStamp: number;
fulfilledTimeStamp?: number;
Expand Down
44 changes: 44 additions & 0 deletions packages/journey-client/api-report/journey-client._utils.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## API Report File for "@forgerock/journey-client"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts
import type { AuthResponse } from '@forgerock/sdk-types';
import type { Callback } from '@forgerock/sdk-types';
import type { CallbackType } from '@forgerock/sdk-types';
import { Step } from '@forgerock/sdk-types';
import type { StepType } from '@forgerock/sdk-types';

// @public
export class BaseCallback {
constructor(payload: Callback);
getInputValue(selector?: number | string): unknown;
getOutputByName<T>(name: string, defaultValue: T): T;
getOutputValue(selector?: number | string): unknown;
getType(): CallbackType;
// (undocumented)
payload: Callback;
setInputValue(value: unknown, selector?: number | string | RegExp): void;
}

// @public (undocumented)
export type CallbackFactory = (callback: Callback) => BaseCallback;

// @public (undocumented)
export function createJourneyStep(payload: Step, callbackFactory?: CallbackFactory): JourneyStep;

// @public (undocumented)
export type JourneyStep = AuthResponse & {
type: StepType.Step;
payload: Step;
callbacks: BaseCallback[];
getCallbackOfType: <T extends BaseCallback>(type: CallbackType) => T;
getCallbacksOfType: <T extends BaseCallback>(type: CallbackType) => T[];
setCallbackValue: (type: CallbackType, value: unknown) => void;
getDescription: () => string | undefined;
getHeader: () => string | undefined;
getStage: () => string | undefined;
};

// (No @packageDocumentation comment for this package)
```
3 changes: 3 additions & 0 deletions packages/journey-client/api-report/journey-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { GenericError } from '@forgerock/sdk-types';
import { isValidWellknownUrl } from '@forgerock/sdk-utilities';
import { JourneyClientConfig } from '@forgerock/sdk-types';
import { JourneyServerConfig } from '@forgerock/sdk-types';
import { LegacyServerConfig } from '@forgerock/sdk-types';
import { LogLevel } from '@forgerock/sdk-logger';
import { NameValue } from '@forgerock/sdk-types';
import { PolicyKey } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -256,6 +257,8 @@ export class KbaCreateCallback extends BaseCallback {
setQuestion(question: string): void;
}

export { LegacyServerConfig }

export { LogLevel }

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { GenericError } from '@forgerock/sdk-types';
import { isValidWellknownUrl } from '@forgerock/sdk-utilities';
import { JourneyClientConfig } from '@forgerock/sdk-types';
import { JourneyServerConfig } from '@forgerock/sdk-types';
import { LegacyServerConfig } from '@forgerock/sdk-types';
import { LogLevel } from '@forgerock/sdk-logger';
import { NameValue } from '@forgerock/sdk-types';
import { PolicyKey } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -243,6 +244,8 @@ export class KbaCreateCallback extends BaseCallback {
setQuestion(question: string): void;
}

export { LegacyServerConfig }

export { LogLevel }

// @public (undocumented)
Expand Down
1 change: 1 addition & 0 deletions packages/journey-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"type": "module",
"exports": {
".": "./dist/src/index.js",
"./_utils": "./dist/src/_utils.js",
"./device": "./dist/src/lib/device/device-profile.js",
"./package.json": "./package.json",
"./policy": "./dist/src/lib/policy/policy.js",
Expand Down
9 changes: 9 additions & 0 deletions packages/journey-client/src/_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @private
* @description Creates a decorated `Step` object with convenience functions.
* Used mostly for mocking and some advanced, edge cases.
*/
export { createJourneyStep } from './lib/step.utils.js';
export type { JourneyStep } from './lib/step.types.js';
export type { CallbackFactory } from './lib/callbacks/factory.js';
export { BaseCallback } from './lib/callbacks/base-callback.js';
2 changes: 1 addition & 1 deletion packages/journey-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2020 - 2026 Ping Identity Corporation. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
Expand Down
106 changes: 106 additions & 0 deletions packages/journey-client/src/lib/client.store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,112 @@ describe('journey-client', () => {
});
});

describe('baseUrl mode', () => {
const baseUrl = 'https://am.example.com/am';
const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] };

function setupBaseUrlFetch() {
mockFetch.mockImplementation((input: RequestInfo | URL) => {
const url = getUrlFromInput(input);
if (url.includes('/authenticate')) {
return Promise.resolve(new Response(JSON.stringify(mockStepResponse)));
}
if (url.includes('/sessions')) {
return Promise.resolve(new Response(null, { status: 200 }));
}
return Promise.reject(new Error(`Unexpected fetch: ${url}`));
});
}

test('journey_BaseUrl_NoRealmPath_UsesRootAuthenticate', async () => {
setupBaseUrlFetch();

const client = await journey({
config: { serverConfig: { baseUrl } },
});
await client.start();

expect(mockFetch).toHaveBeenCalledTimes(1);
const request = mockFetch.mock.calls[0][0] as Request;
expect(request.url).toBe('https://am.example.com/am/json/realms/root/authenticate');
});

test('journey_BaseUrl_WithRealmPath_UsesRealmAuthenticate', async () => {
setupBaseUrlFetch();

const client = await journey({
config: { serverConfig: { baseUrl }, realmPath: 'alpha' },
});
await client.start();

expect(mockFetch).toHaveBeenCalledTimes(1);
const request = mockFetch.mock.calls[0][0] as Request;
expect(request.url).toBe(
'https://am.example.com/am/json/realms/root/realms/alpha/authenticate',
);
});

test('journey_BaseUrl_TrailingSlashInput_NormalizesCorrectly', async () => {
setupBaseUrlFetch();

const client = await journey({
config: { serverConfig: { baseUrl: `${baseUrl}/` } },
});
await client.start();

const request = mockFetch.mock.calls[0][0] as Request;
expect(request.url).toBe('https://am.example.com/am/json/realms/root/authenticate');
});

test('journey_BaseUrl_Terminate_UsesRootSessionsUrl', async () => {
setupBaseUrlFetch();

const client = await journey({
config: { serverConfig: { baseUrl } },
});
await client.terminate();

expect(mockFetch).toHaveBeenCalledTimes(1);
const request = mockFetch.mock.calls[0][0] as Request;
expect(request.url).toBe(
'https://am.example.com/am/json/realms/root/sessions/?_action=logout',
);
});

test('journey_BaseUrl_WithRealmPath_Terminate_UsesRealmSessionsUrl', async () => {
setupBaseUrlFetch();

const client = await journey({
config: { serverConfig: { baseUrl }, realmPath: 'alpha' },
});
await client.terminate();

expect(mockFetch).toHaveBeenCalledTimes(1);
const request = mockFetch.mock.calls[0][0] as Request;
expect(request.url).toBe(
'https://am.example.com/am/json/realms/root/realms/alpha/sessions/?_action=logout',
);
});

test('journey_BaseUrl_WithRealmPath_DoesNotWarn', async () => {
setupBaseUrlFetch();
const customLogger = {
error: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
};

await journey({
config: { serverConfig: { baseUrl }, realmPath: 'alpha' },
logger: { level: 'warn', custom: customLogger },
});

const warnCalls = customLogger.warn.mock.calls.map((c) => c[0] as string);
expect(warnCalls.find((msg) => msg.includes('realmPath'))).toBeUndefined();
});
});

describe('unified JSON config entry', () => {
test('accepts unified JSON config and initializes successfully', async () => {
setupMockFetch();
Expand Down
Loading
Loading