diff --git a/android/build.gradle b/android/build.gradle index 051ecfd..f3dbc6b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,8 @@ +def RN_VERSION = new File(['node', '--print',"JSON.parse(require('fs').readFileSync(require.resolve('react-native/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim()); +def RN_VERSION_STRINGIFY = RN_VERSION.toString(); +def MINOR_VERSION = RN_VERSION_STRINGIFY.substring(RN_VERSION_STRINGIFY.indexOf(".") + 1, RN_VERSION_STRINGIFY.lastIndexOf(".")); +def IS_NEW_REACT_NATIVE_VERSION = Integer.parseInt(MINOR_VERSION) >= 71; + buildscript { repositories { google() @@ -76,6 +81,14 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + dataBinding { + enabled = true + } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } + } } repositories { @@ -87,8 +100,21 @@ repositories { dependencies { // For < 0.71, this will be from the local maven repo // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" + if (IS_NEW_REACT_NATIVE_VERSION) { + implementation "com.facebook.react:react-android:+" + } else { + implementation "com.facebook.react:react-native:+" + } + + implementation 'com.squareup.okhttp3:okhttp:3.12.1' + implementation 'com.squareup.okio:okio:1.15.0' + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.android.material:material:1.5.0' + + // Note: FaceTec SDK implementation + implementation files("./libs/facetec-sdk-9.6.47.aar") + implementation fileTree(dir: 'libs', include: ['*.aar']) } if (isNewArchitectureEnabled()) { diff --git a/android/gradle.properties b/android/gradle.properties index bb4c3df..255172d 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -3,3 +3,5 @@ ReactNativeCapfaceSdk_minSdkVersion=21 ReactNativeCapfaceSdk_targetSdkVersion=31 ReactNativeCapfaceSdk_compileSdkVersion=31 ReactNativeCapfaceSdk_ndkversion=21.4.7075529 +org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=512m +android.useAndroidX=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..41dfb87 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/libs/facetec-sdk-9.6.47.aar b/android/libs/facetec-sdk-9.6.47.aar new file mode 100644 index 0000000..81601a4 Binary files /dev/null and b/android/libs/facetec-sdk-9.6.47.aar differ diff --git a/android/src/main/java/com/capitual/processors/AuthenticateProcessor.java b/android/src/main/java/com/capitual/processors/AuthenticateProcessor.java new file mode 100644 index 0000000..4d8999b --- /dev/null +++ b/android/src/main/java/com/capitual/processors/AuthenticateProcessor.java @@ -0,0 +1,128 @@ +package com.capitual.processors; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import com.capitual.processors.helpers.ThemeUtils; +import com.capitual.reactnativecapfacesdk.ReactNativeCapfaceSdkModule; +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.*; + +public class AuthenticateProcessor extends Processor implements FaceTecFaceScanProcessor { + private boolean success = false; + private final String principalKey = "authenticateMessage"; + private final ReactNativeCapfaceSdkModule capFaceModule; + private final ReadableMap data; + private final ThemeUtils capThemeUtils = new ThemeUtils(); + + public AuthenticateProcessor(String sessionToken, Context context, ReactNativeCapfaceSdkModule capFaceModule, + ReadableMap data) { + this.capFaceModule = capFaceModule; + this.data = data; + + capFaceModule.sendEvent("onCloseModal", true); + FaceTecSessionActivity.createAndLaunchSession(context, AuthenticateProcessor.this, sessionToken); + } + + public void processSessionWhileFaceTecSDKWaits(final FaceTecSessionResult sessionResult, + final FaceTecFaceScanResultCallback faceScanResultCallback) { + capFaceModule.setLatestSessionResult(sessionResult); + + if (sessionResult.getStatus() != FaceTecSessionStatus.SESSION_COMPLETED_SUCCESSFULLY) { + NetworkingHelpers.cancelPendingRequests(); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not session completed successfully!", "FaceTecDifferentStatus"); + return; + } + + JSONObject parameters = new JSONObject(); + try { + if (this.data != null) { + parameters.put("data", new JSONObject(this.data.toHashMap())); + } + parameters.put("faceScan", sessionResult.getFaceScanBase64()); + parameters.put("auditTrailImage", sessionResult.getAuditTrailCompressedBase64()[0]); + parameters.put("lowQualityAuditTrailImage", sessionResult.getLowQualityAuditTrailCompressedBase64()[0]); + parameters.put("externalDatabaseRefID", capFaceModule.getLatestExternalDatabaseRefID()); + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to create JSON payload for upload.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/match-3d-3d") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + faceScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + + if (wasProcessed) { + FaceTecCustomization.overrideResultScreenSuccessMessage = capThemeUtils + .handleMessage(principalKey, "successMessage", "Authenticated"); + success = faceScanResultCallback.proceedToNextStep(scanResultBlob); + if (success) { + capFaceModule.processorPromise.resolve(true); + } + } else { + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to values processed!", + "FaceTecWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/android/src/main/java/com/capitual/processors/Config.java b/android/src/main/java/com/capitual/processors/Config.java new file mode 100644 index 0000000..6ccd004 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/Config.java @@ -0,0 +1,230 @@ +package com.capitual.processors; + +import android.content.Context; + +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.*; +import com.capitual.processors.helpers.ThemeUtils; +import com.capitual.reactnativecapfacesdk.R; + +import java.util.HashMap; +import java.util.Map; + +import okhttp3.Headers; +import okhttp3.Request; + +public class Config { + public static String DeviceKeyIdentifier; + public static String BaseURL; + public static String PublicFaceScanEncryptionKey; + public static String ProductionKeyText; + public static ReadableMap Theme; + public static ReadableMap RequestHeaders; + private static final ThemeUtils CapThemeUtils = new ThemeUtils(); + + private static Map parseReadableMapToMap() { + Map headers = new HashMap(); + + if (RequestHeaders == null) { + return headers; + } + + for (Map.Entry entry : RequestHeaders.toHashMap().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + headers.put(key, value == null ? "" : value.toString()); + } + return headers; + } + + private static Headers parseHeadersMapToHeaders(Map headersMap, String httpMethod) { + okhttp3.Request.Builder buildHeader = new Request.Builder() + .header("X-Device-Key", DeviceKeyIdentifier) + .header("X-User-Agent", FaceTecSDK.createFaceTecAPIUserAgentString("")); + + if (!httpMethod.equals("GET")) { + buildHeader = buildHeader.header("Content-Type", "application/json"); + } + + for (Map.Entry entry : headersMap.entrySet()) { + buildHeader = buildHeader.header(entry.getKey(), entry.getValue()); + } + + okhttp3.Request requestHeader = buildHeader + .url(Config.BaseURL) + .build(); + + return requestHeader.headers(); + } + + public static Headers getHeaders(String httpMethod) { + Map headersMap = parseReadableMapToMap(); + okhttp3.Headers headers = parseHeadersMapToHeaders(headersMap, httpMethod.toUpperCase()); + return headers; + } + + public static void setTheme(ReadableMap theme) { + Theme = theme; + } + + public static void setDevice(String device) { + DeviceKeyIdentifier = device; + } + + public static void setUrl(String url) { + BaseURL = url; + } + + public static void setKey(String key) { + PublicFaceScanEncryptionKey = key; + } + + public static void setProductionKeyText(String keyText) { + ProductionKeyText = keyText; + } + + public static void setHeaders(ReadableMap headers) { + RequestHeaders = headers; + } + + public static boolean hasConfig() { + return DeviceKeyIdentifier != null + && BaseURL != null + && PublicFaceScanEncryptionKey != null + && ProductionKeyText != null; + } + + public static void initializeFaceTecSDKFromAutogeneratedConfig( + Context context, + boolean isDeveloperMode, + FaceTecSDK.InitializeCallback callback) { + if (isDeveloperMode) { + FaceTecSDK.initializeInDevelopmentMode( + context, + DeviceKeyIdentifier, + PublicFaceScanEncryptionKey, + callback); + } else { + FaceTecSDK.initializeInProductionMode( + context, + ProductionKeyText, + DeviceKeyIdentifier, + PublicFaceScanEncryptionKey, + callback); + } + } + + public static FaceTecCustomization retrieveConfigurationWizardCustomization() { + FaceTecCancelButtonCustomization.ButtonLocation cancelButtonLocation = CapThemeUtils + .handleButtonLocation("cancelButtonLocation"); + + FaceTecSecurityWatermarkImage securityWatermarkImage = FaceTecSecurityWatermarkImage.FACETEC; + + FaceTecCustomization defaultCustomization = new FaceTecCustomization(); + + defaultCustomization.getFrameCustomization().cornerRadius = CapThemeUtils.handleBorderRadius("frameCornerRadius"); + defaultCustomization.getFrameCustomization().backgroundColor = CapThemeUtils.handleColor("frameBackgroundColor"); + defaultCustomization.getFrameCustomization().borderColor = CapThemeUtils.handleColor("frameBorderColor"); + + defaultCustomization.getOverlayCustomization().brandingImage = CapThemeUtils.handleImage("logoImage", + R.drawable.facetec_your_app_logo); + defaultCustomization.getOverlayCustomization().backgroundColor = CapThemeUtils + .handleColor("overlayBackgroundColor"); + + defaultCustomization.getGuidanceCustomization().backgroundColors = CapThemeUtils.handleColor( + "guidanceBackgroundColorsAndroid"); + defaultCustomization.getGuidanceCustomization().foregroundColor = CapThemeUtils.handleColor( + "guidanceForegroundColor", + "#272937"); + defaultCustomization.getGuidanceCustomization().buttonBackgroundNormalColor = CapThemeUtils.handleColor( + "guidanceButtonBackgroundNormalColor", "#026ff4"); + defaultCustomization.getGuidanceCustomization().buttonBackgroundDisabledColor = CapThemeUtils.handleColor( + "guidanceButtonBackgroundDisabledColor", "#b3d4fc"); + defaultCustomization.getGuidanceCustomization().buttonBackgroundHighlightColor = CapThemeUtils.handleColor( + "guidanceButtonBackgroundHighlightColor", "#0264dc"); + defaultCustomization.getGuidanceCustomization().buttonTextNormalColor = CapThemeUtils.handleColor( + "guidanceButtonTextNormalColor"); + defaultCustomization.getGuidanceCustomization().buttonTextDisabledColor = CapThemeUtils.handleColor( + "guidanceButtonTextDisabledColor"); + defaultCustomization.getGuidanceCustomization().buttonTextHighlightColor = CapThemeUtils.handleColor( + "guidanceButtonTextHighlightColor"); + defaultCustomization.getGuidanceCustomization().retryScreenImageBorderColor = CapThemeUtils.handleColor( + "guidanceRetryScreenImageBorderColor"); + defaultCustomization.getGuidanceCustomization().retryScreenOvalStrokeColor = CapThemeUtils.handleColor( + "guidanceRetryScreenOvalStrokeColor"); + + defaultCustomization.getOvalCustomization().strokeColor = CapThemeUtils.handleColor("ovalStrokeColor", "#026ff4"); + defaultCustomization.getOvalCustomization().progressColor1 = CapThemeUtils.handleColor("ovalFirstProgressColor", + "#0264dc"); + defaultCustomization.getOvalCustomization().progressColor2 = CapThemeUtils.handleColor("ovalSecondProgressColor", + "#0264dc"); + + defaultCustomization.getFeedbackCustomization().backgroundColors = CapThemeUtils.handleColor( + "feedbackBackgroundColorsAndroid", + "#026ff4"); + defaultCustomization.getFeedbackCustomization().textColor = CapThemeUtils.handleColor("feedbackTextColor"); + + defaultCustomization.getCancelButtonCustomization().customImage = CapThemeUtils.handleImage("cancelImage", + R.drawable.facetec_cancel); + defaultCustomization.getCancelButtonCustomization().setLocation(cancelButtonLocation); + + defaultCustomization.getResultScreenCustomization().backgroundColors = CapThemeUtils.handleColor( + "resultScreenBackgroundColorsAndroid"); + defaultCustomization.getResultScreenCustomization().foregroundColor = CapThemeUtils.handleColor( + "resultScreenForegroundColor", + "#272937"); + defaultCustomization.getResultScreenCustomization().activityIndicatorColor = CapThemeUtils.handleColor( + "resultScreenActivityIndicatorColor", "#026ff4"); + defaultCustomization.getResultScreenCustomization().resultAnimationBackgroundColor = CapThemeUtils.handleColor( + "resultScreenResultAnimationBackgroundColor", "#026ff4"); + defaultCustomization.getResultScreenCustomization().resultAnimationForegroundColor = CapThemeUtils.handleColor( + "resultScreenResultAnimationForegroundColor"); + defaultCustomization.getResultScreenCustomization().uploadProgressFillColor = CapThemeUtils.handleColor( + "resultScreenUploadProgressFillColor", "#026ff4"); + + defaultCustomization.securityWatermarkImage = securityWatermarkImage; + + defaultCustomization.getIdScanCustomization().selectionScreenBackgroundColors = CapThemeUtils.handleColor( + "idScanSelectionScreenBackgroundColorsAndroid"); + defaultCustomization.getIdScanCustomization().selectionScreenForegroundColor = CapThemeUtils.handleColor( + "idScanSelectionScreenForegroundColor", "#272937"); + defaultCustomization.getIdScanCustomization().reviewScreenForegroundColor = CapThemeUtils.handleColor( + "idScanReviewScreenForegroundColor"); + defaultCustomization.getIdScanCustomization().reviewScreenTextBackgroundColor = CapThemeUtils.handleColor( + "idScanReviewScreenTextBackgroundColor", "#026ff4"); + defaultCustomization.getIdScanCustomization().captureScreenForegroundColor = CapThemeUtils.handleColor( + "idScanCaptureScreenForegroundColor"); + defaultCustomization.getIdScanCustomization().captureScreenTextBackgroundColor = CapThemeUtils.handleColor( + "idScanCaptureScreenTextBackgroundColor", "#026ff4"); + defaultCustomization.getIdScanCustomization().buttonBackgroundNormalColor = CapThemeUtils.handleColor( + "idScanButtonBackgroundNormalColor", "#026ff4"); + defaultCustomization.getIdScanCustomization().buttonBackgroundDisabledColor = CapThemeUtils.handleColor( + "idScanButtonBackgroundDisabledColor", "#b3d4fc"); + defaultCustomization.getIdScanCustomization().buttonBackgroundHighlightColor = CapThemeUtils.handleColor( + "idScanButtonBackgroundHighlightColor", "#0264dc"); + defaultCustomization.getIdScanCustomization().buttonTextNormalColor = CapThemeUtils.handleColor( + "idScanButtonTextNormalColor"); + defaultCustomization.getIdScanCustomization().buttonTextDisabledColor = CapThemeUtils.handleColor( + "idScanButtonTextDisabledColor"); + defaultCustomization.getIdScanCustomization().buttonTextHighlightColor = CapThemeUtils.handleColor( + "idScanButtonTextHighlightColor"); + defaultCustomization.getIdScanCustomization().captureScreenBackgroundColor = CapThemeUtils.handleColor( + "idScanCaptureScreenBackgroundColor"); + defaultCustomization.getIdScanCustomization().captureFrameStrokeColor = CapThemeUtils.handleColor( + "idScanCaptureFrameStrokeColor"); + + return defaultCustomization; + } + + public static FaceTecCustomization retrieveLowLightConfigurationWizardCustomization() { + return retrieveConfigurationWizardCustomization(); + } + + public static FaceTecCustomization retrieveDynamicDimmingConfigurationWizardCustomization() { + return retrieveConfigurationWizardCustomization(); + } + + public static FaceTecCustomization currentCustomization = retrieveConfigurationWizardCustomization(); + public static FaceTecCustomization currentLowLightCustomization = retrieveLowLightConfigurationWizardCustomization(); + public static FaceTecCustomization currentDynamicDimmingCustomization = retrieveDynamicDimmingConfigurationWizardCustomization(); +} diff --git a/android/src/main/java/com/capitual/processors/EnrollmentProcessor.java b/android/src/main/java/com/capitual/processors/EnrollmentProcessor.java new file mode 100644 index 0000000..afb7bf9 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/EnrollmentProcessor.java @@ -0,0 +1,128 @@ +package com.capitual.processors; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import com.capitual.processors.helpers.ThemeUtils; +import com.capitual.reactnativecapfacesdk.ReactNativeCapfaceSdkModule; +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.*; + +public class EnrollmentProcessor extends Processor implements FaceTecFaceScanProcessor { + private boolean success = false; + private final String principalKey = "enrollMessage"; + private final ReactNativeCapfaceSdkModule capFaceModule; + private final ReadableMap data; + private final ThemeUtils capThemeUtils = new ThemeUtils(); + + public EnrollmentProcessor(String sessionToken, Context context, ReactNativeCapfaceSdkModule capFaceModule, + ReadableMap data) { + this.capFaceModule = capFaceModule; + this.data = data; + + capFaceModule.sendEvent("onCloseModal", true); + FaceTecSessionActivity.createAndLaunchSession(context, EnrollmentProcessor.this, sessionToken); + } + + public void processSessionWhileFaceTecSDKWaits(final FaceTecSessionResult sessionResult, + final FaceTecFaceScanResultCallback faceScanResultCallback) { + capFaceModule.setLatestSessionResult(sessionResult); + + if (sessionResult.getStatus() != FaceTecSessionStatus.SESSION_COMPLETED_SUCCESSFULLY) { + NetworkingHelpers.cancelPendingRequests(); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not session completed successfully!", "FaceTecDifferentStatus"); + return; + } + + JSONObject parameters = new JSONObject(); + try { + if (this.data != null) { + parameters.put("data", new JSONObject(this.data.toHashMap())); + } + parameters.put("faceScan", sessionResult.getFaceScanBase64()); + parameters.put("auditTrailImage", sessionResult.getAuditTrailCompressedBase64()[0]); + parameters.put("lowQualityAuditTrailImage", sessionResult.getLowQualityAuditTrailCompressedBase64()[0]); + parameters.put("externalDatabaseRefID", capFaceModule.getLatestExternalDatabaseRefID()); + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to create JSON payload for upload.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/enrollment-3d") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + faceScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + + if (wasProcessed) { + FaceTecCustomization.overrideResultScreenSuccessMessage = capThemeUtils.handleMessage(principalKey, + "successMessage", "Liveness\nConfirmed"); + success = faceScanResultCallback.proceedToNextStep(scanResultBlob); + if (success) { + capFaceModule.processorPromise.resolve(true); + } + } else { + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to values processed!", + "FaceTecWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/android/src/main/java/com/capitual/processors/LivenessCheckProcessor.java b/android/src/main/java/com/capitual/processors/LivenessCheckProcessor.java new file mode 100644 index 0000000..69d3996 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/LivenessCheckProcessor.java @@ -0,0 +1,126 @@ +package com.capitual.processors; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import com.capitual.processors.helpers.ThemeUtils; +import com.capitual.reactnativecapfacesdk.ReactNativeCapfaceSdkModule; +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.*; + +public class LivenessCheckProcessor extends Processor implements FaceTecFaceScanProcessor { + private boolean success = false; + private final String principalKey = "livenessMessage"; + private final ReactNativeCapfaceSdkModule capFaceModule; + private final ReadableMap data; + private final ThemeUtils capThemeUtils = new ThemeUtils(); + + public LivenessCheckProcessor(String sessionToken, Context context, ReactNativeCapfaceSdkModule capFaceModule, + ReadableMap data) { + this.capFaceModule = capFaceModule; + this.data = data; + + capFaceModule.sendEvent("onCloseModal", true); + FaceTecSessionActivity.createAndLaunchSession(context, LivenessCheckProcessor.this, sessionToken); + } + + public void processSessionWhileFaceTecSDKWaits(final FaceTecSessionResult sessionResult, + final FaceTecFaceScanResultCallback faceScanResultCallback) { + capFaceModule.setLatestSessionResult(sessionResult); + + if (sessionResult.getStatus() != FaceTecSessionStatus.SESSION_COMPLETED_SUCCESSFULLY) { + NetworkingHelpers.cancelPendingRequests(); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not session completed successfully!", "FaceTecDifferentStatus"); + return; + } + + JSONObject parameters = new JSONObject(); + try { + if (this.data != null) { + parameters.put("data", new JSONObject(this.data.toHashMap())); + } + parameters.put("faceScan", sessionResult.getFaceScanBase64()); + parameters.put("auditTrailImage", sessionResult.getAuditTrailCompressedBase64()[0]); + parameters.put("lowQualityAuditTrailImage", sessionResult.getLowQualityAuditTrailCompressedBase64()[0]); + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to create JSON payload for upload.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/liveness-3d") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + faceScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + if (wasProcessed) { + FaceTecCustomization.overrideResultScreenSuccessMessage = capThemeUtils.handleMessage(principalKey, + "successMessage", "Liveness\nConfirmed"); + success = faceScanResultCallback.proceedToNextStep(scanResultBlob); + if (success) { + capFaceModule.processorPromise.resolve(true); + } + } else { + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to values processed!", + "FaceTecWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/android/src/main/java/com/capitual/processors/NetworkingHelpers.java b/android/src/main/java/com/capitual/processors/NetworkingHelpers.java new file mode 100644 index 0000000..53bbda3 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/NetworkingHelpers.java @@ -0,0 +1,196 @@ +package com.capitual.processors; + +import android.os.Build; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class NetworkingHelpers { + private static OkHttpClient _apiClient = null; + + private static OkHttpClient createApiClient() { + OkHttpClient client = new OkHttpClient.Builder() + .callTimeout(180, TimeUnit.SECONDS) + .connectTimeout(180, TimeUnit.SECONDS) + .readTimeout(180, TimeUnit.SECONDS) + .writeTimeout(180, TimeUnit.SECONDS) + .build(); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + try { + client = new OkHttpClient.Builder() + .sslSocketFactory(new TLSSocketFactory()) + .build(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return client; + } + return client; + } + + public static String OK_HTTP_BUILDER_TAG = "APIRequest"; + + public static synchronized OkHttpClient getApiClient() { + if (_apiClient == null) { + _apiClient = createApiClient(); + } + return _apiClient; + } + + static public void cancelPendingRequests() { + OkHttpClient client = getApiClient(); + + for (Call call : client.dispatcher().queuedCalls()) { + if (Objects.equals(call.request().tag(), OK_HTTP_BUILDER_TAG)) + call.cancel(); + } + + for (Call call : client.dispatcher().runningCalls()) { + if (Objects.equals(call.request().tag(), OK_HTTP_BUILDER_TAG)) + call.cancel(); + } + } +} + +class ProgressRequestBody extends RequestBody { + private final RequestBody requestBody; + private Listener listener; + + ProgressRequestBody(RequestBody requestBody, Listener listener) { + this.requestBody = requestBody; + this.listener = listener; + } + + @Override + public MediaType contentType() { + return requestBody.contentType(); + } + + @Override + public long contentLength() throws IOException { + return requestBody.contentLength(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + ProgressStream progressStream = new ProgressStream(sink.outputStream(), contentLength()); + BufferedSink progressSink = Okio.buffer(Okio.sink(progressStream)); + requestBody.writeTo(progressSink); + progressSink.flush(); + } + + protected final class ProgressStream extends OutputStream { + private final OutputStream stream; + private long totalBytes; + private long bytesSent; + + ProgressStream(OutputStream stream, long totalBytes) { + this.stream = stream; + this.totalBytes = totalBytes; + } + + @Override + public void write(@NonNull byte[] b, int off, int len) throws IOException { + this.stream.write(b, off, len); + if (len < b.length) { + this.bytesSent += len; + } else { + this.bytesSent += b.length; + } + listener.onUploadProgressChanged(this.bytesSent, this.totalBytes); + } + + @Override + public void write(int b) throws IOException { + this.stream.write(b); + this.bytesSent += 1; + listener.onUploadProgressChanged(this.bytesSent, this.totalBytes); + } + } + + interface Listener { + void onUploadProgressChanged(long bytesWritten, long totalBytes); + } +} + +class TLSSocketFactory extends SSLSocketFactory { + private SSLSocketFactory delegate; + + public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + delegate = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return enableTLSOnSocket(delegate.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return enableTLSOnSocket(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) + throws IOException { + return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSOnSocket(Socket socket) { + if (socket != null && (socket instanceof SSLSocket)) { + ((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); + } + return socket; + } +} diff --git a/android/src/main/java/com/capitual/processors/PhotoIDMatchProcessor.java b/android/src/main/java/com/capitual/processors/PhotoIDMatchProcessor.java new file mode 100644 index 0000000..3e4848c --- /dev/null +++ b/android/src/main/java/com/capitual/processors/PhotoIDMatchProcessor.java @@ -0,0 +1,343 @@ +package com.capitual.processors; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; + +import com.capitual.processors.helpers.ThemeUtils; +import com.capitual.reactnativecapfacesdk.ReactNativeCapfaceSdkModule; +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.*; + +public class PhotoIDMatchProcessor extends Processor implements FaceTecFaceScanProcessor, FaceTecIDScanProcessor { + private final String principalKey = "photoIdMatchMessage"; + private final String latestExternalDatabaseRefID; + private final ReadableMap data; + private final ReactNativeCapfaceSdkModule capFaceModule; + private final ThemeUtils capThemeUtils = new ThemeUtils(); + private boolean success = false; + private boolean faceScanWasSuccessful = false; + + public PhotoIDMatchProcessor(String sessionToken, Context context, ReactNativeCapfaceSdkModule capFaceModule, + ReadableMap data) { + this.capFaceModule = capFaceModule; + this.latestExternalDatabaseRefID = this.capFaceModule.getLatestExternalDatabaseRefID(); + this.data = data; + + FaceTecCustomization.setIDScanUploadMessageOverrides( + // Upload of ID front-side has started. + capThemeUtils.handleMessage(principalKey, "frontSideUploadStarted", "Uploading\nEncrypted\nID Scan"), + // Upload of ID front-side is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "frontSideStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID front-side to the Server is complete. + capThemeUtils.handleMessage(principalKey, "frontSideUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID front-side is complete and we are waiting for the Server to + // finish processing and respond. + capThemeUtils.handleMessage(principalKey, "frontSideUploadCompleteAwaitingProcessing", + "Processing ID Scan"), + // Upload of ID back-side has started. + capThemeUtils.handleMessage(principalKey, "backSideUploadStarted", + "Uploading\nEncrypted\nBack of ID"), + // Upload of ID back-side is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "backSideStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID back-side to Server is complete. + capThemeUtils.handleMessage(principalKey, "backSideUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID back-side is complete and we are waiting for the Server to + // finish processing and respond. + capThemeUtils.handleMessage(principalKey, "backSideUploadCompleteAwaitingProcessing", + "Processing Back of ID"), + // Upload of User Confirmed Info has started. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadStarted", + "Uploading\nYour Confirmed Info"), + // Upload of User Confirmed Info is still uploading to Server after an extended + // period of time. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of User Confirmed Info to the Server is complete. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of User Confirmed Info is complete and we are waiting for the Server + // to finish processing and respond. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadCompleteAwaitingProcessing", + "Processing"), + // Upload of NFC Details has started. + capThemeUtils.handleMessage(principalKey, "nfcUploadStarted", + "Uploading Encrypted\nNFC Details"), + // Upload of NFC Details is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "nfcStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of NFC Details to the Server is complete. + capThemeUtils.handleMessage(principalKey, "nfcUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of NFC Details is complete and we are waiting for the Server to finish + // processing and respond. + capThemeUtils.handleMessage(principalKey, "nfcUploadCompleteAwaitingProcessing", + "Processing\nNFC Details"), + // Upload of ID Details has started. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadStarted", + "Uploading Encrypted\nID Details"), + // Upload of ID Details is still uploading to Server after an extended period of + // time. + capThemeUtils.handleMessage(principalKey, "skippedNFCStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID Details to the Server is complete. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID Details is complete and we are waiting for the Server to finish + // processing and respond. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadCompleteAwaitingProcessing", + "Processing\nID Details")); + + capFaceModule.sendEvent("onCloseModal", true); + FaceTecSessionActivity.createAndLaunchSession(context, PhotoIDMatchProcessor.this, PhotoIDMatchProcessor.this, + sessionToken); + } + + public void processSessionWhileFaceTecSDKWaits(final FaceTecSessionResult sessionResult, + final FaceTecFaceScanResultCallback faceScanResultCallback) { + capFaceModule.setLatestSessionResult(sessionResult); + + if (sessionResult.getStatus() != FaceTecSessionStatus.SESSION_COMPLETED_SUCCESSFULLY) { + NetworkingHelpers.cancelPendingRequests(); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not session completed successfully!", "FaceTecDifferentStatus"); + return; + } + + JSONObject parameters = new JSONObject(); + try { + if (this.data != null) { + parameters.put("data", new JSONObject(this.data.toHashMap())); + } + parameters.put("faceScan", sessionResult.getFaceScanBase64()); + parameters.put("auditTrailImage", sessionResult.getAuditTrailCompressedBase64()[0]); + parameters.put("lowQualityAuditTrailImage", sessionResult.getLowQualityAuditTrailCompressedBase64()[0]); + parameters.put("externalDatabaseRefID", this.latestExternalDatabaseRefID); + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to create JSON payload for upload.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/enrollment-3d") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + faceScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + + if (wasProcessed) { + FaceTecCustomization.overrideResultScreenSuccessMessage = capThemeUtils.handleMessage( + principalKey, "successMessage", + "Liveness\nConfirmed"); + faceScanWasSuccessful = faceScanResultCallback.proceedToNextStep(scanResultBlob); + } else { + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to liveness values processed!", + "FaceTecLivenessWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + faceScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public void processIDScanWhileFaceTecSDKWaits(final FaceTecIDScanResult idScanResult, + final FaceTecIDScanResultCallback idScanResultCallback) { + capFaceModule.setLatestIDScanResult(idScanResult); + + if (idScanResult.getStatus() != FaceTecIDScanStatus.SUCCESS) { + NetworkingHelpers.cancelPendingRequests(); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not success!", "FaceTecDifferentStatus"); + return; + } + + final int minMatchLevel = 3; + + JSONObject parameters = new JSONObject(); + try { + parameters.put("externalDatabaseRefID", this.latestExternalDatabaseRefID); + parameters.put("idScan", idScanResult.getIDScanBase64()); + parameters.put("minMatchLevel", minMatchLevel); + + ArrayList frontImagesCompressedBase64 = idScanResult.getFrontImagesCompressedBase64(); + ArrayList backImagesCompressedBase64 = idScanResult.getBackImagesCompressedBase64(); + if (frontImagesCompressedBase64.size() > 0) { + parameters.put("idScanFrontImage", frontImagesCompressedBase64.get(0)); + } + if (backImagesCompressedBase64.size() > 0) { + parameters.put("idScanBackImage", backImagesCompressedBase64.get(0)); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/match-3d-2d-idscan") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + idScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + + if (wasProcessed) { + FaceTecCustomization.setIDScanResultScreenMessageOverrides( + // Successful scan of ID front-side (ID Types with no back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSide", + "ID Scan Complete"), + // Successful scan of ID front-side (ID Types that have a back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSideBackNext", + "Front of ID\nScanned"), + // Successful scan of ID front-side (ID Types that do have NFC but do not have a + // back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSideNFCNext", + "Front of ID\nScanned"), + // Successful scan of the ID back-side (ID Types that do not have NFC). + capThemeUtils.handleMessage(principalKey, "successBackSide", + "ID Scan Complete"), + // Successful scan of the ID back-side (ID Types that do have NFC). + capThemeUtils.handleMessage(principalKey, "successBackSideNFCNext", + "Back of ID\nScanned"), + // Successful scan of a Passport that does not have NFC. + capThemeUtils.handleMessage(principalKey, "successPassport", + "Passport Scan Complete"), + // Successful scan of a Passport that does have NFC. + capThemeUtils.handleMessage(principalKey, "successPassportNFCNext", + "Passport Scanned"), + // Successful upload of final IDScan containing User-Confirmed ID Text. + capThemeUtils.handleMessage(principalKey, "successUserConfirmation", + "Photo ID Scan\nComplete"), + // Successful upload of the scanned NFC chip information. + capThemeUtils.handleMessage(principalKey, "successNFC", + "ID Scan Complete"), + // Case where a Retry is needed because the Face on the Photo ID did not Match + // the User's Face highly enough. + capThemeUtils.handleMessage(principalKey, "retryFaceDidNotMatch", + "Face Didn't Match\nHighly Enough"), + // Case where a Retry is needed because a Full ID was not detected with high + // enough confidence. + capThemeUtils.handleMessage(principalKey, "retryIDNotFullyVisible", + "ID Document\nNot Fully Visible"), + // Case where a Retry is needed because the OCR did not produce good enough + // results and the User should Retry with a better capture. + capThemeUtils.handleMessage(principalKey, "retryOCRResultsNotGoodEnough", + "ID Text Not Legible"), + // Case where there is likely no OCR Template installed for the document the + // User is attempting to scan. + capThemeUtils.handleMessage(principalKey, "retryIDTypeNotSupported", + "ID Type Mismatch\nPlease Try Again"), + // Case where NFC Scan was skipped due to the user's interaction or an + // unexpected error. + capThemeUtils.handleMessage(principalKey, "skipOrErrorNFC", + "ID Details\nUploaded")); + + success = idScanResultCallback.proceedToNextStep(scanResultBlob); + if (success) { + capFaceModule.processorPromise.resolve(true); + } + } else { + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to scan values processed!", + "FaceTecScanWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/android/src/main/java/com/capitual/processors/PhotoIDScanProcessor.java b/android/src/main/java/com/capitual/processors/PhotoIDScanProcessor.java new file mode 100644 index 0000000..a360ece --- /dev/null +++ b/android/src/main/java/com/capitual/processors/PhotoIDScanProcessor.java @@ -0,0 +1,260 @@ +package com.capitual.processors; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.capitual.reactnativecapfacesdk.ReactNativeCapfaceSdkModule; +import com.facebook.react.bridge.ReadableMap; +import com.facetec.sdk.FaceTecCustomization; +import com.facetec.sdk.FaceTecIDScanProcessor; +import com.facetec.sdk.FaceTecIDScanResult; +import com.facetec.sdk.FaceTecIDScanResultCallback; +import com.facetec.sdk.FaceTecIDScanStatus; +import com.facetec.sdk.FaceTecSessionActivity; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import com.capitual.processors.helpers.ThemeUtils; + +public class PhotoIDScanProcessor extends Processor implements FaceTecIDScanProcessor { + private boolean success = false; + private final String principalKey = "photoIdScanMessage"; + private final ReadableMap data; + private final ReactNativeCapfaceSdkModule capFaceModule; + private final ThemeUtils capThemeUtils = new ThemeUtils(); + + public PhotoIDScanProcessor(String sessionToken, Context context, ReactNativeCapfaceSdkModule capFaceModule, + ReadableMap data) { + this.capFaceModule = capFaceModule; + this.data = data; + + FaceTecCustomization.setIDScanUploadMessageOverrides( + // Upload of ID front-side has started. + capThemeUtils.handleMessage(principalKey, "frontSideUploadStarted", "Uploading\nEncrypted\nID Scan"), + // Upload of ID front-side is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "frontSideStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID front-side to the Server is complete. + capThemeUtils.handleMessage(principalKey, "frontSideUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID front-side is complete and we are waiting for the Server to + // finish processing and respond. + capThemeUtils.handleMessage(principalKey, "frontSideUploadCompleteAwaitingProcessing", + "Processing ID Scan"), + // Upload of ID back-side has started. + capThemeUtils.handleMessage(principalKey, "backSideUploadStarted", + "Uploading\nEncrypted\nBack of ID"), + // Upload of ID back-side is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "backSideStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID back-side to Server is complete. + capThemeUtils.handleMessage(principalKey, "backSideUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID back-side is complete and we are waiting for the Server to + // finish processing and respond. + capThemeUtils.handleMessage(principalKey, "backSideUploadCompleteAwaitingProcessing", + "Processing Back of ID"), + // Upload of User Confirmed Info has started. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadStarted", + "Uploading\nYour Confirmed Info"), + // Upload of User Confirmed Info is still uploading to Server after an extended + // period of time. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of User Confirmed Info to the Server is complete. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of User Confirmed Info is complete and we are waiting for the Server + // to finish processing and respond. + capThemeUtils.handleMessage(principalKey, "userConfirmedInfoUploadCompleteAwaitingProcessing", + "Processing"), + // Upload of NFC Details has started. + capThemeUtils.handleMessage(principalKey, "nfcUploadStarted", + "Uploading Encrypted\nNFC Details"), + // Upload of NFC Details is still uploading to Server after an extended period + // of time. + capThemeUtils.handleMessage(principalKey, "nfcStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of NFC Details to the Server is complete. + capThemeUtils.handleMessage(principalKey, "nfcUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of NFC Details is complete and we are waiting for the Server to finish + // processing and respond. + capThemeUtils.handleMessage(principalKey, "nfcUploadCompleteAwaitingProcessing", + "Processing\nNFC Details"), + // Upload of ID Details has started. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadStarted", + "Uploading Encrypted\nID Details"), + // Upload of ID Details is still uploading to Server after an extended period of + // time. + capThemeUtils.handleMessage(principalKey, "skippedNFCStillUploading", + "Still Uploading...\nSlow Connection"), + // Upload of ID Details to the Server is complete. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadCompleteAwaitingResponse", + "Upload Complete"), + // Upload of ID Details is complete and we are waiting for the Server to finish + // processing and respond. + capThemeUtils.handleMessage(principalKey, "skippedNFCUploadCompleteAwaitingProcessing", + "Processing\nID Details")); + + capFaceModule.sendEvent("onCloseModal", true); + FaceTecSessionActivity.createAndLaunchSession(context, PhotoIDScanProcessor.this, sessionToken); + } + + public void processIDScanWhileFaceTecSDKWaits(final FaceTecIDScanResult idScanResult, + final FaceTecIDScanResultCallback idScanResultCallback) { + capFaceModule.setLatestIDScanResult(idScanResult); + + if (idScanResult.getStatus() != FaceTecIDScanStatus.SUCCESS) { + NetworkingHelpers.cancelPendingRequests(); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Status is not success!", "FaceTecDifferentStatus"); + return; + } + + JSONObject parameters = new JSONObject(); + try { + if (this.data != null) { + parameters.put("data", new JSONObject(this.data.toHashMap())); + } + parameters.put("idScan", idScanResult.getIDScanBase64()); + + ArrayList frontImagesCompressedBase64 = idScanResult.getFrontImagesCompressedBase64(); + ArrayList backImagesCompressedBase64 = idScanResult.getBackImagesCompressedBase64(); + if (frontImagesCompressedBase64.size() > 0) { + parameters.put("idScanFrontImage", frontImagesCompressedBase64.get(0)); + } + if (backImagesCompressedBase64.size() > 0) { + parameters.put("idScanBackImage", backImagesCompressedBase64.get(0)); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to create JSON payload for upload."); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to create JSON payload for upload.", + "JSONError"); + } + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(Config.BaseURL + "/idscan-only") + .headers(Config.getHeaders("POST")) + .post(new ProgressRequestBody( + RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()), + new ProgressRequestBody.Listener() { + @Override + public void onUploadProgressChanged(long bytesWritten, long totalBytes) { + final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes); + idScanResultCallback.uploadProgress(uploadProgressPercent); + } + })) + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + boolean wasProcessed = responseJSON.getBoolean("wasProcessed"); + String scanResultBlob = responseJSON.getString("scanResultBlob"); + + if (wasProcessed) { + FaceTecCustomization.setIDScanResultScreenMessageOverrides( + // Successful scan of ID front-side (ID Types with no back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSide", + "ID Scan Complete"), + // Successful scan of ID front-side (ID Types that have a back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSideBackNext", + "Front of ID\nScanned"), + // Successful scan of ID front-side (ID Types that do have NFC but do not have a + // back-side). + capThemeUtils.handleMessage(principalKey, "successFrontSideNFCNext", + "Front of ID\nScanned"), + // Successful scan of the ID back-side (ID Types that do not have NFC). + capThemeUtils.handleMessage(principalKey, "successBackSide", + "ID Scan Complete"), + // Successful scan of the ID back-side (ID Types that do have NFC). + capThemeUtils.handleMessage(principalKey, "successBackSideNFCNext", + "Back of ID\nScanned"), + // Successful scan of a Passport that does not have NFC. + capThemeUtils.handleMessage(principalKey, "successPassport", + "Passport Scan Complete"), + // Successful scan of a Passport that does have NFC. + capThemeUtils.handleMessage(principalKey, "successPassportNFCNext", + "Passport Scanned"), + // Successful upload of final IDScan containing User-Confirmed ID Text. + capThemeUtils.handleMessage(principalKey, "successUserConfirmation", + "Photo ID Scan\nComplete"), + // Successful upload of the scanned NFC chip information. + capThemeUtils.handleMessage(principalKey, "successNFC", + "ID Scan Complete"), + // Case where a Retry is needed because the Face on the Photo ID did not Match + // the User's Face highly enough. + capThemeUtils.handleMessage(principalKey, "retryFaceDidNotMatch", + "Face Didn't Match\nHighly Enough"), + // Case where a Retry is needed because a Full ID was not detected with high + // enough confidence. + capThemeUtils.handleMessage(principalKey, "retryIDNotFullyVisible", + "ID Document\nNot Fully Visible"), + // Case where a Retry is needed because the OCR did not produce good enough + // results and the User should Retry with a better capture. + capThemeUtils.handleMessage(principalKey, "retryOCRResultsNotGoodEnough", + "ID Text Not Legible"), + // Case where there is likely no OCR Template installed for the document the + // User is attempting to scan. + capThemeUtils.handleMessage(principalKey, "retryIDTypeNotSupported", + "ID Type Mismatch\nPlease Try Again"), + // Case where NFC Scan was skipped due to the user's interaction or an + // unexpected error. + capThemeUtils.handleMessage(principalKey, "skipOrErrorNFC", + "ID Details\nUploaded")); + + success = idScanResultCallback.proceedToNextStep(scanResultBlob); + if (success) { + capFaceModule.processorPromise.resolve(true); + } + } else { + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("FaceTec SDK wasn't have to values processed!", + "FaceTecWasntProcessed"); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting to parse JSON result.", + "JSONError"); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + idScanResultCallback.cancel(); + capFaceModule.sendEvent("onCloseModal", false); + capFaceModule.processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + }); + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/android/src/main/java/com/capitual/processors/Processor.java b/android/src/main/java/com/capitual/processors/Processor.java new file mode 100644 index 0000000..075b0ac --- /dev/null +++ b/android/src/main/java/com/capitual/processors/Processor.java @@ -0,0 +1,5 @@ +package com.capitual.processors; + +public abstract class Processor { + public abstract boolean isSuccess(); +} diff --git a/android/src/main/java/com/capitual/processors/ThemeHelpers.java b/android/src/main/java/com/capitual/processors/ThemeHelpers.java new file mode 100644 index 0000000..b5f88b1 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/ThemeHelpers.java @@ -0,0 +1,50 @@ +package com.capitual.processors; + +import com.capitual.reactnativecapfacesdk.R; + +import com.facebook.react.bridge.ReadableMap; + +import com.facetec.sdk.FaceTecCustomization; +import com.facetec.sdk.FaceTecSDK; + +public class ThemeHelpers { + public static void setAppTheme(ReadableMap options) { + Config.setTheme(options); + Config.currentCustomization = getCustomizationForTheme(); + Config.currentLowLightCustomization = getLowLightCustomizationForTheme(); + Config.currentDynamicDimmingCustomization = getDynamicDimmingCustomizationForTheme(); + + FaceTecSDK.setCustomization(Config.currentCustomization); + FaceTecSDK.setLowLightCustomization(Config.currentLowLightCustomization); + FaceTecSDK.setDynamicDimmingCustomization(Config.currentDynamicDimmingCustomization); + } + + public static FaceTecCustomization getCustomizationForTheme() { + FaceTecCustomization currentCustomization = new FaceTecCustomization(); + currentCustomization = Config.retrieveConfigurationWizardCustomization(); + currentCustomization + .getIdScanCustomization().customNFCStartingAnimation = R.drawable.facetec_nfc_starting_animation; + currentCustomization + .getIdScanCustomization().customNFCScanningAnimation = R.drawable.facetec_nfc_scanning_animation; + currentCustomization + .getIdScanCustomization().customNFCCardStartingAnimation = R.drawable.facetec_nfc_card_starting_animation; + currentCustomization + .getIdScanCustomization().customNFCCardScanningAnimation = R.drawable.facetec_nfc_card_scanning_animation; + + return currentCustomization; + } + + static FaceTecCustomization getLowLightCustomizationForTheme() { + FaceTecCustomization currentLowLightCustomization = getCustomizationForTheme(); + currentLowLightCustomization = Config.retrieveLowLightConfigurationWizardCustomization(); + + return currentLowLightCustomization; + } + + static FaceTecCustomization getDynamicDimmingCustomizationForTheme() { + FaceTecCustomization currentDynamicDimmingCustomization = getCustomizationForTheme(); + currentDynamicDimmingCustomization = Config.retrieveDynamicDimmingConfigurationWizardCustomization(); + + return currentDynamicDimmingCustomization; + } +} diff --git a/android/src/main/java/com/capitual/processors/helpers/ThemeUtils.java b/android/src/main/java/com/capitual/processors/helpers/ThemeUtils.java new file mode 100644 index 0000000..26baa75 --- /dev/null +++ b/android/src/main/java/com/capitual/processors/helpers/ThemeUtils.java @@ -0,0 +1,103 @@ +package com.capitual.processors.helpers; + +import com.facebook.react.bridge.ReactApplicationContext; + +import com.facetec.sdk.*; + +import android.graphics.Color; +import android.util.Log; + +import com.capitual.processors.Config; + +import org.json.JSONObject; + +public class ThemeUtils { + private static ReactApplicationContext reactContext; + + private Boolean themeAndKeyIsntExists(String key) { + return Config.Theme == null || !Config.Theme.hasKey(key) || Config.Theme.isNull(key); + } + + private int parseColor(String key, int defaultColor) { + final String color = Config.Theme.getString(key); + return color.isEmpty() ? defaultColor : Color.parseColor(color); + } + + public void setReactContext(ReactApplicationContext context) { + reactContext = context; + } + + public String handleMessage(String key, String child, String defaultMessage) { + try { + if (this.themeAndKeyIsntExists(key)) { + return defaultMessage; + } + final JSONObject rootObject = new JSONObject(Config.Theme.toHashMap()); + final JSONObject message = rootObject.getJSONObject(key); + final Boolean childIsntExists = !message.has(child) || message.isNull(child); + return childIsntExists ? defaultMessage : message.getString(child); + } catch (Exception error) { + Log.d("JSONError", "Some error occurred while parsing JSON!"); + return defaultMessage; + } + } + + public int handleColor(String key) { + final int defaultColor = Color.parseColor("#ffffff"); + if (this.themeAndKeyIsntExists(key)) { + return defaultColor; + } + return this.parseColor(key, defaultColor); + } + + public int handleColor(String key, String defaultColor) { + if (this.themeAndKeyIsntExists(key)) { + return Color.parseColor(defaultColor); + } + return this.parseColor(key, Color.parseColor(defaultColor)); + } + + public int handleBorderRadius(String key) { + final int defaultBorderRadius = 20; + if (this.themeAndKeyIsntExists(key)) { + return defaultBorderRadius; + } + final int borderRadius = Config.Theme.getInt(key); + return borderRadius < 0 ? defaultBorderRadius : borderRadius; + } + + public int handleImage(String key, int defaultImage) { + if (this.themeAndKeyIsntExists(key) || reactContext == null) { + return defaultImage; + } + final String imageName = Config.Theme.getString(key); + if (imageName.isEmpty()) { + return defaultImage; + } + final String packageName = reactContext.getPackageName(); + final int resourceId = reactContext.getResources().getIdentifier(imageName, "drawable", packageName); + return resourceId == 0 ? defaultImage : resourceId; + } + + public FaceTecCancelButtonCustomization.ButtonLocation handleButtonLocation(String key) { + final FaceTecCancelButtonCustomization.ButtonLocation defaultLocation = FaceTecCancelButtonCustomization.ButtonLocation.TOP_RIGHT; + if (this.themeAndKeyIsntExists(key)) { + return defaultLocation; + } + final String buttonLocation = Config.Theme.getString(key); + if (buttonLocation.isEmpty()) { + return defaultLocation; + } + + switch (buttonLocation) { + case "TOP_RIGHT": + return FaceTecCancelButtonCustomization.ButtonLocation.TOP_RIGHT; + case "TOP_LEFT": + return FaceTecCancelButtonCustomization.ButtonLocation.TOP_LEFT; + case "DISABLED": + return FaceTecCancelButtonCustomization.ButtonLocation.DISABLED; + default: + return defaultLocation; + } + } +} diff --git a/android/src/main/java/com/capitual/reactnativecapfacesdk/ReactNativeCapfaceSdkModule.java b/android/src/main/java/com/capitual/reactnativecapfacesdk/ReactNativeCapfaceSdkModule.java index 46912ce..2c13e52 100644 --- a/android/src/main/java/com/capitual/reactnativecapfacesdk/ReactNativeCapfaceSdkModule.java +++ b/android/src/main/java/com/capitual/reactnativecapfacesdk/ReactNativeCapfaceSdkModule.java @@ -1,19 +1,52 @@ package com.capitual.reactnativecapfacesdk; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.capitual.processors.helpers.ThemeUtils; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.module.annotations.ReactModule; +import android.util.Log; + +import com.facebook.react.bridge.ReadableMap; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.Call; +import okhttp3.Callback; + +import com.capitual.processors.*; + +import static java.util.UUID.randomUUID; + +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facetec.sdk.*; + @ReactModule(name = ReactNativeCapfaceSdkModule.NAME) public class ReactNativeCapfaceSdkModule extends ReactContextBaseJavaModule { public static final String NAME = "ReactNativeCapfaceSdk"; + private static final ThemeUtils capThemeUtils = new ThemeUtils(); + private final ReactApplicationContext reactContext; + private boolean isInitialized = false; + private boolean isSessionPreparingToLaunch = false; + private String latestExternalDatabaseRefID = ""; + public Promise processorPromise; + public Processor latestProcessor; + public FaceTecSessionResult latestSessionResult; + public FaceTecIDScanResult latestIDScanResult; - public ReactNativeCapfaceSdkModule(ReactApplicationContext reactContext) { - super(reactContext); + public ReactNativeCapfaceSdkModule(ReactApplicationContext context) { + super(context); + reactContext = context; + capThemeUtils.setReactContext(context); } @Override @@ -22,11 +55,263 @@ public String getName() { return NAME; } + private boolean isDeveloperMode(Map params) { + if (params.containsKey("isDeveloperMode")) { + return params.get("isDeveloperMode").toString().equals("true"); + } + return false; + } + + private void handleFaceTecSDKConfiguration(Map params, ReadableMap headers) { + try { + Config.setDevice(params.get("device").toString()); + Config.setUrl(params.get("url").toString()); + Config.setKey(params.get("key").toString()); + Config.setProductionKeyText(params.get("productionKey").toString()); + Config.setHeaders(headers); + } catch (Exception error) { + Log.d("Capitual - SDK", "Error while setting FaceTecSDK configuration!"); + } + } + + @ReactMethod + public void initializeSdk(ReadableMap params, ReadableMap headers, com.facebook.react.bridge.Callback callback) { + if (params == null) { + isInitialized = false; + callback.invoke(false); + Log.d("Capitual - SDK", "No parameters provided!"); + return; + } + + handleFaceTecSDKConfiguration(params.toHashMap(), headers); + + if (Config.hasConfig()) { + Config.initializeFaceTecSDKFromAutogeneratedConfig( + reactContext, + isDeveloperMode(params.toHashMap()), + new FaceTecSDK.InitializeCallback() { + @Override + public void onCompletion(final boolean successful) { + isInitialized = successful; + callback.invoke(successful); + if (!successful) { + String status = FaceTecSDK.getStatus(reactContext).toString(); + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + } else { + Log.d("Capitual - SDK", "FaceTecSDK initialized!"); + } + } + }); + } else { + isInitialized = false; + callback.invoke(false); + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + } + + this.handleTheme(Config.Theme); + } + + interface SessionTokenCallback { + void onSessionTokenReceived(String sessionToken); + } + + public void getSessionToken(final SessionTokenCallback sessionTokenCallback) { + okhttp3.Request request = new okhttp3.Request.Builder() + .headers(Config.getHeaders("GET")) + .url(Config.BaseURL + "/session-token") + .get() + .build(); + + NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + Log.d("Capitual - HTTPS", "Exception raised while attempting HTTPS call."); + if (processorPromise != null) { + processorPromise.reject("Exception raised while attempting HTTPS call.", "HTTPSError"); + } + } + + @Override + public void onResponse(Call call, okhttp3.Response response) throws IOException { + String responseString = response.body().string(); + response.body().close(); + try { + JSONObject responseJSON = new JSONObject(responseString); + if (responseJSON.has("sessionToken")) { + sessionTokenCallback.onSessionTokenReceived(responseJSON.getString("sessionToken")); + } else { + final String errorMessage = responseJSON.has("errorMessage") + ? responseJSON.getString("errorMessage") + : "Response JSON is missing sessionToken."; + if (processorPromise != null) { + processorPromise.reject(errorMessage, "JSONError"); + } + } + } catch (JSONException e) { + e.printStackTrace(); + Log.d("Capitual - JSON", "Exception raised while attempting to parse JSON result."); + if (processorPromise != null) { + processorPromise.reject("Exception raised while attempting to parse JSON result.", "JSONError"); + } + } + } + }); + } + + public void setLatestSessionResult(FaceTecSessionResult sessionResult) { + this.latestSessionResult = sessionResult; + } + + public void setLatestIDScanResult(FaceTecIDScanResult idScanResult) { + this.latestIDScanResult = idScanResult; + } + + public void resetLatestResults() { + this.latestSessionResult = null; + this.latestIDScanResult = null; + } + + public void setLatestExternalDatabaseRefID(String externalDatabaseRefID) { + this.latestExternalDatabaseRefID = externalDatabaseRefID; + } + + public String getLatestExternalDatabaseRefID() { + return this.latestExternalDatabaseRefID; + } + + public void setProcessorPromise(Promise promise) { + this.processorPromise = promise; + } + + public void sendEvent(@NonNull String eventName, @Nullable Boolean eventValue) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, eventValue); + } + + @ReactMethod + public void addListener(String eventName) { + } + + @ReactMethod + public void removeListeners(Integer count) { + } + + @ReactMethod + public void handleLivenessCheck(ReadableMap data, Promise promise) { + setProcessorPromise(promise); + if (!isInitialized) { + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + this.processorPromise.reject("FaceTecSDK doesn't initialized!", "FaceTecDoenstInitialized"); + return; + } + + isSessionPreparingToLaunch = true; + + getSessionToken(new SessionTokenCallback() { + @Override + public void onSessionTokenReceived(String sessionToken) { + resetLatestResults(); + isSessionPreparingToLaunch = false; + latestProcessor = new LivenessCheckProcessor(sessionToken, reactContext.getCurrentActivity(), + ReactNativeCapfaceSdkModule.this, data); + } + }); + } + + @ReactMethod + public void handleEnrollUser(ReadableMap data, Promise promise) { + setProcessorPromise(promise); + if (!isInitialized) { + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + this.processorPromise.reject("FaceTecSDK doesn't initialized!", "FaceTecDoenstInitialized"); + return; + } + + isSessionPreparingToLaunch = true; + + getSessionToken(new SessionTokenCallback() { + @Override + public void onSessionTokenReceived(String sessionToken) { + resetLatestResults(); + isSessionPreparingToLaunch = false; + setLatestExternalDatabaseRefID("android_capitual_app_" + randomUUID()); + latestProcessor = new EnrollmentProcessor(sessionToken, reactContext.getCurrentActivity(), + ReactNativeCapfaceSdkModule.this, data); + } + }); + } + + @ReactMethod + public void handleAuthenticateUser(ReadableMap data, Promise promise) { + setProcessorPromise(promise); + if (!isInitialized) { + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + this.processorPromise.reject("FaceTecSDK doesn't initialized!", "FaceTecDoenstInitialized"); + return; + } + + isSessionPreparingToLaunch = true; + + getSessionToken(new SessionTokenCallback() { + @Override + public void onSessionTokenReceived(String sessionToken) { + resetLatestResults(); + isSessionPreparingToLaunch = false; + latestProcessor = new AuthenticateProcessor(sessionToken, reactContext.getCurrentActivity(), + ReactNativeCapfaceSdkModule.this, data); + } + }); + } + + @ReactMethod + public void handlePhotoIDMatch(ReadableMap data, Promise promise) { + setProcessorPromise(promise); + if (!isInitialized) { + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + this.processorPromise.reject("FaceTecSDK doesn't initialized!", "FaceTecDoenstInitialized"); + return; + } + + isSessionPreparingToLaunch = true; + + getSessionToken(new SessionTokenCallback() { + @Override + public void onSessionTokenReceived(String sessionToken) { + resetLatestResults(); + isSessionPreparingToLaunch = false; + setLatestExternalDatabaseRefID("android_capitual_app_" + randomUUID()); + latestProcessor = new PhotoIDMatchProcessor(sessionToken, reactContext.getCurrentActivity(), + ReactNativeCapfaceSdkModule.this, data); + } + }); + } + + @ReactMethod + public void handlePhotoIDScan(ReadableMap data, Promise promise) { + setProcessorPromise(promise); + if (!isInitialized) { + Log.d("Capitual - SDK", "FaceTecSDK doesn't initialized!"); + this.processorPromise.reject("FaceTecSDK doesn't initialized!", "FaceTecDoenstInitialized"); + return; + } + + isSessionPreparingToLaunch = true; + + getSessionToken(new SessionTokenCallback() { + @Override + public void onSessionTokenReceived(String sessionToken) { + resetLatestResults(); + isSessionPreparingToLaunch = false; + latestProcessor = new PhotoIDScanProcessor(sessionToken, reactContext.getCurrentActivity(), + ReactNativeCapfaceSdkModule.this, data); + } + }); + } - // Example method - // See https://reactnative.dev/docs/native-modules-android @ReactMethod - public void multiply(double a, double b, Promise promise) { - promise.resolve(a * b); + public void handleTheme(ReadableMap options) { + ThemeHelpers.setAppTheme(options); } } diff --git a/android/src/main/res/anim/custom_check_animation.xml b/android/src/main/res/anim/custom_check_animation.xml new file mode 100644 index 0000000..5d0ddf2 --- /dev/null +++ b/android/src/main/res/anim/custom_check_animation.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/android/src/main/res/anim/custom_circle_stroke_end_animation.xml b/android/src/main/res/anim/custom_circle_stroke_end_animation.xml new file mode 100644 index 0000000..3242658 --- /dev/null +++ b/android/src/main/res/anim/custom_circle_stroke_end_animation.xml @@ -0,0 +1,11 @@ + + + + diff --git a/android/src/main/res/anim/custom_circle_stroke_path_animation.xml b/android/src/main/res/anim/custom_circle_stroke_path_animation.xml new file mode 100644 index 0000000..054a344 --- /dev/null +++ b/android/src/main/res/anim/custom_circle_stroke_path_animation.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/android/src/main/res/anim/custom_circle_stroke_width_animation.xml b/android/src/main/res/anim/custom_circle_stroke_width_animation.xml new file mode 100644 index 0000000..f65dd95 --- /dev/null +++ b/android/src/main/res/anim/custom_circle_stroke_width_animation.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/android/src/main/res/anim/custom_cross_1_animation.xml b/android/src/main/res/anim/custom_cross_1_animation.xml new file mode 100644 index 0000000..a386102 --- /dev/null +++ b/android/src/main/res/anim/custom_cross_1_animation.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/android/src/main/res/anim/custom_cross_2_animation.xml b/android/src/main/res/anim/custom_cross_2_animation.xml new file mode 100644 index 0000000..cdb40bc --- /dev/null +++ b/android/src/main/res/anim/custom_cross_2_animation.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/android/src/main/res/anim/retry_custom_animation.xml b/android/src/main/res/anim/retry_custom_animation.xml new file mode 100644 index 0000000..92d5c9f --- /dev/null +++ b/android/src/main/res/anim/retry_custom_animation.xml @@ -0,0 +1,14 @@ + + + + diff --git a/android/src/main/res/animator/zoom_enter_right_slow.xml b/android/src/main/res/animator/zoom_enter_right_slow.xml new file mode 100644 index 0000000..d863bce --- /dev/null +++ b/android/src/main/res/animator/zoom_enter_right_slow.xml @@ -0,0 +1,6 @@ + diff --git a/android/src/main/res/animator/zoom_exit_left_slow.xml b/android/src/main/res/animator/zoom_exit_left_slow.xml new file mode 100644 index 0000000..931f909 --- /dev/null +++ b/android/src/main/res/animator/zoom_exit_left_slow.xml @@ -0,0 +1,6 @@ + diff --git a/android/src/main/res/drawable-nodpi/facetec_active_torch.png b/android/src/main/res/drawable-nodpi/facetec_active_torch.png new file mode 100644 index 0000000..c8f98fc Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_active_torch.png differ diff --git a/android/src/main/res/drawable-nodpi/facetec_camera.png b/android/src/main/res/drawable-nodpi/facetec_camera.png new file mode 100644 index 0000000..cbab01d Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_camera.png differ diff --git a/android/src/main/res/drawable-nodpi/facetec_cancel.png b/android/src/main/res/drawable-nodpi/facetec_cancel.png new file mode 100644 index 0000000..10e83c2 Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_cancel.png differ diff --git a/android/src/main/res/drawable-nodpi/facetec_inactive_torch.png b/android/src/main/res/drawable-nodpi/facetec_inactive_torch.png new file mode 100644 index 0000000..f71c317 Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_inactive_torch.png differ diff --git a/android/src/main/res/drawable-nodpi/facetec_review.png b/android/src/main/res/drawable-nodpi/facetec_review.png new file mode 100644 index 0000000..d93830b Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_review.png differ diff --git a/android/src/main/res/drawable-nodpi/facetec_your_app_logo.png b/android/src/main/res/drawable-nodpi/facetec_your_app_logo.png new file mode 100644 index 0000000..f522212 Binary files /dev/null and b/android/src/main/res/drawable-nodpi/facetec_your_app_logo.png differ diff --git a/android/src/main/res/drawable-nodpi/splash_screen.xml b/android/src/main/res/drawable-nodpi/splash_screen.xml new file mode 100644 index 0000000..4ba0f2a --- /dev/null +++ b/android/src/main/res/drawable-nodpi/splash_screen.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/src/main/res/drawable/facetec_additional_review_animation.xml b/android/src/main/res/drawable/facetec_additional_review_animation.xml new file mode 100644 index 0000000..c1f38dc --- /dev/null +++ b/android/src/main/res/drawable/facetec_additional_review_animation.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/facetec_nfc_card_scanning_animation.xml b/android/src/main/res/drawable/facetec_nfc_card_scanning_animation.xml new file mode 100644 index 0000000..91bcce7 --- /dev/null +++ b/android/src/main/res/drawable/facetec_nfc_card_scanning_animation.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/facetec_nfc_card_starting_animation.xml b/android/src/main/res/drawable/facetec_nfc_card_starting_animation.xml new file mode 100644 index 0000000..a170574 --- /dev/null +++ b/android/src/main/res/drawable/facetec_nfc_card_starting_animation.xml @@ -0,0 +1,860 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/facetec_nfc_scanning_animation.xml b/android/src/main/res/drawable/facetec_nfc_scanning_animation.xml new file mode 100644 index 0000000..638254d --- /dev/null +++ b/android/src/main/res/drawable/facetec_nfc_scanning_animation.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/facetec_nfc_starting_animation.xml b/android/src/main/res/drawable/facetec_nfc_starting_animation.xml new file mode 100644 index 0000000..2f3ba94 --- /dev/null +++ b/android/src/main/res/drawable/facetec_nfc_starting_animation.xml @@ -0,0 +1,2049 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/rn_edit_text_material.xml b/android/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000..ad39ba5 --- /dev/null +++ b/android/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/android/src/main/res/mipmap-hdpi/appicon.png b/android/src/main/res/mipmap-hdpi/appicon.png new file mode 100644 index 0000000..42e66bb Binary files /dev/null and b/android/src/main/res/mipmap-hdpi/appicon.png differ diff --git a/android/src/main/res/mipmap-hdpi/ic_launcher.png b/android/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/android/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/src/main/res/mipmap-mdpi/appicon.png b/android/src/main/res/mipmap-mdpi/appicon.png new file mode 100644 index 0000000..1b8e1ec Binary files /dev/null and b/android/src/main/res/mipmap-mdpi/appicon.png differ diff --git a/android/src/main/res/mipmap-mdpi/ic_launcher.png b/android/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/android/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/src/main/res/mipmap-xhdpi/appicon.png b/android/src/main/res/mipmap-xhdpi/appicon.png new file mode 100644 index 0000000..858aee5 Binary files /dev/null and b/android/src/main/res/mipmap-xhdpi/appicon.png differ diff --git a/android/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/android/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/src/main/res/mipmap-xxhdpi/appicon.png b/android/src/main/res/mipmap-xxhdpi/appicon.png new file mode 100644 index 0000000..0a9fb11 Binary files /dev/null and b/android/src/main/res/mipmap-xxhdpi/appicon.png differ diff --git a/android/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/android/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/src/main/res/mipmap-xxxhdpi/appicon.png b/android/src/main/res/mipmap-xxxhdpi/appicon.png new file mode 100644 index 0000000..134ee6e Binary files /dev/null and b/android/src/main/res/mipmap-xxxhdpi/appicon.png differ diff --git a/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/src/main/res/strings.xml b/android/src/main/res/strings.xml new file mode 100644 index 0000000..8014b13 --- /dev/null +++ b/android/src/main/res/strings.xml @@ -0,0 +1,5 @@ + + + 認証する + 登録します + \ No newline at end of file diff --git a/android/src/main/res/values-ja/strings.xml b/android/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..8014b13 --- /dev/null +++ b/android/src/main/res/values-ja/strings.xml @@ -0,0 +1,5 @@ + + + 認証する + 登録します + \ No newline at end of file diff --git a/android/src/main/res/values-pt-rBR/strings.xml b/android/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..102588b --- /dev/null +++ b/android/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,5 @@ + + + Inscrever + Autenticar + \ No newline at end of file diff --git a/android/src/main/res/values-pt-rPT/strings.xml b/android/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000..102588b --- /dev/null +++ b/android/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,5 @@ + + + Inscrever + Autenticar + \ No newline at end of file diff --git a/android/src/main/res/values-w820dp/dimens.xml b/android/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/android/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml new file mode 100644 index 0000000..24cc862 --- /dev/null +++ b/android/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #808080 + #000000 + #3F51B5 + #303F9F + #FF4081 + diff --git a/android/src/main/res/values/dimens.xml b/android/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/android/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..f8fc825 --- /dev/null +++ b/android/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + FaceTec SDK + Enroll User + Authenticate User + 3D Liveness Check + Match Face to ID + Scan & OCR ID + View Session Result + View Audit Trail + Design Showcase + Network Connection Slow… + diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml new file mode 100644 index 0000000..9abaf76 --- /dev/null +++ b/android/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/src/main/res/xml/network_security_config.xml b/android/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..9d3dd94 --- /dev/null +++ b/android/src/main/res/xml/network_security_config.xml @@ -0,0 +1,14 @@ + + + + + + + + + capitual.io + + + capitual.app + + diff --git a/example/package.json b/example/package.json index c2db17c..fb00972 100644 --- a/example/package.json +++ b/example/package.json @@ -12,6 +12,7 @@ "md5": "^2.3.0", "react": "18.2.0", "react-native": "0.72.4", + "@capitual/react-native-capface-sdk": "file:../capitual-react-native-capface-sdk-0.1.0.tgz", "react-native-device-info": "^10.9.0" }, "devDependencies": { diff --git a/package.json b/package.json index e4a5e69..a84a215 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,9 @@ "prepack": "bob build", "release": "release-it", "example": "npm run --cwd example", - "build:android": "cd example/android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a", - "build:ios": "cd example/ios && xcodebuild -workspace ReactNativeCapfaceSdkExample.xcworkspace -scheme ReactNativeCapfaceSdkExample -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO", "bootstrap": "npm run example && npm install && npm run example pods", - "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build" + "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build", + "podspec": "pod ipc spec capitual-react-native-capface-sdk.podspec" }, "keywords": [ "react-native", diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx deleted file mode 100644 index bf84291..0000000 --- a/src/__tests__/index.test.tsx +++ /dev/null @@ -1 +0,0 @@ -it.todo('write a test');