diff --git a/packages/davinci-client/api-report/davinci-client.api.md b/packages/davinci-client/api-report/davinci-client.api.md index b2528bf664..b96fc65b97 100644 --- a/packages/davinci-client/api-report/davinci-client.api.md +++ b/packages/davinci-client/api-report/davinci-client.api.md @@ -178,7 +178,7 @@ export interface CollectorErrors { } // @public (undocumented) -export type Collectors = FlowCollector | PasswordCollector | TextCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; +export type Collectors = FlowCollector | PasswordCollector | TextCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; // @public export type CollectorValueType = T extends { @@ -212,7 +212,7 @@ export type CollectorValueType = T extends { } ? string[] : string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) -export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | FidoRegistrationField | FidoAuthenticationField | PollingField; +export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; // @public (undocumented) export interface ContinueNode { @@ -267,13 +267,11 @@ export function davinci(input: { resume: (input: { continueToken: string; }) => Promise; - start: (options?: StartOptions | undefined) => Promise; + start: (options?: StartOptions | undefined) => Promise; update: (collector: T) => Updater; validate: (collector: SingleValueCollectors | ObjectValueCollectors | MultiValueCollectors | AutoCollectors) => Validator; - poll: (collector: PollingCollector) => Poller; + pollStatus: (collector: PollingCollector) => Poller; getClient: () => { - status: "start"; - } | { action: string; collectors: Collectors[]; description?: string; @@ -287,6 +285,8 @@ export function davinci(input: { status: "error"; } | { status: "failure"; + } | { + status: "start"; } | { authorization?: { code?: string; @@ -297,7 +297,7 @@ export function davinci(input: { getCollectors: () => Collectors[]; getError: () => DaVinciError | null; getErrorCollectors: () => CollectorErrors[]; - getNode: () => ContinueNode | StartNode | ErrorNode | FailureNode | SuccessNode; + getNode: () => ContinueNode | ErrorNode | FailureNode | StartNode | SuccessNode; getServer: () => { _links?: Links; id?: string; @@ -306,8 +306,6 @@ export function davinci(input: { href?: string; eventName?: string; status: "continue"; - } | { - status: "start"; } | { _links?: Links; eventName?: string; @@ -323,6 +321,8 @@ export function davinci(input: { interactionId?: string; interactionToken?: string; status: "failure"; + } | { + status: "start"; } | { _links?: Links; eventName?: string; @@ -1035,7 +1035,7 @@ export type InferNoValueCollectorType = T exten export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; // @public (undocumented) -export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; +export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : T extends 'PhoneNumberExtensionCollector' ? PhoneNumberExtensionCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; // @public (undocumented) export type InitFlow = () => Promise; @@ -1170,8 +1170,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { - getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; +export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { + getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; }; // @public (undocumented) @@ -1283,10 +1283,10 @@ export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRe export type ObjectValueCollector = ObjectOptionsCollectorWithObjectValue | ObjectOptionsCollectorWithStringValue | ObjectValueCollectorWithObjectValue; // @public (undocumented) -export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; +export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; // @public -export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; +export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'PhoneNumberExtensionCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; // @public (undocumented) export interface ObjectValueCollectorWithObjectValue, OV = Record> { @@ -1328,13 +1328,68 @@ export type PasswordCollector = SingleValueCollectorNoValue<'PasswordCollector'> // @public (undocumented) export type PhoneNumberCollector = ObjectValueCollectorWithObjectValue<'PhoneNumberCollector', PhoneNumberInputValue, PhoneNumberOutputValue>; +// @public (undocumented) +export interface PhoneNumberExtensionCollector { + // (undocumented) + category: 'ObjectValueCollector'; + // (undocumented) + error: string | null; + // (undocumented) + id: string; + // (undocumented) + input: { + key: string; + value: PhoneNumberExtensionInputValue; + type: string; + validation: (ValidationRequired | ValidationPhoneNumber)[] | null; + }; + // (undocumented) + name: string; + // (undocumented) + output: { + key: string; + label: string; + type: string; + extensionLabel: string; + value: PhoneNumberExtensionOutputValue; + }; + // (undocumented) + type: 'PhoneNumberExtensionCollector'; +} + +// @public (undocumented) +export type PhoneNumberExtensionField = PhoneNumberField & { + showExtension: boolean; + extensionLabel: string; +}; + +// @public (undocumented) +export interface PhoneNumberExtensionInputValue { + // (undocumented) + countryCode: string; + // (undocumented) + extension: string; + // (undocumented) + phoneNumber: string; +} + +// @public (undocumented) +export interface PhoneNumberExtensionOutputValue { + // (undocumented) + countryCode?: string; + // (undocumented) + extension?: string; + // (undocumented) + phoneNumber?: string; +} + // @public (undocumented) export type PhoneNumberField = { type: 'PHONE_NUMBER'; key: string; label: string; - defaultCountryCode: string | null; required: boolean; + defaultCountryCode: string | null; validatePhoneNumber: boolean; }; @@ -1724,7 +1779,7 @@ export type UnknownField = Record; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: string | string[] | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; index?: number; }, string>; 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 2321431a0a..78e36b50a9 100644 --- a/packages/davinci-client/api-report/davinci-client.types.api.md +++ b/packages/davinci-client/api-report/davinci-client.types.api.md @@ -178,7 +178,7 @@ export interface CollectorErrors { } // @public (undocumented) -export type Collectors = FlowCollector | PasswordCollector | TextCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; +export type Collectors = FlowCollector | PasswordCollector | TextCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; // @public export type CollectorValueType = T extends { @@ -212,7 +212,7 @@ export type CollectorValueType = T extends { } ? string[] : string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) -export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | FidoRegistrationField | FidoAuthenticationField | PollingField; +export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; // @public (undocumented) export interface ContinueNode { @@ -267,13 +267,11 @@ export function davinci(input: { resume: (input: { continueToken: string; }) => Promise; - start: (options?: StartOptions | undefined) => Promise; + start: (options?: StartOptions | undefined) => Promise; update: (collector: T) => Updater; validate: (collector: SingleValueCollectors | ObjectValueCollectors | MultiValueCollectors | AutoCollectors) => Validator; - poll: (collector: PollingCollector) => Poller; + pollStatus: (collector: PollingCollector) => Poller; getClient: () => { - status: "start"; - } | { action: string; collectors: Collectors[]; description?: string; @@ -287,6 +285,8 @@ export function davinci(input: { status: "error"; } | { status: "failure"; + } | { + status: "start"; } | { authorization?: { code?: string; @@ -297,7 +297,7 @@ export function davinci(input: { getCollectors: () => Collectors[]; getError: () => DaVinciError | null; getErrorCollectors: () => CollectorErrors[]; - getNode: () => ContinueNode | StartNode | ErrorNode | FailureNode | SuccessNode; + getNode: () => ContinueNode | ErrorNode | FailureNode | StartNode | SuccessNode; getServer: () => { _links?: Links; id?: string; @@ -306,8 +306,6 @@ export function davinci(input: { href?: string; eventName?: string; status: "continue"; - } | { - status: "start"; } | { _links?: Links; eventName?: string; @@ -323,6 +321,8 @@ export function davinci(input: { interactionId?: string; interactionToken?: string; status: "failure"; + } | { + status: "start"; } | { _links?: Links; eventName?: string; @@ -1032,7 +1032,7 @@ export type InferNoValueCollectorType = T exten export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; // @public (undocumented) -export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; +export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : T extends 'PhoneNumberExtensionCollector' ? PhoneNumberExtensionCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; // @public (undocumented) export type InitFlow = () => Promise; @@ -1167,8 +1167,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { - getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; +export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { + getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; }; // @public (undocumented) @@ -1280,10 +1280,10 @@ export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRe export type ObjectValueCollector = ObjectOptionsCollectorWithObjectValue | ObjectOptionsCollectorWithStringValue | ObjectValueCollectorWithObjectValue; // @public (undocumented) -export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; +export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; // @public -export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; +export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'PhoneNumberExtensionCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; // @public (undocumented) export interface ObjectValueCollectorWithObjectValue, OV = Record> { @@ -1325,13 +1325,68 @@ export type PasswordCollector = SingleValueCollectorNoValue<'PasswordCollector'> // @public (undocumented) export type PhoneNumberCollector = ObjectValueCollectorWithObjectValue<'PhoneNumberCollector', PhoneNumberInputValue, PhoneNumberOutputValue>; +// @public (undocumented) +export interface PhoneNumberExtensionCollector { + // (undocumented) + category: 'ObjectValueCollector'; + // (undocumented) + error: string | null; + // (undocumented) + id: string; + // (undocumented) + input: { + key: string; + value: PhoneNumberExtensionInputValue; + type: string; + validation: (ValidationRequired | ValidationPhoneNumber)[] | null; + }; + // (undocumented) + name: string; + // (undocumented) + output: { + key: string; + label: string; + type: string; + extensionLabel: string; + value: PhoneNumberExtensionOutputValue; + }; + // (undocumented) + type: 'PhoneNumberExtensionCollector'; +} + +// @public (undocumented) +export type PhoneNumberExtensionField = PhoneNumberField & { + showExtension: boolean; + extensionLabel: string; +}; + +// @public (undocumented) +export interface PhoneNumberExtensionInputValue { + // (undocumented) + countryCode: string; + // (undocumented) + extension: string; + // (undocumented) + phoneNumber: string; +} + +// @public (undocumented) +export interface PhoneNumberExtensionOutputValue { + // (undocumented) + countryCode?: string; + // (undocumented) + extension?: string; + // (undocumented) + phoneNumber?: string; +} + // @public (undocumented) export type PhoneNumberField = { type: 'PHONE_NUMBER'; key: string; label: string; - defaultCountryCode: string | null; required: boolean; + defaultCountryCode: string | null; validatePhoneNumber: boolean; }; @@ -1721,7 +1776,7 @@ export type UnknownField = Record; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: string | string[] | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; index?: number; }, string>; diff --git a/packages/davinci-client/src/lib/client.store.ts b/packages/davinci-client/src/lib/client.store.ts index e99dc64018..15c5dee8b0 100644 --- a/packages/davinci-client/src/lib/client.store.ts +++ b/packages/davinci-client/src/lib/client.store.ts @@ -344,16 +344,8 @@ export async function davinci({ | FidoAuthenticationInputValue, index?: number, ) { - try { - store.dispatch(nodeSlice.actions.update({ id, value, index })); - return null; - } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - return { - type: 'internal_error', - error: { message: errorMessage, type: 'internal_error' }, - }; - } + store.dispatch(nodeSlice.actions.update({ id, value, index })); + return null; }; }, diff --git a/packages/davinci-client/src/lib/node.reducer.test.ts b/packages/davinci-client/src/lib/node.reducer.test.ts index 2d2ba361ab..a83b47c848 100644 --- a/packages/davinci-client/src/lib/node.reducer.test.ts +++ b/packages/davinci-client/src/lib/node.reducer.test.ts @@ -344,7 +344,7 @@ describe('The node collector reducer', () => { ]); }); - it('should throw with no collectors', () => { + it('should add an UnknownCollector with error when no matching collector is found', () => { const action = { type: 'node/update', payload: { @@ -372,10 +372,13 @@ describe('The node collector reducer', () => { }, }, ]; - expect(() => nodeCollectorReducer(state, action)).toThrowError('No collector found to update'); + const result = nodeCollectorReducer(state, action); + const errorCollector = result.find((c) => c.id === 'submit-1'); + expect(errorCollector?.error).toBe('No collector found to update'); + expect(errorCollector?.category).toBe('UnknownCollector'); }); - it('should throw with no Action Collector', () => { + it('should set error on ActionCollector when update is attempted', () => { const action = { type: 'node/update', payload: { @@ -415,8 +418,141 @@ describe('The node collector reducer', () => { }, }, ]; - expect(() => nodeCollectorReducer(state, action)).toThrowError( - 'ActionCollectors are read-only', + const result = nodeCollectorReducer(state, action); + const collector = result.find((c) => c.id === 'submit-1'); + expect(collector?.error).toBe('ActionCollectors are read-only'); + }); + + it('should set error on NoValueCollector when update is attempted', () => { + const state: QrCodeCollector[] = [ + { + category: 'NoValueCollector', + error: null, + type: 'QrCodeCollector', + id: 'qr-0', + name: 'qr', + output: { + key: 'qr', + label: 'QR Code', + type: 'QR_CODE', + src: 'data:image/png;base64,abc', + }, + }, + ]; + const action = { type: 'node/update', payload: { id: 'qr-0', value: 'anything' } }; + const result = nodeCollectorReducer(state, action); + expect(result.find((c) => c.id === 'qr-0')?.error).toBe( + 'NoValueCollectors, like ReadOnlyCollectors, are read-only', + ); + }); + + it('should set error on collector when value is undefined', () => { + const state: TextCollector[] = [ + { + category: 'SingleValueCollector', + error: null, + type: 'TextCollector', + id: 'username-0', + name: 'username', + input: { key: 'username', value: '', type: 'TEXT' }, + output: { key: 'username', label: 'Username', type: 'TEXT', value: '' }, + }, + ]; + const action = { + type: 'node/update', + payload: { id: 'username-0', value: undefined as unknown as string }, + }; + const result = nodeCollectorReducer(state, action); + expect(result.find((c) => c.id === 'username-0')?.error).toBe( + 'Value argument cannot be undefined', + ); + }); + + it('should clear error on collector when a valid value is provided after an error', () => { + const state: TextCollector[] = [ + { + category: 'SingleValueCollector', + error: 'Value argument must be a string', + type: 'TextCollector', + id: 'username-0', + name: 'username', + input: { key: 'username', value: '', type: 'TEXT' }, + output: { key: 'username', label: 'Username', type: 'TEXT', value: '' }, + }, + ]; + const action = { type: 'node/update', payload: { id: 'username-0', value: 'validString' } }; + const result = nodeCollectorReducer(state, action); + const collector = result.find((c) => c.id === 'username-0') as TextCollector | undefined; + expect(collector?.error).toBeNull(); + expect(collector?.input.value).toBe('validString'); + }); + + it('should set error on SingleValueCollector when value is not a string', () => { + const state: TextCollector[] = [ + { + category: 'SingleValueCollector', + error: null, + type: 'TextCollector', + id: 'username-0', + name: 'username', + input: { key: 'username', value: '', type: 'TEXT' }, + output: { key: 'username', label: 'Username', type: 'TEXT', value: '' }, + }, + ]; + const action = { + type: 'node/update', + payload: { id: 'username-0', value: 42 as unknown as string }, + }; + const result = nodeCollectorReducer(state, action); + expect(result.find((c) => c.id === 'username-0')?.error).toBe( + 'Value argument must be a string', + ); + }); + + it('should set error on MultiValueCollector when value is an object', () => { + const state: MultiSelectCollector[] = [ + { + category: 'MultiValueCollector', + error: null, + type: 'MultiSelectCollector', + id: 'multi-0', + name: 'multi', + input: { key: 'multi', value: [], type: 'MULTI_SELECT', validation: null }, + output: { + key: 'multi', + label: 'Multi', + type: 'MULTI_SELECT', + value: [], + options: [{ label: 'A', value: 'a' }], + }, + }, + ]; + const action = { + type: 'node/update', + payload: { id: 'multi-0', value: { bad: true } as unknown as string }, + }; + const result = nodeCollectorReducer(state, action); + expect(result.find((c) => c.id === 'multi-0')?.error).toBe( + 'MultiValueCollector does not accept an object', + ); + }); + + it('should set error on PollingCollector when update is attempted', () => { + const state: PollingCollector[] = [ + { + category: 'SingleValueAutoCollector', + error: null, + type: 'PollingCollector', + id: 'poll-0', + name: 'poll', + input: { key: 'poll', value: '', type: 'POLLING' }, + output: { key: 'poll', type: 'POLLING', config: { pollInterval: 1000, pollRetries: 3 } }, + }, + ]; + const action = { type: 'node/update', payload: { id: 'poll-0', value: 'anything' } }; + const result = nodeCollectorReducer(state, action); + expect(result.find((c) => c.id === 'poll-0')?.error).toBe( + 'This collector type does not support value updates', ); }); diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts index 3c84780761..7ea60b1ca9 100644 --- a/packages/davinci-client/src/lib/node.reducer.ts +++ b/packages/davinci-client/src/lib/node.reducer.ts @@ -238,17 +238,41 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build */ .addCase(updateCollectorValues, (state, action) => { const collector = state.find((collector) => collector.id === action.payload.id); + if (!collector) { - throw new Error('No collector found to update'); + state.push({ + category: 'UnknownCollector', + error: 'No collector found to update', + type: 'UnknownCollector', + id: action.payload.id, + name: action.payload.id, + output: { + key: action.payload.id, + label: action.payload.id, + type: 'UnknownCollector', + }, + }); + return; } + if (collector.category === 'ActionCollector') { - throw new Error('ActionCollectors are read-only'); + collector.error = 'ActionCollectors are read-only'; + return; } + if (collector.category === 'NoValueCollector') { - throw new Error('NoValueCollectors, like ReadOnlyCollectors, are read-only'); + collector.error = 'NoValueCollectors, like ReadOnlyCollectors, are read-only'; + return; } + + if (collector.type === 'PollingCollector') { + collector.error = 'This collector type does not support value updates'; + return; + } + if (action.payload.value === undefined) { - throw new Error('Value argument cannot be undefined'); + collector.error = 'Value argument cannot be undefined'; + return; } if ( @@ -257,16 +281,20 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build collector.category === 'SingleValueAutoCollector' ) { if (typeof action.payload.value !== 'string') { - throw new Error('Value argument must be a string'); + collector.error = 'Value argument must be a string'; + return; } + collector.error = null; collector.input.value = action.payload.value; return; } if (collector.category === 'MultiValueCollector') { if (typeof action.payload.value !== 'string' && !Array.isArray(action.payload.value)) { - throw new Error('MultiValueCollector does not accept an object'); + collector.error = 'MultiValueCollector does not accept an object'; + return; } + collector.error = null; if (Array.isArray(action.payload.value)) { collector.input.value = [...action.payload.value]; } else { @@ -277,99 +305,120 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build if (collector.type === 'DeviceAuthenticationCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } - // Iterate through the options object and find option to update const option = collector.output.options.find( (option) => option.value === action.payload.value, ); - if (!option) { - throw new Error('No option found matching value to update'); + collector.error = 'No option found matching value to update'; + return; } - - // Remap values back to DaVinci spec + collector.error = null; collector.input.value = { type: option.type, id: option.value, value: option.content, }; + return; } if (collector.type === 'DeviceRegistrationCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } - - // Iterate through the options object and find option to update const option = collector.output.options.find( (option) => option.value === action.payload.value, ); - if (!option) { - throw new Error('No option found matching value to update'); + collector.error = 'No option found matching value to update'; + return; } - + collector.error = null; collector.input.value = option.type; + return; } if (collector.type === 'PhoneNumberCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } if (typeof action.payload.value !== 'object') { - throw new Error('Value argument must be an object'); + collector.error = 'Value argument must be an object'; + return; } if (!('phoneNumber' in action.payload.value) || !('countryCode' in action.payload.value)) { - throw new Error('Value argument must contain a phoneNumber and countryCode property'); + collector.error = 'Value argument must contain a phoneNumber and countryCode property'; + return; } + collector.error = null; collector.input.value = action.payload.value; + return; } if (collector.type === 'PhoneNumberExtensionCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } if (typeof action.payload.value !== 'object') { - throw new Error('Value argument must be an object'); + collector.error = 'Value argument must be an object'; + return; } if ( !('phoneNumber' in action.payload.value) || !('countryCode' in action.payload.value) || !('extension' in action.payload.value) ) { - throw new Error( - 'Value argument must contain a phoneNumber, countryCode, and extension property', - ); + collector.error = + 'Value argument must contain a phoneNumber, countryCode, and extension property'; + return; } + collector.error = null; collector.input.value = action.payload.value; + return; } if (collector.type === 'FidoRegistrationCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } if (typeof action.payload.value !== 'object') { - throw new Error('Value argument must be an object'); + collector.error = 'Value argument must be an object'; + return; } if (!('attestationValue' in action.payload.value)) { - throw new Error('Value argument must contain an attestationValue property'); + collector.error = 'Value argument must contain an attestationValue property'; + return; } + collector.error = null; collector.input.value = action.payload.value; + return; } if (collector.type === 'FidoAuthenticationCollector') { if (typeof action.payload.id !== 'string') { - throw new Error('Index argument must be a string'); + collector.error = 'Index argument must be a string'; + return; } if (typeof action.payload.value !== 'object') { - throw new Error('Value argument must be an object'); + collector.error = 'Value argument must be an object'; + return; } if (!('assertionValue' in action.payload.value)) { - throw new Error('Value argument must contain an assertionValue property'); + collector.error = 'Value argument must contain an assertionValue property'; + return; } + collector.error = null; collector.input.value = action.payload.value; + return; } + + collector.error = 'This collector type does not support value updates'; }) /** * Using the `pollCollectorValues` const (e.g. `'node/poll'`) to add the case