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
59 changes: 59 additions & 0 deletions .changeset/sdks-5067-unified-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
'@forgerock/sdk-utilities': minor
'@forgerock/sdk-types': minor
'@forgerock/sdk-logger': patch
'@forgerock/sdk-oidc': minor
'@forgerock/oidc-client': minor
'@forgerock/journey-client': minor
'@forgerock/davinci-client': minor
---

Add unified cross-platform SDK configuration support

New utility functions in `@forgerock/sdk-utilities` convert the cross-platform unified JSON config schema into each client's native config shape. Validation and mapping are owned entirely by the utilities layer — client factories remain typed to their existing config interfaces.

**New in `@forgerock/sdk-utilities`:**

- `makeOidcConfig(json)` — validates and maps unified JSON → `OidcConfig`; throws on invalid input
- `makeJourneyConfig(json)` — validates and maps unified JSON → `JourneyClientConfig`; throws on invalid input
- `makeDavinciConfig(json)` — validates and maps unified JSON → `DaVinciConfig`; throws on invalid input
- `UnifiedSdkConfig`, `UnifiedOidcConfig`, `UnifiedJourneyConfig` types
- `validateUnifiedSdkConfig` / `validateUnifiedOidcConfig` — pure validation returning `Either<T, ConfigValidationError[]>`
- `unifiedToOidcConfig`, `unifiedToJourneyConfig`, `unifiedToDavinciConfig` — pure mappers returning `Either<T, ConfigValidationError>`
- `isUnifiedSdkConfig` discriminator
- `AuthDisplayValue`, `AuthPromptValue` types (canonical source — shared between `OidcConfig` and `GetAuthorizationUrlOptions`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify where AuthDisplayValue / AuthPromptValue are actually owned.

This bullet still says sdk-utilities is the “canonical source,” but the PR summary and the sdk-types section place those types there. Please reword this to say sdk-utilities consumes or re-exports them, or move the ownership note into the sdk-types section so the changeset doesn’t describe two sources of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/sdks-5067-unified-config.md at line 24, The changeset bullet for
`AuthDisplayValue` and `AuthPromptValue` types contradicts other parts of the PR
documentation by claiming `sdk-utilities` is the canonical source when
`sdk-types` is actually documented as owning these types. Either reword this
bullet point to clarify that `sdk-utilities` re-exports or consumes these types
(removing the "canonical source" claim), or move the canonical source ownership
note into the `sdk-types` section of the changeset where it accurately reflects
the actual location of these type definitions. Choose whichever approach is
consistent with the PR summary and other documentation sections.


**Usage:**

```ts
import { makeDavinciConfig } from '@forgerock/sdk-utilities';

const client = await davinci({ config: makeDavinciConfig(unifiedJsonConfig) });
```

**New in `@forgerock/sdk-types`:**

- `OidcConfig`, `JourneyClientConfig`, `DaVinciConfig` moved here as canonical types (previously mirrored in `sdk-utilities` as `Mapped*` types)
- `AuthDisplayValue`, `AuthPromptValue` types added (renamed from `OidcDisplayValue`/`OidcPromptValue`)
- `GetAuthorizationUrlOptions` extended with `loginHint`, `nonce`, `display`, `uiLocales`, `acrValues`; `prompt` widened to include `'select_account'`

**Updated in `@forgerock/sdk-logger`:**

- `LogLevel` now re-exported from `@forgerock/sdk-types` (single source of truth); runtime behaviour unchanged

**New in `@forgerock/sdk-oidc`:**

- `buildAuthorizeParams` forwards all new OIDC authorize params into the URL

**New in `@forgerock/oidc-client`:**

- `endSession` appends `post_logout_redirect_uri` when `signOutRedirectUri` is set on config
- Authorize URL construction forwards `loginHint`, `state`, `nonce`, `display`, `prompt`, `uiLocales`, `acrValues`, `additionalParameters` from config

**New in `@forgerock/journey-client`:**

- No API change — consume `makeJourneyConfig` at call-site to use unified JSON config

**New in `@forgerock/davinci-client`:**

- No API change — consume `makeDavinciConfig` at call-site to use unified JSON config
2 changes: 1 addition & 1 deletion e2e/journey-suites/src/login.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2025 - 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
8 changes: 2 additions & 6 deletions packages/davinci-client/api-report/davinci-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import { ActionCreatorWithPayload } from '@reduxjs/toolkit';
import { ActionTypes } from '@forgerock/sdk-request-middleware';
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import { CustomLogger } from '@forgerock/sdk-logger';
import type { DaVinciConfig } from '@forgerock/sdk-types';
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import type { FetchBaseQueryMeta } from '@reduxjs/toolkit/query';
import { GenericError } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -506,11 +506,7 @@ export type DaVinciCacheEntry = {
// @public (undocumented)
export type DavinciClient = Awaited<ReturnType<typeof davinci>>;

// @public (undocumented)
export interface DaVinciConfig extends AsyncLegacyConfigOptions {
// (undocumented)
responseType?: string;
}
export { DaVinciConfig }

// @public (undocumented)
export interface DaVinciError extends Omit<GenericError, 'error'> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import { ActionCreatorWithPayload } from '@reduxjs/toolkit';
import { ActionTypes } from '@forgerock/sdk-request-middleware';
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import { CustomLogger } from '@forgerock/sdk-logger';
import type { DaVinciConfig } from '@forgerock/sdk-types';
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import type { FetchBaseQueryMeta } from '@reduxjs/toolkit/query';
import { GenericError } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -506,11 +506,7 @@ export type DaVinciCacheEntry = {
// @public (undocumented)
export type DavinciClient = Awaited<ReturnType<typeof davinci>>;

// @public (undocumented)
export interface DaVinciConfig extends AsyncLegacyConfigOptions {
// (undocumented)
responseType?: string;
}
export { DaVinciConfig }

// @public (undocumented)
export interface DaVinciError extends Omit<GenericError, 'error'> {
Expand Down
57 changes: 57 additions & 0 deletions packages/davinci-client/src/lib/client.store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { davinci } from './client.store.js';
import { makeDavinciConfig } from '@forgerock/sdk-utilities';
import type { DaVinciConfig } from './config.types.js';

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -181,3 +182,59 @@ describe('davinci client — cache', () => {
});
});
});

