Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions e2e/davinci-app/components/polling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2025 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 type {
PollingCollector,
PollingStatus,
InternalErrorResponse,
Updater,
ContinueNode,
} from '@forgerock/davinci-client/types';

export default function pollingComponent(
formEl: HTMLFormElement,
collector: PollingCollector,
poll: (
collector: PollingCollector,
) => Promise<PollingStatus | ContinueNode | InternalErrorResponse>,
updater: Updater<PollingCollector>,
submitForm: () => Promise<void>,
) {
const button = document.createElement('button');
button.type = 'button';
button.value = collector.output.key;
button.innerHTML = 'Start polling';
formEl.appendChild(button);

button.onclick = async () => {
const p = document.createElement('p');
p.innerText = 'Polling...';
formEl?.appendChild(p);

// TODO: support continue polling
const status = await poll(collector);
if (typeof status !== 'string' && 'error' in status) {
console.error(status.error?.message);

const errEl = document.createElement('p');
errEl.innerText = 'Polling error: ' + status.error?.message;
formEl?.appendChild(errEl);
return;
}

const result = updater(status);
if (result && 'error' in result) {
console.error(result.error.message);

const errEl = document.createElement('p');
errEl.innerText = 'Polling error: ' + result.error.message;
formEl?.appendChild(errEl);
return;
}

const resultEl = document.createElement('p');
resultEl.innerText = 'Polling result: ' + JSON.stringify(status, null, 2);
formEl?.appendChild(resultEl);

await submitForm();
};
}
9 changes: 9 additions & 0 deletions e2e/davinci-app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import multiValueComponent from './components/multi-value.js';
import labelComponent from './components/label.js';
import objectValueComponent from './components/object-value.js';
import fidoComponent from './components/fido.js';
import pollingComponent from './components/polling.js';

const loggerFn = {
error: () => {
Expand Down Expand Up @@ -262,6 +263,14 @@ const urlParams = new URLSearchParams(window.location.search);
davinciClient.update(collector), // Returns an update function for this collector
submitForm,
);
} else if (collector.type === 'PollingCollector') {
pollingComponent(
formEl, // You can ignore this; it's just for rendering
collector, // This is the plain object of the collector
davinciClient.poll, // Returns a poll function
davinciClient.update(collector), // Returns an update function for this collector
submitForm,
);
} else if (collector.type === 'FlowCollector') {
flowLinkComponent(
formEl, // You can ignore this; it's just for rendering
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"author": "ForgeRock",
"scripts": {
"build": "nx affected --target=build",
"build": "nx sync && nx affected --target=build",
"changeset": "changeset",
"ci:release": "pnpm nx run-many -t build --no-agents && pnpm publish -r --no-git-checks && changeset tag",
"ci:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm nx format:write --uncommitted",
Expand Down
72 changes: 71 additions & 1 deletion packages/davinci-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { CustomLogger, logger as loggerFn, LogLevel } from '@forgerock/sdk-logge
import { createStorage } from '@forgerock/storage';
import { isGenericError, createWellknownError } from '@forgerock/sdk-utilities';

import { createClientStore, handleUpdateValidateError, RootState } from './client.store.utils.js';
import {
createClientStore,
handleChallengePolling,
handleContinuePolling,
handleUpdateValidateError,
RootState,
} from './client.store.utils.js';
import { nodeSlice } from './node.slice.js';
import { davinciApi } from './davinci.api.js';
import { configSlice } from './config.slice.js';
Expand All @@ -34,6 +40,7 @@ import type {
ObjectValueCollectors,
PhoneNumberInputValue,
AutoCollectors,
PollingCollector,
MultiValueCollectors,
FidoRegistrationInputValue,
FidoAuthenticationInputValue,
Expand All @@ -44,6 +51,7 @@ import type {
NodeStates,
Updater,
Validator,
PollingStatus,
} from './client.types.js';
import { returnValidator } from './collector.utils.js';
import type { ContinueNode, StartNode } from './node.types.js';
Expand Down Expand Up @@ -404,6 +412,68 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
return returnValidator(collectorToUpdate);
},

/**
* @method: poll - Poll for updates for a polling collector
* @returns {Promise<PollingStatus | ContinueNode | InternalErrorResponse>} - Returns a promise that resolves to
* a polling status or error for challenge polling. Returns a promise that resolves to the next node or
* an error for continue polling
*/
poll: async (
collector: PollingCollector,
): Promise<PollingStatus | ContinueNode | InternalErrorResponse> => {
try {
if (collector.type !== 'PollingCollector') {
log.error('Collector provided to poll is not a PollingCollector');
return {
error: {
message: 'Collector provided to poll is not a PollingCollector',
type: 'argument_error',
},
type: 'internal_error',
};
}

const pollChallengeStatus = collector.output.config.pollChallengeStatus;
const challenge = collector.output.config.challenge;

if (challenge && pollChallengeStatus === true) {
// Challenge Polling
return await handleChallengePolling({
collector,
challenge,
store,
log,
});
} else if (!challenge && !pollChallengeStatus) {
// Continue polling
return await handleContinuePolling({
collector,
store,
log,
});
} else {
log.error('Invalid polling collector configuration');
return {
error: {
message: 'Invalid polling collector configuration',
type: 'internal_error',
},
type: 'internal_error',
};
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
log.error(errorMessage);
return {
error: {
message: errorMessage || 'An unexpected error occurred during poll operation',
type: 'internal_error',
},
type: 'internal_error',
};
}
},

/**
* @method client - Selector to get the node.client from state
* @returns {Node.client} - the client property from the current node
Expand Down
Loading
Loading