import type { EventSubscription } from 'react-native'; import NativeReclaimInappModule, * as NativeReclaimInappModuleTypes from './NativeInappRnSdk'; /** * [ReclaimVerification] is the main class for interacting with the Reclaim verification system. * It provides methods to start verification processes, manage platform configurations, * and handle verification options. * * The class can be instantiated with a custom platform implementation, or will use * the default [PlatformImpl] if none is provided. */ export class ReclaimVerification { public platform: ReclaimVerification.Platform; private static defaultPlatform: ReclaimVerification.Platform | null = null; private static isStrictConfigCheckEnabled: boolean = false; private static lastOverrides: ReclaimVerification.OverrideConfig | null = null; private static lastVerificationOptions: ReclaimVerification.VerificationOptions | null = null; public constructor(platform?: ReclaimVerification.Platform) { if (platform) { this.platform = platform; } else { if (ReclaimVerification.defaultPlatform == null) { ReclaimVerification.defaultPlatform = new PlatformImpl(); } this.platform = ReclaimVerification.defaultPlatform; } } /** * Enables or disables strict configuration checks. When enabled, * [startVerification], [startVerificationFromUrl], and [startVerificationFromJson] * will throw if any field of [OverrideConfig] was not provided via [setOverrides], * or if any option of [VerificationOptions] was not provided via [setVerificationOptions]. * * Disabled by default. */ public setStrictConfigCheck(enabled: boolean) { ReclaimVerification.isStrictConfigCheckEnabled = enabled === true; } public async startVerification( request: ReclaimVerification.Request ): Promise { ReclaimVerification.assertStrictConfig(); return this.platform.startVerification(request); } public async startVerificationFromUrl( requestUrl: string ): Promise { ReclaimVerification.assertStrictConfig(); return this.platform.startVerificationFromUrl(requestUrl); } public async startVerificationFromJson( template: Record ): Promise { ReclaimVerification.assertStrictConfig(); return this.platform.startVerificationFromJson(template); } public async ping(): Promise { return this.platform.ping(); } public setOverrides(overrides: ReclaimVerification.OverrideConfig) { ReclaimVerification.lastOverrides = overrides; return this.platform.setOverrides(overrides); } public clearAllOverrides() { ReclaimVerification.lastOverrides = null; return this.platform.clearAllOverrides(); } public setVerificationOptions( options?: ReclaimVerification.VerificationOptions | null ) { ReclaimVerification.lastVerificationOptions = options ?? null; return this.platform.setVerificationOptions(options); } public parseLog(log: string): ReclaimVerification.LogEntry { return this.platform.parseLog(log); } public setConsoleLogging(enabled: boolean) { return this.platform.setConsoleLogging({ enabled: enabled == true, }); } public addEventListener< T extends keyof ReclaimVerification.EventListener.EventMap, >( type: T, listener: ReclaimVerification.EventListener.EventListener ): ReclaimVerification.EventListener.CancelEventSubscription { return this.platform.addEventListener(type, listener); } private static assertStrictConfig() { if (!ReclaimVerification.isStrictConfigCheckEnabled) return; const overrides = ReclaimVerification.lastOverrides; const overrideKeys: (keyof ReclaimVerification.OverrideConfig)[] = [ 'provider', 'featureOptions', 'logConsumer', 'sessionManagement', 'appInfo', 'capabilityAccessToken', ]; const missingOverrides = overrides ? overrideKeys.filter((k) => overrides[k] === undefined) : overrideKeys; if (missingOverrides.length > 0) { throw new Error( `[ReclaimVerification] Strict config check failed: setOverrides was ${overrides ? 'called without' : 'not called with'} the following fields: ${missingOverrides.join(', ')}. Provide values for every field or disable strict mode via setStrictConfigCheck(false).` ); } const options = ReclaimVerification.lastVerificationOptions; const optionKeys: (keyof ReclaimVerification.VerificationOptions)[] = [ 'canDeleteCookiesBeforeVerificationStarts', 'fetchAttestorAuthenticationRequest', 'claimCreationType', 'canAutoSubmit', 'isCloseButtonVisible', 'locale', 'useTeeOperator', ]; const missingOptions = options ? optionKeys.filter((k) => options[k] === undefined) : optionKeys; if (missingOptions.length > 0) { throw new Error( `[ReclaimVerification] Strict config check failed: setVerificationOptions was ${options ? 'called without' : 'not called with'} the following options: ${missingOptions.join(', ')}. Provide values for every option or disable strict mode via setStrictConfigCheck(false).` ); } } } /** * This namespace provides types involved in initiating and managing the verification process * for proving claims about user data through various providers. */ export namespace ReclaimVerification { /** * Represents user's session information for a verification attempt. * This data class contains the necessary data to identify and validate a verification session. */ export type SessionInformation = NativeReclaimInappModuleTypes.SessionInformation; /** * Represents a request for a verification attempt. * * You can create a request using the [ReclaimVerification.Request] constructor or the [ReclaimVerification.Request.fromManifestMetaData] factory method. */ export type Request = NativeReclaimInappModuleTypes.Request & { providerVersion?: ProviderVersion; }; export class ProviderVersion implements NativeReclaimInappModuleTypes.ProviderVersion { resolvedVersion: string; versionExpression: string; private constructor(exactVersion: string, versionExpression: string) { this.resolvedVersion = exactVersion; this.versionExpression = versionExpression; } static resolved( exactVersion: string, versionExpression?: string ): ProviderVersion { return new ProviderVersion( exactVersion, versionExpression ?? exactVersion ); } static from(versionExpression: string = ''): ProviderVersion { return new ProviderVersion(versionExpression, versionExpression); } } /** * Contains the proof and response data after verification */ export interface Response extends NativeReclaimInappModuleTypes.Response { proofs: ReclaimResult.Proof[]; } export namespace ReclaimResult { export interface Proof { identifier: string; signatures: string[]; /** * A data associated with this [Proof]. * The data type of this object is dynamic and can be any JSON serializable Javascript object. */ publicData?: any | null; witnesses: WitnessData[]; claimData: ProviderClaimData; } export const isProof = (value: Record): value is Proof => { return ( typeof value === 'object' && value !== null && 'identifier' in value && 'signatures' in value && 'witnesses' in value ); }; export const asProofs = (proofs: Record[]): Proof[] => { return proofs.filter(isProof); }; export interface ProviderClaimData { owner: string; provider: string; /// int timestampS: number; /// int epoch: number; context: string; identifier: string; parameters: string; } export interface WitnessData { id: string; url: string; } } export interface VerificationOptions { /** * Whether to delete cookies before user journey starts in the client web view. * Defaults to true. */ canDeleteCookiesBeforeVerificationStarts?: boolean; /** * A callback to host that returns an authentication request when a Reclaim HTTP provider is provided. * Used for verifying authentication to attestor. */ fetchAttestorAuthenticationRequest?: ( reclaimHttpProviderJsonString: string ) => Promise; claimCreationType?: 'standalone' | 'meChain'; // Optional /** * Whether to automatically submit the proof after generation. Defaults to true. */ canAutoSubmit?: boolean; // Optional /** * Whether the close button is visible. Defaults to true. */ isCloseButtonVisible?: boolean; // Optional /** * A language code & Country code for localization that should be enforced in the verification flow. */ locale?: string | null; /** * Enables use of Reclaim's TEE+MPC protocol for HTTP Request claim verification and * attestation. * * When set to `true`, the verification will use Trusted Execution Environment * (TEE) with Multi-Party Computation (MPC) for enhanced security. * * When set to `false`, the standard Reclaim's proxy attestor verification flow is used. * * When `null` (default), inappsdk decides whether to use TEE based on * a feature flag. */ useTeeOperator?: boolean | null; } export type SetConsoleLoggingOptions = NativeReclaimInappModuleTypes.SetConsoleLoggingOptions; export namespace Overrides { export interface ProviderInformation { url?: string; jsonString?: string; callback?: ( request: NativeReclaimInappModuleTypes.ProviderInformationRequest ) => Promise; } export type FeatureOptions = NativeReclaimInappModuleTypes.FeatureOptions; export interface LogConsumer { /** * Handler for consuming logs exported from the SDK. * Defaults to false. */ onLogs?: (logJsonString: string, cancel: () => void) => void; /** * When enabled, logs are sent to reclaim that can be used to help you. * Defaults to true. */ canSdkCollectTelemetry?: boolean; /** * Defaults to enabled when not in release mode. */ canSdkPrintLogs?: boolean; } export interface SessionManagement { onLog: (event: NativeReclaimInappModuleTypes.SessionLogEvent) => void; /** * Receive request for creating a session and return a session id. * @param event Receive request for creating a session and return a session id. * @returns A session id. */ onSessionCreateRequest: ( event: NativeReclaimInappModuleTypes.SessionCreateRequestEvent ) => Promise; onSessionUpdateRequest: ( event: NativeReclaimInappModuleTypes.SessionUpdateRequestEvent ) => Promise; } export interface SessionInitResponse { sessionId: string; resolvedProviderVersion: string; } export type ReclaimAppInfo = NativeReclaimInappModuleTypes.ReclaimAppInfo; } export namespace EventListener { export type SessionIdentityUpdate = NativeReclaimInappModuleTypes.ReclaimSessionIdentityUpdate; export interface EventMap { sessionIdentityUpdate: SessionIdentityUpdate; } export type EventListener = ( event: EventMap[T] ) => void | Promise; export type CancelEventSubscription = () => void; } export type OverrideConfig = { provider?: Overrides.ProviderInformation; featureOptions?: Overrides.FeatureOptions; logConsumer?: Overrides.LogConsumer; sessionManagement?: Overrides.SessionManagement; appInfo?: Overrides.ReclaimAppInfo; capabilityAccessToken?: string | null; }; export enum LogLevel { // Failures & Errors SEVERE = 'SEVERE', WARNING = 'WARNING', INFO = 'INFO', // Configurations CONFIG = 'CONFIG', // Verbose debugging logs FINE = 'FINE', // PII Logging FINER = 'FINER', // PII & Extremely Verbose debugging logs FINEST = 'FINEST', } /// Enum representing different types of logging events in the Reclaim SDK. export enum LogEventType { /// The verification flow has been initiated by the user. VERIFICATION_FLOW_STARTED, /// Checking if the current environment is a Reclaim verifier. IS_RECLAIM_VERIFIER, /// Checking if the current environment is a Reclaim inapp SDK. IS_RECLAIM_INAPPSDK, /// An update for inapp SDK is available. Use is still allowed. UPDATE_AVAILABLE, /// An update for inapp SDK is available and the current version cannot be used anymore. SDK_OUTDATED, /// An exception indicating that the platform is not supported for Reclaim verification. RECLAIM_VERIFICATION_PLATFORM_NOT_SUPPORTED_EXCEPTION, /// An exception for an invalid Reclaim request. INVALID_REQUEST_RECLAIM_EXCEPTION, /// The Reclaim verification process was dismissed by the user. RECLAIM_VERIFICATION_DISMISSED, /// An exception indicating the verification process was cancelled for the user. RECLAIM_VERIFICATION_CANCELLED_EXCEPTION, /// An exception during the loading of a verification provider. RECLAIM_VERIFICATION_PROVIDER_LOAD_EXCEPTION, /// An exception when initializing a Reclaim session. RECLAIM_INIT_SESSION_EXCEPTION, /// An exception for an expired Reclaim session. RECLAIM_EXPIRED_SESSION_EXCEPTION, /// The Reclaim verification process was skipped. RECLAIM_VERIFICATION_SKIPPED, /// An exception related to attestor authentication. RECLAIM_ATTESTOR_AUTH_EXCEPTION, /// Sending request to load initial url LOADING_INITIAL_URL, /// The first web page for verification is ready. WEB_PAGE_READY, /// Page loading started PAGE_LOADING_STARTED, /// Page loading stopped PAGE_LOADING_STOPPED, /// An exception when no user activity is detected during verification. RECLAIM_VERIFICATION_NO_ACTIVITY_DETECTED_EXCEPTION, /// A network request has been intercepted. REQUEST_INTERCEPTED, /// A network request has been matched against the provider's requirements. REQUEST_MATCHED, /// The provider script has requested a claim. PROVIDER_SCRIPT_REQUESTED_CLAIM, /// The claim is being prepared. PREPARING_CLAIM, /// The claim parameters are being validated. VALIDATING_CLAIM_PARAMETERS, /// An XPath match requirement for claim validation has failed. X_PATH_MATCH_REQUIREMENT_FAILED, /// A JSONPath match requirement for claim validation has failed. JSON_PATH_MATCH_REQUIREMENT_FAILED, /// A regex match requirement for claim validation has failed. REGEX_MATCH_REQUIREMENT_FAILED, /// NO_PARAMETERS_FOUND, /// An exception for failed claim parameter validation. CLAIM_PARAMETER_VALIDATION_FAILED_EXCEPTION, /// A warning that no response matched the provider's requirements. NO_RESPONSE_MATCH_WARNING, /// The process of creating a claim is starting. STARTING_CLAIM_CREATION, /// The claim creation process has officially started. CLAIM_CREATION_STARTED, /// The attestor is not responding. ATTESTOR_NOT_RESPONDING, /// An exception indicating the claim creation was cancelled. CLAIM_CREATION_CANCELLED_EXCEPTION, /// A proof has been successfully generated. PROOF_GENERATED, /// An exception for a failed proof generation. PROOF_GENERATION_FAILED_EXCEPTION, /// An exception indicating that the claim creation process has timed out. CLAIM_CREATION_TIMED_OUT_EXCEPTION, /// A result has been received. RESULT_RECEIVED, /// The generated proof is being submitted. SUBMITTING_PROOF, /// The proof has been successfully submitted. PROOF_SUBMITTED, /// The proof submission has failed. PROOF_SUBMISSION_FAILED, } export type LogEntry = { logLine: string; /** * Timestamp in nanoseconds since unix epoch */ ts: string; /** * Same as `ts` but as JavaScript Date */ datetime: Date; /** * Logger name */ type: string; /** * Session id */ sessionId: string | '' | 'unknown'; /** * Provider id */ providerId: string; /** * App id */ appId: string; /** * Log level */ logLevel: keyof typeof LogLevel; /** * Log event */ eventType?: keyof typeof LogEventType | ''; }; export enum ExceptionType { Cancelled = 'Cancelled', Dismissed = 'Dismissed', SessionExpired = 'SessionExpired', Failed = 'Failed', } export class ReclaimPlatformException extends Error { readonly innerError: Error; readonly reason?: string; readonly details?: any; constructor(message: string, innerError: Error) { super(message); this.innerError = innerError; this.reason = innerError.message; if ('userInfo' in innerError) { const details: any = innerError.userInfo; this.details = details; if ('message' in details) { this.reason = details.message || this.reason; } } } static isReclaimPlatformException( error: Error ): error is ReclaimPlatformException { return error instanceof ReclaimPlatformException; } } export class ReclaimVerificationException extends Error { readonly 'innerError': Error; readonly 'type': ExceptionType; readonly 'sessionId': string; readonly 'didSubmitManualVerification': boolean; readonly 'reason': string; 'constructor'( message: string, innerError: Error, type: ExceptionType, sessionId: string, didSubmitManualVerification: boolean, reason: string ) { super(message); this.innerError = innerError; this.type = type; this.sessionId = sessionId; this.didSubmitManualVerification = didSubmitManualVerification; this.reason = reason; } private static 'fromTypeName'(name: string): ExceptionType { switch (name) { case 'cancelled': case 'org.reclaimprotocol.inapp_sdk.ReclaimVerification.ReclaimVerificationException.Cancelled': return ExceptionType.Cancelled; case 'dismissed': case 'org.reclaimprotocol.inapp_sdk.ReclaimVerification.ReclaimVerificationException.Dismissed': return ExceptionType.Dismissed; case 'sessionExpired': case 'org.reclaimprotocol.inapp_sdk.ReclaimVerification.ReclaimVerificationException.SessionExpired': return ExceptionType.SessionExpired; case 'failed': case 'org.reclaimprotocol.inapp_sdk.ReclaimVerification.ReclaimVerificationException.Failed': return ExceptionType.Failed; } return ExceptionType.Failed; } static 'fromError'( error: Error, sessionIdHint: string ): ReclaimVerificationException { if ('userInfo' in error) { // From native, we send information about error in userInfo let userInfo = error.userInfo as any; if (userInfo) { let type = ReclaimVerification.ReclaimVerificationException.fromTypeName( userInfo.errorType ); let maybeSessionId = userInfo?.sessionId; return new ReclaimVerificationException( error.message, error, type, typeof maybeSessionId === 'string' && maybeSessionId ? maybeSessionId : sessionIdHint, userInfo?.didSubmitManualVerification ?? false, userInfo?.reason ?? '' ); } } return new ReclaimVerificationException( error.message, error, ReclaimVerification.ExceptionType.Failed, sessionIdHint, false, '' ); } static 'isReclaimVerificationException'( error: Error ): error is ReclaimVerificationException { return error instanceof ReclaimVerificationException; } } export abstract class Platform { abstract startVerification( request: ReclaimVerification.Request ): Promise; abstract startVerificationFromUrl( requestUrl: string ): Promise; abstract startVerificationFromJson( template: Record ): Promise; abstract ping(): Promise; abstract setOverrides( config: ReclaimVerification.OverrideConfig ): Promise; abstract clearAllOverrides(): Promise; abstract setVerificationOptions( options?: ReclaimVerification.VerificationOptions | null ): Promise; abstract parseLog(log: string): ReclaimVerification.LogEntry; abstract setConsoleLogging( options?: ReclaimVerification.SetConsoleLoggingOptions | null ): Promise; abstract addEventListener< T extends keyof ReclaimVerification.EventListener.EventMap, >( type: T, listener: ReclaimVerification.EventListener.EventListener ): ReclaimVerification.EventListener.CancelEventSubscription; } } export class PlatformImpl extends ReclaimVerification.Platform { override async startVerification( request: ReclaimVerification.Request ): Promise { try { const response = await NativeReclaimInappModule.startVerification(request); return { ...response, proofs: ReclaimVerification.ReclaimResult.asProofs(response.proofs), }; } catch (error) { console.info({ error, }); if (error instanceof Error) { throw ReclaimVerification.ReclaimVerificationException.fromError( error, request.session?.sessionId ?? '' ); } throw error; } } override async startVerificationFromUrl( requestUrl: string ): Promise { try { const response = await NativeReclaimInappModule.startVerificationFromUrl(requestUrl); return { ...response, proofs: ReclaimVerification.ReclaimResult.asProofs(response.proofs), }; } catch (error) { console.info({ error, }); if (error instanceof Error) { throw ReclaimVerification.ReclaimVerificationException.fromError( error, '' ); } throw error; } } override async startVerificationFromJson( template: Record ): Promise { try { const response = await NativeReclaimInappModule.startVerificationFromJson( JSON.stringify(template) ); return { ...response, proofs: ReclaimVerification.ReclaimResult.asProofs(response.proofs), }; } catch (error) { console.info({ error, }); if (error instanceof Error) { throw ReclaimVerification.ReclaimVerificationException.fromError( error, '' ); } throw error; } } override async ping(): Promise { return await NativeReclaimInappModule.ping(); } private previousSessionManagementCancelCallback: null | (() => void) = null; disposeSessionManagement() { let callback = this.previousSessionManagementCancelCallback; if (callback != null && callback != undefined) { callback(); } this.previousSessionManagementCancelCallback = null; } private previousLogSubscription: EventSubscription | null = null; disposeLogListener() { this.previousLogSubscription?.remove(); this.previousLogSubscription = null; } private previousProviderRequestCancelCallback: null | (() => void) = null; private disposeProviderRequestListener() { let callback = this.previousProviderRequestCancelCallback; if (callback != null && callback != undefined) { callback(); } this.previousProviderRequestCancelCallback = null; } override async setOverrides({ provider, featureOptions, logConsumer, sessionManagement, appInfo, capabilityAccessToken, }: ReclaimVerification.OverrideConfig) { let providerCallback = provider?.callback; let providerOverride = !provider ? null : { url: provider?.url, jsonString: provider?.jsonString, canFetchProviderInformationFromHost: !!providerCallback, }; if (providerCallback) { this.disposeProviderRequestListener(); let providerRequestSubscription = NativeReclaimInappModule.onProviderInformationRequest(async (event) => { try { let result = await providerCallback(event); NativeReclaimInappModule.replyWithString(event.replyId, result); } catch (error) { console.error(error); NativeReclaimInappModule.replyWithString(event.replyId, ''); } }); const cancel = () => { providerRequestSubscription.remove(); }; this.previousProviderRequestCancelCallback = cancel; } const onLogsListener = logConsumer?.onLogs; let logConsumerRequest = !logConsumer ? undefined : { enableLogHandler: !!onLogsListener, canSdkCollectTelemetry: logConsumer?.canSdkCollectTelemetry, canSdkPrintLogs: logConsumer?.canSdkPrintLogs, }; if (onLogsListener) { this.disposeLogListener(); const cancel = () => { this.previousLogSubscription?.remove(); this.previousLogSubscription = null; }; this.previousLogSubscription = NativeReclaimInappModule.onLogs((arg) => { onLogsListener(arg, cancel); }); } let sessionManagementRequest = !sessionManagement ? undefined : { // A handler is provided, so we don't let SDK manage sessions enableSdkSessionManagement: false, }; if (sessionManagement) { this.disposeSessionManagement(); let sessionCreateSubscription = NativeReclaimInappModule.onSessionCreateRequest(async (event) => { const replyId = event.replyId; try { let result = await sessionManagement.onSessionCreateRequest(event); NativeReclaimInappModule.replyWithString( replyId, JSON.stringify(result) ); } catch (error) { console.error(error); // Send an empty string to indicate failure NativeReclaimInappModule.replyWithString(replyId, ''); } }); let sessionUpdateSubscription = NativeReclaimInappModule.onSessionUpdateRequest(async (event) => { const replyId = event.replyId; try { let result = await sessionManagement.onSessionUpdateRequest(event); NativeReclaimInappModule.reply(replyId, result); } catch (error) { console.error(error); NativeReclaimInappModule.reply(replyId, false); } }); let sessionLogsSubscription = NativeReclaimInappModule.onSessionLogs( (event) => { try { sessionManagement.onLog(event); } catch (error) { console.error(error); } } ); const cancel = () => { sessionCreateSubscription.remove(); sessionUpdateSubscription.remove(); sessionLogsSubscription.remove(); }; this.previousSessionManagementCancelCallback = cancel; } try { return await NativeReclaimInappModule.setOverrides({ provider: providerOverride, featureOptions, logConsumer: logConsumerRequest, sessionManagement: sessionManagementRequest, appInfo, capabilityAccessToken, }); } catch (error) { throw new ReclaimVerification.ReclaimPlatformException( 'Failed to set overrides', error as Error ); } } override async clearAllOverrides() { this.disposeProviderRequestListener(); this.disposeLogListener(); this.disposeSessionManagement(); return NativeReclaimInappModule.clearAllOverrides(); } private previousAttestorAuthRequestCancelCallback: null | (() => void) = null; disposeAttestorAuthRequestListener() { let callback = this.previousAttestorAuthRequestCancelCallback; if (callback != null && callback != undefined) { callback(); } this.previousAttestorAuthRequestCancelCallback = null; } override async setVerificationOptions( options?: ReclaimVerification.VerificationOptions | null ): Promise { let args: NativeReclaimInappModuleTypes.VerificationOptions | null = null; if (options) { let canUseAttestorAuthenticationRequest = !!options.fetchAttestorAuthenticationRequest; args = { canDeleteCookiesBeforeVerificationStarts: options.canDeleteCookiesBeforeVerificationStarts ?? true, canUseAttestorAuthenticationRequest: canUseAttestorAuthenticationRequest, claimCreationType: options.claimCreationType ?? 'standalone', canAutoSubmit: options.canAutoSubmit ?? true, isCloseButtonVisible: options.isCloseButtonVisible ?? true, locale: options.locale ?? null, useTeeOperator: options.useTeeOperator ?? null, }; if (canUseAttestorAuthenticationRequest) { this.disposeAttestorAuthRequestListener(); let attestorAuthRequestSubscription = NativeReclaimInappModule.onReclaimAttestorAuthRequest( async (event) => { let result = await options.fetchAttestorAuthenticationRequest!( event.reclaimHttpProviderJsonString ); NativeReclaimInappModule.replyWithString(event.replyId, result); } ); const cancel = () => { attestorAuthRequestSubscription.remove(); }; this.previousAttestorAuthRequestCancelCallback = cancel; } } try { return await NativeReclaimInappModule.setVerificationOptions({ options: args, }); } catch (error) { throw new ReclaimVerification.ReclaimPlatformException( 'Failed to set verification options', error as Error ); } } /** * Converts a nanosecond timestamp string back to a JavaScript Date object. * @param {string} timeStampStr - The timestamp string (ms * 1,000,000) * @returns {Date} */ fromTimeStampToDate(timeStampStr: string): Date { try { // 1. Use BigInt to handle the large integer value without precision loss const nanoseconds = BigInt(timeStampStr); // 2. Divide by 1,000,000 to convert back to milliseconds // Note: We use '1000000n' to denote a BigInt literal const milliseconds = Number(nanoseconds / 1000000n); // 3. Create and return the Date object return new Date(milliseconds); } catch (e) { return new Date(); } } override parseLog(log: string): ReclaimVerification.LogEntry { let data = JSON.parse(log) as ReclaimVerification.LogEntry; if (data.ts) { data.datetime = this.fromTimeStampToDate(data.ts); } return data; } override async setConsoleLogging( options?: ReclaimVerification.SetConsoleLoggingOptions | null ): Promise { try { return await NativeReclaimInappModule.setConsoleLogging({ enabled: options?.enabled == true, }); } catch (error) { throw new ReclaimVerification.ReclaimPlatformException( 'Failed to set console logging', error as Error ); } } static listenersCount: Record< keyof ReclaimVerification.EventListener.EventMap, number > = { sessionIdentityUpdate: 0, }; override addEventListener< T extends keyof ReclaimVerification.EventListener.EventMap, >( type: T, listener: ReclaimVerification.EventListener.EventListener ): ReclaimVerification.EventListener.CancelEventSubscription { let subscription: EventSubscription; switch (type) { case 'sessionIdentityUpdate': subscription = NativeReclaimInappModule.onSessionIdentityUpdate( (param) => { try { listener(param); } catch (_) { // ignore listener errors } } ); NativeReclaimInappModule.startEventSubscription(type); PlatformImpl.listenersCount[type] = PlatformImpl.listenersCount[type] + 1; break; default: throw new Error('Tried to subscribe to an unknown event'); } const cancelSubscription = () => { PlatformImpl.listenersCount[type] = PlatformImpl.listenersCount[type] - 1; subscription.remove(); if (PlatformImpl.listenersCount[type] <= 0) { PlatformImpl.listenersCount[type] = 0; try { NativeReclaimInappModule.removeEventSubscription(type); } catch (error) { // ignore subscription removal errors } } }; return cancelSubscription; } }