react-native-persona
Version:
Launch a mobile native implementation of the Persona inquiry flow from React Native.
743 lines (620 loc) • 19.2 kB
text/typescript
import {
EventSubscription,
NativeEventEmitter,
NativeModules,
} from 'react-native';
import { processThemeValues } from './util';
import { Fields, InquiryField, RawInquiryField } from './fields';
export { Fields };
import { Versions } from './versions';
export { Versions };
const { PersonaInquiry2 } = NativeModules;
// Using Opaque types + Smart Constructor enforces validation at
// instantiation time for IDS
declare const Unique: unique symbol;
export type Opaque<T, Tag> = T & { [Unique]: Tag };
type TemplateId = Opaque<string, 'TemplateId'>;
type TemplateVersion = Opaque<string, 'TemplateVersion'>;
type InquiryId = Opaque<string, 'InquiryId'>;
type AccountId = Opaque<string, 'AccountId'>;
export class InvalidTemplateId extends Error {}
export class InvalidTemplateVersion extends Error {}
export class InvalidInquiryId extends Error {}
export class InvalidAccountId extends Error {}
/**
* Run validations that the string is in proper Inquiry token format
* and do a type conversion to InquiryId.
*
* @param candidate
*/
function makeInquiryId(candidate: string): InquiryId {
if (candidate && candidate.startsWith('inq_')) {
return candidate as InquiryId;
}
throw new InvalidInquiryId(
`Valid template IDs start with "inq_". Received: ${candidate} `
);
}
/**
* Run validations that the string is in proper Template token format
* and do a type conversion to TemplateId.
*
* @param candidate
*/
function makeTemplateId(candidate: string): TemplateId {
if (candidate && candidate.startsWith('itmpl_')) {
return candidate as TemplateId;
}
throw new InvalidTemplateId(
`Valid template IDs start with "itmpl_". Received: ${candidate} `
);
}
/**
* Run validations that the string is in proper Template Version token format
* and do a type conversion to TemplateVersion.
*
* @param candidate
*/
function makeTemplateVersion(candidate: string): TemplateVersion {
if (candidate && candidate.startsWith('itmplv_')) {
return candidate as TemplateVersion;
}
throw new InvalidTemplateVersion(
`Valid template versions start with "itmplv_". Received: ${candidate} `
);
}
/**
* Run validations that the string is in proper Template token format
* and do a type conversion to AccountId.
*
* @param candidate
*/
function makeAccountId(candidate: string): AccountId {
if (candidate && candidate.startsWith('act_')) {
return candidate as AccountId;
}
throw new InvalidAccountId(
`Valid account IDs start with "act_". Received: ${candidate} `
);
}
/**
* String enum for environments. These strings will be parsed
* on the native side bridge into Kotlin / Swift enums.
*/
export enum Environment {
SANDBOX = 'sandbox',
PRODUCTION = 'production',
}
/**
* An enum value which determines whether this sdk should use the theme values sent from the server.
*/
export enum ThemeSource {
SERVER = 'server',
/**
* @deprecated Client side theming is deprecated, please configure your theme inside
* the Persona Dashboard and use SERVER as the theme source.
*/
CLIENT = 'client',
}
export interface InquiryOptions {
templateId?: TemplateId;
templateVersion?: TemplateVersion;
inquiryId?: InquiryId;
referenceId?: string;
accountId?: AccountId;
environment?: Environment;
environmentId?: string;
themeSetId?: string;
sessionToken?: string;
returnCollectedData?: boolean;
locale?: String;
// Fields
fields?: Fields;
// Callbacks
onComplete?: OnCompleteCallback;
onCanceled?: OnCanceledCallback;
onError?: OnErrorCallback;
// Customization
iosThemeObject?: Object | null;
themeSource?: ThemeSource | null;
}
type OnCompleteCallback = (
inquiryId: string,
status: string,
fields: Fields,
extraData: ExtraData
) => void;
/**
* Type for collected data that came directly from iOS/Android.
* Needs to be translated to TS classes before returning.
*/
interface ExternalCollectedData {
stepData: any[];
}
export interface ExtraData {
collectedData: CollectedData | null;
}
export interface CollectedData {
stepData: StepData[];
}
export interface StepData {
stepName: string;
}
export class DocumentStepData implements StepData {
stepName: string;
documents: Document[];
constructor() {
this.stepName = '';
this.documents = [];
}
}
export interface Document {
absoluteFilePath: string;
}
export class GovernmentIdStepData implements StepData {
stepName: string;
captures: GovernmentIdCapture[];
constructor() {
this.stepName = '';
this.captures = [];
}
}
export interface GovernmentIdCapture {
idClass: string;
captureMethod: GovernmentIdCaptureMethod;
side: GovernmentIdCaptureSide;
frames: GovernmentIdCaptureFrames[];
}
export enum GovernmentIdCaptureMethod {
Manual = 'Manual',
Auto = 'Auto',
Upload = 'Upload',
}
export enum GovernmentIdCaptureSide {
Front = 'Front',
Back = 'Back',
}
export interface GovernmentIdCaptureFrames {
absoluteFilePath: string;
}
export class SelfieStepData implements StepData {
stepName: string;
centerCapture: SelfieCapture | null;
leftCapture: SelfieCapture | null;
rightCapture: SelfieCapture | null;
constructor() {
this.stepName = '';
this.centerCapture = null;
this.leftCapture = null;
this.rightCapture = null;
}
}
export interface SelfieCapture {
captureMethod: SelfieCaptureMethod;
absoluteFilePath: string;
}
export enum SelfieCaptureMethod {
Manual = 'Manual',
Auto = 'Auto',
}
export class UiStepData implements StepData {
stepName: string;
componentParams: { [key: string]: any };
constructor() {
this.stepName = '';
this.componentParams = {};
}
}
type OnCanceledCallback = (inquiryId?: string, sessionToken?: string) => void;
type OnErrorCallback = (error: Error, errorCode?: string) => void;
const eventEmitter = new NativeEventEmitter(PersonaInquiry2);
export class Inquiry {
templateId?: TemplateId;
templateVersion?: TemplateVersion;
inquiryId?: InquiryId;
referenceId?: string;
accountId?: AccountId;
environment?: Environment;
environmentId?: string;
themeSetId?: string;
sessionToken?: string;
returnCollectedData?: boolean;
locale?: String;
iosThemeObject?: Object | null;
themeSource?: ThemeSource | null;
fields?: Fields | null;
private readonly onComplete?: OnCompleteCallback;
private readonly onCanceled?: OnCanceledCallback;
private readonly onError?: OnErrorCallback;
private onCompleteListener?: EventSubscription;
private onCanceledListener?: EventSubscription;
private onErrorListener?: EventSubscription;
constructor(options: InquiryOptions) {
this.templateId = options.templateId;
this.templateVersion = options.templateVersion;
this.inquiryId = options.inquiryId;
this.referenceId = options.referenceId;
this.accountId = options.accountId;
this.environment = options.environment;
this.environmentId = options.environmentId;
this.themeSetId = options.themeSetId;
this.sessionToken = options.sessionToken;
this.fields = options.fields;
this.returnCollectedData = options.returnCollectedData;
this.locale = options.locale;
// Callbacks
this.onComplete = options.onComplete;
this.onCanceled = options.onCanceled;
this.onError = options.onError;
// Theme object
this.iosThemeObject = options.iosThemeObject;
this.themeSource = options.themeSource;
}
private clearListeners() {
if (this.onCompleteListener) this.onCompleteListener.remove();
if (this.onCanceledListener) this.onCanceledListener.remove();
if (this.onErrorListener) this.onErrorListener.remove();
}
/**
* Create an Inquiry flow builder based on a template ID.
*
* You can find your template ID on the Dashboard under Inquiries > Templates.
* {@link https://app.withpersona.com/dashboard/inquiry-templates}
*
* @param templateId template ID from your Persona Dashboard
* @return builder for the Inquiry flow
*/
static fromTemplate(templateId: string) {
return new TemplateBuilder(makeTemplateId(templateId), null);
}
/**
* Create an Inquiry flow builder based on a template ID version.
*
* You can find your template ID version on the Dashboard under the
* settings view of a specific template.
* {@link https://app.withpersona.com/dashboard/inquiry-templates}
*
* @param templateVersion template version from your Persona Dashboard
* @return builder for the Inquiry flow
*/
static fromTemplateVersion(templateVersion: string) {
return new TemplateBuilder(null, makeTemplateVersion(templateVersion));
}
/**
* Create an Inquiry flow builder based on an inquiry ID.
*
* You will need to generate the inquiry ID on the server. To try it out, you can create an
* inquiry from the Persona Dashboard under "Inquiries". Click on the "Create Inquiry" button
* and copy the inquiry ID from the URL.
* {@link https://app.withpersona.com/dashboard/inquiries}
*
* @param inquiryId inquiry ID from your server
* @return builder for the Inquiry flow
*/
static fromInquiry(inquiryId: string) {
return new InquiryBuilder(makeInquiryId(inquiryId));
}
/**
* Launch the Persona Inquiry.
*/
start() {
this.onCompleteListener = eventEmitter.addListener(
'onComplete',
(event: {
inquiryId: string;
status: string;
fields: Record<string, RawInquiryField>;
collectedData: ExternalCollectedData | null;
}) => {
if (this.onComplete) {
let fields: Fields = {};
for (let key of Object.keys(event.fields || {})) {
let field = event.fields[key];
if (field == undefined) {
fields[key] = new InquiryField.Unknown('null');
continue;
}
switch (field.type) {
case 'integer':
fields[key] = new InquiryField.Integer(
Number.parseInt(field.value)
);
break;
case 'boolean':
fields[key] = new InquiryField.Boolean(field.value);
break;
case 'string':
fields[key] = new InquiryField.String(field.value);
break;
default:
fields[key] = new InquiryField.Unknown(field.type);
break;
}
}
let collectedData: CollectedData | null = null;
let stepData = event.collectedData?.stepData;
if (stepData != null) {
// Translate the step data from JSON to actual class objects
let translatedStepData = [];
for (let stepDatum of stepData) {
switch (stepDatum.type) {
case 'DocumentStepData':
translatedStepData.push(
Object.assign(new DocumentStepData(), stepDatum)
);
break;
case 'GovernmentIdStepData':
translatedStepData.push(
Object.assign(new GovernmentIdStepData(), stepDatum)
);
break;
case 'SelfieStepData':
translatedStepData.push(
Object.assign(new SelfieStepData(), stepDatum)
);
break;
case 'UiStepData':
translatedStepData.push(
Object.assign(new UiStepData(), stepDatum)
);
break;
}
}
collectedData = {
stepData: translatedStepData,
};
}
let extraData: ExtraData = {
collectedData: collectedData,
};
this.onComplete(event.inquiryId, event.status, fields, extraData);
}
this.clearListeners();
}
);
this.onCanceledListener = eventEmitter.addListener(
'onCanceled',
(event: { inquiryId?: string; sessionToken?: string }) => {
if (this.onCanceled)
this.onCanceled(event.inquiryId, event.sessionToken);
this.clearListeners();
}
);
this.onErrorListener = eventEmitter.addListener(
'onError',
(event: { debugMessage: string; errorCode?: string }) => {
if (this.onError)
this.onError(new Error(event.debugMessage), event.errorCode);
this.clearListeners();
}
);
PersonaInquiry2.startInquiry({
templateId: this.templateId,
templateVersion: this.templateVersion,
inquiryId: this.inquiryId,
referenceId: this.referenceId,
accountId: this.accountId,
environment: this.environment,
environmentId: this.environmentId,
themeSetId: this.themeSetId,
sessionToken: this.sessionToken,
fields: this.fields,
returnCollectedData: this.returnCollectedData,
themeSource: this.themeSource,
iosTheme: processThemeValues(this.iosThemeObject || {}),
locale: this.locale,
});
}
}
class InquiryBuilder {
private _inquiryId: InquiryId;
private _sessionToken?: string;
// Callbacks
private _onComplete?: OnCompleteCallback;
private _onCanceled?: OnCanceledCallback;
private _onError?: OnErrorCallback;
private _iosThemeObject?: Object;
private _themeSource: ThemeSource = ThemeSource.SERVER;
private _fields?: Fields;
private _locale?: string;
constructor(inquiryId: InquiryId) {
this._inquiryId = inquiryId;
}
sessionToken(sessionToken: string): InquiryBuilder {
this._sessionToken = sessionToken;
return this;
}
onComplete(callback: OnCompleteCallback): InquiryBuilder {
this._onComplete = callback;
return this;
}
onCanceled(callback: OnCanceledCallback): InquiryBuilder {
this._onCanceled = callback;
return this;
}
onError(callback: OnErrorCallback): InquiryBuilder {
this._onError = callback;
return this;
}
/**
* @deprecated Use iosThemeToUse
*/
iosTheme(themeObject: Object): InquiryBuilder {
this._iosThemeObject = themeObject;
this._themeSource = ThemeSource.CLIENT;
return this;
}
iosThemeToUse(themeObject: Object, themeSource: ThemeSource): InquiryBuilder {
this._iosThemeObject = themeObject;
this._themeSource = themeSource;
return this;
}
locale(locale: string): InquiryBuilder {
this._locale = locale;
return this;
}
build(): Inquiry {
return new Inquiry({
inquiryId: this._inquiryId,
sessionToken: this._sessionToken,
onComplete: this._onComplete,
onCanceled: this._onCanceled,
onError: this._onError,
iosThemeObject: this._iosThemeObject,
themeSource: this._themeSource,
fields: this._fields,
locale: this._locale,
});
}
}
class TemplateBuilder {
private readonly _templateId?: TemplateId;
private readonly _templateVersion?: TemplateVersion;
private _accountId?: AccountId;
private _referenceId?: string;
private _environment?: Environment;
private _environmentId?: string;
private _themeSetId?: string;
private _fields?: Fields;
private _sessionToken?: string;
private _returnCollectedData?: boolean;
private _locale?: string;
// Callbacks
private _onComplete?: OnCompleteCallback;
private _onCanceled?: OnCanceledCallback;
private _onError?: OnErrorCallback;
// Customization
private _iosThemeObject?: Object;
private _themeSource: ThemeSource = ThemeSource.SERVER;
constructor(
templateId?: TemplateId | null,
templateVersion?: TemplateVersion | null
) {
if (templateId != null) {
this._templateId = templateId;
} else if (templateVersion != null) {
this._templateVersion = templateVersion;
} else {
throw new InvalidTemplateId(
`Either templateId or templateVersion needs to be set.`
);
}
return this;
}
returnCollectedData(returnCollectedData: boolean) {
this._returnCollectedData = returnCollectedData;
return this;
}
referenceId(referenceId: string): TemplateBuilder {
if (referenceId == null) {
return this;
}
this._accountId = undefined;
this._referenceId = referenceId;
return this;
}
accountId(accountId: string): TemplateBuilder {
if (accountId == null) {
return this;
}
this._referenceId = undefined;
this._accountId = makeAccountId(accountId);
return this;
}
environment(environment: Environment): TemplateBuilder {
this._environment = environment;
return this;
}
environmentId(environmentId: string): TemplateBuilder {
this._environmentId = environmentId;
return this;
}
themeSetId(themeSetId: string): TemplateBuilder {
this._themeSetId = themeSetId;
return this;
}
sessionToken(sessionToken: string): TemplateBuilder {
this._sessionToken = sessionToken;
return this;
}
fields(fields: Fields): TemplateBuilder {
this._fields = fields;
return this;
}
locale(locale: string): TemplateBuilder {
this._locale = locale;
return this;
}
onComplete(callback: OnCompleteCallback): TemplateBuilder {
this._onComplete = callback;
return this;
}
onCanceled(callback: OnCanceledCallback): TemplateBuilder {
this._onCanceled = callback;
return this;
}
onError(callback: OnErrorCallback): TemplateBuilder {
this._onError = callback;
return this;
}
/**
* @deprecated Use iosThemeToUse
*/
iosTheme(themeObject: Object): TemplateBuilder {
this._iosThemeObject = themeObject;
this._themeSource = ThemeSource.CLIENT;
return this;
}
iosThemeToUse(
themeObject: Object,
themeSource: ThemeSource
): TemplateBuilder {
this._iosThemeObject = themeObject;
this._themeSource = themeSource;
return this;
}
build(): Inquiry {
return new Inquiry({
templateId: this._templateId,
templateVersion: this._templateVersion,
accountId: this._accountId,
referenceId: this._referenceId,
environment: this._environment,
environmentId: this._environmentId,
themeSetId: this._themeSetId,
sessionToken: this._sessionToken,
fields: this._fields,
onComplete: this._onComplete,
onCanceled: this._onCanceled,
onError: this._onError,
iosThemeObject: this._iosThemeObject,
themeSource: this._themeSource,
returnCollectedData: this._returnCollectedData,
locale: this._locale,
});
}
}
/**
* @deprecated Use the `Inquiry` static methods instead
*/
namespace InquiryBuilders {
/**
* @deprecated Use {@link Inquiry#fromInquiry} instead
*/
export function fromInquiry(inquiryId: string) {
return Inquiry.fromInquiry(inquiryId);
}
/**
* @deprecated Use {@link Inquiry#fromTemplate} instead
*/
export function fromTemplate(templateId: string) {
return Inquiry.fromTemplate(templateId);
}
/**
* @deprecated Use {@link Inquiry#fromTemplateVersion} instead
*/
export function fromTemplateVersion(templateVersion: string) {
return Inquiry.fromTemplateVersion(templateVersion);
}
}
export default InquiryBuilders;