// ---------------------------------------------------------------------------

describe('unified JSON config entry', () => {
beforeEach(() => {
vi.stubGlobal('localStorage', makeStorageStub());
vi.stubGlobal('sessionStorage', makeStorageStub());
mockFetchImplementation();
});

afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});

it('accepts unified JSON config and initializes successfully', async () => {
const unifiedConfig = {
oidc: {
clientId: '123456789',
discoveryEndpoint: TEST_WELLKNOWN_URL,
scopes: ['openid', 'profile'],
redirectUri: 'https://example.com/callback',
},
};

const client = await davinci({ config: makeDavinciConfig(unifiedConfig) });
expect(client).toHaveProperty('flow');
expect(client).toHaveProperty('subscribe');
});

it('throws when unified JSON config has missing required field', async () => {
const invalidConfig = {
oidc: {
// clientId missing
discoveryEndpoint: TEST_WELLKNOWN_URL,
scopes: ['openid'],
redirectUri: 'https://example.com/callback',
},
};

expect(() => makeDavinciConfig(invalidConfig)).toThrow(/Invalid unified SDK config/);
});

it('throws when unified JSON config has wrong field type', async () => {
const invalidConfig = {
oidc: {
clientId: '123',
discoveryEndpoint: TEST_WELLKNOWN_URL,
scopes: 'openid', // should be array
redirectUri: 'https://example.com/callback',
},
};

expect(() => makeDavinciConfig(invalidConfig)).toThrow(/Invalid unified SDK config/);
});
});
7 changes: 5 additions & 2 deletions packages/davinci-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2025 - 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 Expand Up @@ -77,7 +77,10 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
custom?: CustomLogger;
};
}) {
const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom });
const log = loggerFn({
level: logger?.level ?? config.log ?? 'error',
custom: logger?.custom,
});
const store = createClientStore({ requestMiddleware, logger: log });
const serverInfo = createStorage<ContinueNode['server']>({
type: 'localStorage',
Expand Down
9 changes: 4 additions & 5 deletions packages/davinci-client/src/lib/config.types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
/*
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2025 - 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.
*/

import type { AsyncLegacyConfigOptions, WellknownResponse } from '@forgerock/sdk-types';
import type { WellknownResponse } from '@forgerock/sdk-types';
import type { DaVinciConfig } from '@forgerock/sdk-types';

export interface DaVinciConfig extends AsyncLegacyConfigOptions {
responseType?: string;
}
export type { DaVinciConfig };

export interface InternalDaVinciConfig extends DaVinciConfig {
wellknownResponse: WellknownResponse;
Expand Down
15 changes: 4 additions & 11 deletions packages/journey-client/api-report/journey-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
```ts

import { ActionTypes } from '@forgerock/sdk-request-middleware';
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import { AuthResponse } from '@forgerock/sdk-types';
import { Callback } from '@forgerock/sdk-types';
import { CallbackType } from '@forgerock/sdk-types';
Expand All @@ -16,6 +15,8 @@ import { FailedPolicyRequirement } from '@forgerock/sdk-types';
import { FailureDetail } from '@forgerock/sdk-types';
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 { LogLevel } from '@forgerock/sdk-logger';
import { NameValue } from '@forgerock/sdk-types';
import { PolicyKey } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -203,11 +204,7 @@ export interface JourneyClient {
}) => Promise<void | GenericError>;
}

// @public
export interface JourneyClientConfig extends AsyncLegacyConfigOptions {
// (undocumented)
serverConfig: JourneyServerConfig;
}
export { JourneyClientConfig }

// @public (undocumented)
export type JourneyLoginFailure = AuthResponse & {
Expand All @@ -232,11 +229,7 @@ export type JourneyLoginSuccess = AuthResponse & {
// @public (undocumented)
export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;

// @public
export interface JourneyServerConfig {
timeout?: number;
wellknown: string;
}
export { JourneyServerConfig }

// @public (undocumented)
export type JourneyStep = AuthResponse & {
Expand Down
15 changes: 4 additions & 11 deletions packages/journey-client/api-report/journey-client.types.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
```ts

import { ActionTypes } from '@forgerock/sdk-request-middleware';
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
import { AuthResponse } from '@forgerock/sdk-types';
import { Callback } from '@forgerock/sdk-types';
import { CallbackType } from '@forgerock/sdk-types';
Expand All @@ -15,6 +14,8 @@ import { FailedPolicyRequirement } from '@forgerock/sdk-types';
import { FailureDetail } from '@forgerock/sdk-types';
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 { LogLevel } from '@forgerock/sdk-logger';
import { NameValue } from '@forgerock/sdk-types';
import { PolicyKey } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -190,11 +191,7 @@ export interface JourneyClient {
}) => Promise<void | GenericError>;
}

// @public
export interface JourneyClientConfig extends AsyncLegacyConfigOptions {
// (undocumented)
serverConfig: JourneyServerConfig;
}
export { JourneyClientConfig }

// @public (undocumented)
export type JourneyLoginFailure = AuthResponse & {
Expand All @@ -219,11 +216,7 @@ export type JourneyLoginSuccess = AuthResponse & {
// @public (undocumented)
export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;

// @public
export interface JourneyServerConfig {
timeout?: number;
wellknown: string;
}
export { JourneyServerConfig }

// @public (undocumented)
export type JourneyStep = AuthResponse & {
Expand Down
45 changes: 44 additions & 1 deletion packages/journey-client/src/lib/client.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @vitest-environment node
/*
* Copyright (c) 2025-2026 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2025 - 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 All @@ -9,6 +9,7 @@
import { afterEach, describe, expect, test, vi } from 'vitest';

import { journey } from './client.store.js';
import { makeJourneyConfig } from '@forgerock/sdk-utilities';
import { createJourneyStep } from './step.utils.js';

import { callbackType, type GenericError, type Step, type WellknownResponse } from '../index.js';
Expand Down Expand Up @@ -559,4 +560,46 @@ describe('journey-client', () => {
expect(request.url).toBe('https://test.com/am/json/realms/root/realms/alpha/authenticate');
});
});

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

const unifiedConfig = {
oidc: {
clientId: 'ignored-by-journey',
discoveryEndpoint: mockWellknownUrl,
scopes: ['openid'],
redirectUri: 'https://example.com/callback',
},
};

const client = await journey({ config: makeJourneyConfig(unifiedConfig) });
expect(client).toHaveProperty('start');
expect(client).toHaveProperty('next');
});

test('throws when unified JSON config has missing required field', async () => {
const invalidConfig = {
oidc: {
// discoveryEndpoint missing — required even for journey
},
};

expect(() => makeJourneyConfig(invalidConfig)).toThrow(/Invalid unified SDK config/);
});

test('throws when unified JSON config has wrong field type', async () => {
const invalidConfig = {
oidc: {
clientId: '123',
discoveryEndpoint: mockWellknownUrl,
scopes: 'openid', // should be array
redirectUri: 'https://example.com/callback',
},
};

expect(() => makeJourneyConfig(invalidConfig)).toThrow(/Invalid unified SDK config/);
});
});
});
8 changes: 5 additions & 3 deletions packages/journey-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025-2026 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2025 - 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 All @@ -12,7 +12,6 @@ import {
isValidWellknownUrl,
createWellknownError,
} from '@forgerock/sdk-utilities';

import type { GenericError } from '@forgerock/sdk-types';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
import type { Step } from '@forgerock/sdk-types';
Expand Down Expand Up @@ -82,7 +81,10 @@ export async function journey<ActionType extends ActionTypes = ActionTypes>({
custom?: CustomLogger;
};
}): Promise<JourneyClient> {
const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom });
const log = loggerFn({
level: logger?.level ?? config.log ?? 'error',
custom: logger?.custom,
});

const ignoredProperties = [
'callbackFactory',
Expand Down
Loading
Loading