Skip to content

Commit fe718dc

Browse files
committed
feat(davinci-client): mfa otp field support
1 parent 6cdc7b8 commit fe718dc

8 files changed

Lines changed: 635 additions & 30 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
SingleValueCollectors,
2929
IdpCollector,
3030
MultiSelectCollector,
31+
ObjectValueCollectors,
3132
} from './collector.types.js';
3233
import type { InitFlow, Updater, Validator } from './client.types.js';
3334
import { returnValidator } from './collector.utils.js';
@@ -167,7 +168,9 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
167168
* @param {SingleValueCollector} collector - the collector to update
168169
* @returns {function} - a function to call for updating collector value
169170
*/
170-
update: (collector: SingleValueCollectors | MultiSelectCollector): Updater => {
171+
update: (
172+
collector: SingleValueCollectors | MultiSelectCollector | ObjectValueCollectors,
173+
): Updater => {
171174
if (!collector.id) {
172175
console.error('Argument for `collector` has no ID');
173176
return function () {
@@ -197,7 +200,8 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
197200
if (
198201
collectorToUpdate.category !== 'MultiValueCollector' &&
199202
collectorToUpdate.category !== 'SingleValueCollector' &&
200-
collectorToUpdate.category !== 'ValidatedSingleValueCollector'
203+
collectorToUpdate.category !== 'ValidatedSingleValueCollector' &&
204+
collectorToUpdate.category !== 'ObjectValueCollector'
201205
) {
202206
console.error(
203207
'Collector is not a MultiValueCollector, SingleValueCollector or ValidatedSingleValueCollector and cannot be updated',

packages/davinci-client/src/lib/collector.types.ts

Lines changed: 114 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* This software may be modified and distributed under the terms
55
* of the MIT license. See the LICENSE file for details.
66
*/
7+
78
/** *********************************************************************
89
* SINGLE-VALUE COLLECTORS
910
*/
@@ -15,10 +16,11 @@ export type SingleValueCollectorTypes =
1516
| 'PasswordCollector'
1617
| 'SingleValueCollector'
1718
| 'SingleSelectCollector'
19+
| 'SingleSelectObjectCollector'
1820
| 'TextCollector'
1921
| 'ValidatedTextCollector';
2022

21-
interface SelectorOptions {
23+
interface SelectorOption {
2224
label: string;
2325
value: string;
2426
}
@@ -90,7 +92,7 @@ export interface SingleSelectCollectorWithValue<T extends SingleValueCollectorTy
9092
label: string;
9193
type: string;
9294
value: string | number | boolean;
93-
options: SelectorOptions[];
95+
options: SelectorOption[];
9496
};
9597
}
9698

@@ -127,7 +129,7 @@ export interface SingleSelectCollectorNoValue<T extends SingleValueCollectorType
127129
key: string;
128130
label: string;
129131
type: string;
130-
options: SelectorOptions[];
132+
options: SelectorOption[];
131133
};
132134
}
133135

@@ -143,16 +145,18 @@ export type InferSingleValueCollectorType<T extends SingleValueCollectorTypes> =
143145
? TextCollector
144146
: T extends 'SingleSelectCollector'
145147
? SingleSelectCollector
146-
: T extends 'PasswordCollector'
147-
? PasswordCollector
148-
: /**
149-
* At this point, we have not passed in a collector type
150-
* or we have explicitly passed in 'SingleValueCollector'
151-
* So we can return either a SingleValueCollector with value
152-
* or without a value.
153-
**/
154-
| SingleValueCollectorWithValue<'SingleValueCollector'>
155-
| SingleValueCollectorNoValue<'SingleValueCollector'>;
148+
: T extends 'ValidatedTextCollector'
149+
? ValidatedTextCollector
150+
: T extends 'PasswordCollector'
151+
? PasswordCollector
152+
: /**
153+
* At this point, we have not passed in a collector type
154+
* or we have explicitly passed in 'SingleValueCollector'
155+
* So we can return either a SingleValueCollector with value
156+
* or without a value.
157+
**/
158+
| SingleValueCollectorWithValue<'SingleValueCollector'>
159+
| SingleValueCollectorNoValue<'SingleValueCollector'>;
156160

157161
/**
158162
* SINGLE-VALUE COLLECTOR TYPES
@@ -198,7 +202,7 @@ export interface MultiValueCollectorWithValue<T extends MultiValueCollectorTypes
198202
label: string;
199203
type: string;
200204
value: string[];
201-
options: SelectorOptions[];
205+
options: SelectorOption[];
202206
};
203207
}
204208

@@ -218,7 +222,7 @@ export interface MultiValueCollectorNoValue<T extends MultiValueCollectorTypes>
218222
label: string;
219223
type: string;
220224
value: string[];
221-
options: SelectorOptions[];
225+
options: SelectorOption[];
222226
};
223227
}
224228

@@ -246,6 +250,101 @@ export type MultiValueCollector<T extends MultiValueCollectorTypes> =
246250

247251
export type MultiSelectCollector = MultiValueCollectorWithValue<'MultiSelectCollector'>;
248252

253+
/** *********************************************************************
254+
* OBJECT COLLECTORS
255+
*/
256+
257+
export type ObjectValueCollectorTypes =
258+
| 'DeviceAuthenticationCollector'
259+
| 'DeviceRegistrationCollector'
260+
| 'ObjectValueCollector'
261+
| 'ObjectSelectCollector';
262+
263+
interface ObjectOptionWithValue {
264+
type: string;
265+
label: string;
266+
content: string;
267+
default: boolean;
268+
value: string;
269+
key: string;
270+
}
271+
272+
interface ObjectOptionNoValue {
273+
type: string;
274+
label: string;
275+
content: string;
276+
value: string;
277+
key: string;
278+
}
279+
280+
interface ObjectValue {
281+
type: string;
282+
id: string;
283+
value: string;
284+
}
285+
286+
export interface ObjectValueCollectorNoValue<T extends ObjectValueCollectorTypes> {
287+
category: 'ObjectValueCollector';
288+
error: string | null;
289+
type: T;
290+
id: string;
291+
name: string;
292+
input: {
293+
key: string;
294+
value: string | null;
295+
type: string;
296+
};
297+
output: {
298+
key: string;
299+
label: string;
300+
type: string;
301+
options: ObjectOptionNoValue[];
302+
};
303+
}
304+
305+
export interface ObjectValueCollectorWithValue<T extends ObjectValueCollectorTypes> {
306+
category: 'ObjectValueCollector';
307+
error: string | null;
308+
type: T;
309+
id: string;
310+
name: string;
311+
input: {
312+
key: string;
313+
value: ObjectValue | null;
314+
type: string;
315+
};
316+
output: {
317+
key: string;
318+
label: string;
319+
type: string;
320+
options: ObjectOptionWithValue[];
321+
};
322+
}
323+
324+
export type InferValueObjectCollectorType<T extends ObjectValueCollectorTypes> =
325+
T extends 'DeviceAuthenticationCollector'
326+
? DeviceAuthenticationCollector
327+
: T extends 'DeviceRegistrationCollector'
328+
? DeviceRegistrationCollector
329+
:
330+
| ObjectValueCollectorWithValue<'ObjectValueCollector'>
331+
| ObjectValueCollectorNoValue<'ObjectValueCollector'>;
332+
333+
export type ObjectValueCollectors =
334+
| ObjectValueCollectorWithValue<'DeviceAuthenticationCollector'>
335+
| ObjectValueCollectorNoValue<'DeviceRegistrationCollector'>
336+
| ObjectValueCollectorWithValue<'ObjectSelectCollector'>
337+
| ObjectValueCollectorNoValue<'ObjectSelectCollector'>;
338+
339+
export type ObjectValueCollector<T extends ObjectValueCollectorTypes> =
340+
| ObjectValueCollectorWithValue<T>
341+
| ObjectValueCollectorNoValue<T>;
342+
343+
export type DeviceRegistrationCollector =
344+
ObjectValueCollectorNoValue<'DeviceRegistrationCollector'>;
345+
export type DeviceAuthenticationCollector =
346+
ObjectValueCollectorWithValue<'DeviceAuthenticationCollector'>;
347+
249348
/** *********************************************************************
250349
* ACTION COLLECTORS
251350
*/

packages/davinci-client/src/lib/collector.utils.test.ts

Lines changed: 124 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import {
1818
returnValidator,
1919
returnReadOnlyCollector,
2020
returnNoValueCollector,
21+
returnObjectSelectCollector,
2122
} from './collector.utils.js';
2223
import type {
2324
DaVinciField,
25+
DeviceAuthenticationFieldValue,
26+
DeviceRegistrationFieldValue,
2427
ReadOnlyFieldValue,
2528
RedirectFieldValue,
2629
StandardFieldValue,
@@ -227,6 +230,12 @@ describe('Action Collectors', () => {
227230
});
228231
});
229232

233+
it('creates an action collector from flow link field type', () => {
234+
const result = returnFlowCollector(mockField, 1);
235+
expect(result.type).toBe('FlowCollector');
236+
expect(result.output).not.toHaveProperty('value');
237+
});
238+
230239
it('handles missing authentication URL for social login', () => {
231240
const result = returnActionCollector(mockField, 1, 'IdpCollector');
232241
if ('url' in result.output) {
@@ -373,7 +382,11 @@ describe('Single Value Collectors', () => {
373382
expect(result.type).toBe('SingleSelectCollector');
374383
expect(result.output).toHaveProperty('value', '');
375384
});
385+
});
386+
});
376387

388+
describe('Multi-Value Collectors', () => {
389+
describe('Specialized Multi-Select Collectors', () => {
377390
it('creates a multi-select collector from combobox field type', () => {
378391
const comboField: DaVinciField = {
379392
type: 'COMBOBOX',
@@ -396,11 +409,118 @@ describe('Single Value Collectors', () => {
396409
expect(result.type).toBe('MultiSelectCollector');
397410
expect(result.output).toHaveProperty('value', []);
398411
});
412+
});
413+
});
399414

400-
it('creates an action collector from flow link field type', () => {
401-
const result = returnFlowCollector(mockField, 1);
402-
expect(result.type).toBe('FlowCollector');
403-
expect(result.output).not.toHaveProperty('value');
415+
describe('Object value collectors', () => {
416+
describe('returnDeviceAuthenticationCollector', () => {
417+
const mockField: DeviceAuthenticationFieldValue = {
418+
key: 'device-auth-key',
419+
label: 'Device Authentication',
420+
type: 'DEVICE_AUTHENTICATION',
421+
devices: [
422+
{
423+
type: 'device1',
424+
iconSrc: 'icon1.png',
425+
title: 'Device 1',
426+
id: '123123',
427+
default: true,
428+
value: 'device1-value',
429+
},
430+
{
431+
type: 'device2',
432+
iconSrc: 'icon2.png',
433+
title: 'Device 2',
434+
id: '345345',
435+
default: false,
436+
value: 'device2-value',
437+
},
438+
],
439+
required: true,
440+
};
441+
442+
const transformedDevices = mockField.devices.map((device) => ({
443+
label: device.title,
444+
value: device.id,
445+
content: device.value,
446+
type: device.type,
447+
key: device.id,
448+
default: device.default,
449+
}));
450+
451+
it('should create a valid DeviceAuthenticationCollector', () => {
452+
const result = returnObjectSelectCollector(mockField, 1);
453+
expect(result).toEqual({
454+
category: 'ObjectValueCollector',
455+
error: null,
456+
type: 'DeviceAuthenticationCollector',
457+
id: 'device-auth-key-1',
458+
name: 'device-auth-key',
459+
input: {
460+
key: mockField.key,
461+
value: null,
462+
type: mockField.type,
463+
},
464+
output: {
465+
key: mockField.key,
466+
label: mockField.label,
467+
type: mockField.type,
468+
options: transformedDevices,
469+
},
470+
});
471+
});
472+
});
473+
474+
describe('returnDeviceRegistrationCollector', () => {
475+
const mockField: DeviceRegistrationFieldValue = {
476+
key: 'device-reg-key',
477+
label: 'Device Registration',
478+
type: 'DEVICE_REGISTRATION',
479+
devices: [
480+
{
481+
type: 'device1',
482+
iconSrc: 'icon1.png',
483+
title: 'Device 1',
484+
description: 'Device 1 Description',
485+
},
486+
{
487+
type: 'device2',
488+
iconSrc: 'icon2.png',
489+
title: 'Device 2',
490+
description: 'Device 2 Description',
491+
},
492+
],
493+
required: true,
494+
};
495+
496+
const transformedDevices = mockField.devices.map((device, idx) => ({
497+
label: device.title,
498+
value: device.type,
499+
content: device.description,
500+
type: device.type,
501+
key: `${device.type}-${idx}`,
502+
}));
503+
504+
it('should create a valid DeviceRegistrationCollector', () => {
505+
const result = returnObjectSelectCollector(mockField, 1);
506+
expect(result).toEqual({
507+
category: 'ObjectValueCollector',
508+
error: null,
509+
type: 'DeviceRegistrationCollector',
510+
id: 'device-reg-key-1',
511+
name: 'device-reg-key',
512+
input: {
513+
key: mockField.key,
514+
value: null,
515+
type: mockField.type,
516+
},
517+
output: {
518+
key: mockField.key,
519+
label: mockField.label,
520+
type: mockField.type,
521+
options: transformedDevices,
522+
},
523+
});
404524
});
405525
});
406526
});

0 commit comments

Comments
 (0)