diff --git a/.changeset/nice-sails-trade.md b/.changeset/nice-sails-trade.md
new file mode 100644
index 00000000000..e1bc0538d12
--- /dev/null
+++ b/.changeset/nice-sails-trade.md
@@ -0,0 +1,28 @@
+---
+'@forgerock/davinci-client': minor
+'@forgerock/journey-client': minor
+'@forgerock/oidc-client': minor
+'@forgerock/sdk-store': minor
+'@forgerock/sdk-oidc': patch
+---
+
+Allow multiple SDK clients to share a single Redux store.
+
+`davinci()`, `journey()`, and `oidc()` now accept an optional `store` option. When two clients share a store they share the OpenID Connect discovery cache, so `.well-known/openid-configuration` is fetched once instead of once per client. `davinci()` and `journey()` expose the store they create as `client.store`; applications that want to own the store themselves can build one with `createSdkStore()` from the new `@forgerock/sdk-store` package.
+
+Omitting `store` is unchanged behaviour: the client creates its own store, exactly as before.
+
+**Request middleware and logging are scoped per client.** Each client's `requestMiddleware` and `logger` are registered against that client alone and are resolved only by its own requests. Middleware passed to `davinci()` or `journey()` is never applied to OIDC requests (`AUTHORIZE`, `PAR`, `TOKEN_EXCHANGE`, `REVOKE`, `USER_INFO`, `END_SESSION`), and middleware passed to `oidc()` is never applied to DaVinci or Journey requests. Both options are honoured on a shared store.
+
+**`oidc()` takes `store` as part of its options object**, alongside `config`, `requestMiddleware`, `logger`, and `storage`, consistent with every other factory in the SDK.
+
+**One OIDC client per store.** `oidc()` mounts at a fixed key, so initialising a second OIDC client on the same store with a different `clientId` returns an `argument_error` rather than silently overwriting the first client's token state. Re-initialising with the same `clientId` is allowed and idempotent. Use a separate store per `clientId`.
+
+Also in this release:
+
+- New `@forgerock/sdk-store` package (`scope:sdk-effects`) holding the single canonical `wellknownApi` instance, the shared store contract (`SdkStore`, `SdkStoreHandle`, `createSdkStore`, `injectClient`), and OpenID Connect discovery helpers (`initWellknownQuery`, `isValidWellknownResponse`). Previously each client package defined its own `wellknownApi`, which meant a separate discovery cache per client.
+- `oidc()` validates its arguments before attaching to a store, so a rejected call no longer leaves a caller-provided store modified.
+- Passing a value that is not an SDK store to `store` returns an `argument_error` instead of throwing.
+- Well-known selectors are now memoized per URL. `createWellknownSelector` previously rebuilt its selector on every call, so its cache never took effect.
+- `@forgerock/sdk-oidc`: `initWellknownQuery` and `isValidWellknownResponse` move to `@forgerock/sdk-store`. Update imports if you were using them directly.
+- `enforce-module-boundaries` lint rule promoted from `warn` to `error` across the repo. All packages pass.
diff --git a/e2e/davinci-app/main.ts b/e2e/davinci-app/main.ts
index 088a7a5807a..f406feb2dde 100644
--- a/e2e/davinci-app/main.ts
+++ b/e2e/davinci-app/main.ts
@@ -13,7 +13,6 @@ import type {
Collectors,
CustomLogger,
DaVinciConfig,
- DavinciClient,
GetClient,
InternalErrorResponse,
NodeStates,
@@ -88,7 +87,11 @@ const requestMiddleware: RequestMiddleware<'DAVINCI_NEXT' | 'DAVINCI_START'>[] =
const urlParams = new URLSearchParams(window.location.search);
(async () => {
- const davinciClient: DavinciClient = await davinci({ config, logger, requestMiddleware });
+ const davinciResult = await davinci({ config, logger, requestMiddleware });
+ if ('error' in davinciResult) {
+ throw new Error(`Failed to initialize davinci client: ${davinciResult.error}`);
+ }
+ const davinciClient = davinciResult;
const oidcResult = await oidc({ config: config as OidcConfig });
if ('error' in oidcResult) {
throw new Error(`Failed to initialize oidc client: ${oidcResult.error}`);
diff --git a/e2e/davinci-app/shared-store.html b/e2e/davinci-app/shared-store.html
new file mode 100644
index 00000000000..e0551b54f43
--- /dev/null
+++ b/e2e/davinci-app/shared-store.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Shared Store Test
+
+
+ initialising…
+
+
+
diff --git a/e2e/davinci-app/shared-store.ts b/e2e/davinci-app/shared-store.ts
new file mode 100644
index 00000000000..555ecb1914d
--- /dev/null
+++ b/e2e/davinci-app/shared-store.ts
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/**
+ * Shared-store smoke test entry point.
+ *
+ * This page is navigated to by the Playwright e2e suite
+ * `shared-store.test.ts` only. It does not connect to any real PingOne
+ * endpoint — the test intercepts every `.well-known` request via
+ * `page.route()` and returns a minimal synthetic response.
+ *
+ * The page reports results by writing to `#status` so the test can
+ * assert via `page.textContent` without any app-specific UI.
+ */
+import { davinci } from '@forgerock/davinci-client';
+import type { DaVinciConfig } from '@forgerock/davinci-client/types';
+import { oidc } from '@forgerock/oidc-client';
+import type { OidcConfig } from '@forgerock/oidc-client/types';
+
+const WELLKNOWN_URL = 'https://sdk-test.example.com/as/.well-known/openid-configuration';
+
+const davinciConfig: DaVinciConfig = {
+ clientId: 'test-davinci-client',
+ redirectUri: window.location.origin,
+ scope: 'openid profile',
+ serverConfig: { wellknown: WELLKNOWN_URL },
+};
+
+const oidcConfig: OidcConfig = {
+ clientId: 'test-oidc-client',
+ redirectUri: window.location.origin,
+ scope: 'openid profile',
+ responseType: 'code',
+ serverConfig: { wellknown: WELLKNOWN_URL },
+};
+
+const statusEl = document.getElementById('status')!;
+
+async function run() {
+ // ── Mode 2: davinci creates the store, oidc attaches ─────────────────────
+ const dvClient = await davinci({ config: davinciConfig });
+ if ('error' in dvClient) {
+ statusEl.textContent = `davinci init error: ${dvClient.error}`;
+ return;
+ }
+
+ const ocClient = await oidc({ config: oidcConfig, store: dvClient.store });
+ if ('error' in ocClient) {
+ statusEl.textContent = `oidc init error: ${ocClient.error}`;
+ return;
+ }
+
+ statusEl.textContent = 'ready';
+}
+
+run().catch((err) => {
+ statusEl.textContent = `unexpected error: ${String(err)}`;
+});
diff --git a/e2e/davinci-app/vite.config.ts b/e2e/davinci-app/vite.config.ts
index 8625a27297c..b885b3911c8 100644
--- a/e2e/davinci-app/vite.config.ts
+++ b/e2e/davinci-app/vite.config.ts
@@ -17,6 +17,7 @@ export default defineConfig({
rollupOptions: {
input: {
main: path.resolve(__dirname, 'index.html'),
+ 'shared-store': path.resolve(__dirname, 'shared-store.html'),
},
output: {
entryFileNames: 'main.js',
diff --git a/e2e/davinci-suites/src/shared-store.test.ts b/e2e/davinci-suites/src/shared-store.test.ts
new file mode 100644
index 00000000000..8ec14164459
--- /dev/null
+++ b/e2e/davinci-suites/src/shared-store.test.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { expect, test } from '@playwright/test';
+
+/**
+ * Verifies that two SDK clients sharing a store fetch the OpenID Connect
+ * discovery document exactly once, regardless of which client initialises
+ * first and regardless of the ownership model.
+ *
+ * The page under test (`/shared-store`) exercises both Mode 2 (davinci owns
+ * the store) and Mode 3 (consumer-created store). Requests to the well-known
+ * URL are intercepted and served with a synthetic response so the test does
+ * not require a live PingOne endpoint.
+ */
+
+const WELLKNOWN_URL = 'https://sdk-test.example.com/as/.well-known/openid-configuration';
+
+const WELLKNOWN_RESPONSE = {
+ issuer: 'https://sdk-test.example.com/as',
+ authorization_endpoint: 'https://sdk-test.example.com/as/authorize',
+ token_endpoint: 'https://sdk-test.example.com/as/token',
+ userinfo_endpoint: 'https://sdk-test.example.com/as/userinfo',
+ jwks_uri: 'https://sdk-test.example.com/as/jwks',
+ revocation_endpoint: 'https://sdk-test.example.com/as/revoke',
+ introspection_endpoint: 'https://sdk-test.example.com/as/introspect',
+ pushed_authorization_request_endpoint: 'https://sdk-test.example.com/as/par',
+};
+
+test('shared store — one .well-known fetch across two clients (mode 2: client-owned)', async ({
+ page,
+}) => {
+ let discoveryFetchCount = 0;
+
+ // Intercept and count every discovery request; fulfil with a synthetic response
+ // so no live credential or network is needed.
+ await page.route(`**/.well-known/**`, async (route) => {
+ discoveryFetchCount++;
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify(WELLKNOWN_RESPONSE),
+ });
+ });
+
+ await page.goto('/shared-store.html', { waitUntil: 'networkidle' });
+
+ // The page reports its own status so we know initialisation completed.
+ await expect(page.locator('#status')).toHaveText('ready', { timeout: 15_000 });
+
+ // Mode 2 (davinci owns the store, oidc attaches): davinci fetches once,
+ // oidc reads from cache — exactly 1 network request for 2 clients.
+ expect(discoveryFetchCount).toBe(1);
+});
+
+test("shared store — oidc attaches to davinci's store, reads discovery from cache", async ({
+ page,
+}) => {
+ const fetchedUrls: string[] = [];
+
+ await page.route(`**/.well-known/**`, async (route) => {
+ fetchedUrls.push(route.request().url());
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify(WELLKNOWN_RESPONSE),
+ });
+ });
+
+ await page.goto('/shared-store.html', { waitUntil: 'networkidle' });
+ await expect(page.locator('#status')).toHaveText('ready', { timeout: 15_000 });
+
+ // Both modes use the same WELLKNOWN_URL, so each URL appears exactly once
+ // across the two calls despite four total client initialisations.
+ const unique = [...new Set(fetchedUrls)];
+ expect(unique).toHaveLength(1);
+ expect(unique[0]).toContain('.well-known');
+
+ // Mode 2: 2 clients (davinci + oidc) on 1 store → exactly 1 fetch.
+ expect(fetchedUrls.length).toBe(1);
+});
diff --git a/e2e/journey-app/main.ts b/e2e/journey-app/main.ts
index 18adaa66009..0fd711d8b32 100644
--- a/e2e/journey-app/main.ts
+++ b/e2e/journey-app/main.ts
@@ -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.
@@ -8,7 +8,7 @@ import './style.css';
import { journey } from '@forgerock/journey-client';
-import type { JourneyClient, RequestMiddleware } from '@forgerock/journey-client/types';
+import type { RequestMiddleware } from '@forgerock/journey-client/types';
import { renderCallbacks } from './callback-map.js';
import { renderDeleteDevicesSection } from './components/delete-device.js';
@@ -62,15 +62,14 @@ if (searchParams.get('middleware') === 'true') {
const formEl = document.getElementById('form') as HTMLFormElement;
const journeyEl = document.getElementById('journey') as HTMLDivElement;
- let journeyClient: JourneyClient;
- try {
- journeyClient = await journey({ config: config, requestMiddleware });
- } catch (error) {
- const message = error instanceof Error ? error.message : 'Unknown error';
+ const journeyResult = await journey({ config: config, requestMiddleware });
+ if ('error' in journeyResult) {
+ const message = journeyResult.error;
console.error('Failed to initialize journey client:', message);
errorEl.textContent = message;
return;
}
+ const journeyClient = journeyResult;
let step = await journeyClient.start({ journey: journeyName });
function renderError() {
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 1176254153d..0047858d6ce 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -110,7 +110,7 @@ export default [
rules: {
'import/extensions': [2, 'ignorePackages'],
'@nx/enforce-module-boundaries': [
- 'warn',
+ 'error',
{
enforceBuildableLibDependency: true,
allow: [],
diff --git a/packages/davinci-client/README.md b/packages/davinci-client/README.md
index 5534564ffd9..a12ab1e61ee 100644
--- a/packages/davinci-client/README.md
+++ b/packages/davinci-client/README.md
@@ -25,7 +25,7 @@ Configure DaVinci Client with the following minimum, required properties:
```ts
// Demo with example values
-import { davinci } from '@forgerock/davinci';
+import { davinci } from '@forgerock/davinci-client';
const davinciClient = await davinci({
config: {
@@ -42,7 +42,7 @@ If you have a need for more than one client, say you need to use two or more dif
```ts
// Demo with example values
-import { davinci } from '@forgerock/davinci';
+import { davinci } from '@forgerock/davinci-client';
const firstDavinciClient = await davinci(/** config 1 **/);
const secondDavinciClient = await davinci(/** config 2 **/);
@@ -63,6 +63,50 @@ interface DaVinciConfig {
}
```
+### Sharing a store with another client
+
+If your application also uses `@forgerock/oidc-client`, the two can share one Redux store so the well-known discovery document is fetched once rather than once per client.
+
+`davinci()` exposes the store it created as `client.store`. Pass it to the other client:
+
+```ts
+import { davinci } from '@forgerock/davinci-client';
+import { oidc } from '@forgerock/oidc-client';
+
+const davinciClient = await davinci({ config });
+
+// Attaches to davinci's store; the discovery document is already cached there.
+const oidcClient = await oidc({ config: oidcConfig, store: davinciClient.store });
+```
+
+Or create the store yourself when neither client is the natural owner:
+
+```ts
+import { createSdkStore } from '@forgerock/sdk-store';
+
+const store = createSdkStore();
+const davinciClient = await davinci({ config, store });
+const oidcClient = await oidc({ config: oidcConfig, store });
+```
+
+Omitting `store` is always valid — the client creates its own, which is the default behaviour.
+
+#### Middleware and logging stay private
+
+Sharing a store shares cached data, not configuration. `requestMiddleware` and `logger` are registered against the client you pass them to, and are resolved only by that client's own requests:
+
+```ts
+const store = createSdkStore();
+
+// Runs for DAVINCI_START, DAVINCI_NEXT, DAVINCI_FLOW and the other DaVinci actions only.
+await davinci({ config, store, requestMiddleware: [davinciMiddleware] });
+
+// Runs for OIDC requests only.
+await oidc({ config: oidcConfig, store, requestMiddleware: [oidcMiddleware] });
+```
+
+Middleware passed here will never run against an OIDC token exchange, and vice versa.
+
### Start a DaVinci flow
Call the `start` method on the returned client API:
@@ -182,10 +226,10 @@ Upon each collector in the array, some will need an `updater`, like the collecto
```ts
// Example SingleValueCollector using the TextCollector
-const collectors = davinci.collectors();
+const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'TextCollector') {
- renderTextCollector(collector, davinci.update(collector));
+ renderTextCollector(collector, davinciClient.update(collector));
}
});
```
@@ -214,7 +258,7 @@ The `SubmitCollector` is associated with the submission of the current node and
```ts
// Example SubmitCollector mapping
-const collectors = davinci.collectors();
+const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'SubmitCollector') {
renderSubmitCollector(
@@ -234,7 +278,7 @@ To do this, you call the `flow` method on the `davinciClient` passing the `key`
```ts
// Example FlowCollector mapping
-const collectors = davinci.collectors();
+const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'FlowCollector') {
renderFlowCollector(collector, davinciClient.flow(collector));
@@ -260,7 +304,7 @@ function renderFlowCollector(collector, startFlow) {
After collecting the needed data, you proceed to the next node in the DaVinci flow by calling the `.next()` method on the same `davinci` client object. This can be the result of a user clicking on the button rendered from the `SubmitCollector`, from the "submit" event of the HTML form itself, or from programmatically triggering the submission in the application layer.
```ts
-let nextStep = davinci.next();
+const nextStep = await davinciClient.next();
```
Note: There's no need to pass anything into the `next` method as the DaVinci Client internally stores the updated object needed for the server.
@@ -284,25 +328,12 @@ When you receive a success node, you will likely want to use the Authorization C
Here's a brief sample of what that might look like in pseudocode:
```ts
-// ... other imports
-
-import { Config, TokenManager } from '@forgerock/javascript-sdk';
-
-// ... other config or initialization code
-
-// This Config.set accepts the same config schema as the davinci function
-Config.set(config);
-
-const node = await davinciClient.next();
-
-if (node.status === 'success') {
- const clientInfo = davinciClient.getClient();
-
- const code = clientInfo.authorization?.code || '';
- const state = clientInfo.authorization?.state || '';
-
- const tokens = await TokenManager.getTokens({ query: { code, state } });
- // user now has session and OIDC tokens
+// oidcClient is an instance of oidc() from @forgerock/oidc-client, configured earlier
+const tokens = await oidcClient.token.exchange(code, state);
+if ('error' in tokens) {
+ console.error('Token exchange failed:', tokens.error);
+} else {
+ console.log('Access token:', tokens.accessToken);
}
```
@@ -320,7 +351,7 @@ if (node.status === 'failure') {
renderError(error);
// ... user clicks button to restart flow
- const freshNode = davinciClient.start();
+ const freshNode = await davinciClient.start();
}
```
diff --git a/packages/davinci-client/api-report/davinci-client.api.md b/packages/davinci-client/api-report/davinci-client.api.md
index a8e738c5514..6f2938c9339 100644
--- a/packages/davinci-client/api-report/davinci-client.api.md
+++ b/packages/davinci-client/api-report/davinci-client.api.md
@@ -17,6 +17,7 @@ import type { MutationResultSelectorResult } from '@reduxjs/toolkit/query';
import { QueryStatus } from '@reduxjs/toolkit/query';
import { Reducer } from '@reduxjs/toolkit';
import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
+import type { SdkStore } from '@forgerock/sdk-store';
import { SerializedError } from '@reduxjs/toolkit';
import { Unsubscribe } from '@reduxjs/toolkit';
@@ -281,7 +282,12 @@ export function davinci(input: {
level: LogLevel;
custom?: CustomLogger;
};
+ store?: unknown;
}): Promise<{
+ error: string;
+ type: "argument_error";
+} | {
+ store: SdkStore;
subscribe: (listener: () => void) => Unsubscribe;
externalIdp: () => (() => Promise);
flow: (action: DaVinciAction) => InitFlow;
diff --git a/packages/davinci-client/api-report/davinci-client.types.api.md b/packages/davinci-client/api-report/davinci-client.types.api.md
index d498f3a14f2..939ed7dfa53 100644
--- a/packages/davinci-client/api-report/davinci-client.types.api.md
+++ b/packages/davinci-client/api-report/davinci-client.types.api.md
@@ -16,6 +16,7 @@ import type { MutationResultSelectorResult } from '@reduxjs/toolkit/query';
import { QueryStatus } from '@reduxjs/toolkit/query';
import { Reducer } from '@reduxjs/toolkit';
import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
+import type { SdkStore } from '@forgerock/sdk-store';
import { SerializedError } from '@reduxjs/toolkit';
import { Unsubscribe } from '@reduxjs/toolkit';
@@ -280,7 +281,12 @@ export function davinci(input: {
level: LogLevel;
custom?: CustomLogger;
};
+ store?: unknown;
}): Promise<{
+ error: string;
+ type: "argument_error";
+} | {
+ store: SdkStore;
subscribe: (listener: () => void) => Unsubscribe;
externalIdp: () => (() => Promise);
flow: (action: DaVinciAction) => InitFlow;
diff --git a/packages/davinci-client/package.json b/packages/davinci-client/package.json
index 0be513d2031..d85c08796d3 100644
--- a/packages/davinci-client/package.json
+++ b/packages/davinci-client/package.json
@@ -30,6 +30,7 @@
"@forgerock/sdk-logger": "workspace:*",
"@forgerock/sdk-oidc": "workspace:*",
"@forgerock/sdk-request-middleware": "workspace:*",
+ "@forgerock/sdk-store": "workspace:*",
"@forgerock/sdk-types": "workspace:*",
"@forgerock/sdk-utilities": "workspace:*",
"@forgerock/storage": "workspace:*",
diff --git a/packages/davinci-client/src/lib/client.store.effects.ts b/packages/davinci-client/src/lib/client.store.effects.ts
index 9923ea4b676..dc306345ec2 100644
--- a/packages/davinci-client/src/lib/client.store.effects.ts
+++ b/packages/davinci-client/src/lib/client.store.effects.ts
@@ -11,7 +11,8 @@ import { FetchBaseQueryError } from '@reduxjs/toolkit/query/react';
import type { logger as loggerFn } from '@forgerock/sdk-logger';
-import type { ClientStore, RootState } from './client.store.utils.js';
+import type { DavinciStore } from './client.store.utils.js';
+import type { RootState } from './davinci.state.js';
import type { PollingStatus, InternalErrorResponse } from './client.types.js';
import type { PollingCollector } from './collector.types.js';
@@ -239,7 +240,7 @@ function challengePollingµ({
}: {
collector: PollingCollector;
challenge: string;
- store: ReturnType;
+ store: DavinciStore;
log: ReturnType;
}): Micro.Micro {
const maxRetries = collector.output.config.pollRetries ?? 60;
@@ -295,7 +296,7 @@ export function pollingµ({
}: {
mode: PollingMode;
collector: PollingCollector;
- store: ReturnType;
+ store: DavinciStore;
log: ReturnType;
}): Micro.Micro {
if (mode._tag === 'challenge') {
diff --git a/packages/davinci-client/src/lib/client.store.test.ts b/packages/davinci-client/src/lib/client.store.test.ts
index ef6c2a96237..a4f2a245365 100644
--- a/packages/davinci-client/src/lib/client.store.test.ts
+++ b/packages/davinci-client/src/lib/client.store.test.ts
@@ -123,6 +123,7 @@ describe('davinci client — cache', () => {
describe('cache.getLatestResponse()', () => {
it('returns a state_error when no flow has been started (no cache key)', async () => {
const client = await davinci({ config: mockConfig });
+ if ('type' in client) throw new Error(`davinci() failed: ${client.error}`);
// Node is in start status — cache.key is null before any start() call
const result = client.cache.getLatestResponse();
@@ -132,6 +133,7 @@ describe('davinci client — cache', () => {
it('returns the raw DaVinci response object — NOT a selector function — after start()', async () => {
const client = await davinci({ config: mockConfig });
+ if ('type' in client) throw new Error(`davinci() failed: ${client.error}`);
await client.start();
const result = client.cache.getLatestResponse();
@@ -150,6 +152,7 @@ describe('davinci client — cache', () => {
describe('cache.getResponseWithId()', () => {
it('returns an argument_error when called with an empty string', async () => {
const client = await davinci({ config: mockConfig });
+ if ('type' in client) throw new Error(`davinci() failed: ${client.error}`);
const result = client.cache.getResponseWithId('');
@@ -158,6 +161,7 @@ describe('davinci client — cache', () => {
it('returns the raw DaVinci response object — NOT a selector function — for a valid request ID', async () => {
const client = await davinci({ config: mockConfig });
+ if ('type' in client) throw new Error(`davinci() failed: ${client.error}`);
await client.start();
const node = client.getNode();
@@ -175,6 +179,7 @@ describe('davinci client — cache', () => {
it('returns a state_error for a requestId not present in cache', async () => {
const client = await davinci({ config: mockConfig });
+ if ('type' in client) throw new Error(`davinci() failed: ${client.error}`);
const result = client.cache.getResponseWithId('non-existent-id');
diff --git a/packages/davinci-client/src/lib/client.store.ts b/packages/davinci-client/src/lib/client.store.ts
index c92def90d84..6bd2ae6b974 100644
--- a/packages/davinci-client/src/lib/client.store.ts
+++ b/packages/davinci-client/src/lib/client.store.ts
@@ -19,15 +19,16 @@ import {
handleUpdateValidateError,
isValidCollectorCategory,
resolveCollectorUpdateValue,
- type RootState,
} from './client.store.utils.js';
+import type { RootState } from './davinci.state.js';
import { pollingµ, getPollingModeµ } from './client.store.effects.js';
import { nodeSlice } from './node.slice.js';
import { davinciApi } from './davinci.api.js';
import { configSlice } from './config.slice.js';
-import { wellknownApi } from './wellknown.api.js';
+import { wellknownApi, assertValidStore } from '@forgerock/sdk-store';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
+import type { SdkStore } from '@forgerock/sdk-store';
/**
* Import the DaVinciRequest types
*/
@@ -71,6 +72,7 @@ export async function davinci({
config,
requestMiddleware,
logger,
+ store: sharedStore,
}: {
config: DaVinciConfig;
requestMiddleware?: RequestMiddleware[];
@@ -78,16 +80,22 @@ export async function davinci({
level: LogLevel;
custom?: CustomLogger;
};
+ /**
+ * An existing SDK store to attach to, so discovery caching and state are
+ * shared with another client. Omit to create a store for this client alone.
+ */
+ store?: unknown;
}) {
const log = loggerFn({
level: logger?.level ?? config.log ?? 'error',
custom: logger?.custom,
});
- const store = createClientStore({ requestMiddleware, logger: log });
- const serverInfo = createStorage({
- type: 'localStorage',
- name: 'serverInfo',
- });
+
+ const storeError = assertValidStore(sharedStore);
+ if (storeError) return storeError;
+
+ const validStore = sharedStore as SdkStore | undefined;
+
if (!config.serverConfig.wellknown) {
const error = new Error(
'`wellknown` property is a required as part of the `config.serverConfig`',
@@ -102,6 +110,13 @@ export async function davinci({
throw error;
}
+ const handle = createClientStore({ requestMiddleware, logger: log, store: validStore });
+ const store = handle.store;
+ const serverInfo = createStorage({
+ type: 'localStorage',
+ name: 'serverInfo',
+ });
+
const { data: openIdResponse, error: fetchError } = await store.dispatch(
wellknownApi.endpoints.configuration.initiate(config.serverConfig.wellknown),
);
@@ -115,6 +130,8 @@ export async function davinci({
store.dispatch(configSlice.actions.set({ ...config, wellknownResponse: openIdResponse }));
return {
+ /** Pass to another SDK client's `store` option to share this store. */
+ store: handle as SdkStore,
// Pass store methods to the client
subscribe: store.subscribe,
diff --git a/packages/davinci-client/src/lib/client.store.utils.ts b/packages/davinci-client/src/lib/client.store.utils.ts
index ae6034f2464..91af0c3b4e3 100644
--- a/packages/davinci-client/src/lib/client.store.utils.ts
+++ b/packages/davinci-client/src/lib/client.store.utils.ts
@@ -4,21 +4,13 @@
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
-import { configureStore } from '@reduxjs/toolkit';
import { Match, Either } from 'effect';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
import type { logger as loggerFn } from '@forgerock/sdk-logger';
import type { GenericError } from '@forgerock/sdk-types';
-import type {
- ErrorNode,
- ContinueNode,
- StartNode,
- SuccessNode,
- Collectors,
- CollectorCategory,
-} from './node.types.js';
+import type { Collectors, CollectorCategory } from './node.types.js';
import type {
CollectorValueType,
CollectorValueTypes,
@@ -26,54 +18,45 @@ import type {
UpdatableCollectors,
} from './client.types.js';
+import { createSdkStore, injectClient } from '@forgerock/sdk-store';
+import type { SdkStore, SdkStoreHandle } from '@forgerock/sdk-store';
+
import { configSlice } from './config.slice.js';
import { nodeSlice } from './node.slice.js';
import { davinciApi } from './davinci.api.js';
-import { wellknownApi } from './wellknown.api.js';
+import type { RootState } from './davinci.state.js';
+
+/**
+ * Creates, or attaches to, the store backing a DaVinci client.
+ *
+ * Passing `store` attaches to an existing SDK store so that discovery caching
+ * and state are shared; omitting it creates one, which is the default.
+ */
export function createClientStore({
requestMiddleware,
logger,
+ store,
}: {
requestMiddleware?: RequestMiddleware[];
logger?: ReturnType;
-}) {
- return configureStore({
- reducer: {
- config: configSlice.reducer,
- node: nodeSlice.reducer,
- [davinciApi.reducerPath]: davinciApi.reducer,
- [wellknownApi.reducerPath]: wellknownApi.reducer,
- },
- middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware({
- thunk: {
- extraArgument: {
- /**
- * This becomes the `api.extra` argument, and will be passed into the
- * customer query wrapper for `baseQuery`
- */
- requestMiddleware,
- logger,
- },
- },
- })
- .concat(davinciApi.middleware)
- .concat(wellknownApi.middleware),
+ store?: SdkStore;
+}): SdkStoreHandle {
+ return injectClient(store ?? createSdkStore(), {
+ api: davinciApi,
+ reducerPath: davinciApi.reducerPath,
+ slices: [configSlice, nodeSlice],
+ requestMiddleware,
+ logger,
});
}
export type ClientStore = typeof createClientStore;
-export type RootState = ReturnType['getState']>;
-
-export interface RootStateWithNode<
- T extends ErrorNode | ContinueNode | StartNode | SuccessNode,
-> extends RootState {
- node: T;
-}
+/** The inner Redux store type — used by effects that need dispatch/getState. */
+export type DavinciStore = SdkStoreHandle['store'];
-export type AppDispatch = ReturnType['dispatch']>;
+export type AppDispatch = DavinciStore['dispatch'];
/**
* @function createInternalError
diff --git a/packages/davinci-client/src/lib/davinci.api.ts b/packages/davinci-client/src/lib/davinci.api.ts
index 05e94872ef6..403ead99763 100644
--- a/packages/davinci-client/src/lib/davinci.api.ts
+++ b/packages/davinci-client/src/lib/davinci.api.ts
@@ -25,13 +25,14 @@ import { createAuthorizeUrl } from '@forgerock/sdk-oidc';
import { handleResponse, transformActionRequest, transformSubmitRequest } from './davinci.utils.js';
-import type { logger as loggerFn } from '@forgerock/sdk-logger';
+import { logger as loggerFn } from '@forgerock/sdk-logger';
+import { clientExtra } from '@forgerock/sdk-store';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
/**
* Import the DaVinci types
*/
-import type { RootStateWithNode } from './client.store.utils.js';
+import type { RootStateWithNode } from './davinci.state.js';
import type {
DaVinciCacheEntry,
OutgoingQueryParams,
@@ -47,9 +48,34 @@ type BaseQueryResponse = Promise<
QueryReturnValue
>;
+const DAVINCI_REDUCER_PATH = 'davinci';
+
+/**
+ * This client's private slot on the store's `extraArgument`.
+ *
+ * Both fields are optional because a shared store may not have had a davinci
+ * slot registered yet; `davinciExtra` substitutes safe defaults so an endpoint
+ * can never fail on a missing slot.
+ */
interface Extras {
- requestMiddleware: RequestMiddleware[];
- logger: ReturnType;
+ requestMiddleware?: RequestMiddleware[];
+ logger?: ReturnType;
+}
+
+/** Fallback so a missing slot degrades to error-level logging, never a crash. */
+const fallbackLogger = loggerFn({ level: 'error' });
+
+/**
+ * Resolves this client's own middleware and logger.
+ *
+ * Reads only the `davinci` slot — never a store-wide value, which on a shared
+ * store would belong to whichever client created it.
+ */
+function davinciExtra(extra: unknown): Required {
+ return clientExtra(extra, DAVINCI_REDUCER_PATH, {
+ requestMiddleware: [],
+ logger: fallbackLogger,
+ });
}
/**
@@ -57,7 +83,7 @@ interface Extras {
@@ -81,9 +107,9 @@ export const davinciApi = createApi({
const requestBody = transformActionRequest(
state.node,
params.action,
- (api.extra as Extras).logger,
+ davinciExtra(api.extra).logger,
);
- const { requestMiddleware, logger } = api.extra as Extras;
+ const { requestMiddleware, logger } = davinciExtra(api.extra);
let href = '';
@@ -126,7 +152,7 @@ export const davinciApi = createApi({
* parameters are pre-typed from the library.
*/
async onQueryStarted(_, api) {
- const logger = (api.extra as Extras).logger;
+ const { logger } = davinciExtra(api.extra);
let response;
try {
@@ -160,7 +186,7 @@ export const davinciApi = createApi({
async queryFn(body, api, __, baseQuery) {
const state = api.getState() as RootStateWithNode;
const links = state.node.server._links;
- const { requestMiddleware, logger } = api.extra as Extras;
+ const { requestMiddleware, logger } = davinciExtra(api.extra);
let requestBody;
let href = '';
@@ -237,7 +263,7 @@ export const davinciApi = createApi({
* parameters are pre-typed from the library.
*/
async onQueryStarted(_, api) {
- const logger = (api.extra as Extras).logger;
+ const { logger } = davinciExtra(api.extra);
let response;
try {
@@ -270,7 +296,7 @@ export const davinciApi = createApi({
* @method queryFn - This is just a wrapper around the fetch call
*/
async queryFn(options, api, __, baseQuery) {
- const { requestMiddleware, logger } = api.extra as Extras;
+ const { requestMiddleware, logger } = davinciExtra(api.extra);
const state = api.getState() as RootStateWithNode;
if (!state) {
@@ -352,7 +378,7 @@ export const davinciApi = createApi({
* parameters are pre-typed from the library.
*/
async onQueryStarted(_, api) {
- const logger = (api.extra as Extras).logger;
+ const { logger } = davinciExtra(api.extra);
let response;
try {
@@ -381,7 +407,7 @@ export const davinciApi = createApi({
*/
resume: builder.query({
async queryFn({ serverInfo, continueToken }, api, _c, baseQuery) {
- const { requestMiddleware, logger } = api.extra as Extras;
+ const { requestMiddleware, logger } = davinciExtra(api.extra);
const links = serverInfo._links;
if (!continueToken) {
@@ -430,7 +456,7 @@ export const davinciApi = createApi({
return response;
},
async onQueryStarted(_, api) {
- const logger = (api.extra as Extras).logger;
+ const { logger } = davinciExtra(api.extra);
let response;
try {
@@ -464,7 +490,7 @@ export const davinciApi = createApi({
*/
poll: builder.mutation({
async queryFn({ endpoint, interactionId }, api, _c, baseQuery) {
- const { requestMiddleware, logger } = api.extra as Extras;
+ const { requestMiddleware, logger } = davinciExtra(api.extra);
const request: FetchArgs = {
url: endpoint,
diff --git a/packages/davinci-client/src/lib/davinci.state.ts b/packages/davinci-client/src/lib/davinci.state.ts
new file mode 100644
index 00000000000..9b4a2dbfbc2
--- /dev/null
+++ b/packages/davinci-client/src/lib/davinci.state.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { combineSlices } from '@reduxjs/toolkit';
+
+import { configSlice } from './config.slice.js';
+import { nodeSlice } from './node.slice.js';
+import { davinciApi } from './davinci.api.js';
+import { wellknownApi } from '@forgerock/sdk-store';
+
+import type { ErrorNode, ContinueNode, StartNode, SuccessNode } from './node.types.js';
+
+/**
+ * The canonical description of the state this client contributes.
+ *
+ * Isolated into its own module to prevent a circular dependency:
+ * `node.reducer.ts` → `client.store.utils.ts` → `node.slice.ts` would cycle.
+ * Nothing that `node.reducer.ts` imports should import from this file.
+ */
+export const rootReducer = combineSlices(configSlice, nodeSlice, davinciApi, wellknownApi);
+
+export type RootState = ReturnType;
+
+export interface RootStateWithNode<
+ T extends ErrorNode | ContinueNode | StartNode | SuccessNode,
+> extends RootState {
+ node: T;
+}
diff --git a/packages/davinci-client/src/lib/store-shape.test.ts b/packages/davinci-client/src/lib/store-shape.test.ts
new file mode 100644
index 00000000000..69b1885332f
--- /dev/null
+++ b/packages/davinci-client/src/lib/store-shape.test.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { describe, expect, it } from 'vitest';
+
+import { createClientStore } from './client.store.utils.js';
+
+/**
+ * `combineSlices` keys each reducer off `slice.reducerPath ?? slice.name`, where
+ * the previous `configureStore({ reducer: { ... } })` form spelled the keys out
+ * literally. That makes the published state shape an implicit consequence of
+ * slice metadata: renaming `nodeSlice.name` would silently reshape the store.
+ *
+ * These assertions pin the shape so such a rename fails loudly here instead of
+ * in a consumer's selectors.
+ */
+describe('davinci store shape', () => {
+ it('exposes exactly the expected top-level state keys', () => {
+ // Arrange
+ const { store } = createClientStore({});
+
+ // Act
+ const keys = Object.keys(store.getState()).sort();
+
+ // Assert
+ expect(keys).toEqual(['config', 'davinci', 'node', 'wellknown']);
+ });
+
+ it('registers this client\u2019s slot on the store extra, keyed by reducerPath', async () => {
+ // Arrange
+ const { store } = createClientStore({});
+ let observed: unknown;
+
+ // Act — a thunk is the supported way to observe extraArgument
+ await store.dispatch(((_dispatch: unknown, _getState: unknown, extra: unknown) => {
+ observed = extra;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as any);
+
+ // Assert
+ expect(observed).toHaveProperty('clients.davinci');
+ });
+});
diff --git a/packages/davinci-client/src/lib/wellknown.api.ts b/packages/davinci-client/src/lib/wellknown.api.ts
index 8b9772094ed..5e7b28ba498 100644
--- a/packages/davinci-client/src/lib/wellknown.api.ts
+++ b/packages/davinci-client/src/lib/wellknown.api.ts
@@ -7,7 +7,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
-import { initWellknownQuery } from '@forgerock/sdk-oidc';
+import { initWellknownQuery } from '@forgerock/sdk-store';
import type { WellknownResponse } from '@forgerock/sdk-types';
import type {
@@ -19,7 +19,7 @@ import type {
/**
* RTK Query API for well-known endpoint discovery.
*
- * Uses the `initWellknownQuery` builder pattern from `@forgerock/sdk-oidc`.
+ * Uses the `initWellknownQuery` builder pattern from `@forgerock/sdk-store`.
* The builder constructs the request and validates the response;
* `fetchBaseQuery` handles the HTTP transport through RTK Query's pipeline.
*/
diff --git a/packages/davinci-client/tsconfig.json b/packages/davinci-client/tsconfig.json
index 141b4ebf5f8..df687466594 100644
--- a/packages/davinci-client/tsconfig.json
+++ b/packages/davinci-client/tsconfig.json
@@ -23,6 +23,9 @@
{
"path": "../sdk-effects/sdk-request-middleware"
},
+ {
+ "path": "../sdk-effects/store"
+ },
{
"path": "../sdk-effects/oidc"
},
diff --git a/packages/davinci-client/tsconfig.lib.json b/packages/davinci-client/tsconfig.lib.json
index 6c7bbdefeff..6db7b33e75b 100644
--- a/packages/davinci-client/tsconfig.lib.json
+++ b/packages/davinci-client/tsconfig.lib.json
@@ -48,6 +48,9 @@
},
{
"path": "../sdk-effects/logger/tsconfig.lib.json"
+ },
+ {
+ "path": "../sdk-effects/store/tsconfig.lib.json"
}
]
}
diff --git a/packages/journey-client/README.md b/packages/journey-client/README.md
index 910ca9462ee..919deeb07b2 100644
--- a/packages/journey-client/README.md
+++ b/packages/journey-client/README.md
@@ -13,6 +13,7 @@
- [API Reference](#api-reference)
- [Working with Callbacks](#working-with-callbacks)
- [Request Middleware](#request-middleware)
+- [Sharing a Store With Another Client](#sharing-a-store-with-another-client)
- [Error Handling](#error-handling)
- [Building](#building)
- [Testing](#testing)
@@ -116,12 +117,13 @@ const client = await journey({
config: JourneyClientConfig,
requestMiddleware?: RequestMiddleware[],
logger?: { level: LogLevel; custom?: CustomLogger },
+ store?: SdkStore,
});
```
**Returns**: `Promise`
-**Throws**: `Error` if the wellknown URL is invalid, the fetch fails, or the server is not a ForgeRock AM instance.
+**Throws**: `Error` if the wellknown URL is invalid, the fetch fails, or the server is not a ForgeRock AM instance. Throws if the `store` argument is provided but is not a valid `SdkStore` handle.
```typescript
try {
@@ -232,6 +234,50 @@ const client = await journey({
| `JOURNEY_NEXT` | Submitting a step |
| `JOURNEY_TERMINATE` | Terminating the session |
+## Sharing a Store With Another Client
+
+If your application also uses `@forgerock/oidc-client`, the two can share one Redux store so the well-known discovery document is fetched once rather than once per client.
+
+`journey()` exposes the store it created as `client.store`. Pass it to the other client:
+
+```typescript
+import { journey } from '@forgerock/journey-client';
+import { oidc } from '@forgerock/oidc-client';
+
+const journeyClient = await journey({ config });
+
+// Attaches to journey's store; the discovery document is already cached there.
+const oidcClient = await oidc({ config: oidcConfig, store: journeyClient.store });
+```
+
+Or create the store yourself when neither client is the natural owner:
+
+```typescript
+import { createSdkStore } from '@forgerock/sdk-store';
+
+const store = createSdkStore();
+const journeyClient = await journey({ config, store });
+const oidcClient = await oidc({ config: oidcConfig, store });
+```
+
+Omitting `store` is always valid — the client creates its own, which is the default behaviour.
+
+### Middleware and logging stay private
+
+Sharing a store shares cached data, not configuration. `requestMiddleware` and `logger` are registered against the client you pass them to, and are resolved only by that client's own requests:
+
+```typescript
+const store = createSdkStore();
+
+// Runs for JOURNEY_START, JOURNEY_NEXT and JOURNEY_TERMINATE only.
+await journey({ config, store, requestMiddleware: [journeyMiddleware] });
+
+// Runs for OIDC requests only.
+await oidc({ config: oidcConfig, store, requestMiddleware: [oidcMiddleware] });
+```
+
+Middleware passed here will never run against an OIDC token exchange, and vice versa.
+
## Error Handling
The `journey()` factory throws on initialization failure. Use try/catch:
diff --git a/packages/journey-client/api-report/journey-client.api.md b/packages/journey-client/api-report/journey-client.api.md
index 6ef87d5d185..8af828a4d4c 100644
--- a/packages/journey-client/api-report/journey-client.api.md
+++ b/packages/journey-client/api-report/journey-client.api.md
@@ -1,508 +1,513 @@
-## 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 { ActionTypes } from '@forgerock/sdk-request-middleware';
-import { AuthResponse } from '@forgerock/sdk-types';
-import { Callback } from '@forgerock/sdk-types';
-import { CallbackType } from '@forgerock/sdk-types';
-import { callbackType } from '@forgerock/sdk-types';
-import { createWellknownError } from '@forgerock/sdk-utilities';
-import { CustomLogger } from '@forgerock/sdk-logger';
-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 { LegacyServerConfig } from '@forgerock/sdk-types';
-import { LogLevel } from '@forgerock/sdk-logger';
-import { makeJourneyConfig } from '@forgerock/sdk-utilities';
-import { NameValue } from '@forgerock/sdk-types';
-import { PolicyKey } from '@forgerock/sdk-types';
-import { PolicyParams } from '@forgerock/sdk-types';
-import { PolicyRequirement } from '@forgerock/sdk-types';
-import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
-import { Step } from '@forgerock/sdk-types';
-import { StepDetail } from '@forgerock/sdk-types';
-import { StepType } from '@forgerock/sdk-types';
-import { WellknownResponse } from '@forgerock/sdk-types';
-
-export { ActionTypes };
-
-// @public
-export class AttributeInputCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getName(): string;
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setValidateOnly(value: boolean): void;
- setValue(value: T): void;
-}
-
-export { AuthResponse };
-
-// @public
-export class BaseCallback {
- constructor(payload: Callback);
- getInputValue(selector?: number | string): unknown;
- getOutputByName(name: string, defaultValue: T): T;
- getOutputValue(selector?: number | string): unknown;
- getType(): CallbackType;
- // (undocumented)
- payload: Callback;
- setInputValue(value: unknown, selector?: number | string | RegExp): void;
-}
-
-export { Callback };
-
-// @public (undocumented)
-export type CallbackFactory = (callback: Callback) => BaseCallback;
-
-export { CallbackType };
-
-export { callbackType };
-
-// @public
-export class ChoiceCallback extends BaseCallback {
- constructor(payload: Callback);
- getChoices(): string[];
- getDefaultChoice(): number;
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setChoiceIndex(index: number): void;
- setChoiceValue(value: string): void;
-}
-
-// @public
-export class ConfirmationCallback extends BaseCallback {
- constructor(payload: Callback);
- getDefaultOption(): number;
- getMessageType(): number;
- getOptions(): string[];
- getOptionType(): number;
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setOptionIndex(index: number): void;
- setOptionValue(value: string): void;
-}
-
-// @public (undocumented)
-export function createCallback(callback: Callback): BaseCallback;
-
-// @public (undocumented)
-export function createJourneyStep(payload: Step, callbackFactory?: CallbackFactory): JourneyStep;
-
-export { createWellknownError };
-
-export { CustomLogger };
-
-// @public
-export class DeviceProfileCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- isLocationRequired(): boolean;
- isMetadataRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setProfile(profile: DeviceProfileData): void;
-}
-
-// @public (undocumented)
-export interface DeviceProfileData {
- // (undocumented)
- identifier: string;
- // (undocumented)
- location?: Geolocation_2 | Record;
- // (undocumented)
- metadata?: {
- hardware: {
- display: {
- [key: string]: string | number | null;
- };
- [key: string]: any;
- };
- browser: {
- [key: string]: string | number | null;
- };
- platform: {
- [key: string]: string | number | null;
- };
- };
-}
-
-export { FailedPolicyRequirement };
-
-export { FailureDetail };
-
-export { GenericError };
-
-// @public (undocumented)
-interface Geolocation_2 {
- // (undocumented)
- latitude: number;
- // (undocumented)
- longitude: number;
-}
-export { Geolocation_2 as Geolocation };
-
-// @public
-export class HiddenValueCallback extends BaseCallback {
- constructor(payload: Callback);
- // (undocumented)
- payload: Callback;
-}
-
-// @public (undocumented)
-export interface IdPValue {
- // (undocumented)
- provider: string;
- // (undocumented)
- uiConfig: {
- [key: string]: string;
- };
-}
-
-// @internal
-export interface InternalJourneyClientConfig {
- // (undocumented)
- error?: GenericError;
- // (undocumented)
- serverConfig: ResolvedServerConfig;
-}
-
-export { isValidWellknownUrl };
-
-// @public
-export function journey(input: {
- config: JourneyClientConfig;
- requestMiddleware?: RequestMiddleware[];
- logger?: {
- level: LogLevel;
- custom?: CustomLogger;
- };
-}): Promise;
-
-// @public
-export interface JourneyClient {
- // (undocumented)
- next: (step: JourneyStep, options?: NextOptions) => Promise;
- // (undocumented)
- redirect: (step: JourneyStep) => Promise;
- // (undocumented)
- resume: (url: string, options?: ResumeOptions) => Promise;
- // (undocumented)
- start: (options?: StartParam) => Promise;
- // (undocumented)
- subscribe: (listener: () => void) => () => void;
- // (undocumented)
- terminate: (options?: { query?: Record }) => Promise;
-}
-
-export { JourneyClientConfig };
-
-// @public (undocumented)
-export type JourneyLoginFailure = AuthResponse & {
- type: StepType.LoginFailure;
- payload: Step;
- getCode: () => number;
- getDetail: () => FailureDetail | undefined;
- getMessage: () => string | undefined;
- getProcessedMessage: (messageCreator?: MessageCreator) => ProcessedPropertyError[];
- getReason: () => string | undefined;
-};
-
-// @public (undocumented)
-export type JourneyLoginSuccess = AuthResponse & {
- type: StepType.LoginSuccess;
- payload: Step;
- getRealm: () => string | undefined;
- getSessionToken: () => string | undefined;
- getSuccessUrl: () => string | undefined;
-};
-
-// @public (undocumented)
-export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;
-
-export { JourneyServerConfig };
-
-// @public (undocumented)
-export type JourneyStep = AuthResponse & {
- type: StepType.Step;
- payload: Step;
- callbacks: BaseCallback[];
- getCallbackOfType: (type: CallbackType) => T;
- getCallbacksOfType: (type: CallbackType) => T[];
- setCallbackValue: (type: CallbackType, value: unknown) => void;
- getDescription: () => string | undefined;
- getHeader: () => string | undefined;
- getStage: () => string | undefined;
-};
-
-// @public
-export class KbaCreateCallback extends BaseCallback {
- constructor(payload: Callback);
- getPredefinedQuestions(): string[];
- getPrompt(): string;
- isAllowedUserDefinedQuestions(): boolean;
- // (undocumented)
- payload: Callback;
- setAnswer(answer: string): void;
- setQuestion(question: string): void;
-}
-
-export { LegacyServerConfig };
-
-export { LogLevel };
-
-// @public (undocumented)
-export interface MessageCreator {
- // (undocumented)
- [key: string]: (
- propertyName: string,
- params?: {
- [key: string]: unknown;
- },
- ) => string;
-}
-
-// @public
-export class MetadataCallback extends BaseCallback {
- constructor(payload: Callback);
- getData(): T;
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class NameCallback extends BaseCallback {
- constructor(payload: Callback);
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setName(name: string): void;
-}
-
-export { NameValue };
-
-// @public (undocumented)
-export interface NextOptions {
- // (undocumented)
- query?: Record;
-}
-
-// @public
-export class PasswordCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): string[];
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setPassword(password: string): void;
-}
-
-// @public
-export class PingOneProtectEvaluationCallback extends BaseCallback {
- constructor(payload: Callback);
- getPauseBehavioralData(): boolean;
- // (undocumented)
- payload: Callback;
- setClientError(errorMessage: string): void;
- setData(data: string): void;
-}
-
-// @public
-export class PingOneProtectInitializeCallback extends BaseCallback {
- constructor(payload: Callback);
- getConfig():
- | Record
- | {
- envId: string;
- consoleLogEnabled: boolean;
- deviceAttributesToIgnore: string[];
- customHost: string;
- lazyMetadata: boolean;
- behavioralDataCollection: boolean;
- deviceKeyRsyncIntervals: number;
- enableTrust: boolean;
- disableTags: boolean;
- disableHub: boolean;
- };
- // (undocumented)
- payload: Callback;
- // (undocumented)
- setClientError(errorMessage: string): void;
-}
-
-export { PolicyKey };
-
-export { PolicyParams };
-
-export { PolicyRequirement };
-
-// @public
-export class PollingWaitCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- getWaitTime(): number;
- // (undocumented)
- payload: Callback;
-}
-
-// @public (undocumented)
-export interface ProcessedPropertyError {
- // (undocumented)
- detail: FailedPolicyRequirement;
- // (undocumented)
- messages: string[];
-}
-
-// @public
-export class ReCaptchaCallback extends BaseCallback {
- constructor(payload: Callback);
- getSiteKey(): string;
- // (undocumented)
- payload: Callback;
- setResult(result: string): void;
-}
-
-// @public
-export class ReCaptchaEnterpriseCallback extends BaseCallback {
- constructor(payload: Callback);
- getApiUrl(): string;
- getElementClass(): string;
- getSiteKey(): string;
- // (undocumented)
- payload: Callback;
- setAction(action: string): void;
- setClientError(error: string): void;
- setPayload(payload: unknown): void;
- setResult(result: string): void;
-}
-
-// @public
-export class RedirectCallback extends BaseCallback {
- constructor(payload: Callback);
- getRedirectUrl(): string;
- // (undocumented)
- payload: Callback;
-}
-
-export { RequestMiddleware };
-
-// @public
-export interface ResolvedServerConfig {
- // (undocumented)
- baseUrl: string;
- // (undocumented)
- paths: {
- authenticate: string;
- sessions: string;
- };
-}
-
-// @public (undocumented)
-export interface ResumeOptions {
- // (undocumented)
- journey?: string;
- // (undocumented)
- query?: Record;
-}
-
-// @public
-export class SelectIdPCallback extends BaseCallback {
- constructor(payload: Callback);
- getProviders(): IdPValue[];
- // (undocumented)
- payload: Callback;
- setProvider(value: string): void;
-}
-
-// @public (undocumented)
-export interface StartParam {
- // (undocumented)
- journey: string;
- // (undocumented)
- query?: Record;
-}
-
-export { Step };
-
-export { StepDetail };
-
-export { StepType };
-
-// @public
-export class SuspendedTextOutputCallback extends TextOutputCallback {
- constructor(payload: Callback);
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class TermsAndConditionsCallback extends BaseCallback {
- constructor(payload: Callback);
- getCreateDate(): Date | null;
- getTerms(): string;
- getVersion(): string;
- // (undocumented)
- payload: Callback;
- setAccepted(accepted?: boolean): void;
-}
-
-// @public
-export class TextInputCallback extends BaseCallback {
- constructor(payload: Callback);
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setInput(input: string): void;
-}
-
-// @public
-export class TextOutputCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- getMessageType(): string;
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class ValidatedCreatePasswordCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setPassword(password: string): void;
- setValidateOnly(value: boolean): void;
-}
-
-// @public
-export class ValidatedCreateUsernameCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setName(name: string): void;
- setValidateOnly(value: boolean): void;
-}
-
-export { WellknownResponse };
-
-// (No @packageDocumentation comment for this package)
-```
+## 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 { ActionTypes } from '@forgerock/sdk-request-middleware';
+import { AuthResponse } from '@forgerock/sdk-types';
+import { Callback } from '@forgerock/sdk-types';
+import { CallbackType } from '@forgerock/sdk-types';
+import { callbackType } from '@forgerock/sdk-types';
+import { createWellknownError } from '@forgerock/sdk-utilities';
+import { CustomLogger } from '@forgerock/sdk-logger';
+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 { LegacyServerConfig } from '@forgerock/sdk-types';
+import { LogLevel } from '@forgerock/sdk-logger';
+import { makeJourneyConfig } from '@forgerock/sdk-utilities';
+import { NameValue } from '@forgerock/sdk-types';
+import { PolicyKey } from '@forgerock/sdk-types';
+import { PolicyParams } from '@forgerock/sdk-types';
+import { PolicyRequirement } from '@forgerock/sdk-types';
+import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
+import type { SdkStore } from '@forgerock/sdk-store';
+import { Step } from '@forgerock/sdk-types';
+import { StepDetail } from '@forgerock/sdk-types';
+import { StepType } from '@forgerock/sdk-types';
+import { WellknownResponse } from '@forgerock/sdk-types';
+
+export { ActionTypes }
+
+// @public
+export class AttributeInputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getName(): string;
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setValidateOnly(value: boolean): void;
+ setValue(value: T): void;
+}
+
+export { AuthResponse }
+
+// @public
+export class BaseCallback {
+ constructor(payload: Callback);
+ getInputValue(selector?: number | string): unknown;
+ getOutputByName(name: string, defaultValue: T): T;
+ getOutputValue(selector?: number | string): unknown;
+ getType(): CallbackType;
+ // (undocumented)
+ payload: Callback;
+ setInputValue(value: unknown, selector?: number | string | RegExp): void;
+}
+
+export { Callback }
+
+// @public (undocumented)
+export type CallbackFactory = (callback: Callback) => BaseCallback;
+
+export { CallbackType }
+
+export { callbackType }
+
+// @public
+export class ChoiceCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getChoices(): string[];
+ getDefaultChoice(): number;
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setChoiceIndex(index: number): void;
+ setChoiceValue(value: string): void;
+}
+
+// @public
+export class ConfirmationCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getDefaultOption(): number;
+ getMessageType(): number;
+ getOptions(): string[];
+ getOptionType(): number;
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setOptionIndex(index: number): void;
+ setOptionValue(value: string): void;
+}
+
+// @public (undocumented)
+export function createCallback(callback: Callback): BaseCallback;
+
+export { createWellknownError }
+
+export { CustomLogger }
+
+// @public
+export class DeviceProfileCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ isLocationRequired(): boolean;
+ isMetadataRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setProfile(profile: DeviceProfileData): void;
+}
+
+// @public (undocumented)
+export interface DeviceProfileData {
+ // (undocumented)
+ identifier: string;
+ // (undocumented)
+ location?: Geolocation_2 | Record;
+ // (undocumented)
+ metadata?: {
+ hardware: {
+ display: {
+ [key: string]: string | number | null;
+ };
+ [key: string]: any;
+ };
+ browser: {
+ [key: string]: string | number | null;
+ };
+ platform: {
+ [key: string]: string | number | null;
+ };
+ };
+}
+
+export { FailedPolicyRequirement }
+
+export { FailureDetail }
+
+export { GenericError }
+
+// @public (undocumented)
+interface Geolocation_2 {
+ // (undocumented)
+ latitude: number;
+ // (undocumented)
+ longitude: number;
+}
+export { Geolocation_2 as Geolocation }
+
+// @public
+export class HiddenValueCallback extends BaseCallback {
+ constructor(payload: Callback);
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public (undocumented)
+export interface IdPValue {
+ // (undocumented)
+ provider: string;
+ // (undocumented)
+ uiConfig: {
+ [key: string]: string;
+ };
+}
+
+// @internal
+export interface InternalJourneyClientConfig {
+ // (undocumented)
+ error?: GenericError;
+ // (undocumented)
+ serverConfig: ResolvedServerConfig;
+}
+
+export { isValidWellknownUrl }
+
+// @public
+export function journey(input: {
+ config: JourneyClientConfig;
+ requestMiddleware?: RequestMiddleware[];
+ logger?: {
+ level: LogLevel;
+ custom?: CustomLogger;
+ };
+ store?: unknown;
+}): Promise;
+
+// @public
+export interface JourneyClient {
+ // (undocumented)
+ next: (step: JourneyStep, options?: NextOptions) => Promise;
+ // (undocumented)
+ redirect: (step: JourneyStep) => Promise;
+ // (undocumented)
+ resume: (url: string, options?: ResumeOptions) => Promise;
+ // (undocumented)
+ start: (options?: StartParam) => Promise;
+ // (undocumented)
+ store: SdkStore;
+ // (undocumented)
+ subscribe: (listener: () => void) => () => void;
+ // (undocumented)
+ terminate: (options?: {
+ query?: Record;
+ }) => Promise;
+}
+
+export { JourneyClientConfig }
+
+// @public (undocumented)
+export type JourneyLoginFailure = AuthResponse & {
+ type: StepType.LoginFailure;
+ payload: Step;
+ getCode: () => number;
+ getDetail: () => FailureDetail | undefined;
+ getMessage: () => string | undefined;
+ getProcessedMessage: (messageCreator?: MessageCreator) => ProcessedPropertyError[];
+ getReason: () => string | undefined;
+};
+
+// @public (undocumented)
+export type JourneyLoginSuccess = AuthResponse & {
+ type: StepType.LoginSuccess;
+ payload: Step;
+ getRealm: () => string | undefined;
+ getSessionToken: () => string | undefined;
+ getSuccessUrl: () => string | undefined;
+};
+
+// @public (undocumented)
+export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;
+
+export { JourneyServerConfig }
+
+// @public (undocumented)
+export type JourneyStep = AuthResponse & {
+ type: StepType.Step;
+ payload: Step;
+ callbacks: BaseCallback[];
+ getCallbackOfType: (type: CallbackType) => T;
+ getCallbacksOfType: (type: CallbackType) => T[];
+ setCallbackValue: (type: CallbackType, value: unknown) => void;
+ getDescription: () => string | undefined;
+ getHeader: () => string | undefined;
+ getStage: () => string | undefined;
+};
+
+// @public
+export class KbaCreateCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPredefinedQuestions(): string[];
+ getPrompt(): string;
+ isAllowedUserDefinedQuestions(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setAnswer(answer: string): void;
+ setQuestion(question: string): void;
+}
+
+export { LegacyServerConfig }
+
+export { LogLevel }
+
+export { makeJourneyConfig }
+
+// @public (undocumented)
+export interface MessageCreator {
+ // (undocumented)
+ [key: string]: (propertyName: string, params?: {
+ [key: string]: unknown;
+ }) => string;
+}
+
+// @public
+export class MetadataCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getData(): T;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class NameCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setName(name: string): void;
+}
+
+export { NameValue }
+
+// @public (undocumented)
+export interface NextOptions {
+ // (undocumented)
+ query?: Record;
+}
+
+// @public
+export class PasswordCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): string[];
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setPassword(password: string): void;
+}
+
+// @public
+export class PingOneProtectEvaluationCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPauseBehavioralData(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setClientError(errorMessage: string): void;
+ setData(data: string): void;
+}
+
+// @public
+export class PingOneProtectInitializeCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getConfig(): Record | {
+ envId: string;
+ consoleLogEnabled: boolean;
+ deviceAttributesToIgnore: string[];
+ customHost: string;
+ lazyMetadata: boolean;
+ behavioralDataCollection: boolean;
+ deviceKeyRsyncIntervals: number;
+ enableTrust: boolean;
+ disableTags: boolean;
+ disableHub: boolean;
+ };
+ // (undocumented)
+ payload: Callback;
+ // (undocumented)
+ setClientError(errorMessage: string): void;
+}
+
+export { PolicyKey }
+
+export { PolicyParams }
+
+export { PolicyRequirement }
+
+// @public
+export class PollingWaitCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ getWaitTime(): number;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public (undocumented)
+export interface ProcessedPropertyError {
+ // (undocumented)
+ detail: FailedPolicyRequirement;
+ // (undocumented)
+ messages: string[];
+}
+
+// @public
+export class ReCaptchaCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getSiteKey(): string;
+ // (undocumented)
+ payload: Callback;
+ setResult(result: string): void;
+}
+
+// @public
+export class ReCaptchaEnterpriseCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getApiUrl(): string;
+ getElementClass(): string;
+ getSiteKey(): string;
+ // (undocumented)
+ payload: Callback;
+ setAction(action: string): void;
+ setClientError(error: string): void;
+ setPayload(payload: unknown): void;
+ setResult(result: string): void;
+}
+
+// @public
+export class RedirectCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getRedirectUrl(): string;
+ // (undocumented)
+ payload: Callback;
+}
+
+export { RequestMiddleware }
+
+// @public
+export interface ResolvedServerConfig {
+ // (undocumented)
+ baseUrl: string;
+ // (undocumented)
+ paths: {
+ authenticate: string;
+ sessions: string;
+ };
+}
+
+// @public (undocumented)
+export interface ResumeOptions {
+ // (undocumented)
+ journey?: string;
+ // (undocumented)
+ query?: Record;
+}
+
+// @public
+export class SelectIdPCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getProviders(): IdPValue[];
+ // (undocumented)
+ payload: Callback;
+ setProvider(value: string): void;
+}
+
+// @public (undocumented)
+export interface StartParam {
+ // (undocumented)
+ journey: string;
+ // (undocumented)
+ query?: Record;
+}
+
+export { Step }
+
+export { StepDetail }
+
+export { StepType }
+
+// @public
+export class SuspendedTextOutputCallback extends TextOutputCallback {
+ constructor(payload: Callback);
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class TermsAndConditionsCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getCreateDate(): Date | null;
+ getTerms(): string;
+ getVersion(): string;
+ // (undocumented)
+ payload: Callback;
+ setAccepted(accepted?: boolean): void;
+}
+
+// @public
+export class TextInputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setInput(input: string): void;
+}
+
+// @public
+export class TextOutputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ getMessageType(): string;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class ValidatedCreatePasswordCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setPassword(password: string): void;
+ setValidateOnly(value: boolean): void;
+}
+
+// @public
+export class ValidatedCreateUsernameCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setName(name: string): void;
+ setValidateOnly(value: boolean): void;
+}
+
+export { WellknownResponse }
+
+// (No @packageDocumentation comment for this package)
+
+```
diff --git a/packages/journey-client/api-report/journey-client.types.api.md b/packages/journey-client/api-report/journey-client.types.api.md
index 59ff118690a..f73b91af616 100644
--- a/packages/journey-client/api-report/journey-client.types.api.md
+++ b/packages/journey-client/api-report/journey-client.types.api.md
@@ -1,501 +1,493 @@
-## 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 { ActionTypes } from '@forgerock/sdk-request-middleware';
-import { AuthResponse } from '@forgerock/sdk-types';
-import { Callback } from '@forgerock/sdk-types';
-import { CallbackType } from '@forgerock/sdk-types';
-import { createWellknownError } from '@forgerock/sdk-utilities';
-import { CustomLogger } from '@forgerock/sdk-logger';
-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 { LegacyServerConfig } from '@forgerock/sdk-types';
-import { LogLevel } from '@forgerock/sdk-logger';
-import { NameValue } from '@forgerock/sdk-types';
-import { PolicyKey } from '@forgerock/sdk-types';
-import { PolicyParams } from '@forgerock/sdk-types';
-import { PolicyRequirement } from '@forgerock/sdk-types';
-import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
-import { Step } from '@forgerock/sdk-types';
-import { StepDetail } from '@forgerock/sdk-types';
-import { StepType } from '@forgerock/sdk-types';
-import { WellknownResponse } from '@forgerock/sdk-types';
-
-export { ActionTypes };
-
-// @public
-export class AttributeInputCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getName(): string;
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setValidateOnly(value: boolean): void;
- setValue(value: T): void;
-}
-
-export { AuthResponse };
-
-// @public
-export class BaseCallback {
- constructor(payload: Callback);
- getInputValue(selector?: number | string): unknown;
- getOutputByName(name: string, defaultValue: T): T;
- getOutputValue(selector?: number | string): unknown;
- getType(): CallbackType;
- // (undocumented)
- payload: Callback;
- setInputValue(value: unknown, selector?: number | string | RegExp): void;
-}
-
-export { Callback };
-
-// @public (undocumented)
-export type CallbackFactory = (callback: Callback) => BaseCallback;
-
-export { CallbackType };
-
-// @public
-export class ChoiceCallback extends BaseCallback {
- constructor(payload: Callback);
- getChoices(): string[];
- getDefaultChoice(): number;
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setChoiceIndex(index: number): void;
- setChoiceValue(value: string): void;
-}
-
-// @public
-export class ConfirmationCallback extends BaseCallback {
- constructor(payload: Callback);
- getDefaultOption(): number;
- getMessageType(): number;
- getOptions(): string[];
- getOptionType(): number;
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setOptionIndex(index: number): void;
- setOptionValue(value: string): void;
-}
-
-// @public (undocumented)
-export function createCallback(callback: Callback): BaseCallback;
-
-export { createWellknownError };
-
-export { CustomLogger };
-
-// @public
-export class DeviceProfileCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- isLocationRequired(): boolean;
- isMetadataRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setProfile(profile: DeviceProfileData): void;
-}
-
-// @public (undocumented)
-export interface DeviceProfileData {
- // (undocumented)
- identifier: string;
- // (undocumented)
- location?: Geolocation_2 | Record;
- // (undocumented)
- metadata?: {
- hardware: {
- display: {
- [key: string]: string | number | null;
- };
- [key: string]: any;
- };
- browser: {
- [key: string]: string | number | null;
- };
- platform: {
- [key: string]: string | number | null;
- };
- };
-}
-
-export { FailedPolicyRequirement };
-
-export { FailureDetail };
-
-export { GenericError };
-
-// @public (undocumented)
-interface Geolocation_2 {
- // (undocumented)
- latitude: number;
- // (undocumented)
- longitude: number;
-}
-export { Geolocation_2 as Geolocation };
-
-// @public
-export class HiddenValueCallback extends BaseCallback {
- constructor(payload: Callback);
- // (undocumented)
- payload: Callback;
-}
-
-// @public (undocumented)
-export interface IdPValue {
- // (undocumented)
- provider: string;
- // (undocumented)
- uiConfig: {
- [key: string]: string;
- };
-}
-
-// @internal
-export interface InternalJourneyClientConfig {
- // (undocumented)
- error?: GenericError;
- // (undocumented)
- serverConfig: ResolvedServerConfig;
-}
-
-export { isValidWellknownUrl };
-
-// @public
-export function journey(input: {
- config: JourneyClientConfig;
- requestMiddleware?: RequestMiddleware[];
- logger?: {
- level: LogLevel;
- custom?: CustomLogger;
- };
-}): Promise;
-
-// @public
-export interface JourneyClient {
- // (undocumented)
- next: (step: JourneyStep, options?: NextOptions) => Promise;
- // (undocumented)
- redirect: (step: JourneyStep) => Promise;
- // (undocumented)
- resume: (url: string, options?: ResumeOptions) => Promise;
- // (undocumented)
- start: (options?: StartParam) => Promise;
- // (undocumented)
- subscribe: (listener: () => void) => () => void;
- // (undocumented)
- terminate: (options?: { query?: Record }) => Promise;
-}
-
-export { JourneyClientConfig };
-
-// @public (undocumented)
-export type JourneyLoginFailure = AuthResponse & {
- type: StepType.LoginFailure;
- payload: Step;
- getCode: () => number;
- getDetail: () => FailureDetail | undefined;
- getMessage: () => string | undefined;
- getProcessedMessage: (messageCreator?: MessageCreator) => ProcessedPropertyError[];
- getReason: () => string | undefined;
-};
-
-// @public (undocumented)
-export type JourneyLoginSuccess = AuthResponse & {
- type: StepType.LoginSuccess;
- payload: Step;
- getRealm: () => string | undefined;
- getSessionToken: () => string | undefined;
- getSuccessUrl: () => string | undefined;
-};
-
-// @public (undocumented)
-export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;
-
-export { JourneyServerConfig };
-
-// @public (undocumented)
-export type JourneyStep = AuthResponse & {
- type: StepType.Step;
- payload: Step;
- callbacks: BaseCallback[];
- getCallbackOfType: (type: CallbackType) => T;
- getCallbacksOfType: (type: CallbackType) => T[];
- setCallbackValue: (type: CallbackType, value: unknown) => void;
- getDescription: () => string | undefined;
- getHeader: () => string | undefined;
- getStage: () => string | undefined;
-};
-
-// @public
-export class KbaCreateCallback extends BaseCallback {
- constructor(payload: Callback);
- getPredefinedQuestions(): string[];
- getPrompt(): string;
- isAllowedUserDefinedQuestions(): boolean;
- // (undocumented)
- payload: Callback;
- setAnswer(answer: string): void;
- setQuestion(question: string): void;
-}
-
-export { LegacyServerConfig };
-
-export { LogLevel };
-
-// @public (undocumented)
-export interface MessageCreator {
- // (undocumented)
- [key: string]: (
- propertyName: string,
- params?: {
- [key: string]: unknown;
- },
- ) => string;
-}
-
-// @public
-export class MetadataCallback extends BaseCallback {
- constructor(payload: Callback);
- getData(): T;
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class NameCallback extends BaseCallback {
- constructor(payload: Callback);
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setName(name: string): void;
-}
-
-export { NameValue };
-
-// @public (undocumented)
-export interface NextOptions {
- // (undocumented)
- query?: Record;
-}
-
-// @public
-export class PasswordCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): string[];
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setPassword(password: string): void;
-}
-
-// @public
-export class PingOneProtectEvaluationCallback extends BaseCallback {
- constructor(payload: Callback);
- getPauseBehavioralData(): boolean;
- // (undocumented)
- payload: Callback;
- setClientError(errorMessage: string): void;
- setData(data: string): void;
-}
-
-// @public
-export class PingOneProtectInitializeCallback extends BaseCallback {
- constructor(payload: Callback);
- getConfig():
- | Record
- | {
- envId: string;
- consoleLogEnabled: boolean;
- deviceAttributesToIgnore: string[];
- customHost: string;
- lazyMetadata: boolean;
- behavioralDataCollection: boolean;
- deviceKeyRsyncIntervals: number;
- enableTrust: boolean;
- disableTags: boolean;
- disableHub: boolean;
- };
- // (undocumented)
- payload: Callback;
- // (undocumented)
- setClientError(errorMessage: string): void;
-}
-
-export { PolicyKey };
-
-export { PolicyParams };
-
-export { PolicyRequirement };
-
-// @public
-export class PollingWaitCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- getWaitTime(): number;
- // (undocumented)
- payload: Callback;
-}
-
-// @public (undocumented)
-export interface ProcessedPropertyError {
- // (undocumented)
- detail: FailedPolicyRequirement;
- // (undocumented)
- messages: string[];
-}
-
-// @public
-export class ReCaptchaCallback extends BaseCallback {
- constructor(payload: Callback);
- getSiteKey(): string;
- // (undocumented)
- payload: Callback;
- setResult(result: string): void;
-}
-
-// @public
-export class ReCaptchaEnterpriseCallback extends BaseCallback {
- constructor(payload: Callback);
- getApiUrl(): string;
- getElementClass(): string;
- getSiteKey(): string;
- // (undocumented)
- payload: Callback;
- setAction(action: string): void;
- setClientError(error: string): void;
- setPayload(payload: unknown): void;
- setResult(result: string): void;
-}
-
-// @public
-export class RedirectCallback extends BaseCallback {
- constructor(payload: Callback);
- getRedirectUrl(): string;
- // (undocumented)
- payload: Callback;
-}
-
-export { RequestMiddleware };
-
-// @public
-export interface ResolvedServerConfig {
- // (undocumented)
- baseUrl: string;
- // (undocumented)
- paths: {
- authenticate: string;
- sessions: string;
- };
-}
-
-// @public (undocumented)
-export interface ResumeOptions {
- // (undocumented)
- journey?: string;
- // (undocumented)
- query?: Record;
-}
-
-// @public
-export class SelectIdPCallback extends BaseCallback {
- constructor(payload: Callback);
- getProviders(): IdPValue[];
- // (undocumented)
- payload: Callback;
- setProvider(value: string): void;
-}
-
-// @public (undocumented)
-export interface StartParam {
- // (undocumented)
- journey: string;
- // (undocumented)
- query?: Record;
-}
-
-export { Step };
-
-export { StepDetail };
-
-export { StepType };
-
-// @public
-export class SuspendedTextOutputCallback extends TextOutputCallback {
- constructor(payload: Callback);
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class TermsAndConditionsCallback extends BaseCallback {
- constructor(payload: Callback);
- getCreateDate(): Date | null;
- getTerms(): string;
- getVersion(): string;
- // (undocumented)
- payload: Callback;
- setAccepted(accepted?: boolean): void;
-}
-
-// @public
-export class TextInputCallback extends BaseCallback {
- constructor(payload: Callback);
- getPrompt(): string;
- // (undocumented)
- payload: Callback;
- setInput(input: string): void;
-}
-
-// @public
-export class TextOutputCallback extends BaseCallback {
- constructor(payload: Callback);
- getMessage(): string;
- getMessageType(): string;
- // (undocumented)
- payload: Callback;
-}
-
-// @public
-export class ValidatedCreatePasswordCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setPassword(password: string): void;
- setValidateOnly(value: boolean): void;
-}
-
-// @public
-export class ValidatedCreateUsernameCallback extends BaseCallback {
- constructor(payload: Callback);
- getFailedPolicies(): PolicyRequirement[];
- getPolicies(): Record;
- getPrompt(): string;
- isRequired(): boolean;
- // (undocumented)
- payload: Callback;
- setName(name: string): void;
- setValidateOnly(value: boolean): void;
-}
-
-export { WellknownResponse };
-
-// (No @packageDocumentation comment for this package)
-```
+## 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 { ActionTypes } from '@forgerock/sdk-request-middleware';
+import { AuthResponse } from '@forgerock/sdk-types';
+import { Callback } from '@forgerock/sdk-types';
+import { CallbackType } from '@forgerock/sdk-types';
+import { createWellknownError } from '@forgerock/sdk-utilities';
+import { CustomLogger } from '@forgerock/sdk-logger';
+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 { LegacyServerConfig } from '@forgerock/sdk-types';
+import { LogLevel } from '@forgerock/sdk-logger';
+import { NameValue } from '@forgerock/sdk-types';
+import { PolicyKey } from '@forgerock/sdk-types';
+import { PolicyParams } from '@forgerock/sdk-types';
+import { PolicyRequirement } from '@forgerock/sdk-types';
+import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
+import type { SdkStore } from '@forgerock/sdk-store';
+import { Step } from '@forgerock/sdk-types';
+import { StepDetail } from '@forgerock/sdk-types';
+import { StepType } from '@forgerock/sdk-types';
+import { WellknownResponse } from '@forgerock/sdk-types';
+
+export { ActionTypes }
+
+// @public
+export class AttributeInputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getName(): string;
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setValidateOnly(value: boolean): void;
+ setValue(value: T): void;
+}
+
+export { AuthResponse }
+
+// @public
+export class BaseCallback {
+ constructor(payload: Callback);
+ getInputValue(selector?: number | string): unknown;
+ getOutputByName(name: string, defaultValue: T): T;
+ getOutputValue(selector?: number | string): unknown;
+ getType(): CallbackType;
+ // (undocumented)
+ payload: Callback;
+ setInputValue(value: unknown, selector?: number | string | RegExp): void;
+}
+
+export { Callback }
+
+// @public (undocumented)
+export type CallbackFactory = (callback: Callback) => BaseCallback;
+
+export { CallbackType }
+
+// @public
+export class ChoiceCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getChoices(): string[];
+ getDefaultChoice(): number;
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setChoiceIndex(index: number): void;
+ setChoiceValue(value: string): void;
+}
+
+// @public
+export class ConfirmationCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getDefaultOption(): number;
+ getMessageType(): number;
+ getOptions(): string[];
+ getOptionType(): number;
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setOptionIndex(index: number): void;
+ setOptionValue(value: string): void;
+}
+
+// @public (undocumented)
+export function createCallback(callback: Callback): BaseCallback;
+
+export { createWellknownError }
+
+export { CustomLogger }
+
+// @public
+export class DeviceProfileCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ isLocationRequired(): boolean;
+ isMetadataRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setProfile(profile: DeviceProfileData): void;
+}
+
+// @public (undocumented)
+export interface DeviceProfileData {
+ // (undocumented)
+ identifier: string;
+ // (undocumented)
+ location?: Geolocation_2 | Record;
+ // (undocumented)
+ metadata?: {
+ hardware: {
+ display: {
+ [key: string]: string | number | null;
+ };
+ [key: string]: any;
+ };
+ browser: {
+ [key: string]: string | number | null;
+ };
+ platform: {
+ [key: string]: string | number | null;
+ };
+ };
+}
+
+export { FailedPolicyRequirement }
+
+export { FailureDetail }
+
+export { GenericError }
+
+// @public (undocumented)
+interface Geolocation_2 {
+ // (undocumented)
+ latitude: number;
+ // (undocumented)
+ longitude: number;
+}
+export { Geolocation_2 as Geolocation }
+
+// @public
+export class HiddenValueCallback extends BaseCallback {
+ constructor(payload: Callback);
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public (undocumented)
+export interface IdPValue {
+ // (undocumented)
+ provider: string;
+ // (undocumented)
+ uiConfig: {
+ [key: string]: string;
+ };
+}
+
+// @internal
+export interface InternalJourneyClientConfig {
+ // (undocumented)
+ error?: GenericError;
+ // (undocumented)
+ serverConfig: ResolvedServerConfig;
+}
+
+export { isValidWellknownUrl }
+
+// @public
+export interface JourneyClient {
+ // (undocumented)
+ next: (step: JourneyStep, options?: NextOptions) => Promise;
+ // (undocumented)
+ redirect: (step: JourneyStep) => Promise;
+ // (undocumented)
+ resume: (url: string, options?: ResumeOptions) => Promise;
+ // (undocumented)
+ start: (options?: StartParam) => Promise;
+ // (undocumented)
+ store: SdkStore;
+ // (undocumented)
+ subscribe: (listener: () => void) => () => void;
+ // (undocumented)
+ terminate: (options?: {
+ query?: Record;
+ }) => Promise;
+}
+
+export { JourneyClientConfig }
+
+// @public (undocumented)
+export type JourneyLoginFailure = AuthResponse & {
+ type: StepType.LoginFailure;
+ payload: Step;
+ getCode: () => number;
+ getDetail: () => FailureDetail | undefined;
+ getMessage: () => string | undefined;
+ getProcessedMessage: (messageCreator?: MessageCreator) => ProcessedPropertyError[];
+ getReason: () => string | undefined;
+};
+
+// @public (undocumented)
+export type JourneyLoginSuccess = AuthResponse & {
+ type: StepType.LoginSuccess;
+ payload: Step;
+ getRealm: () => string | undefined;
+ getSessionToken: () => string | undefined;
+ getSuccessUrl: () => string | undefined;
+};
+
+// @public (undocumented)
+export type JourneyResult = JourneyStep | JourneyLoginSuccess | JourneyLoginFailure | GenericError;
+
+export { JourneyServerConfig }
+
+// @public (undocumented)
+export type JourneyStep = AuthResponse & {
+ type: StepType.Step;
+ payload: Step;
+ callbacks: BaseCallback[];
+ getCallbackOfType: (type: CallbackType) => T;
+ getCallbacksOfType: (type: CallbackType) => T[];
+ setCallbackValue: (type: CallbackType, value: unknown) => void;
+ getDescription: () => string | undefined;
+ getHeader: () => string | undefined;
+ getStage: () => string | undefined;
+};
+
+// @public
+export class KbaCreateCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPredefinedQuestions(): string[];
+ getPrompt(): string;
+ isAllowedUserDefinedQuestions(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setAnswer(answer: string): void;
+ setQuestion(question: string): void;
+}
+
+export { LegacyServerConfig }
+
+export { LogLevel }
+
+// @public (undocumented)
+export interface MessageCreator {
+ // (undocumented)
+ [key: string]: (propertyName: string, params?: {
+ [key: string]: unknown;
+ }) => string;
+}
+
+// @public
+export class MetadataCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getData(): T;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class NameCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setName(name: string): void;
+}
+
+export { NameValue }
+
+// @public (undocumented)
+export interface NextOptions {
+ // (undocumented)
+ query?: Record;
+}
+
+// @public
+export class PasswordCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): string[];
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setPassword(password: string): void;
+}
+
+// @public
+export class PingOneProtectEvaluationCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPauseBehavioralData(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setClientError(errorMessage: string): void;
+ setData(data: string): void;
+}
+
+// @public
+export class PingOneProtectInitializeCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getConfig(): Record | {
+ envId: string;
+ consoleLogEnabled: boolean;
+ deviceAttributesToIgnore: string[];
+ customHost: string;
+ lazyMetadata: boolean;
+ behavioralDataCollection: boolean;
+ deviceKeyRsyncIntervals: number;
+ enableTrust: boolean;
+ disableTags: boolean;
+ disableHub: boolean;
+ };
+ // (undocumented)
+ payload: Callback;
+ // (undocumented)
+ setClientError(errorMessage: string): void;
+}
+
+export { PolicyKey }
+
+export { PolicyParams }
+
+export { PolicyRequirement }
+
+// @public
+export class PollingWaitCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ getWaitTime(): number;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public (undocumented)
+export interface ProcessedPropertyError {
+ // (undocumented)
+ detail: FailedPolicyRequirement;
+ // (undocumented)
+ messages: string[];
+}
+
+// @public
+export class ReCaptchaCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getSiteKey(): string;
+ // (undocumented)
+ payload: Callback;
+ setResult(result: string): void;
+}
+
+// @public
+export class ReCaptchaEnterpriseCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getApiUrl(): string;
+ getElementClass(): string;
+ getSiteKey(): string;
+ // (undocumented)
+ payload: Callback;
+ setAction(action: string): void;
+ setClientError(error: string): void;
+ setPayload(payload: unknown): void;
+ setResult(result: string): void;
+}
+
+// @public
+export class RedirectCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getRedirectUrl(): string;
+ // (undocumented)
+ payload: Callback;
+}
+
+export { RequestMiddleware }
+
+// @public
+export interface ResolvedServerConfig {
+ // (undocumented)
+ baseUrl: string;
+ // (undocumented)
+ paths: {
+ authenticate: string;
+ sessions: string;
+ };
+}
+
+// @public (undocumented)
+export interface ResumeOptions {
+ // (undocumented)
+ journey?: string;
+ // (undocumented)
+ query?: Record;
+}
+
+// @public
+export class SelectIdPCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getProviders(): IdPValue[];
+ // (undocumented)
+ payload: Callback;
+ setProvider(value: string): void;
+}
+
+// @public (undocumented)
+export interface StartParam {
+ // (undocumented)
+ journey: string;
+ // (undocumented)
+ query?: Record;
+}
+
+export { Step }
+
+export { StepDetail }
+
+export { StepType }
+
+// @public
+export class SuspendedTextOutputCallback extends TextOutputCallback {
+ constructor(payload: Callback);
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class TermsAndConditionsCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getCreateDate(): Date | null;
+ getTerms(): string;
+ getVersion(): string;
+ // (undocumented)
+ payload: Callback;
+ setAccepted(accepted?: boolean): void;
+}
+
+// @public
+export class TextInputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getPrompt(): string;
+ // (undocumented)
+ payload: Callback;
+ setInput(input: string): void;
+}
+
+// @public
+export class TextOutputCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getMessage(): string;
+ getMessageType(): string;
+ // (undocumented)
+ payload: Callback;
+}
+
+// @public
+export class ValidatedCreatePasswordCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setPassword(password: string): void;
+ setValidateOnly(value: boolean): void;
+}
+
+// @public
+export class ValidatedCreateUsernameCallback extends BaseCallback {
+ constructor(payload: Callback);
+ getFailedPolicies(): PolicyRequirement[];
+ getPolicies(): Record;
+ getPrompt(): string;
+ isRequired(): boolean;
+ // (undocumented)
+ payload: Callback;
+ setName(name: string): void;
+ setValidateOnly(value: boolean): void;
+}
+
+export { WellknownResponse }
+
+// (No @packageDocumentation comment for this package)
+
+```
diff --git a/packages/journey-client/package.json b/packages/journey-client/package.json
index 38a1039d63c..badecc0352a 100644
--- a/packages/journey-client/package.json
+++ b/packages/journey-client/package.json
@@ -34,8 +34,8 @@
},
"dependencies": {
"@forgerock/sdk-logger": "workspace:*",
- "@forgerock/sdk-oidc": "workspace:*",
"@forgerock/sdk-request-middleware": "workspace:*",
+ "@forgerock/sdk-store": "workspace:*",
"@forgerock/sdk-types": "workspace:*",
"@forgerock/sdk-utilities": "workspace:*",
"@forgerock/storage": "workspace:*",
diff --git a/packages/journey-client/src/lib/client.store.test.ts b/packages/journey-client/src/lib/client.store.test.ts
index 9f2f3de96da..f063e9447b9 100644
--- a/packages/journey-client/src/lib/client.store.test.ts
+++ b/packages/journey-client/src/lib/client.store.test.ts
@@ -8,7 +8,7 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
-import { journey } from './client.store.js';
+import { journey, type JourneyClient } from './client.store.js';
import { makeJourneyConfig } from '@forgerock/sdk-utilities';
import { createJourneyStep } from './step.utils.js';
@@ -108,7 +108,7 @@ describe('journey-client', () => {
test('journey_WellknownConfig_ReturnsClientWithAllMethods', async () => {
setupMockFetch();
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
expect(client.start).toBeInstanceOf(Function);
expect(client.next).toBeInstanceOf(Function);
@@ -142,7 +142,7 @@ describe('journey-client', () => {
const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(mockStepResponse);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const step = await client.start();
expect(step).toBeDefined();
@@ -169,7 +169,7 @@ describe('journey-client', () => {
};
setupMockFetch(failurePayload, 401);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const result = await client.start();
expect(result).toBeDefined();
@@ -207,7 +207,7 @@ describe('journey-client', () => {
};
setupMockFetch(nextStepPayload);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const nextStep = await client.next(initialStep, {});
expect(nextStep).toBeDefined();
@@ -237,7 +237,7 @@ describe('journey-client', () => {
};
setupMockFetch(failurePayload, 401);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const result = await client.next(initialStep, {});
expect(result).toBeDefined();
@@ -267,7 +267,7 @@ describe('journey-client', () => {
vi.stubGlobal('window', { location: { assign: assignMock } });
setupMockFetch();
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
await client.redirect(step);
expect(mockStorageInstance.set).toHaveBeenCalledWith({ step: step.payload });
@@ -285,7 +285,7 @@ describe('journey-client', () => {
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(nextStepPayload);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
const step = await client.resume(resumeUrl, {});
@@ -314,7 +314,7 @@ describe('journey-client', () => {
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(nextStepPayload);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl =
'https://app.com/callback?code=123&state=abc&error=access_denied&errorCode=E1&errorMessage=oops&form_post_entry=fp&nonce=n1&RelayState=rs&responsekey=rk&scope=openid&suspendedId=s1';
await client.resume(resumeUrl, {});
@@ -342,7 +342,7 @@ describe('journey-client', () => {
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(nextStepPayload);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
await client.resume(resumeUrl, { query: { code: 'override' } });
@@ -363,7 +363,7 @@ describe('journey-client', () => {
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(nextStepPayload);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
const step = await client.resume(resumeUrl, {});
@@ -384,7 +384,7 @@ describe('journey-client', () => {
mockStorageInstance.get.mockResolvedValue(undefined);
setupMockFetch();
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
await expect(client.resume(resumeUrl)).rejects.toThrow(
@@ -399,7 +399,7 @@ describe('journey-client', () => {
const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] };
setupMockFetch(mockStepResponse);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const resumeUrl = 'https://app.com/callback?foo=bar';
const step = await client.resume(resumeUrl, {});
@@ -419,7 +419,7 @@ describe('journey-client', () => {
test('start_NoDataFromServer_ReturnsGenericError', async () => {
setupMockFetch(null);
- const client = await journey({ config: mockConfig });
+ const client = (await journey({ config: mockConfig })) as JourneyClient;
const result = await client.start();
expect(isGenericError(result)).toBe(true);
@@ -453,7 +453,7 @@ describe('journey-client', () => {
return Promise.resolve(new Response(JSON.stringify(mockStepResponse)));
});
- const client = await journey({ config: localhostConfig });
+ const client = (await journey({ config: localhostConfig })) as JourneyClient;
await client.start();
expect(mockFetch).toHaveBeenCalledTimes(2);
@@ -553,7 +553,7 @@ describe('journey-client', () => {
return Promise.resolve(new Response(JSON.stringify(mockStepResponse)));
});
- const client = await journey({ config: alphaConfig });
+ const client = (await journey({ config: alphaConfig })) as JourneyClient;
await client.start();
const request = mockFetch.mock.calls[1][0] as Request;
@@ -581,9 +581,9 @@ describe('journey-client', () => {
test('journey_BaseUrl_NoRealmPath_UsesRootAuthenticate', async () => {
setupBaseUrlFetch();
- const client = await journey({
+ const client = (await journey({
config: { serverConfig: { baseUrl } },
- });
+ })) as JourneyClient;
await client.start();
expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -594,9 +594,9 @@ describe('journey-client', () => {
test('journey_BaseUrl_WithRealmPath_UsesRealmAuthenticate', async () => {
setupBaseUrlFetch();
- const client = await journey({
+ const client = (await journey({
config: { serverConfig: { baseUrl }, realmPath: 'alpha' },
- });
+ })) as JourneyClient;
await client.start();
expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -609,9 +609,9 @@ describe('journey-client', () => {
test('journey_BaseUrl_TrailingSlashInput_NormalizesCorrectly', async () => {
setupBaseUrlFetch();
- const client = await journey({
+ const client = (await journey({
config: { serverConfig: { baseUrl: `${baseUrl}/` } },
- });
+ })) as JourneyClient;
await client.start();
const request = mockFetch.mock.calls[0][0] as Request;
@@ -621,9 +621,9 @@ describe('journey-client', () => {
test('journey_BaseUrl_Terminate_UsesRootSessionsUrl', async () => {
setupBaseUrlFetch();
- const client = await journey({
+ const client = (await journey({
config: { serverConfig: { baseUrl } },
- });
+ })) as JourneyClient;
await client.terminate();
expect(mockFetch).toHaveBeenCalledTimes(1);
@@ -636,9 +636,9 @@ describe('journey-client', () => {
test('journey_BaseUrl_WithRealmPath_Terminate_UsesRealmSessionsUrl', async () => {
setupBaseUrlFetch();
- const client = await journey({
+ const client = (await journey({
config: { serverConfig: { baseUrl }, realmPath: 'alpha' },
- });
+ })) as JourneyClient;
await client.terminate();
expect(mockFetch).toHaveBeenCalledTimes(1);
diff --git a/packages/journey-client/src/lib/client.store.ts b/packages/journey-client/src/lib/client.store.ts
index 5ea9604ed66..84276819d5d 100644
--- a/packages/journey-client/src/lib/client.store.ts
+++ b/packages/journey-client/src/lib/client.store.ts
@@ -14,6 +14,7 @@ import {
getEndpointPath,
} from '@forgerock/sdk-utilities';
import type { GenericError } from '@forgerock/sdk-types';
+import type { SdkStore } from '@forgerock/sdk-store';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
import type { Step } from '@forgerock/sdk-types';
@@ -24,7 +25,7 @@ import { createStorage } from '@forgerock/storage';
import * as Either from 'effect/Either';
import { createJourneyObject, parseJourneyResponse } from './journey.utils.js';
import type { JourneyResult } from './journey.utils.js';
-import { wellknownApi } from './wellknown.api.js';
+import { wellknownApi, assertValidStore, getClientForReducerPath } from '@forgerock/sdk-store';
import type { JourneyStep } from './step.utils.js';
import type { JourneyClientConfig } from './config.types.js';
@@ -33,6 +34,7 @@ import type { NextOptions, StartParam, ResumeOptions } from './interfaces.js';
/** The journey client instance returned by the `journey()` function. */
export interface JourneyClient {
+ store: SdkStore;
subscribe: (listener: () => void) => () => void;
start: (options?: StartParam) => Promise;
next: (step: JourneyStep, options?: NextOptions) => Promise;
@@ -74,6 +76,7 @@ export async function journey({
config,
requestMiddleware,
logger,
+ store: sharedStore,
}: {
config: JourneyClientConfig;
requestMiddleware?: RequestMiddleware[];
@@ -81,7 +84,12 @@ export async function journey({
level: LogLevel;
custom?: CustomLogger;
};
-}): Promise {
+ /**
+ * An existing SDK store to attach to, so discovery caching and state are
+ * shared with another client. Omit to create a store for this client alone.
+ */
+ store?: unknown;
+}): Promise {
const log = loggerFn({
level: logger?.level ?? config.log ?? 'error',
custom: logger?.custom,
@@ -113,7 +121,24 @@ export async function journey({
);
}
- const store = createJourneyStore({ requestMiddleware, logger: log });
+ const storeError = assertValidStore(sharedStore);
+ if (storeError) return storeError;
+
+ const validStore = sharedStore as SdkStore | undefined;
+
+ if (validStore) {
+ const existing = getClientForReducerPath(validStore, journeyApi.reducerPath);
+ if (existing) {
+ return {
+ error:
+ 'This store already has a journey client attached. Use a separate store per journey client.',
+ type: 'argument_error' as const,
+ };
+ }
+ }
+
+ const handle = createJourneyStore({ requestMiddleware, logger: log, store: validStore });
+ const store = handle.store;
if ('baseUrl' in config.serverConfig) {
const { baseUrl } = config.serverConfig;
@@ -180,6 +205,7 @@ export async function journey({
});
const self: JourneyClient = {
+ store: handle as SdkStore,
subscribe: store.subscribe,
start: async (options?: StartParam) => {
diff --git a/packages/journey-client/src/lib/client.store.utils.ts b/packages/journey-client/src/lib/client.store.utils.ts
index 7c08201f872..c9a939caec9 100644
--- a/packages/journey-client/src/lib/client.store.utils.ts
+++ b/packages/journey-client/src/lib/client.store.utils.ts
@@ -7,40 +7,47 @@
import { logger as loggerFn } from '@forgerock/sdk-logger';
import { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
-import { combineReducers, configureStore } from '@reduxjs/toolkit';
+
+import { combineSlices } from '@reduxjs/toolkit';
import { configSlice } from './config.slice.js';
import { journeyApi } from './journey.api.js';
-import { wellknownApi } from './wellknown.api.js';
+import { createSdkStore, injectClient, wellknownApi } from '@forgerock/sdk-store';
+
+import type { SdkStore, SdkStoreHandle } from '@forgerock/sdk-store';
+
+/**
+ * The canonical description of the state this client contributes.
+ *
+ * The runtime store is assembled by `injectClient`, which TypeScript cannot
+ * follow across lazy injection. Combining the same slices here lets the state
+ * type be *derived* from them rather than hand-written, so it cannot drift from
+ * what is actually mounted. Exported so the derived state type resolves for
+ * consumers, and so an application can compose the reducer itself if it wants.
+ */
+export const rootReducer = combineSlices(journeyApi, configSlice, wellknownApi);
-const rootReducer = combineReducers({
- [journeyApi.reducerPath]: journeyApi.reducer,
- [configSlice.name]: configSlice.reducer,
- [wellknownApi.reducerPath]: wellknownApi.reducer,
-});
+export type RootState = ReturnType;
+/**
+ * Creates, or attaches to, the store backing a Journey client.
+ *
+ * Passing `store` attaches to an existing SDK store so that discovery caching
+ * and state are shared; omitting it creates one, which is the default.
+ */
export const createJourneyStore = ({
requestMiddleware,
logger,
+ store,
}: {
requestMiddleware?: RequestMiddleware[];
logger?: ReturnType;
-}) => {
- return configureStore({
- reducer: rootReducer,
- middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware({
- serializableCheck: true,
- thunk: {
- extraArgument: {
- requestMiddleware,
- logger,
- },
- },
- })
- .concat(journeyApi.middleware)
- .concat(wellknownApi.middleware),
+ store?: SdkStore;
+}): SdkStoreHandle =>
+ injectClient(store ?? createSdkStore(), {
+ api: journeyApi,
+ reducerPath: journeyApi.reducerPath,
+ slices: [configSlice],
+ requestMiddleware,
+ logger,
});
-};
-
-export type RootState = ReturnType;
diff --git a/packages/journey-client/src/lib/journey.api.ts b/packages/journey-client/src/lib/journey.api.ts
index 66c4da9a299..e503df99403 100644
--- a/packages/journey-client/src/lib/journey.api.ts
+++ b/packages/journey-client/src/lib/journey.api.ts
@@ -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.
@@ -10,7 +10,8 @@ import { REQUESTED_WITH, getEndpointPath, stringify, resolve } from '@forgerock/
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
import type { Step } from '@forgerock/sdk-types';
-import type { logger as loggerFn } from '@forgerock/sdk-logger';
+import { logger as loggerFn } from '@forgerock/sdk-logger';
+import { clientExtra } from '@forgerock/sdk-store';
import type {
BaseQueryApi,
BaseQueryFn,
@@ -84,13 +85,37 @@ function configureSessionRequest(): RequestInit {
return init;
}
+const JOURNEY_REDUCER_PATH = 'journeyReducer';
+
+/**
+ * This client's private slot on the store's `extraArgument`.
+ *
+ * Optional because a shared store may not have had a journey slot registered
+ * yet; `journeyExtra` substitutes safe defaults.
+ */
interface Extras {
- requestMiddleware: RequestMiddleware[];
- logger: ReturnType;
+ requestMiddleware?: RequestMiddleware[];
+ logger?: ReturnType;
+}
+
+/** Fallback so a missing slot degrades to error-level logging, never a crash. */
+const fallbackLogger = loggerFn({ level: 'error' });
+
+/**
+ * Resolves this client's own middleware and logger.
+ *
+ * Reads only the `journeyReducer` slot — never a store-wide value, which on a
+ * shared store would belong to whichever client created it.
+ */
+function journeyExtra(extra: unknown): Required {
+ return clientExtra(extra, JOURNEY_REDUCER_PATH, {
+ requestMiddleware: [],
+ logger: fallbackLogger,
+ });
}
export const journeyApi = createApi({
- reducerPath: 'journeyReducer',
+ reducerPath: JOURNEY_REDUCER_PATH,
baseQuery: fetchBaseQuery({
baseUrl: '/',
prepareHeaders: (headers: Headers) => {
@@ -121,7 +146,7 @@ export const journeyApi = createApi({
const url = constructUrl(serverConfig, options?.journey, query);
const request = configureRequest();
- const { requestMiddleware } = api.extra as Extras;
+ const { requestMiddleware } = journeyExtra(api.extra);
const response = await initQuery({ ...request, url: url }, 'begin', {
type: 'service',
@@ -153,7 +178,7 @@ export const journeyApi = createApi({
const url = constructUrl(serverConfig, undefined, query);
const request = configureRequest(step);
- const { requestMiddleware } = api.extra as Extras;
+ const { requestMiddleware } = journeyExtra(api.extra);
const response = await initQuery({ ...request, url }, 'continue')
.applyMiddleware(requestMiddleware)
@@ -183,7 +208,7 @@ export const journeyApi = createApi({
const url = constructSessionsUrl(serverConfig, query);
const request = configureSessionRequest();
- const { requestMiddleware } = api.extra as Extras;
+ const { requestMiddleware } = journeyExtra(api.extra);
const response = await initQuery({ ...request, url }, 'terminate')
.applyMiddleware(requestMiddleware)
diff --git a/packages/journey-client/src/lib/store-shape.test.ts b/packages/journey-client/src/lib/store-shape.test.ts
new file mode 100644
index 00000000000..d2ea5926676
--- /dev/null
+++ b/packages/journey-client/src/lib/store-shape.test.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { describe, expect, it } from 'vitest';
+
+import { createJourneyStore } from './client.store.utils.js';
+
+/**
+ * `combineSlices` keys each reducer off `slice.reducerPath ?? slice.name`, where
+ * the previous `combineReducers({ ... })` form spelled the keys out literally.
+ * That makes the published state shape an implicit consequence of slice
+ * metadata: renaming `configSlice.name` would silently reshape the store.
+ *
+ * These assertions pin the shape so such a rename fails loudly here instead of
+ * in a consumer's selectors.
+ */
+describe('journey store shape', () => {
+ it('exposes exactly the expected top-level state keys', () => {
+ // Arrange
+ const { store } = createJourneyStore({});
+
+ // Act
+ const keys = Object.keys(store.getState()).sort();
+
+ // Assert
+ expect(keys).toEqual(['config', 'journeyReducer', 'wellknown']);
+ });
+
+ it('registers this client\u2019s slot on the store extra, keyed by reducerPath', async () => {
+ // Arrange
+ const { store } = createJourneyStore({});
+ let observed: unknown;
+
+ // Act — a thunk is the supported way to observe extraArgument
+ await store.dispatch(((_dispatch: unknown, _getState: unknown, extra: unknown) => {
+ observed = extra;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as any);
+
+ // Assert
+ expect(observed).toHaveProperty('clients.journeyReducer');
+ });
+});
diff --git a/packages/journey-client/src/lib/wellknown.api.ts b/packages/journey-client/src/lib/wellknown.api.ts
index d8f2dde9367..b0d550e183a 100644
--- a/packages/journey-client/src/lib/wellknown.api.ts
+++ b/packages/journey-client/src/lib/wellknown.api.ts
@@ -6,7 +6,7 @@
*/
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
-import { initWellknownQuery } from '@forgerock/sdk-oidc';
+import { initWellknownQuery } from '@forgerock/sdk-store';
import type { WellknownResponse } from '@forgerock/sdk-types';
import type {
@@ -18,7 +18,7 @@ import type {
/**
* RTK Query API for well-known endpoint discovery.
*
- * Uses the `initWellknownQuery` builder pattern from `@forgerock/sdk-oidc`.
+ * Uses the `initWellknownQuery` builder pattern from `@forgerock/sdk-store`.
* The builder constructs the request and validates the response;
* `fetchBaseQuery` handles the HTTP transport through RTK Query's pipeline.
*/
diff --git a/packages/journey-client/tsconfig.lib.json b/packages/journey-client/tsconfig.lib.json
index ca3f899b8dc..573c9f707ea 100644
--- a/packages/journey-client/tsconfig.lib.json
+++ b/packages/journey-client/tsconfig.lib.json
@@ -28,10 +28,10 @@
"path": "../sdk-types/tsconfig.lib.json"
},
{
- "path": "../sdk-effects/sdk-request-middleware/tsconfig.lib.json"
+ "path": "../sdk-effects/store/tsconfig.lib.json"
},
{
- "path": "../sdk-effects/oidc/tsconfig.lib.json"
+ "path": "../sdk-effects/sdk-request-middleware/tsconfig.lib.json"
},
{
"path": "../sdk-effects/logger/tsconfig.lib.json"
diff --git a/packages/oidc-client/README.md b/packages/oidc-client/README.md
index 4119d84729e..27bea7eb711 100644
--- a/packages/oidc-client/README.md
+++ b/packages/oidc-client/README.md
@@ -10,6 +10,7 @@ The oidc module follows the [OIDC](https://openid.net/specs/openid-connect-core-
- [Initialization](#initialization)
- [Configuration Options](#configuration-options)
- [Quick Start](#quick-start)
+- [Sharing a Store With Another Client](#sharing-a-store-with-another-client)
- [API Reference](#api-reference)
- [authorize](#authorize)
- [token](#token)
@@ -54,11 +55,17 @@ The `oidc()` initialization function accepts the following configuration:
- **wellknown** (required) - URL to the OIDC provider's well-known configuration endpoint
- **clientId** (required) - Your application's client ID registered with the OIDC provider
- **redirectUri** (required) - The URI where the OIDC provider will redirect after authentication
-- **scope** (required) - Space-separated list of requested scopes (e.g., `'openid profile email'`)
+- **scope** (optional, default: `'openid'`) - Space-separated list of requested scopes (e.g., `'openid profile email'`)
- **storage** (optional) - Storage configuration for tokens (defaults to localStorage)
- **timeout** (optional) - Request timeout in milliseconds
- **additionalParameters** (optional) - Additional parameters to include in authorization requests
+The `oidc()` function also accepts:
+
+- **requestMiddleware** (optional) - Middleware applied to this client's requests only
+- **logger** (optional) - Log level and custom logger for this client only
+- **store** (optional) - An existing SDK store to attach to. See [Sharing a Store](#sharing-a-store-with-another-client)
+
## Quick Start
Here's a minimal example to get started:
@@ -82,6 +89,65 @@ const user = await oidcClient.user.info();
await oidcClient.user.logout();
```
+## Sharing a Store With Another Client
+
+If your application also uses `@forgerock/davinci-client` or `@forgerock/journey-client`, the clients can share one Redux store. The well-known discovery document is then fetched once rather than once per client.
+
+Pass the other client's `store` handle:
+
+```js
+import { davinci } from '@forgerock/davinci-client';
+import { oidc } from '@forgerock/oidc-client';
+
+const davinciClient = await davinci({ config: davinciConfig });
+
+// Attaches to davinci's store; the discovery document is already cached there.
+const oidcClient = await oidc({ config: oidcConfig, store: davinciClient.store });
+```
+
+Or create the store yourself when neither client is the natural owner:
+
+```js
+import { createSdkStore } from '@forgerock/sdk-store';
+
+const store = createSdkStore();
+const davinciClient = await davinci({ config: davinciConfig, store });
+const oidcClient = await oidc({ config: oidcConfig, store });
+```
+
+Omitting `store` is always valid — the client creates its own, which is the default behaviour.
+
+### Middleware and logging stay private
+
+Sharing a store shares cached data, not configuration. Your `requestMiddleware` and `logger` are registered against this client alone and are only applied to OIDC requests:
+
+```js
+const oidcClient = await oidc({
+ config,
+ store,
+ // Runs for AUTHORIZE, PAR, TOKEN_EXCHANGE, REVOKE, USER_INFO and END_SESSION only.
+ requestMiddleware: [myOidcMiddleware],
+ logger: { level: 'debug' },
+});
+```
+
+Middleware passed to `davinci()` or `journey()` will never run against an OIDC token exchange, and middleware passed here will never run against their requests.
+
+### One OIDC client per store
+
+`oidc()` mounts at a fixed key in the store, so two OIDC clients sharing one store would overwrite each other's token state. Initialising a second client with a different `clientId` returns an `argument_error`:
+
+```js
+const store = createSdkStore();
+await oidc({ config: { ...config, clientId: 'app-one' }, store });
+
+const second = await oidc({ config: { ...config, clientId: 'app-two' }, store });
+// { error: "This store is already in use by an OIDC client with clientId 'app-one'. ...",
+// type: 'argument_error' }
+```
+
+Re-initialising with the _same_ `clientId` is allowed and idempotent. If you need two clientIds, give each its own store.
+
## API Reference
### authorize
@@ -326,7 +392,7 @@ const tokens = await oidcClient.token.get({
if ('error' in tokens) {
console.error('Failed to retrieve tokens:', tokens.error);
} else {
- console.log('Access token:', tokens.access_token);
+ console.log('Access token:', tokens.accessToken);
}
```
diff --git a/packages/oidc-client/api-report/oidc-client.api.md b/packages/oidc-client/api-report/oidc-client.api.md
index bcdce96e278..51e35326f5a 100644
--- a/packages/oidc-client/api-report/oidc-client.api.md
+++ b/packages/oidc-client/api-report/oidc-client.api.md
@@ -6,9 +6,9 @@
import { ActionTypes } from '@forgerock/sdk-request-middleware';
import { BaseQueryFn } from '@reduxjs/toolkit/query';
+import { CombinedSliceReducer } from '@reduxjs/toolkit';
import { CombinedState } from '@reduxjs/toolkit/query';
import { CustomLogger } from '@forgerock/sdk-logger';
-import { EnhancedStore } from '@reduxjs/toolkit';
import { FetchArgs } from '@reduxjs/toolkit/query';
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { FetchBaseQueryMeta } from '@reduxjs/toolkit/query';
@@ -17,18 +17,15 @@ import { GetAuthorizationUrlOptions } from '@forgerock/sdk-types';
import type { JWTPayload } from 'jose';
import { logger } from '@forgerock/sdk-logger';
import { LogLevel } from '@forgerock/sdk-logger';
-import { LogMessage } from '@forgerock/sdk-logger';
import { makeOidcConfig } from '@forgerock/sdk-utilities';
import { MutationDefinition } from '@reduxjs/toolkit/query';
import { OidcConfig } from '@forgerock/sdk-types';
import { QueryDefinition } from '@reduxjs/toolkit/query';
import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
import { ResponseType as ResponseType_2 } from '@forgerock/sdk-types';
+import type { SdkStore } from '@forgerock/sdk-store';
+import type { SdkStoreHandle } from '@forgerock/sdk-store';
import { StorageConfig } from '@forgerock/storage';
-import { StoreEnhancer } from '@reduxjs/toolkit';
-import { ThunkDispatch } from '@reduxjs/toolkit';
-import { Tuple } from '@reduxjs/toolkit';
-import { UnknownAction } from '@reduxjs/toolkit';
import { Unsubscribe } from '@reduxjs/toolkit';
import { WellknownResponse } from '@forgerock/sdk-types';
@@ -114,119 +111,16 @@ export interface AuthorizeSuccessResponse {
// @public (undocumented)
export type BuildAuthorizationData = [string, GetAuthorizationUrlOptions];
-// @public (undocumented)
-export type ClientStore = ReturnType;
+// @public
+export type ClientStore = ReturnType['store'];
// @public
export function createClientStore(input: {
requestMiddleware?: RequestMiddleware[];
logger?: ReturnType;
-}): EnhancedStore< {
-oidc: CombinedState< {
-authorizeFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
-par: MutationDefinition< {
-endpoint: string;
-body: URLSearchParams;
-}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
-sessionCheckIframe: MutationDefinition< {
-url: string;
-responseType: SessionCheckResponseType;
-}, BaseQueryFn, never, {
-params: Record;
-}, "oidc", unknown>;
-sessionCheckFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, {
-status: 204;
-}, "oidc", unknown>;
-authorizeIframe: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
-endSession: MutationDefinition< {
-idToken: string;
-endpoint: string;
-signOutRedirectUri?: string;
-}, BaseQueryFn, never, null, "oidc", unknown>;
-exchange: MutationDefinition< {
-code: string;
-config: OidcConfig;
-endpoint: string;
-verifier?: string;
-}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
-revoke: MutationDefinition< {
-accessToken: string;
-clientId?: string;
-endpoint: string;
-}, BaseQueryFn, never, object, "oidc", unknown>;
-userInfo: MutationDefinition< {
-accessToken: string;
-endpoint: string;
-}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
-}, never, "oidc">;
-wellknown: CombinedState< {
-configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
-}, never, "wellknown">;
-}, UnknownAction, Tuple<[StoreEnhancer< {
-dispatch: ThunkDispatch< {
-oidc: CombinedState< {
-authorizeFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
-par: MutationDefinition< {
-endpoint: string;
-body: URLSearchParams;
-}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
-sessionCheckIframe: MutationDefinition< {
-url: string;
-responseType: SessionCheckResponseType;
-}, BaseQueryFn, never, {
-params: Record;
-}, "oidc", unknown>;
-sessionCheckFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, {
-status: 204;
-}, "oidc", unknown>;
-authorizeIframe: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
-endSession: MutationDefinition< {
-idToken: string;
-endpoint: string;
-signOutRedirectUri?: string;
-}, BaseQueryFn, never, null, "oidc", unknown>;
-exchange: MutationDefinition< {
-code: string;
-config: OidcConfig;
-endpoint: string;
-verifier?: string;
-}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
-revoke: MutationDefinition< {
-accessToken: string;
-clientId?: string;
-endpoint: string;
-}, BaseQueryFn, never, object, "oidc", unknown>;
-userInfo: MutationDefinition< {
-accessToken: string;
-endpoint: string;
-}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
-}, never, "oidc">;
-wellknown: CombinedState< {
-configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
-}, never, "wellknown">;
-}, {
-requestMiddleware: RequestMiddleware[] | undefined;
-logger: {
-changeLevel: (level: LogLevel) => void;
-error: (...args: LogMessage[]) => void;
-warn: (...args: LogMessage[]) => void;
-info: (...args: LogMessage[]) => void;
-debug: (...args: LogMessage[]) => void;
-} | undefined;
-}, UnknownAction>;
-}>, StoreEnhancer]>>;
+ store?: SdkStore;
+ clientId?: string;
+}): SdkStoreHandle;
export { CustomLogger }
@@ -278,22 +172,16 @@ export interface OauthTokens {
}
// @public
-export function oidc(input: {
- config: OidcConfig;
- requestMiddleware?: RequestMiddleware[];
- logger?: {
- level: LogLevel;
- custom?: CustomLogger;
- };
- storage?: Partial;
-}): Promise<{
+export function oidc(raw: RawOidcArgs): Promise void) => Unsubscribe;
authorize: {
url: (options?: GetAuthorizationUrlOptions) => Promise;
@@ -318,6 +206,9 @@ export type OidcClient = Awaited>;
export { OidcConfig }
+// @public (undocumented)
+export type OidcRootState = ReturnType;
+
// @public (undocumented)
export type OptionalAuthorizeOptions = Partial;
@@ -329,6 +220,18 @@ export interface PushAuthorizationResponse {
request_uri: string;
}
+// @public
+export type RawOidcArgs = {
+ config: OidcConfig;
+ requestMiddleware?: RequestMiddleware[];
+ logger?: {
+ level: LogLevel;
+ custom?: CustomLogger;
+ };
+ storage?: Partial;
+ store?: unknown;
+};
+
export { RequestMiddleware }
export { ResponseType_2 as ResponseType }
@@ -346,6 +249,150 @@ export type RevokeSuccessResult = {
deleteResponse: null;
};
+// @public
+export const rootReducer: CombinedSliceReducer< {
+oidc: CombinedState< {
+authorizeFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
+par: MutationDefinition< {
+endpoint: string;
+body: URLSearchParams;
+}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
+sessionCheckIframe: MutationDefinition< {
+url: string;
+responseType: SessionCheckResponseType;
+}, BaseQueryFn, never, {
+params: Record;
+}, "oidc", unknown>;
+sessionCheckFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, {
+status: 204;
+}, "oidc", unknown>;
+authorizeIframe: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
+endSession: MutationDefinition< {
+idToken: string;
+endpoint: string;
+signOutRedirectUri?: string;
+}, BaseQueryFn, never, null, "oidc", unknown>;
+exchange: MutationDefinition< {
+code: string;
+config: OidcConfig;
+endpoint: string;
+verifier?: string;
+}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
+revoke: MutationDefinition< {
+accessToken: string;
+clientId?: string;
+endpoint: string;
+}, BaseQueryFn, never, object, "oidc", unknown>;
+userInfo: MutationDefinition< {
+accessToken: string;
+endpoint: string;
+}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
+}, never, "oidc">;
+wellknown: CombinedState< {
+configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
+}, never, "wellknown">;
+}, {
+oidc: CombinedState< {
+authorizeFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
+par: MutationDefinition< {
+endpoint: string;
+body: URLSearchParams;
+}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
+sessionCheckIframe: MutationDefinition< {
+url: string;
+responseType: SessionCheckResponseType;
+}, BaseQueryFn, never, {
+params: Record;
+}, "oidc", unknown>;
+sessionCheckFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, {
+status: 204;
+}, "oidc", unknown>;
+authorizeIframe: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
+endSession: MutationDefinition< {
+idToken: string;
+endpoint: string;
+signOutRedirectUri?: string;
+}, BaseQueryFn, never, null, "oidc", unknown>;
+exchange: MutationDefinition< {
+code: string;
+config: OidcConfig;
+endpoint: string;
+verifier?: string;
+}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
+revoke: MutationDefinition< {
+accessToken: string;
+clientId?: string;
+endpoint: string;
+}, BaseQueryFn, never, object, "oidc", unknown>;
+userInfo: MutationDefinition< {
+accessToken: string;
+endpoint: string;
+}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
+}, never, "oidc">;
+wellknown: CombinedState< {
+configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
+}, never, "wellknown">;
+}, Partial<{
+oidc: CombinedState< {
+authorizeFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
+par: MutationDefinition< {
+endpoint: string;
+body: URLSearchParams;
+}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
+sessionCheckIframe: MutationDefinition< {
+url: string;
+responseType: SessionCheckResponseType;
+}, BaseQueryFn, never, {
+params: Record;
+}, "oidc", unknown>;
+sessionCheckFetch: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, {
+status: 204;
+}, "oidc", unknown>;
+authorizeIframe: MutationDefinition< {
+url: string;
+}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
+endSession: MutationDefinition< {
+idToken: string;
+endpoint: string;
+signOutRedirectUri?: string;
+}, BaseQueryFn, never, null, "oidc", unknown>;
+exchange: MutationDefinition< {
+code: string;
+config: OidcConfig;
+endpoint: string;
+verifier?: string;
+}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
+revoke: MutationDefinition< {
+accessToken: string;
+clientId?: string;
+endpoint: string;
+}, BaseQueryFn, never, object, "oidc", unknown>;
+userInfo: MutationDefinition< {
+accessToken: string;
+endpoint: string;
+}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
+}, never, "oidc">;
+wellknown: CombinedState< {
+configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
+}, never, "wellknown">;
+}>>;
+
// @public (undocumented)
export type RootState = ReturnType;
diff --git a/packages/oidc-client/api-report/oidc-client.types.api.md b/packages/oidc-client/api-report/oidc-client.types.api.md
index 02a90f352a5..4d282f83a14 100644
--- a/packages/oidc-client/api-report/oidc-client.types.api.md
+++ b/packages/oidc-client/api-report/oidc-client.types.api.md
@@ -6,9 +6,9 @@
import { ActionTypes } from '@forgerock/sdk-request-middleware';
import { BaseQueryFn } from '@reduxjs/toolkit/query';
+import { CombinedSliceReducer } from '@reduxjs/toolkit';
import { CombinedState } from '@reduxjs/toolkit/query';
import { CustomLogger } from '@forgerock/sdk-logger';
-import { EnhancedStore } from '@reduxjs/toolkit';
import { FetchArgs } from '@reduxjs/toolkit/query';
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query';
import { FetchBaseQueryMeta } from '@reduxjs/toolkit/query';
@@ -17,17 +17,14 @@ import { GetAuthorizationUrlOptions } from '@forgerock/sdk-types';
import type { JWTPayload } from 'jose';
import { logger } from '@forgerock/sdk-logger';
import { LogLevel } from '@forgerock/sdk-logger';
-import { LogMessage } from '@forgerock/sdk-logger';
import { MutationDefinition } from '@reduxjs/toolkit/query';
import { OidcConfig } from '@forgerock/sdk-types';
import { QueryDefinition } from '@reduxjs/toolkit/query';
import { RequestMiddleware } from '@forgerock/sdk-request-middleware';
import { ResponseType as ResponseType_2 } from '@forgerock/sdk-types';
+import type { SdkStore } from '@forgerock/sdk-store';
+import type { SdkStoreHandle } from '@forgerock/sdk-store';
import { StorageConfig } from '@forgerock/storage';
-import { StoreEnhancer } from '@reduxjs/toolkit';
-import { ThunkDispatch } from '@reduxjs/toolkit';
-import { Tuple } from '@reduxjs/toolkit';
-import { UnknownAction } from '@reduxjs/toolkit';
import { Unsubscribe } from '@reduxjs/toolkit';
import { WellknownResponse } from '@forgerock/sdk-types';
@@ -113,119 +110,16 @@ export interface AuthorizeSuccessResponse {
// @public (undocumented)
export type BuildAuthorizationData = [string, GetAuthorizationUrlOptions];
-// @public (undocumented)
-export type ClientStore = ReturnType;
+// @public
+export type ClientStore = ReturnType['store'];
// @public
export function createClientStore(input: {
requestMiddleware?: RequestMiddleware[];
logger?: ReturnType;
-}): EnhancedStore< {
-oidc: CombinedState< {
-authorizeFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
-par: MutationDefinition< {
-endpoint: string;
-body: URLSearchParams;
-}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
-sessionCheckIframe: MutationDefinition< {
-url: string;
-responseType: SessionCheckResponseType;
-}, BaseQueryFn, never, {
-params: Record;
-}, "oidc", unknown>;
-sessionCheckFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, {
-status: 204;
-}, "oidc", unknown>;
-authorizeIframe: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
-endSession: MutationDefinition< {
-idToken: string;
-endpoint: string;
-signOutRedirectUri?: string;
-}, BaseQueryFn, never, null, "oidc", unknown>;
-exchange: MutationDefinition< {
-code: string;
-config: OidcConfig;
-endpoint: string;
-verifier?: string;
-}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
-revoke: MutationDefinition< {
-accessToken: string;
-clientId?: string;
-endpoint: string;
-}, BaseQueryFn, never, object, "oidc", unknown>;
-userInfo: MutationDefinition< {
-accessToken: string;
-endpoint: string;
-}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
-}, never, "oidc">;
-wellknown: CombinedState< {
-configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
-}, never, "wellknown">;
-}, UnknownAction, Tuple<[StoreEnhancer< {
-dispatch: ThunkDispatch< {
-oidc: CombinedState< {
-authorizeFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizeSuccessResponse, "oidc", unknown>;
-par: MutationDefinition< {
-endpoint: string;
-body: URLSearchParams;
-}, BaseQueryFn, never, PushAuthorizationResponse, "oidc", unknown>;
-sessionCheckIframe: MutationDefinition< {
-url: string;
-responseType: SessionCheckResponseType;
-}, BaseQueryFn, never, {
-params: Record;
-}, "oidc", unknown>;
-sessionCheckFetch: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, {
-status: 204;
-}, "oidc", unknown>;
-authorizeIframe: MutationDefinition< {
-url: string;
-}, BaseQueryFn, never, AuthorizationSuccess, "oidc", unknown>;
-endSession: MutationDefinition< {
-idToken: string;
-endpoint: string;
-signOutRedirectUri?: string;
-}, BaseQueryFn, never, null, "oidc", unknown>;
-exchange: MutationDefinition< {
-code: string;
-config: OidcConfig;
-endpoint: string;
-verifier?: string;
-}, BaseQueryFn, never, TokenExchangeResponse, "oidc", unknown>;
-revoke: MutationDefinition< {
-accessToken: string;
-clientId?: string;
-endpoint: string;
-}, BaseQueryFn, never, object, "oidc", unknown>;
-userInfo: MutationDefinition< {
-accessToken: string;
-endpoint: string;
-}, BaseQueryFn, never, UserInfoResponse, "oidc", unknown>;
-}, never, "oidc">;
-wellknown: CombinedState< {
-configuration: QueryDefinition, never, WellknownResponse, "wellknown", unknown>;
-}, never, "wellknown">;
-}, {
-requestMiddleware: RequestMiddleware[] | undefined;
-logger: {
-changeLevel: (level: LogLevel) => void;
-error: (...args: LogMessage[]) => void;
-warn: (...args: LogMessage[]) => void;
-info: (...args: LogMessage[]) => void;
-debug: (...args: LogMessage[]) => void;
-} | undefined;
-}, UnknownAction>;
-}>, StoreEnhancer]>>;
+ store?: SdkStore;
+ clientId?: string;
+}): SdkStoreHandle;
export { CustomLogger }
@@ -275,22 +169,16 @@ export interface OauthTokens {
}
// @public
-export function oidc(input: {
- config: OidcConfig;
- requestMiddleware?: RequestMiddleware[];
- logger?: {
- level: LogLevel;
- custom?: CustomLogger;
- };
- storage?: Partial;
-}): Promise<{
+export function oidc(raw: RawOidcArgs): Promise