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
58 changes: 58 additions & 0 deletions .changeset/sdks-5067-unified-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
'@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>`
- `AuthDisplayValue`, `AuthPromptValue` types (canonical source — shared between `OidcConfig` and `GetAuthorizationUrlOptions`)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

**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
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