Skip to content

Commit f705d4d

Browse files
authored
Merge pull request #727 from ForgeRock/SDKS-5100-metadata-collector
feat(davinci-client): add metadata collector (SDKS-5100)
2 parents 9549894 + fc0caf2 commit f705d4d

20 files changed

Lines changed: 477 additions & 88 deletions

‎.changeset/real-swans-leave.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@forgerock/davinci-client': minor
3+
---
4+
5+
Add MetadataCollector support
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { MetadataCollector, Updater, MetadataError } from '@forgerock/davinci-client/types';
8+
9+
export default function metadataComponent(
10+
formEl: HTMLFormElement,
11+
updater: Updater<MetadataCollector>,
12+
submitForm: () => Promise<void>,
13+
) {
14+
const successBtn = document.createElement('button');
15+
successBtn.type = 'button';
16+
successBtn.innerHTML = 'Metadata Success';
17+
18+
const errorBtn = document.createElement('button');
19+
errorBtn.type = 'button';
20+
errorBtn.innerHTML = 'Metadata Error';
21+
22+
formEl.appendChild(successBtn);
23+
formEl.appendChild(errorBtn);
24+
25+
successBtn.onclick = async () => {
26+
updater({ status: 'succeeded' });
27+
await submitForm();
28+
};
29+
30+
errorBtn.onclick = async () => {
31+
const metadataError: MetadataError = {
32+
code: 'ERROR_CODE',
33+
message: 'Operation cancelled',
34+
};
35+
36+
updater(metadataError);
37+
await submitForm();
38+
};
39+
}

‎e2e/davinci-app/main.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import qrCodeComponent from './components/qr-code.js';
3838
import formImageComponent from './components/form-image.js';
3939
import pollingComponent from './components/polling.js';
4040
import booleanComponent from './components/boolean.js';
41+
import metadataComponent from './components/metadata.js';
4142

4243
const loggerFn = {
4344
error: () => {
@@ -289,6 +290,12 @@ const urlParams = new URLSearchParams(window.location.search);
289290
davinciClient.update(collector), // Returns an update function for this collector
290291
submitForm,
291292
);
293+
} else if (collector.type === 'MetadataCollector') {
294+
metadataComponent(
295+
formEl, // You can ignore this; it's just for rendering
296+
davinciClient.update(collector), // Returns an update function for this collector
297+
submitForm,
298+
);
292299
} else if (collector.type === 'BooleanCollector') {
293300
booleanComponent(formEl, collector, davinciClient.update(collector));
294301
} else if (collector.type === 'ValidatedBooleanCollector') {

‎e2e/davinci-app/server-configs.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const serverConfigs: Record<string, DaVinciConfig> = {
9393
},
9494
},
9595
/**
96-
* Polling
96+
* AutoCollectors: Polling, Metadata
9797
*/
9898
'31a587ce-9aa4-4f36-a09f-78cd8a0a74a0': {
9999
clientId: '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0',
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import { expect, test } from '@playwright/test';
8+
import { asyncEvents } from './utils/async-events.js';
9+
10+
test.describe('Metadata Collector', () => {
11+
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
12+
const davinciPolicy = '7793be21a14dd80c1d26b367e81ea985';
13+
14+
test('should submit with success status when metadata collection succeeds', async ({ page }) => {
15+
const { navigate } = asyncEvents(page);
16+
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);
17+
18+
await page.getByRole('button', { name: 'Sign On' }).click();
19+
await expect(page.getByRole('button', { name: 'Metadata Success' })).toBeVisible();
20+
21+
await page.getByRole('button', { name: 'Metadata Success' }).click();
22+
23+
await expect(page.getByRole('heading', { name: 'Message' })).toBeVisible();
24+
await expect(page.getByText('"status":"succeeded"')).toBeVisible();
25+
});
26+
27+
test('should submit with error details when metadata collection fails', async ({ page }) => {
28+
const { navigate } = asyncEvents(page);
29+
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);
30+
31+
await page.getByRole('button', { name: 'Sign On' }).click();
32+
await expect(page.getByRole('button', { name: 'Metadata Error' })).toBeVisible();
33+
34+
await page.getByRole('button', { name: 'Metadata Error' }).click();
35+
36+
await expect(page.getByRole('heading', { name: 'Message' })).toBeVisible();
37+
await expect(page.getByText('"code":"ERROR_CODE"')).toBeVisible();
38+
await expect(page.getByText('"message":"Operation cancelled"')).toBeVisible();
39+
});
40+
});

‎packages/davinci-client/api-report/davinci-client.api.md‎

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export interface AutoCollector<C extends AutoCollectorCategories, T extends Auto
140140
export type AutoCollectorCategories = 'SingleValueAutoCollector' | 'ObjectValueAutoCollector';
141141

