Ping SDKs

Configure DaVinci client for JavaScript properties

Applies to:

  • DaVinci client for Android

  • DaVinci client for iOS

  • DaVinci client for JavaScript

Configure DaVinci client properties to connect to PingOne and step through an associated DaVinci flow.

Installing and importing the DaVinci client

To install and import the DaVinci client:

  1. Install the DaVinci client into your JavaScript apps using npm:

    Install the DaVinci client
    npm install @forgerock/davinci-client
  2. Import the DaVinci client as a named import:

    Import the DaVinci client
    import { davinci } from '@forgerock/davinci-client';

Configuring connection properties

Pass a config object into davinci to initialize a client with the required connection properties.

The following properties are available for configuring the DaVinci client for JavaScript:

Properties
Property Description Required?

serverConfig

An interface for configuring how the SDK contacts the PingAM instance.

Contains wellknown and timeout.

Yes

serverConfig: {wellknown}

Your PingOne server’s .well-known/openid-configuration endpoint.

Example:

https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration

Yes

serverConfig: {timeout}

A timeout, in milliseconds, for each request that communicates with your server.

For example, for 30 seconds specify 30000.

Defaults to 5000 (5 seconds).

No

clientId

The client_id of the OAuth 2.0 client profile to use.

For example, 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

Yes

scope

A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces.

For example, openid profile email address revoke.

No

responseType

The type of OAuth 2.0 flow to use, either code or token.

Defaults to code.

No

Example

The following shows a full DaVinci client configuration:

Configure DaVinci client connection properties
import { davinci } from '@forgerock/davinci';

const davinciClient = await davinci({
  config: {
    clientId: '6c7eb89a-66e9-ab12-cd34-eeaf795650b2',
    serverConfig: {
      wellknown: 'https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration',
      timeout: 3000,
    },
    scope: 'openid profile email address revoke',
    responseType: 'code',
  },
});

Stepping through DaVinci flows

To authenticate your users the Ping SDK for JavaScript DaVinci client must start the flow, and step through each node.

Starting a DaVinci flow

To start a DaVinci flow, call the start() method on your new client object:

Start a DaVinci flow
let node = await davinciClient.start();

Determining DaVinci flow node type

Each step of the flow returns one of four node types:

continue

This type indicates there is input required from the client. The node object for this type contains a list of collector objects, which describe the information it requires from the client.

success

This type indicates the flow is complete, and authentication was successful.

error

This type indicates an error in the data sent to the server. For example, an email address in an incorrect format, or a password that does not meet complexity requirements.

You can correct the error and resubmit to continue the flow.

failure

This type indicates that the flow could not be completed and must be restarted. This can be caused by a server error, or a timeout.

Use node.status to determine which node type the server has returned:

Determine node type using the node.status property
let node = await davinciClient.start();

switch (node.status) {
  case 'continue':
    return renderContinue();
  case 'success':
    return renderSuccess();
  case 'error':
    return renderError();
  default: // Handle 'failure' node type
    return renderFailure();
}

Handling DaVinci flow collectors in continue nodes

The continue node type contains a list of collector objects. These collectors define what information or action to request from the user, or browser.

There are two categories of collector:

SingleValueCollector

A request for a single value, such as a username or password string.

Show the interface for SingleValueCollector
export interface SingleValueCollector {
  category: 'SingleValueCollector';
  type: SingleValueCollectorTypes;
  id: string;
  name: string;
  input: {
    key: string;
    value: string | number | boolean;
    type: string;
  };
  output: {
    key: string;
    label: string;
    type: string;
    value: string;
  };
}
ActionCollector

Represents an action the user can take, such as a button or a link they can select. For example, a link that jumps to a password recovery section of a flow.

Show the interface for ActionCollector
export interface ActionCollector {
  category: 'ActionCollector';
  type: ActionCollectorTypes;
  id: string;
  name: string;
  output: {
    key: string;
    label: string;
    type: string;
    url?: string;
  };
}

