UNPKG

react-native-persona

Version:

Launch a mobile native implementation of the Persona inquiry flow from React Native.

743 lines (620 loc) 19.2 kB
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;