142142
// @public (undocumented)
143-
export type AutoCollectors = ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | SingleValueAutoCollector | ObjectValueAutoCollector;
143+
export type AutoCollectors = ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | MetadataCollector | PollingCollector | SingleValueAutoCollector | ObjectValueAutoCollector;
144144

145145
// @public (undocumented)
146146
export type AutoCollectorTypes = SingleValueAutoCollectorTypes | ObjectValueAutoCollectorTypes;
@@ -173,7 +173,7 @@ export interface CollectorRichContent {
173173
}
174174

175175
// @public (undocumented)
176-
export type Collectors = FlowCollector | PasswordCollector | ValidatedPasswordCollector | TextCollector | BooleanCollector | ValidatedBooleanCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | RichTextCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | ImageCollector | UnknownCollector;
176+
export type Collectors = FlowCollector | MetadataCollector | PasswordCollector | ValidatedPasswordCollector | TextCollector | BooleanCollector | ValidatedBooleanCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | RichTextCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | ImageCollector | UnknownCollector;
177177

178178
// @public
179179
export type CollectorValueType<T> = T extends {
@@ -211,6 +211,8 @@ export type CollectorValueType<T> = T extends {
211211
} ? FidoRegistrationInputValue : T extends {
212212
type: 'FidoAuthenticationCollector';
213213
} ? FidoAuthenticationInputValue : T extends {
214+
type: 'MetadataCollector';
215+
} ? Record<string, unknown> | MetadataError : T extends {
214216
category: 'SingleValueCollector';
215217
} ? string : T extends {
216218
category: 'ValidatedSingleValueCollector';
@@ -225,10 +227,10 @@ export type CollectorValueType<T> = T extends {
225227
} ? never : CollectorValueTypes;
226228

227229
// @public
228-
export type CollectorValueTypes = string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue;
230+
export type CollectorValueTypes = string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue | MetadataError;
229231

230232
// @public (undocumented)
231-
export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField;
233+
export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField | MetadataField;
232234

233235
// @public (undocumented)
234236
export interface ContinueNode {
@@ -283,7 +285,7 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
283285
resume: (input: {
284286
continueToken: string;
285287
}) => Promise<InternalErrorResponse | NodeStates>;
286-
start: <QueryParams extends OutgoingQueryParams = OutgoingQueryParams>(options?: StartOptions<QueryParams> | undefined) => Promise<ContinueNode | ErrorNode | FailureNode | StartNode | SuccessNode>;
288+
start: <QueryParams extends OutgoingQueryParams = OutgoingQueryParams>(options?: StartOptions<QueryParams> | undefined) => Promise<ContinueNode | ErrorNode | StartNode | SuccessNode | FailureNode>;
287289
update: <T extends SingleValueCollectors | MultiSelectCollector | ObjectValueCollectors | AutoCollectors>(collector: T) => Updater<T>;
288290
validate: (collector: SingleValueCollectors | ObjectValueCollectors | MultiValueCollectors | AutoCollectors) => Validator;
289291
pollStatus: (collector: PollingCollector) => Poller;
@@ -293,27 +295,27 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
293295
description?: string;
294296
name?: string;
295297
status: "continue";
298+
} | {
299+
status: "start";
296300
} | {
297301
action: string;
298302
collectors: Collectors[];
299303
description?: string;
300304
name?: string;
301305
status: "error";
302-
} | {
303-
status: "failure";
304-
} | {
305-
status: "start";
306306
} | {
307307
authorization?: {
308308
code?: string;
309309
state?: string;
310310
};
311311
status: "success";
312+
} | {
313+
status: "failure";
312314
} | null;
313315
getCollectors: () => Collectors[];
314316
getError: () => DaVinciError | null;
315317
getErrorCollectors: () => CollectorErrors[];
316-
getNode: () => ContinueNode | ErrorNode | FailureNode | StartNode | SuccessNode;
318+
getNode: () => ContinueNode | ErrorNode | StartNode | SuccessNode | FailureNode;
317319
getServer: () => {
318320
_links?: Links;
319321
id?: string;
@@ -322,6 +324,8 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
322324
href?: string;
323325
eventName?: string;
324326
status: "continue";
327+
} | {
328+
status: "start";
325329
} | {
326330
_links?: Links;
327331
eventName?: string;
@@ -332,22 +336,20 @@ export function davinci<ActionType extends ActionTypes = ActionTypes>(input: {
332336
} | {
333337
_links?: Links;
334338
eventName?: string;
335-
href?: string;
336339
id?: string;
337340
interactionId?: string;
338341
interactionToken?: string;
339-
status: "failure";
340-
} | {
341-
status: "start";
342+
href?: string;
343+
session?: string;
344+
status: "success";
342345
} | {
343346
_links?: Links;
344347
eventName?: string;
348+
href?: string;
345349
id?: string;
346350
interactionId?: string;
347351
interactionToken?: string;
348-
href?: string;
349-
session?: string;
350-
status: "success";
352+
status: "failure";
351353
} | null;
352354
cache: {
353355
getLatestResponse: () => ({
@@ -636,6 +638,9 @@ export interface DaVinciRequest {
636638
};
637639
}
638640

641+
// @public
642+
export type DaVinciRequestValueTypes = string | number | boolean | (string | number | boolean)[] | DeviceValue | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue | MetadataError;
643+
639644
// @public (undocumented)
640645
export interface DaVinciSuccessResponse extends DaVinciBaseResponse {
641646
// (undocumented)
@@ -968,7 +973,7 @@ export type ImageField = {
968973
export type InferActionCollectorType<T extends ActionCollectorTypes> = T extends 'IdpCollector' ? IdpCollector : T extends 'SubmitCollector' ? SubmitCollector : T extends 'FlowCollector' ? FlowCollector : ActionCollectorWithUrl<'ActionCollector'> | ActionCollectorNoUrl<'ActionCollector'>;
969974

970975
// @public
971-
export type InferAutoCollectorType<T extends AutoCollectorTypes> = T extends 'ProtectCollector' ? ProtectCollector : T extends 'PollingCollector' ? PollingCollector : T extends 'FidoRegistrationCollector' ? FidoRegistrationCollector : T extends 'FidoAuthenticationCollector' ? FidoAuthenticationCollector : T extends 'ObjectValueAutoCollector' ? ObjectValueAutoCollector : SingleValueAutoCollector;
976+
export type InferAutoCollectorType<T extends AutoCollectorTypes> = T extends 'ProtectCollector' ? ProtectCollector : T extends 'PollingCollector' ? PollingCollector : T extends 'FidoRegistrationCollector' ? FidoRegistrationCollector : T extends 'FidoAuthenticationCollector' ? FidoAuthenticationCollector : T extends 'MetadataCollector' ? MetadataCollector : T extends 'ObjectValueAutoCollector' ? ObjectValueAutoCollector : SingleValueAutoCollector;
972977

973978
// @public
974979
export type InferMultiValueCollectorType<T extends MultiValueCollectorTypes> = T extends 'MultiSelectCollector' ? MultiValueCollectorWithValue<'MultiSelectCollector'> : MultiValueCollectorWithValue<'MultiValueCollector'> | MultiValueCollectorNoValue<'MultiValueCollector'>;
@@ -1005,6 +1010,27 @@ export interface Links {
10051010

10061011
export { LogLevel }
10071012

1013+
// @public (undocumented)
1014+
export type MetadataCollector = AutoCollector<'ObjectValueAutoCollector', 'MetadataCollector', MetadataCollectorInputValue, Record<string, unknown>>;
1015+
1016+
// @public (undocumented)
1017+
export type MetadataCollectorInputValue = Record<string, unknown> | MetadataError;
1018+
1019+
// @public
1020+
export interface MetadataError {
1021+
// (undocumented)
1022+
code: string;
1023+
// (undocumented)
1024+
message: string;
1025+
}
1026+
1027+
// @public (undocumented)
1028+
export type MetadataField = {
1029+
type: 'METADATA';
1030+
key: string;
1031+
payload: Record<string, unknown>;
1032+
};
1033+
10081034
// @public (undocumented)
10091035
export type MultiSelectCollector = MultiValueCollectorWithValue<'MultiSelectCollector'>;
10101036

@@ -1222,7 +1248,7 @@ export interface ObjectOptionsCollectorWithStringValue<T extends ObjectValueColl
12221248
export type ObjectValueAutoCollector = AutoCollector<'ObjectValueAutoCollector', 'ObjectValueAutoCollector', Record<string, unknown>>;
12231249

12241250
// @public (undocumented)
1225-
export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRegistrationCollector' | 'FidoAuthenticationCollector';
1251+
export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRegistrationCollector' | 'FidoAuthenticationCollector' | 'MetadataCollector';
12261252

12271253
// @public (undocumented)
12281254
export type ObjectValueCollector<T extends ObjectValueCollectorTypes> = ObjectOptionsCollectorWithObjectValue<T> | ObjectOptionsCollectorWithStringValue<T> | ObjectValueCollectorWithObjectValue<T>;

0 commit comments

Comments
 (0)