Within each category of collector there are specific collector types. For example, in the SingleValueCollector category there are TextCollector and PasswordCollector types.

To complete a DaVinci flow we recommend that you implement a component for each connector type you will encounter in the flow. Then you can iterate through the flow and handle each collector as you encounter it.

Example 1. TextCollector

This example shows how to update a collector with a value gathered from your user.

Pass both a collector and updater object into a component that renders the appropriate user interface, captures the user’s input, and then updates the collector, ready to return to the server.

Example TextCollector mapping
const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
  if (collector.type === 'TextCollector') {
    renderTextCollector(collector, davinciClient.update(collector));
  }
});

Mutating the node object, the collectors array, or any other properties does not alter the internal state of the DaVinci client.

The internal data the client stores is immutable and can only be updated using the provided APIs, not through property assignment.

Your renderTextCollector would resemble the following:

Example TextCollector updater component
function renderTextCollector(collector, updater) {
  // ... component logic

  function onClick(event) {
    updater(event.target.value);
  }

  // render code
}

Example 2. FlowCollector

This example shows how change from the current flow to an alternate flow, such as a reset password or registration flow.

To switch flows, call the flow method on the davinciClient passing the key property to identify the new flow.

Example FlowCollector mapping
const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
  if (collector.type === 'FlowCollector') {
    renderFlowCollector(collector, davinciClient.flow(collector));
  }
});

This returns a function you can call when the user interacts with it.

Example flowCollector component
function renderFlowCollector(collector, startFlow) {
  // ... component logic

  function onClick(event) {
    startFlow();
  }

  // render code
}

Example 3. SubmitCollector

This example shows how submit the current node and its collected values back to the server. The collection of the data is already complete so an updater component is not required. This collector only renders the button for the user to submit the collected data.

Example SubmitCollector mapping
const collectors = davinciClient.getCollectors();
collectors.map((collector) => {
  if (collector.type === 'SubmitCollector') {
    renderSubmitCollector(
      collector, // This is the only argument you will need to pass
    );
  }
});

Continuing a DaVinci flow

After collecting the data for a node you can proceed to the next node in the flow by calling the next() method on your DaVinci client object.

This can be the result of a user clicking on the button rendered from the SubmitCollector, from the submit event of an HTML form, or by programmatically triggering the submission in the application layer.

Continue a DaVinci flow using next()
let nextStep = davinciClient.next();

You do not need to pass any parameters into the next method as the DaVinci client internally stores the updated object, ready to return to the PingOne server.

The server responds with a new node object, just like when starting a flow initially.

Loop again through conditional checks on the new node’s type to render the appropriate UI or take the appropriate action.

Handling DaVinci flow error nodes

DaVinci flows return the error node type when it receives data that is incorrect, but you can fix the data and resubmit. For example, an email value submitted in an invalid format or a new password that is too short.

This is different than a failure node type which you cannot resubmit and instead you must restart the entire flow.

You can retain a reference to the node you submit in case the next node you receive is an error type. If so, you can re-render the previous form, and inject the error information from the new error node.

After the user revises the data call next() as you did before.

Handling DaVinci flow failure nodes

DaVinci flows return the failure node type if there has been an issue that prevents the flow from continuing. For example, the flow times out or suffers an HTTP 500 server error.

You should offer to restart the flow on receipt of a failure node type.

Restart a DaVinci flow on receipt of a failure node type
const node = await davinciClient.next();

if (node.status === 'failure') {
  const error = davinciClient.getError();
  renderError(error);

  // ... user clicks button to restart flow
  const freshNode = davinciClient.start();
}

Handling DaVinci flow success nodes

DaVinci flows return the success node type when the user completes the flow and PingOne issues them a session.

On receipt of a success node type you should use the OAuth 2.0 authorization Code and state properties from the node and use them to obtain an access token on behalf of the user.

To obtain an access token, leverage the Ping SDK for JavaScript.

Example of obtaining an access token using the Ping SDK for JavaScript
// ... other imports
import { davinci } from '@forgerock/davinci-client';
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
}