This module provides native-backed PingOne DaVinci orchestration for React Native on Android and iOS. DaVinci is a flexible authentication and authorization library that drives server-defined flows through a simple node-based API. Your app calls start() to launch a flow, receives a node describing the current step, gathers the required input, and calls next() to advance — repeating until the flow reaches a terminal SuccessNode, ErrorNode, or FailureNode.
Note: This module requires that the
@ping-identity/rn-coremodule is already set up and installed.
# Install & setup the core module
yarn add @ping-identity/rn-core
# Install the rn-davinci module
yarn add @ping-identity/rn-davinci
# If you are developing your app using iOS, run this command
cd ios && pod install
Optional integration packages:
yarn add @ping-identity/rn-storage
yarn add @ping-identity/rn-logger
Use this baseline configuration first.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
const client = createDaVinciClient({
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
},
},
});
Add modules.oidc.storage when you need native-backed OIDC token persistence.
Configure it only if you need persistent token storage; otherwise omit storage values.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
import { CacheStrategy, configureOidcStorage } from '@ping-identity/rn-storage';
const oidcStorage = configureOidcStorage({
android: {
fileName: 'davinci-oidc',
keyAlias: 'davinci-oidc',
strongBoxPreferred: true,
cacheStrategy: CacheStrategy.CACHE_ON_FAILURE,
},
ios: {
account: 'com.example.app.oidc',
encryptor: true,
cacheable: false,
},
});
const client = createDaVinciClient({
timeout: 30000,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid', 'profile', 'email'],
storage: oidcStorage,
},
},
});
Pass optional integrations through config.modules. The JS API is createDaVinciClient(config).
When provided, the storage handle in modules.oidc.storage must come from
configureOidcStorage(...).
If you install the logger package, pass a JS logger instance created via
@ping-identity/rn-logger.
If the logger package is not installed/configured, do not pass logger values in DaVinci config.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
import { logger } from '@ping-identity/rn-logger';
const jsLogger = logger({ level: 'debug' });
const client = createDaVinciClient({
logger: jsLogger,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid'],
},
},
});
const firstNode = await client.start();
const nextNode = await client.next({
collectors: [
{ key: 'user', value: 'demo-user' },
{ key: 'password', value: 'demo-password' },
],
});
const session = await client.user();
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
await client.revoke();
await client.logoutUser();
await client.dispose();
Handle node states explicitly in your UI flow:
const node = await client.start();
switch (node.type) {
case 'ContinueNode':
await client.next({
collectors: [{ key: 'user', value: 'demo-user' }],
});
break;
case 'ErrorNode':
console.log(node.message);
break;
case 'FailureNode':
console.log(node.cause ?? node.message);
break;
case 'SuccessNode':
console.log('Authenticated — session:', node.session.value);
break;
}
After a DaVinci flow completes successfully, use the following operations to inspect and manage the active user session:
const userSession = await client.user();
if (userSession) {
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
await client.revoke();
}
await client.logoutUser();
user() returns token payload (accessToken, optional refreshToken, optional expiresIn).refresh() refreshes token payload for the active user.userinfo() fetches user claims for the active user.revoke() revokes access/refresh tokens for the active user.logoutUser() signs out and clears the active DaVinci user session.useDaVinci does not auto-advance nodes. Progression policy is app-controlled via explicit next(...) calls.
import { useDaVinci } from '@ping-identity/rn-davinci';
const { node, start, next, loading, error } = useDaVinci(client);
await start();
if (node?.type === 'ContinueNode') {
await next({
collectors: [{ key: 'user', value: 'demo-user' }],
});
}
import { DaVinciProvider, useDaVinci } from '@ping-identity/rn-davinci';
function App(): React.ReactElement {
return (
<DaVinciProvider config={config}>
<AuthNavigator />
</DaVinciProvider>
);
}
function LoginScreen(): React.ReactElement {
const { node, start, next, loading } = useDaVinci();
if (!node) {
return <Button title="Sign In" onPress={start} />;
}
if (node.type === 'SuccessNode') {
return <Text>Authenticated</Text>;
}
if (node.type === 'ContinueNode') {
return <DaVinciForm node={node} onNext={next} loading={loading} />;
}
return <Text>{node.message}</Text>;
}
useDaVinciFormimport { useDaVinci, useDaVinciForm } from '@ping-identity/rn-davinci';
const { node, next } = useDaVinci(client);
const form = useDaVinciForm(node);
form.setValueByType('TEXT', 'demo-user');
form.setValueByType('PASSWORD', 'demo-password');
if (form.canSubmit) {
await next(form.input);
}
useDaVinciForm is headless. It manages normalized collectors and submit planning, but does not render UI and does not auto-run collectors.
Each normalized collector includes executionMode and requiresUserInput.
executionMode |
Meaning | requiresUserInput default |
|---|---|---|
manual |
Collector value is submitted from form/planned input. | true |
immediate |
Activating the collector immediately advances the flow without waiting for other fields. | false |
output_only |
Display/label collector, no input value expected. | false |
integration_required |
Collector is handled by an external integration package before submit. | false |
unsupported |
Collector type is not currently handled by the bridge or any registered integration. | false |
FLOW_BUTTON, FLOW_LINK, and ACTION collectors bypass other form fields and immediately advance the flow. Use submitFlow(key) inside a DaVinciProvider tree:
const form = useDaVinciForm(node);
// Activates "Forgot Password" flow link directly — no other field values are included.
await form.submitFlow('forgot-password');
The following collector types are supported on Android and iOS:
| Collector Type | Description | Input Handling |
|---|---|---|
TEXT |
Single-line text input. | Manual input |
PASSWORD |
Masked password input. | Manual input |
PASSWORD_VERIFY |
Password-confirmation variant of PASSWORD. |
Manual input |
SINGLE_SELECT |
Single-select input. | Manual input |
DROPDOWN |
Single-select dropdown. | Manual input |
RADIO |
Single-select radio group. | Manual input |
MULTI_SELECT |
Multi-select input. | Manual input |
COMBOBOX |
Multi-select combobox. | Manual input |
CHECKBOX |
Multi-select checkbox group. | Manual input |
PHONE_NUMBER |
Phone number input with country code. | Manual input |
DEVICE_REGISTRATION |
Device picker for registration. | Manual input |
DEVICE_AUTHENTICATION |
Device picker for authentication. | Manual input |
SUBMIT_BUTTON |
Triggers form submission immediately. | Immediate |
ACTION |
Action button that advances the flow immediately. | Immediate |
FLOW_BUTTON |
Flow button that advances the flow immediately. | Immediate |
FLOW_LINK |
Flow link that advances the flow immediately. | Immediate |
SINGLE_CHECKBOX |
Single checkbox or toggle (boolean field). | Manual input |
LABEL |
Read-only display content. | Output-only |
READ_ONLY_TEXT |
Read-only text / agreement content. | Output-only |
POLLING |
Async polling collector — see Polling and QR code flows. | Output-only |
QR_CODE |
Display-only QR code — see Polling and QR code flows. | Output-only |
FIDO2 |
FIDO passkey registration or authentication; narrow by action. |
Integration |
Integration-dependent collectors are surfaced in node payloads with
executionMode: 'integration_required'. Their minimum generic shape is key,
type, and optional raw; the owning integration package may provide additional
fields and the operation needed before next(). Use the integration package's
exported collector type/constant and pass its type in handledCollectorTypes:
import { socialLoginCollectorType } from '@ping-identity/rn-external-idp';
const form = useDaVinciForm(node, {
handledCollectorTypes: new Set([socialLoginCollectorType]),
});
Known collector-specific fields should be accessed only after narrowing with the
owning package's type or helper. Generic integration collectors do not guarantee
fields such as label, options, or value.
FIDO2 is owned by @ping-identity/rn-fido. It is one collector type, with
action: 'REGISTER' for publicKeyCredentialCreationOptions or
action: 'AUTHENTICATE' for publicKeyCredentialRequestOptions. Create a FIDO
client before the node is normalized so it registers FIDO2 with the integration
registry, and include the type in handledCollectorTypes:
import {
createFidoClient,
fidoCollectorType,
type FidoCollector,
} from '@ping-identity/rn-fido';
const fido = createFidoClient();
const form = useDaVinciForm(node, {
handledCollectorTypes: new Set([fidoCollectorType]),
});
for (const collector of node.collectors) {
const fidoCollector = collector as FidoCollector;
if (fidoCollector.action === 'REGISTER') {
await fido.registerForDaVinci(daVinci, { index: 0 });
} else if (fidoCollector.action === 'AUTHENTICATE') {
await fido.authenticateForDaVinci(daVinci, { index: 0 });
}
}
// The native collector retains the ceremony result for submission.
await daVinci.next({ collectors: [] });
The ceremony must be completed before calling next, and the FIDO collector key
must not be passed in next input. These DaVinci methods use native ceremony
defaults; no React Native ceremony customization options are exposed.
When the native SDK cannot instantiate a collector from the server payload, the bridge surfaces it in ContinueNode.unsupportedFields:
if (node.type === 'ContinueNode' && node.unsupportedFields?.length) {
console.warn('Unsupported fields present:', node.unsupportedFields);
}
Each entry has key and type so the UI can render a placeholder or block submission.
All promise rejections throw a DaVinciError instance, which extends PingError extends Error.
Use instanceof to narrow the error type:
import { DaVinciError } from '@ping-identity/rn-davinci';
try {
await client.start();
} catch (err) {
if (err instanceof DaVinciError) {
console.log(err.code, err.type, err.message);
}
}
Stable DaVinci error codes:
DAVINCI_CONFIG_ERRORDAVINCI_START_ERRORDAVINCI_NEXT_ERRORDAVINCI_COLLECTOR_APPLY_ERRORDAVINCI_SESSION_ERRORDAVINCI_LOGOUT_ERRORDAVINCI_DISPOSE_ERRORDAVINCI_POLL_ERRORDAVINCI_ARGUMENT_ERRORDAVINCI_STATE_ERRORDAVINCI_MISSING_INTEGRATION_ERRORDAVINCI_UNKNOWN_ERRORThis project is licensed under the MIT License - see the LICENSE file for details