Skip to content

Commit 9ce0aff

Browse files
committed
fix(journey-client): extend JourneyClientConfig from AsyncLegacyConfigOptions
Allow the same config object to be shared across journey-client, davinci-client, and oidc-client. Properties like clientId, scope, and redirectUri are accepted but not used — a warning is logged when they are provided.
1 parent e09904f commit 9ce0aff

5 files changed

Lines changed: 184 additions & 3 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@forgerock/journey-client': patch
3+
---
4+
5+
Extend `JourneyClientConfig` from `AsyncLegacyConfigOptions` so the same config object can be shared across journey-client, davinci-client, and oidc-client
6+
7+
- `clientId`, `scope`, `redirectUri`, and other inherited properties are now accepted but ignored — a warning is logged when they are provided
8+
- `serverConfig.wellknown` remains required

packages/journey-client/src/lib/client.store.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,71 @@ describe('journey-client', () => {
359359
});
360360
});
361361

362+
describe('config property warnings', () => {
363+
test('journey_ExtraConfigProperties_LogsWarning', async () => {
364+
setupMockFetch();
365+
const customLogger = {
366+
error: vi.fn(),
367+
warn: vi.fn(),
368+
info: vi.fn(),
369+
debug: vi.fn(),
370+
};
371+
372+
await journey({
373+
config: {
374+
serverConfig: { wellknown: mockWellknownUrl },
375+
clientId: 'test-client',
376+
scope: 'openid',
377+
redirectUri: 'https://example.com/callback',
378+
},
379+
logger: { level: 'warn', custom: customLogger },
380+
});
381+
382+
expect(customLogger.warn).toHaveBeenCalledTimes(1);
383+
expect(customLogger.warn).toHaveBeenCalledWith(expect.stringContaining('clientId'));
384+
expect(customLogger.warn).toHaveBeenCalledWith(expect.stringContaining('scope'));
385+
expect(customLogger.warn).toHaveBeenCalledWith(expect.stringContaining('redirectUri'));
386+
});
387+
388+
test('journey_SingleIgnoredProperty_LogsWarning', async () => {
389+
setupMockFetch();
390+
const customLogger = {
391+
error: vi.fn(),
392+
warn: vi.fn(),
393+
info: vi.fn(),
394+
debug: vi.fn(),
395+
};
396+
397+
await journey({
398+
config: {
399+
serverConfig: { wellknown: mockWellknownUrl },
400+
clientId: 'test-client',
401+
},
402+
logger: { level: 'warn', custom: customLogger },
403+
});
404+
405+
expect(customLogger.warn).toHaveBeenCalledTimes(1);
406+
expect(customLogger.warn).toHaveBeenCalledWith(expect.stringContaining('clientId'));
407+
});
408+
409+
test('journey_MinimalConfig_NoWarning', async () => {
410+
setupMockFetch();
411+
const customLogger = {
412+
error: vi.fn(),
413+
warn: vi.fn(),
414+
info: vi.fn(),
415+
debug: vi.fn(),
416+
};
417+
418+
await journey({
419+
config: { serverConfig: { wellknown: mockWellknownUrl } },
420+
logger: { level: 'warn', custom: customLogger },
421+
});
422+
423+
expect(customLogger.warn).not.toHaveBeenCalled();
424+
});
425+
});
426+
362427
describe('subrealm inference', () => {
363428
test('journey_WellknownWithSubrealm_DerivesCorrectPaths', async () => {
364429
const alphaConfig: JourneyClientConfig = {

packages/journey-client/src/lib/client.store.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export interface JourneyClient {
5050
* It uses AM-proprietary endpoints for callback-based authentication trees.
5151
*
5252
* @param options - Configuration options for the journey client
53-
* @param options.config - Server configuration with required wellknown URL
53+
* @param options.config - Configuration options (see {@link JourneyClientConfig}); only `serverConfig.wellknown` is required
5454
* @param options.requestMiddleware - Optional middleware for request customization
5555
* @param options.logger - Optional logger configuration
5656
* @returns A journey client instance
@@ -86,6 +86,35 @@ export async function journey<ActionType extends ActionTypes = ActionTypes>({
8686
}): Promise<JourneyClient> {
8787
const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom });
8888

89+
const ignoredProperties = [
90+
'callbackFactory',
91+
'clientId',
92+
'middleware',
93+
'oauthThreshold',
94+
'platformHeader',
95+
'prefix',
96+
'realmPath',
97+
'redirectUri',
98+
'scope',
99+
'tokenStore',
100+
'tree',
101+
'type',
102+
] as const;
103+
104+
const providedIgnored: string[] = ignoredProperties.filter(
105+
(prop) => config[prop] !== undefined,
106+
);
107+
108+
if (config.serverConfig?.timeout !== undefined) {
109+
providedIgnored.push('serverConfig.timeout');
110+
}
111+
112+
if (providedIgnored.length > 0) {
113+
log.warn(
114+
`The following configuration properties are not used by journey-client and will be ignored: ${providedIgnored.join(', ')}`,
115+
);
116+
}
117+
89118
const store = createJourneyStore({ requestMiddleware, logger: log });
90119

91120
const { wellknown } = config.serverConfig;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import { describe, expectTypeOf, it } from 'vitest';
8+
import type {
9+
JourneyClientConfig,
10+
JourneyServerConfig,
11+
InternalJourneyClientConfig,
12+
} from './config.types.js';
13+
import type { AsyncLegacyConfigOptions } from '@forgerock/sdk-types';
14+
import type { ResolvedServerConfig } from './wellknown.utils.js';
15+
16+
describe('Config Types', () => {
17+
describe('JourneyClientConfig', () => {
18+
it('should extend AsyncLegacyConfigOptions', () => {
19+
expectTypeOf<JourneyClientConfig>().toExtend<AsyncLegacyConfigOptions>();
20+
});
21+
22+
it('should narrow serverConfig to JourneyServerConfig', () => {
23+
expectTypeOf<JourneyClientConfig['serverConfig']>().toExtend<JourneyServerConfig>();
24+
expectTypeOf<JourneyClientConfig['serverConfig']['wellknown']>().toBeString();
25+
});
26+
27+
it('should reject config without wellknown', () => {
28+
// @ts-expect-error - wellknown is required on serverConfig
29+
const config: JourneyClientConfig = { serverConfig: {} };
30+
// This assertion verifies the variable's runtime shape doesn't satisfy the full type.
31+
expectTypeOf(config).not.toMatchObjectType<Required<JourneyClientConfig>>();
32+
});
33+
34+
it('should allow AsyncLegacyConfigOptions properties', () => {
35+
const config: JourneyClientConfig = {
36+
clientId: 'test-client',
37+
scope: 'openid profile',
38+
redirectUri: 'https://app.example.com/callback',
39+
serverConfig: {
40+
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
41+
timeout: 30000,
42+
},
43+
};
44+
expectTypeOf(config).toExtend<JourneyClientConfig>();
45+
});
46+
47+
it('should not require inherited properties like clientId', () => {
48+
const config: JourneyClientConfig = {
49+
serverConfig: {
50+
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
51+
},
52+
};
53+
expectTypeOf(config).toExtend<JourneyClientConfig>();
54+
});
55+
56+
it('should have optional timeout on serverConfig', () => {
57+
expectTypeOf<JourneyClientConfig['serverConfig']>().toHaveProperty('timeout');
58+
});
59+
});
60+
61+
describe('InternalJourneyClientConfig', () => {
62+
it('should have ResolvedServerConfig', () => {
63+
expectTypeOf<InternalJourneyClientConfig>()
64+
.toHaveProperty('serverConfig')
65+
.toExtend<ResolvedServerConfig>();
66+
});
67+
68+
it('should have optional error', () => {
69+
expectTypeOf<InternalJourneyClientConfig>().toHaveProperty('error').toBeNullable();
70+
});
71+
});
72+
});

packages/journey-client/src/lib/config.types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* of the MIT license. See the LICENSE file for details.
66
*/
77

8-
import type { GenericError } from '@forgerock/sdk-types';
8+
import type { AsyncLegacyConfigOptions, GenericError } from '@forgerock/sdk-types';
99
import type { ResolvedServerConfig } from './wellknown.utils.js';
1010

1111
/**
@@ -17,11 +17,18 @@ import type { ResolvedServerConfig } from './wellknown.utils.js';
1717
export interface JourneyServerConfig {
1818
/** Required OIDC discovery endpoint URL */
1919
wellknown: string;
20+
/** Optional request timeout in milliseconds. Included for config-sharing compatibility with other clients. */
21+
timeout?: number;
2022
}
2123

2224
/**
2325
* Configuration for creating a journey client instance.
2426
*
27+
* Extends {@link AsyncLegacyConfigOptions} so that the same config object can
28+
* be shared across journey-client, davinci-client, and oidc-client. Properties
29+
* like `clientId`, `scope`, and `redirectUri` are accepted but not used by
30+
* journey-client — a warning is logged when they are provided.
31+
*
2532
* @example
2633
* ```typescript
2734
* const config: JourneyClientConfig = {
@@ -31,7 +38,7 @@ export interface JourneyServerConfig {
3138
* };
3239
* ```
3340
*/
34-
export interface JourneyClientConfig {
41+
export interface JourneyClientConfig extends AsyncLegacyConfigOptions {
3542
serverConfig: JourneyServerConfig;
3643
}
3744

0 commit comments

Comments
 (0)