<script src="https://vision-cdn.trustingsocial.com/tvweb-sdk/<VERSION>/build/tvweb-sdk.standalone.min.js"></script>
NOTE:
jsdelivr
/unpkg
CDN is not recommended as production builds may be affected by the stability of the CDN.jsDeliver
/unpkg
CDN, @latest
version is not recommended as production builds will be directly affected by version updates.public/index.html
file of your project.<!-- add script to public/index.html -->
<script src="https://vision-cdn.trustingsocial.com/tvweb-sdk/<VERSION>/build/tvweb-sdk.standalone.min.js"></script>
Full example code here
const tv = new TVWebSDK.SDK({
container: document.getElementById("container"),
lang: "vi",
enableAntiDebug: false,
});
// Main functions:
tv.runWarmUpReadIDCard(); // deprecated from version 5.7.4, please use runPreloadEKYCResources to replace
tv.runPreloadEKYCResources(); // Since 5.7.4+
tv.readIDCardUIOnly(params);
tv.readIDCardWithApiCall(params);
tv.livenessDetection(params);
import React, { useEffect, useRef } from "react";
function TVSDKExample() {
const refTVWebSDK = useRef(null);
useEffect(() => {
refTVWebSDK.current = new TVWebSDK.SDK({
container: document.getElementById("web-sdk-container"),
lang: "vi",
});
}, []);
// the rest example please follow Vanilla JS above
return <div id="web-sdk-container" />;
}
// config
// in angular.json, add this to script
"scripts": [
...
"node_modules/@tsocial/tvweb-sdk/build/tvweb-sdk.standalone.min.js"
...
]
// usage
declare var TVWebSDK: any;
// the rest example please follow Vanilla JS above
// Please follow Vanilla JS above
Example:
const tv = new TVWebSDK.SDK({
container: document.getElementById("container"),
lang: "vi",
enableAntiDebug: false,
});
Required | Type | Default | Description | |
---|---|---|---|---|
container | yes | DOM Element | DOM element to render the UI | |
lang | no | string | en | language to display (en or vi ) |
country | no | string | vn | country |
assetRoot | no | string | Default value is Trusting Social's CDN | where to load images/icons. |
enableAntiDebug | no | boolean | true | if enabled, sdk will try to prevent users from opening devtools in browsers |
customUrls | no | object | default custom urls | Besides assets, SDK also needs to load external resources (models, libraries...). This param allows to customize where to load those resources. |
resourceRoot | no | string | Default value is Trusting Social's CDN | Since version 5.7.8+, use this in case the client wants to bundle all resources from a root URL. |
warmupMessage | no | object | { en: 'Warming up... Please wait a moment', vi: 'Đang khởi động, vui lòng chờ trong giây lát...' } | Since version 5.8.3 |
themeVersion | no | string | v2 | This allows you to change between SDK available themes. Please refer to this section. |
enableVoice | no | boolean | false | Available from version 5.25.0. Enable voice instructions for SDK, only applied for Read ID Card, Flash Liveness and Passive Liveness V2 |
Note:
For locale customization with lang
and country
, please refer to this section.
The client can customize resource urls via customUrls
parameter.
How to customize these urls by your CDN. Please reference here
const customUrls = {
blazefaceModelUrl:
"https://storage.googleapis.com/tfhub-tfjs-modules/tensorflow/tfjs-model/blazeface/1/default/1/model.json",
opencvUrl:
"https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/libs/opencv-4.6.0.js",
idCardModelUrl:
"https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/models/id-card/tflite/model-v3.tflite",
wechatQRModelsUrl:
"https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/wechat_qr_models/",
tfScriptUrls: {
tfUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js",
tfBackendWasmUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf-backend-wasm.js",
tfBackendCpuUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]",
tfBackendWebGlUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]",
tfLiteUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf-tflite.min.js",
blazefaceScriptUrl:
"https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]",
},
};
// And pass it to SDK via init method
const tv = new TVWebSDK.SDK({
// ...
customUrls: customUrls,
});
For these specific resources blazefaceModelUrl
, opencvUrl
, idCardModelUrl
, tfUrl
, tfBackendWasmUrl
, tfBackendCpuUrl
, tfBackendWebGlUrl
, tfLiteUrl
, blazefaceScriptUrl
and SDK's resources (JS lib and assets):
Please contact the administrator to get the full resources tree and we will help the client to publish it to the client's CDN.
Note: We recommend the client use this option.
For example: after the resources are ready on your CDN. please use these settings.
const myCdnUrlPrefix = "<YOUR CDN URL>/";
const customUrls = {
blazefaceModelUrl: `${myCdnUrlPrefix}models/1.0.0/blazeface/model.json`,
opencvUrl: `${myCdnUrlPrefix}opencv/4.6.0/cv.js`,
idCardModelUrl: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/model-v3.tflite`,
wechatQRModelsUrl: `${myCdnUrlPrefix}models/1.0.0/webchat_qr/`,
tfScriptUrls: {
tfUrl: `${myCdnUrlPrefix}tfjs/3.20.0/tf.min.js`,
tfBackendWasmUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-wasm.js`,
tfBackendCpuUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-cpu.js`,
tfBackendWebGlUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-webgl.js`,
tfLiteUrl: `${myCdnUrlPrefix}tflite/0.0.1-alpha.8/tflite.min.js`,
blazefaceScriptUrl: `${myCdnUrlPrefix}blazeface/0.0.7/bf.js`,
},
idCardModelUrls: {
model_vn: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/id_card_detector_v2_lite.tflite`,
model_ph: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/ph_card_model.tflite`,
},
};
const sdkVersion = "5.12.4";
const tv = new TVWebSDK.SDK({
// other setting as SDK defined
assetRoot: myCdnUrlPrefix + "tvweb-sdk/" + sdkVersion + "/assets",
customUrls: customUrls,
});
Since version 5.7.9+: Client can use this option if the client host all resources from a CDN
const myCdnUrlPrefix = "YOUR CDN URL";
const sdkVersion = "5.12.4";
const tv = new TVWebSDK.SDK({
// other setting as SDK defined
assetRoot: myCdnUrlPrefix + "tvweb-sdk/" + sdkVersion + "/assets",
resourceRoot: myCdnUrlPrefix, // only available from 5.7.9+
});
For the best user experience, this function should be called before calling readIDCardUIOnly
or readIDCardWithApiCall
to preload resources and warm up models.
That way, this long process can be skipped when users start the journey.
Note: This method was deprecated from version 5.7.4. You can use runPreloadEKYCResources to replace it.
To start a UI flow where we can get id card info from users (images, QR code, recorded videos...)
Params
Required | Type | Default | Description | |
---|---|---|---|---|
clientSettings | yes | object | Settings to run SDK features. This object should have the same shape as settings from this api | |
onError | yes | function | Callback when errors happen. An error object will be returned. | |
steps | no | object | TVWebSDK.defaultReadIDCardSteps | UI flow configuration |
allowedCardTypes | no | array | [] | (Only works if enabled in settings) SDK only allows certain card types. E.g. if it has value of ['vn.cccd'] , SDK will alert that only CCCD card is allowed. Default [] or not pass in this value, SDK will accept all card types. Note: currently only apply for Vietnam from version 5.16.3, available values are listed below |
detectIdCard | no | function | () => Promise.resolve({ card_label: '' }) | This function receives { cardType, image: { blob, encrypted: { hex: <encrypted file encoded in hex string format> } }, cardSide } and should return or resolve to this object { card_label: <CARD_LABEL>} . Api to detect card label can be found here |
onStepDone | no | async function | Empty function | Callback every time users complete a step. onStepDoneResult will be returned. When you handle api in this function, please use async funtion (return Promise or using await) to wait the api complete before SDK move to next step, otherwise SDK will process to next step while the api is loading. |
outputEncryptionSettings | no | object | { key: '' } | If key is provided, output files will be encrypted and returned via encrypted field. Please see here. |
logCredentials | no | object | {} | Config for tracking user's behavior while using |
title | no | string | '' | Only available for some custom build. Title of IDCard screen. |
customTexts | no | object | {} | Only available from SDK version 5.21.0. Please refer to this section |
onClose | no | function | Empty function | Callback when users click the X button |
flipVideoHorizontal | no | boolean | null | Flip video horizontally. Please refer to this section |
Card type | Description |
---|---|
vn.national_id | CMND, CCCD, VN Passport |
vn.cmnd_old | CMND old version |
vn.cmnd_new | CMND |
vn.cccd | CCCD |
vn.cccd_new | CCCD with chip |
vn.tcc | TCC |
vn.passport | VN Passport |
ph.national_id | PH National ID card |
global.passport | Global passport |
onStepDoneResult
has this shape (some fields may be missing depending on the settings and the specific step):
{
stepNumber: number, // the current step number
cardSide, // card side of current step
cardType, // card type of current step
image: {
blob, // the captured image of ID card
encrypted: {
hex: string // will be returned if outputEncryptionSettings.key is provided
}
},
error, // error of the captured image (if any). usually it is `id_detector_error_blur`, please check `Possible Error Objects` below.
qrScannedResult: {
result: string, // the detected QR code
validateResult: {
valid: boolean // whether the detected QR code follows new Vietnamese ID pattern
},
image: {
blob, // QR image
encrypted: {
hex: string // will be returned if outputEncryptionSettings.key is provided
}
},
recordedVideos: [ // recorded videos of QR capture process
{
id: string, // ignore
frames: [] // array of frames in base64 format
}
]
},
recordedVideos: [ // recorded videos of the whole process
{
id: string, // ignore
frames: [] // array of frames in base64 format
}
],
apiResult // will be returned only if the SDK is called APIs by itself
}
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes capture ID card
function handleStepDoneIDCard({
stepNumber,
cardSide,
cardType,
image,
qrScannedResult,
recordedVideos,
apiResult,
}) {
console.log("card side:", cardSide); // Front or back
console.log("card type:", cardType); // cmnd_old, cccd, cccd_new
console.log("image", image);
// The client should upload the image to server and call API to TrustingSocial
// Show image to UI
const imgEl = document.createElement("img");
imgEl.width = "300";
imgEl.src = createObjectURL(image.blob);
if (qrScannedResult) {
// QR raw code and QR image
const { result, image } = qrScannedResult;
// The client should upload the image to server and call API to TrustingSocial
if (image && image.blob) {
// Show QR image to UI
const qrImgEl = document.createElement("img");
qrImgEl.width = "200";
qrImgEl.src = URL.createObjectURL(image.blob);
}
}
}
// This callback is called from SDK to get card type to continue flow in some special cases.
// Client should call this API https://ekyc.trustingsocial.com/api-reference/customer-api#detect-id-cards
async function handleDetectIdCard(props) {
const { cardType, image, cardSide } = props;
const apiClient = new trustvisionSdk.default(
inputAccessKey.value,
inputSecretKey.value,
inputApiUrl.value
);
const resultUpload = await apiClient.uploadImage({
file: image.blob,
label: `id_card.${cardType}.${cardSide}`,
});
const imageId = resultUpload.data.image_id;
const resultDetect = await apiClient.httpClient.post(
"/v1/detect_id_cards_sync",
{
card_type: cardType,
image: {
id: imageId,
},
}
);
return _.get(resultDetect, "data.image.cards.0");
}
// Init parameters to capture ID card
const idCardProps = {
onError: (e) => {
// Handle error and destroy SDK
tv.closeSDK();
},
onClose: () => {
tv.closeSDK();
},
detectIdCard: handleDetectIdCard,
onStepDone: handleStepDoneIDCard,
};
// Start capture ID Card
tv.readIDCardUIOnly(idCardProps);
This api works the same as UI only version with the addition of calling api by SDK.
Params
The same as these params with an addition of the followings:
Required | Type | Description | |
---|---|---|---|
apiCredentials | yes | ApiCredentials | Keys to make api call{ accessKey, secretKey, apiUrl } |
serviceSettings | no | ServiceSettings | Settings to enable or disable API service calls from SDK. |
flowId | no | FlowId | Flow ID to get corresponding client settings |
Available from SDK version 5.13.8
Context:
We record logs for tracking user's behavior while using SDK. We will provide a log key beside api key but only used for sending log events. It is important to keep the log key secure, just like the API key. We have 2 ways to configure:
Your BE server will call this api to generate a temporary credential and return this temporary credential to client side, then pass this temporary credential to SDK.
logCredentials: {
accessKey: 'YOUR ACCESS KEY',
secretKey: 'YOUR SECRET KEY',
apiUrl: 'LOG EVENT URL',
userId: 'X-REQUEST-ID'
}
Your BE server will follow this guide to generate a temporary signature and return this temporary signature to client side, then pass this temporary signature to SDK.
logCredentials: {
logSignature: {
timestamp: '', // X-TV-Timestamp
signature: '', // Authorization
logUrl: '', // API url for logging event
},
userId: 'X-REQUEST-ID'
}
Note: Only generate 1 temporary credential/signature in 1 session for consistent logs.
To start the QR scanning step, we can use the readIDCardUIOnly
or readIDCardWithApiCall
method same as above, but with the steps
property as below:
const qrScanStep = {
scannerType: TVWebSDK.ScannerType.QR_CODE,
title: 'Quét mã QR trên CCCD',
titleIcon: ''
enableConfirmPopup: false,
};
const idCardProps = {
...
steps: [qrScanStep],
};
tv.readIDCardUIOnly(idCardProps);
This function starts liveness detection flow.
Params
A single object with these properties
Required | Description | Type | Default | |
---|---|---|---|---|
mode | yes | Mode to operate | LivenessMode | |
onLivenessDetectionDone | yes | Callback when liveness detection flow is done. | function(livenessResults: LivenessResults) | |
onError | yes | Callback when errors happen. | function(error: Error) | |
clientSettings | yes | Available from v5.3.1. This settings come from TS server and depend on your accessKey and secretKey .When apiCheck enable, this setting will be automated get from TS server and bind to SDK. Anything pass to this props will overwrite settings come with apiCheck .When you call api by your self, use this api to get client settings, and pass response to this props. | object | |
apiCheck | no | If true , sdk will call APIs itself (apiCredentials is needed). | boolean | false |
apiCredentials | no | Available from v5.3.1. Keys to make api calls | ApiCredentials | { accessKey: "", secretKey: "", apiUrl: "" } |
customErrors | no | Available from v3.10.1. Custom the errors show on SDK. See example below. | object | null |
onProcessing | no | Available from v4.3.0. From v4.3.0, SDK will resize output images to 400x400 before return. So this is the callback when SDK start resize images. Use to close camera or show loading. The resize process complete on onLivenessDetectionDone .If apiCheck is enable, this callback also includes api call time. | function | Empty function |
onReset | no | Available from v5.16.6. Callback when liveness flow is reset. For example, when timeout happens, this callback will be triggered. | function | Empty function |
captureFrameSettings | no | Available from v5.3.1. Settings for record sequence frames. enable : On/Off record.framesIntervalTime (millisecond): Adjust frames capture per second, only capture 1 frame in framesIntervalTime . When set to 0, SDK will capture full potential of device can handle.framesBatchLength : If value is 0, only 1 array sequence frames return in onLivenessDetectionDone . If value greater than 0, SDK will return sequence frames by batch in onFramesCaptured with each batch have framesBatchLength length. | object | { enable: false, framesIntervalTime: 180, framesBatchLength: 0 } |
onFramesCaptured | no | Available from v5.3.1. The callback when SDK return sequence by batch. | function | Empty function |
outputEncryptionSettings | no | Available from v5.8.0. If key is provided, output files will be encrypted and returned via encrypted field. Please see here. | object | { key: '' } |
cameraScale | no | Available from v5.9.0. Scale up camera image in appropriate distance. | number ([1.0..2.0]) | 1.0 |
offsetFaceY | no | Available from v5.9.3. Support large screen device (like Kiosk) to adjust "face area circle" for suitable user's height. Default is 0, pass a negative number to move "circle" to top, a positive number to move "circle" to bottom. | number (in pixel) | 0 |
logCredentials | no | Config for tracking user's behavior while using | object | {} |
title | no | Only available for some custom build. Title of EKYC screen. | string | "" |
frontalMinSize | no | Available from v5.13.9. Scale last frontal image to frontalMinSize * frontalMinSize . The recommended value is 720 . The scaled image will be returned in the frontalScaledImage field in LivenessResults` | number | null |
customTexts | no | Only available from SDK version 5.21.0. Please refer to this section | object | {} |
onClose | no | Callback when users click the X button | function | Empty function |
serviceSettings | no | Settings to enable or disable API service calls from SDK. | ServiceSettings | - |
flipVideoHorizontal | no | Flip video horizontally. Please refer to this section | boolean | null |
flowId | no | Flow ID to get corresponding client settings | FlowId | "" |
type LivenessResults = {
steps: Array<{
// gesture images
name: String; // step name
image: {
blob: Blob;
encrypted: {
// will be returned if outputEncryptionSettings.key is provided
hex: String; // encrypted image in base64 string
};
id: String; // id of image has been uploaded, appears only when apiCheck is enabled
};
}>;
frontalFaces: Array<T>; // list of frontal image
frontalFacesEncrypted: Array<String>; // list of frontal image encrypted in base64 string, will be returned if outputEncryptionSettings.key is provided
frontalScaledImage: Blob; // the scaled frontal image if frontalMinSize was specified
capturedFrames: Array<T>; // sequence frames array record liveness process.
video: Blob; // the raw video of liveness process, return if turn on via access key settings
apiCheckPassed: boolean; // whether all liveness checks passed, appears only when apiCheck is enabled.
verifyFaceLivenessResult: LivenessResult<T>; // appears only when apiCheck is enabled.
verifyFacePortraitResult: SanityResult<T>; // appears only when apiCheck is enabled.
};
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes capture selfie
function handleLivenessDetectionDone(result) {
// Host site will use these parameters to call API verify liveness check
// Reference API https://ekyc.trustingsocial.com/api-reference/customer-api#1-request-5
const { frontalFaces, steps, capturedFrames } = result;
// frontalFaces: list frontal images mapping with API param 'images'
console.log("frontal faces: ", frontalFaces);
frontalFaces.forEach((blob) => {
// Upload list frontal image to server
});
/* steps: list gesture image mapping with API param 'gesture_images', each element contains params
name: mapping to 'gesture'
image: mapping to 'images'[0], always array with 1 element
*/
console.log("gestures faces: ", steps);
steps.forEach((s) => {
// Upload list gesture image to server
});
// capturedFrames: list frames mapping with API param 'videos'
// Upload frames batch to server
console.log("video frames batch: ", capturedFrames);
}
const livenessDetectionMode = TVWebSDK.Constants.Mode.FLASH_16;
const livenessProps = {
mode: livenessDetectionMode,
onLivenessDetectionDone: handleLivenessDetectionDone,
captureFrameSettings: {
enable: true,
framesIntervalTime: 180,
framesBatchLength: 15,
},
onError: (e) => {
// Handle error and close SDK
console.log(e);
tv.closeSDK();
},
frontCamera: true,
onProcessing: () => {
// Show loading
setTimeout(() => {
tv.closeSDK();
}, 250);
},
onClose: () => {
document.body.style.height = "auto";
tv.closeSDK();
},
clientSettings,
};
// Start capture selfie
tv.livenessDetection(livenessProps);
tv.livenessDetection({
...otherProps
customErrors={{
face_too_small: {
code: 'face_too_small',
msg: {
en: 'Face too small',
vi: 'Đưa camera lại gần',
},
},
face_too_large: {
code: 'face_too_large',
msg: {
en: 'Face too large',
vi: 'Đưa camera ra xa',
},
},
}}
})
List errors can custom:
no_face: {
code: 'no_face',
msg: {
en: 'Face not found',
vi: 'Không tìm thấy khuôn mặt',
},
},
partial_face: {
code: 'partial_face',
msg: {
en: 'Face out of the box',
vi: 'Không tìm thấy toàn bộ khuôn mặt',
},
},
multiple_faces: {
code: 'multiple_faces',
msg: {
en: 'Too many faces',
vi: 'Quá nhiều khuôn mặt',
},
},
face_too_small: {
code: 'face_too_small',
msg: {
en: 'Face too small',
vi: 'Khuôn mặt quá nhỏ',
},
},
face_too_large: {
code: 'face_too_large',
msg: {
en: 'Face too large',
vi: 'Khuôn mặt quá to',
},
},
face_out_of_box: {
code: 'face_out_of_box',
msg: {
en: 'Face is out of box',
vi: 'Khuôn mặt không nằm trọn vẹn trong khung ảnh',
},
},
Available from version 5.18.0
.
This function starts face authentication flow.
Params
A single object with these properties
Required | Description | Type | Default | |
---|---|---|---|---|
mode | yes | Mode to operate | string (one of [TVWebSDK.Constants.FaceAuthenticationMode.REGISTRATION, TVWebSDK.Constants.FaceAuthenticationMode.AUTHENTICATION] ) | |
authMethod | yes | Authentication method to operate | string (one of [TVWebSDK.Constants.FaceAuthMethod.STANDARD_AUTHEN, TVWebSDK.Constants.FaceAuthMethod.ADVANCED_AUTHEN_ACTIVE, TVWebSDK.Constants.FaceAuthMethod.EDGE_AUTHEN, TVWebSDK.Constants.FaceAuthMethod.ADVANCED_AUTHEN_FLASH, TVWebSDK.Constants.FaceAuthMethod.LIGHT_AUTHEN] ) | |
onDone | yes | Callback when face authentication flow is done. | function(faceAuthenticationResult: FaceAuthenticationResult) | |
onError | yes | Callback when errors happen. | function(error: Error) | |
clientSettings | yes | This settings come from TS server and depend on your accessKey and secretKey .When apiCheck enable, this setting will be automated get from TS server and bind to SDK. Anything pass to this props will overwrite settings come with apiCheck .When you call api by your self, use this api to get client settings, and pass response to this props. | object | |
apiCheck | no | If true , sdk will call APIs itself (apiCredentials is needed). | boolean | false |
apiCredentials | no | Keys to make api calls | ApiCredentials | { accessKey: "", secretKey: "", apiUrl: "" } |
onClose | no | Callback when users click the X button | function | Empty function |
authType | no | Authentication type, please see here | string | transfer |
captureFrameSettings | no | Settings for record sequence frames.enable : On/Off record.framesIntervalTime (millisecond): Adjust frames capture per second, only capture 1 frame in framesIntervalTime . When set to 0, SDK will capture full potential of device can handle.framesBatchLength : If value is 0, only 1 array sequence frames return in onLivenessDetectionDone . If value greater than 0, SDK will return sequence frames by batch in onFramesCaptured with each batch have framesBatchLength length. | object | { enable: false, framesIntervalTime: 180, framesBatchLength: 0 } |
onFramesCaptured | no | The callback when SDK return sequence by batch. | function | Empty function |
outputEncryptionSettings | no | If key is provided, output files will be encrypted and returned via encrypted field. Please see here. | object | { key: '' } |
logCredentials | no | Config for tracking user's behavior while using | object | {} |
flowId | no | Flow ID to get corresponding client settings | FlowId | "face_authen" |
serviceSettings | no | Settings to enable or disable API service calls from SDK. | ServiceSettings | - |
type FaceImage = {
id?: string,
base64?: string,
blob: Blob,
label: string,
metadata?: string, // JSON String
encrypted?: string, // Only availble when outputEncryptionSettings.key is provided
}
type VideoFrame = {
index: number,
base64: string,
label: string,
metadata?: string, // JSON String
}
type Video = {
id?: string,
metadata?: string, // JSON String
frames: VideoFrame[]
}
type GestureFace = {
gesture: 'up' | 'left' | 'right' | 'down',
images: FaceImage[]
}
type FaceAuthenticationResult = {
customerUserId: string,
faces: FaceImage[],
videos: Video[],
gestureFaces: GestureFace[],
selfieType: 'light' | 'passive' | 'active' | 'flash_edge' | 'flash_advanced',
faceType: 'selfie' | 'id_card' | null, // only available on REGISTRATION mode
apiResult: { // response from api
data: {...}
}
}
Sample code
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes face authentication flow
async function onFaceAuthenticationDone(result: FaceAuthenticationResult) {
// Host site will use these parameters to call Face Authentication API
const {
customerUserId,
faces,
videos,
gestureFaces,
selfieType,
faceType,
apiResult,
} = result;
// Upload face images to server
const uploadedFaceImages = (
await Promise.all(
faces.map((face) => {
// Upload face image to server
})
)
)
.map
// Map to correct API format
();
// Upload gestureFaces to server
const uploadedGestureImages = (
await Promise.all(
gestureFaces.map((gestureFace) => {
// Upload gesture image to server
})
)
)
.map
// Map to correct API format
();
const apiPayload = {
cus_user_id: customerUserId,
faces: uploadedFaceImages,
videos: videos,
gesture_faces: uploadedGestureImages,
selfie_type: selfieType,
face_type: faceType, // Only use in REGISTRATION mode
// Only use in AUTHENTICATION mode
// Please refer https://ekyc.trustingsocial.com/api-reference/customer-api#sample-request-26:~:text=use%2Dcase%20differently.-,auth_type,-description
auth_type: "",
};
// Call REGISTER or AUTHENTICATE API
// API Reference
// REGISTER: https://ekyc.trustingsocial.com/api-reference/customer-api#request-face-authentication-registration
// AUTHENTICATE: https://ekyc.trustingsocial.com/api-reference/customer-api#request-face-authentication
// Call API here...
// For REGISTER only
// If API call is successful, we will need to store the customerUserId for future use
localStorage.setItem("tv:cus-user-id", customerUserId);
}
// Get SDK Client Settings from Server
// API Reference: https://ekyc.trustingsocial.com/api-reference/customer-api#get-client-settings
const clientSettings = {};
const mode = TVWebSDK.Constants.FaceAuthenticationMode.AUTHENTICATION;
const authMethod = TVWebSDK.Constants.FaceAuthMethod.EDGE_AUTHEN;
const faceAuthenProps = {
mode: mode,
authMethod: authMethod,
onDone: onFaceAuthenticationDone,
clientSettings: clientSettings,
onError: (e) => {
// Handle error and close SDK
console.log(e);
tv.closeSDK();
},
onClose: () => {
document.body.style.height = "auto";
tv.closeSDK();
},
};
// Start Face authentication
tv.faceAuthentication(faceAuthenProps);
Type: string
Value | Description | Version |
---|---|---|
flash_32 | Flash 32 mode | 5.18.0 |
flash_16 | Flash 16 mode | 5.18.0 |
flash_8 | Flash 8 mode | 5.18.0 |
passive_v2 | Passive V2 mode | 5.24.0 |
flash | Flash mode | 5.14.0 |
flash_advanced | Flash advanced mode | 5.18.0 |
flash_edge | Flash edge mode | 5.18.0 |
active | Active mode | |
passive | Passive mode |
Type: object
Property | Description | Type | Default | Version |
---|---|---|---|---|
enableGetClientSettings | Enable/Disable get client settings | boolean | true | 5.21.1 |
enableUploadFrames | Enable/Disable upload frames | boolean | true | 5.19.0 |
enableUploadImages | Enable/Disable upload images | boolean | true | 5.19.0 |
enableVerifySanityPortrait | Enable/Disable verify sanity for Portrait image service | boolean | true | 5.19.0 |
enableVerifySanityIDCard | Enable/Disable verify sanity for ID Card image service | boolean | true | 5.19.0 |
enableVerifyFaceLiveness | Enable/Disable verify face liveness service | boolean | true | 5.19.0 |
enableDetectIDCardTampering | Enable/Disable detect ID Card tampering service | boolean | true | 5.19.0 |
enableReadIDCardInfo | Enable/Disable read ID Card info service | boolean | true | 5.19.0 |
enableRegisterFace | Enable/Disable register face service | boolean | true | 5.19.0 |
enableAuthenticateFace | Enable/Disable authenticate face service | boolean | true | 5.19.0 |
Note: Besides enableGetClientSettings
and enableUploadFrames
, other services are only available when enableUploadImages
is set to true
.
Type: object
Keys to make API calls.
Property | Description | Type | Default |
---|---|---|---|
accessKey | Access Key | string | \"\" |
secretKey | Secret Key | string | \"\" |
apiUrl | API base URL | string | \"\" |
Type: string
Value | Description | Version |
---|---|---|
face_authen | Value for Face Authencation flow | 5.21.1 |
onboarding | Value for Onboarding flow | 5.21.1 |
To open native camera instead of custom camera
Required | Type | Default | Description | |
---|---|---|---|---|
useFileBrowse | no | boolean | false | option to open file browse (gallery) or camera |
accept | no | string | image/* | this param specifies a filter for what file types the user can pick from the file input dialog box |
frontCamera | no | boolean | false | which camera to open |
onCaptureDone | no | function | Empty function | Callback to return { file, encrypted: { hex } } where file is of type File and encrypted.hex (encrypted file encoded in hex string) will be returned if outputEncryptionSettings.key is provided |
outputEncryptionSettings | no | object | { key: '' } | if key is provided, output files will be encrypted and returned as encrypted object as above |
Return value: { openCamera }
where openCamera
is the function you can use to trigger opening camera.
Example:
https://unpkg.com/@tsocial/tvweb-sdk@latest/examples/native-camera/index.html
function handleCaptureDone({ file }) {
imgResultEl.src = URL.createObjectURL(file);
imgResultEl.onload = function () {
URL.revokeObjectURL(this.src);
};
}
const { openCamera } = tv.initNativeCamera({
frontCamera: false,
onCaptureDone: handleCaptureDone,
});
buttonOpenCamera.onclick = openCamera;
Notes: Some browsers don't open native camera directly at first. They'll show a popup to choose File Browser or Open Camera... instead.
For the best user experience, this function should be called before calling readIDCardUIOnly
, readIDCardWithApiCall
, livenessDetection
to preload heavy resources and cache them. we should call this method when the first page was loaded. it will load resources in the background and not impact to user experience.
Note: Supported since 5.7.4+
Stop the camera immediately Sample code:
tv.destroyView();
Alias for destroyView
tv.closeSDK();
Available from v5.9.0. Get list camera on deivce and choose the right camera. Get list camera:
tv.getListCamera().then((result) => {
const cameras = result.filter(
(device) => device.deviceId && device.kind === "videoinput"
);
});
Output:
Use deviceId
to open the desired camera
tv.readIDCardUIOnly({
...
// others props
defaultCameraId: deviceId
});
---
tv.livenessDetection({
...
// others props
defaultCameraId: deviceId
});
You can also choose to flip the camera stream horizontally by passing flipVideoHorizontal
to readIDCardUIOnly
or livenessDetection
service. Its default value is null
.
Note: This is only available from version 5.19.0
tv.readIDCardUIOnly({
...
// others props
flipVideoHorizontal: false
});
---
tv.livenessDetection({
...
// others props
flipVideoHorizontal: true
});
Compare face from readIDCard result & livenessDetection result.
Params: a single object with these properties
Required | Description | Type | Default | |
---|---|---|---|---|
accessKey, secretKey, apiUrl | yes | Keys to make api calls | string | |
image1 | yes | The id of the front card image returns from onSuccess in readIDCard method. | string | |
image2 | yes | The id of the first image returns from onLivenessDetectionDone (verifyFaceLivenessResult) in livenessDetection method. | string | |
onSuccess | yes | Success callback | function | |
onError | yes | Error callback | function |
Sample code:
tv.compareFaces({
accessKey: "YOUR ACCESS KEY",
secretKey: "YOUR SECRET KEY",
apiUrl: "https://tv-staging.trustingsocial.com/api",
image1: "image_id from upload API",
image2: "image_id from upload API",
onSuccess: (rs) => console.log(rs),
onError: (e) => console.log(e),
});
Only available from SDK version 5.18.0
An async function to get device information.
const metadata = await tv.getDeviceInfo();
console.log(metadata);
Device information will be returned as an object with the following properties (will return null
if SDK cannot get information of that property):
Property | Value |
---|---|
id | string or null |
udid | string or null |
sn | string or null |
imei | string or null |
manufacturer | string or null |
deviceName | string or null |
wlanMac | string or null |
phoneNumber | string or null |
location | { longitude: string or null, latitude: string or null } |
Only available from SDK version 5.21.0
Each country has its default locales as follows:
Country | Locales available | Default |
---|---|---|
Vietnam (vn) | vi or en | vi |
Philippines (ph) | en | en |
India (in) | en | en |
The default locale will be chosen based on the country
and lang
that are defined when you init the SDK.
If you wish to overwrite the default texts, you can pass the customTexts
object prop to your flow. The available texts to overwrite are listed below.
Note: Some previously customized texts/errors in older versions will be kept for backward compatibility. Please contact us if you need to update them.
Key | Default en locale | Default vi locale | Note |
---|---|---|---|
not_supported | Camera is not well-supported on this device/browser. Please try another one. | Camera không được hỗ trợ. Vui lòng thử trình duyệt hoặc thiết bị khác. | |
camera_timeout | Can't access camera. Please check camera permission, try again or try another browser/device | Không thể truy cập camera. Vui lòng kiểm tra quyền truy cập camera, đóng các tab/app khác đang sử dụng camera hoặc thử lại trên trình duyệt/thiết bị khác. | |
wrong_orientation | You are using landscape mode. Please rotate screen to portrait mode and retry. | Bạn đang sử dụng chế độ màn hình xoay ngang. Vui lòng quay màn hình theo chiều dọc và thực hiện lại. | |
unable_to_load_model | Unable to load model on this device/browser. Please try another one. | Không thể load model. Vui lòng thử trình duyệt hoặc thiết bị khác. | |
no_permission | Please grant the permission to access the camera. | Vui lòng cấp quyền để truy cập camera. | |
no_face | Face not found | Không tìm thấy khuôn mặt | |
partial_face | Face out of the box | Không tìm thấy toàn bộ khuôn mặt | |
multiple_faces | Too many faces | Quá nhiều khuôn mặt | |
face_too_small | Face too small | Khuôn mặt quá nhỏ | |
face_too_large | Face too large | Khuôn mặt quá to | |
face_out_of_box | Face is out of box | Khuôn mặt không nằm trọn vẹn trong khung ảnh | |
liveness_too_fast | Please turn your face SLOWLY | Vui lòng quay CHẬM lại | |
liveness_terminated | Please try again. | Vui lòng thực hiện lại. | |
liveness_terminated_face_tracking | You need to turn your face SLOWLY. Please try again. | Bạn cần quay CHẬM lại. Vui lòng thực hiện lại. | |
liveness_terminated_no_face | Your face was missing for too long. Please try again. | Khuôn mặt không nằm trong khung hình. Xin vui lòng thử lại. | |
liveness_terminated_time_out | You need to complete gestures as instructed within <%= x %> seconds. Please try again. | Bạn cần hoàn thành động tác theo chỉ dẫn trong <%= x %> giây. Xin vui lòng thử lại. | Must include <%= x %> as-is to show countdown |
id_detector_validate_angle_error | Please place ID card straight, not tilted | Vui lòng đặt giấy tờ thẳng, không bị nghiêng khi chụp | |
id_detector_no_cards | Please capture the <%= side %> side of ID card (occupy 50-95% of the frame) | Vui lòng chụp mặt <%= side %> của giấy tờ (chiếm 50-95% khung hình) | Must include <%= side %> as-is to show card side |
id_detector_card_too_small | Please move ID card CLOSER (occupy 50-95% of the frame) | Vui lòng đưa giấy tờ lại GẦN hơn (chiếm 50-95% khung hình) | |
id_detector_error_incomplete | Please move ID card FARTHER (occupy 50-95% of the frame) | Vui lòng đưa giấy tờ ra XA hơn (chiếm 50-95% khung hình) | |
glare_detected | Glare is detected. Please adjust the shooting angle | Ảnh bị chói / lóa. Vui lòng điều chỉnh góc chụp. | |
id_detector_error_front_side_no_faces | Please capture the front side of ID card | Vui lòng chụp mặt trước của giấy tờ | |
id_detector_error_front_side_multiple_faces | Please capture the front side of one ID card only (remove other items out of the frame) | Vui lòng chụp mặt trước 1 giấy tờ duy nhất | |
id_detector_error_back_side_has_faces | Please capture the back side of ID card | Vui lòng chụp mặt sau của giấy tờ | |
id_detector_error_blur | Blurry photo detected. Please capture again for a clearer image | Ảnh bị mờ. Vui lòng chụp lại để có ảnh rõ nét hơn | |
not_frontal_face | Please look straight to camera. | Vui lòng nhìn thẳng vào camera. | |
not_readable | Camera already in use by another app/tab. | Camera đang được sử dụng bởi ứng dụng/tab khác. | |
close_eye | Open your eyes clearly | Vui lòng mở to mắt | |
front_card_title | Front side of ID card | Mặt trước CMND/CCCD | |
back_card_title | Back side of ID card | Mặt sau CMND/CCCD | |
qr_title | Scan QR code on ID card | Quét mã QR trên CCCD | |
front_card_description | Please put the front side of ID card into the frame | Vui lòng đặt CMND mặt trước vào trong khung | |
back_card_description | Please put the back side of ID card into the frame | Vui lòng đặt CMND mặt sau vào trong khung | |
btn_ok | OK | Tôi đã hiểu | |
front | front | trước | |
back | back | sau | |
btn_use_this_picture | Use this picture | Dùng ảnh này | |
btn_try_again | Try again | Thử lại | |
confirm_popup_valid_qr | QR code scanned | Đã quét được mã QR | |
confirm_popup_invalid_qr | Could not scan QR code | Không quét được mã QR | |
remaining_time | Remaining time: <%= x %> seconds | Thời gian còn lại: <%= x %> giây | Must include <%= x %> as-is to show countdown |
timeout_instruction | You need to take the picture within <%= x %> seconds. Please try again. | Bạn cần hoàn thành việc chụp giấy tờ trong <%= x %> giây. Vui lòng thử lại. | Must include <%= x %> as-is to show countdown |
warmup_intro | Warming up... Please wait a moment | Đang khởi động, vui lòng chờ trong giây lát... | |
[TVWebSDK.Constants.FaceDirection.LEFT] | Turn your face to the left | Quay mặt chậm qua trái | |
[TVWebSDK.Constants.FaceDirection.RIGHT] | Turn your face to the right | Quay mặt chậm qua phải | |
[TVWebSDK.Constants.FaceDirection.UP] | Turn your face up | Ngước mặt chậm lên trên | |
[TVWebSDK.Constants.FaceDirection.FRONTAL] | Look straight into the camera | Nhìn thẳng camera | |
qr_instructions_text | Please follow the instructions below to scan QR code:\n Move camera to the position of QR code on ID card. Align QR code to fit into camera frame to scan it | Vui lòng quét mã QR theo hướng dẫn sau:\n Đưa camera vào vị trí mã QR trên CCCD. Canh vừa mã QR vào 4 góc khung hình để quét mã | |
qr_instructions_start_button | Scan QR code | ||
qr_instructions_countdown | Start in <%= x %> s | Bắt đầu trong <%= x %> giây | Must include <%= x %> as-is to show countdown |
qr_invalid_popup_title | Invalid QR code | QR không hợp lệ | |
qr_invalid_popup_description | Please check and make sure scanning QR code on ID card | Vui lòng kiểm tra và đảm bảo việc thực hiện quét QR code trên CCCD thật | |
qr_invalid_popup_retry_button | Scan QR (on ID card) | Quét lại QR (trên CCCD) | |
qr_invalid_popup_skip_button | Skip | Bỏ qua | |
qr_tooltip | Fit QR code to 4 corners of camera frame and try to move camera [in or out] to scan QR code | Canh vừa mã QR vào 4 góc khung hình và thử dịch chuyển camera [xa - gần] để quét được mã QR | |
session_timeout_popup_description | Scan time exceeded. Please try again.\n Position the camera at the QR code on the card. Align the four corners and try moving the camera [far - near] so that the phone camera can focus more accurately. | Quá thời gian quét. Vui lòng thực hiện lại.\n Đưa camera vào vị trí mã QR trên thẻ. Canh vừa vào 4 góc và thử dịch chuyển camera [xa - gần] để camera điện thoại lấy nét chuẩn hơn. | |
session_timeout_popup_retry_button | Retry | Thử lại | |
session_timeout_popup_skip_button | Skip | Bỏ qua |
Usage example:
tv.readIDCardUIOnly({
... // others props
customTexts: {
front_card_title: 'My custom front card title',
front_card_description: 'My custom front card description',
}
});
tv.livenessDetection({
... // others props
customTexts: {
[TVWebSDK.Constants.FaceDirection.LEFT]: 'My custom turn left instruction',
[TVWebSDK.Constants.FaceDirection.RIGHT]: 'My custom turn right instruction',
}
});
Only available from SDK version 5.19.0
This prop is used to change the theme of the SDK. The default theme is v2
. The available themes are v1
and v2
. Please see the images below for the differences between the two themes.
Default UI for v1
Default UI for v2
SDK works best on these major browsers (Chrome, Firefox, Safari):
IE | Edge | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|---|
>11 | >79 | >36 | >53 | >11 | >40 |
iOS Safari | Facbook Web Browser in iOS | Facbook Web Browser in Android | Opera mini | Opera Mobile | Chrome for Android | Chrome for iOS | Firefox for Android | UC Browser for Android |
---|---|---|---|---|---|---|---|---|
>11 | not supported | not supported | not supported | >43, but there are some unexpected behaviors (browser ask to choose Front/Back camera) | >85 | iOS>=14.3 | >79 | not supported |
Please refer Browser compatibility on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia for more information
To make Web SDK work on a WebView, the WebView implementer must supports getUserMedia API and handle camera permission requests correctly.
For Android WebView, the implementer also needs to set the setMediaPlaybackRequiresUserGesture config to false
. Otherwise, the camera video cannot be autoplay (the WebView requires a user gesture to play media).
For iOS WebView, there is a known issue which tracked here. You should take a look at that issue before you implement your WebView on iOS.
The default settings use CDNs to deliver assets/resources to user browsers, but Vietnamese internet access to those CDNs may encounter issues sometimes. To prevent that and improve user experience, we highly recommend hosting those resources in Vietnam. Please check default custom urls and notes for self-hosted decision.
not_supported: {
code: 'not_supported',
msg: {
en: 'Camera is not well-supported on this device/browser. Please try another one.',
vi: 'Camera không được hỗ trợ. Vui lòng thử trình duyệt hoặc thiết bị khác.',
},
},
camera_timeout: {
code: 'camera_timeout',
msg: {
en: `Can't access camera. Please check camera permission, try again or try another browser/device`,
vi:
'Không thể truy cập camera. Vui lòng kiểm tra quyền truy cập camera, đóng các tab/app khác đang sử dụng camera hoặc thử lại trên trình duyệt/thiết bị khác.',
},
},
unable_to_load_model: {
code: 'unable_to_load_model',
msg: {
en: `Unable to load model on this device/browser. Please try another one.`,
vi: 'Không thể load model. Vui lòng thử trình duyệt hoặc thiết bị khác.',
},
},
no_permission: {
code: 'no_permission',
msg: {
en: 'Please grant the permission to access the camera.',
vi: 'Vui lòng cấp quyền để truy cập camera.',
},
},
api_call_error: {
code: 'api_call_error',
msg: {
en: 'Api call error',
vi: 'Lỗi call api',
},
},
upload_error: {
code: 'upload_error',
msg: {
en: 'Upload error',
vi: 'Lỗi upload',
},
},
sanity_check_error: {
code: 'sanity_check_error',
msg: {
en: 'Sanity check error',
vi: 'Lỗi check sanity',
},
},
read_id_card_error: {
code: 'read_id_card_error',
msg: {
en: 'Read id card error',
vi: 'Lỗi read id card',
},
},
detect_id_tampering_error: {
code: 'detect_id_tampering_error',
msg: {
en: 'Detect id tampering error',
vi: 'Lỗi detect id tampering',
},
},
detect_id_card_error: {
code: 'detect_id_card_error',
msg: {
en: `Can't detect id card type`,
vi: 'Không thể detect id card type',
},
},
missing_front_id_card: {
code: 'missing_front_id_card',
msg: {
en: 'Missing front id',
vi: 'Không tìm thấy front id',
},
},
server_error: {
code: 'server_error',
msg: 'Server error',
},
not_readable: {
code: 'CameraInUseError',
msg: {
vi: 'Camera đang được sử dụng bởi ứng dụng/tab khác.',
en: 'Camera already in use by another app/tab.',
},
},
invalid_card_type_config: {
code: 'invalid_card_type_config',
msg: {
en: 'Invalid card type config',
vi: 'Cấu hình loại thẻ không hợp lệ',
},
},
max_retry_reached: {
code: 'max_retry_reached',
msg: {
vi: 'Đã quá số lần thử',
en: 'Maximum retries reached',
},
},
sdk_timeout_reached: {
code: 'sdk_timeout_reached',
msg: {
vi: 'Đã quá thời gian thực hiện',
en: 'SDK timeout reached',
},
},
license_invalid: {
code: 'license_invalid',
msg: {
en: 'Invalid license',
vi: 'License không hợp lệ',
},
},
license_rate_limit_exceeded: {
code: 'license_rate_limit_exceeded',
msg: {
en: 'Invalid license',
vi: 'Quá số lần thử',
},
},
license_internal_server_error: {
code: 'license_internal_server_error',
msg: {
en: 'Internal server error',
vi: 'Lỗi server',
},
},
license_unknown_error: {
code: 'license_unknown_error',
msg: {
en: 'Unknown error',
vi: 'Lỗi không xác định',
},
},
{
code: < DOMException > // refer: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Exceptions
}
Errors can be found in TVWebSDK.Constants.Errors
Example and note when submit SDK's result to TS server.
Note: If captureFrameSettings
is enabled, please refer to this section
const payload = {
label: 'portrait',
metadata: JSON.stringify({
gesture_score: <Double>, // image.leftRightScore
gesture: <String>, // step name, 'frontal' if image is frontal
}),
};
if (outputEncryptionSettings && outputEncryptionSettings.key) {
apiClient.httpClient.post(
'/v1/images',
{
base64: image.encrypted.hex,
...payload,
}, // additional payload
{ 'X-TV-Key': outputEncryptionSettings.key } // additional header
);
} else {
apiClient.uploadImage({
file: <Blob>,
...payload
})
}
apiClient.verifyFaceLiveness({
images: Array<{ // list 'frontalFaces' have been upload, please keep the order the same with origin `frontalFaces` array
id: <String>, // imageId return from upload api
}>,
gesture_images: Array<{ // list 'steps' image have been upload
gesture: <String>, // step name
images: [{ id: <String> }], // imageId return from upload api
}>,
videos: [{ frames: capturedFrames }], // add this param if captureFrameSettings is enable
});
apiClient.verifyPortraitSanity({
image: {
id: <String>, // imageId of last frontal image in 'frontalFaces' have been uploaded
},
});
captureFrameSettings
is enabled, with framesBatchLength
value > 0, it is recommended to use onFramesCaptured
prop to catch sequence batch returned by SDK.onFramesCaptured
will be called whenever the SDK has captured the same number of frames that equals to framesBatchLength
file_id
Sample code for onFramesCaptured
:
const uploadBatchPromises = [];
async function onFramesCaptured(frames) {
const uploadPromise = uploadFrames({ frames });
uploadBatchPromises.push(uploadPromise);
}
videos
param in verifyFaceLiveness
api call won't need to add heavy frames. It just needs array of file ids.await
for the last batch of frames to be uploaded before proceeding to get result. It can be done by something similar to this:const batchFileIds = await Promise.all(uploadBatchPromises).map((response) => ({
id: response?.data?.file_id ?? -1,
}));
const verifyResult = await verifyFaceLiveness({
images,
gesture_images,
videos: batchFileIds,
});
uploadBatchPromises
array. And we can achieve that through onReset
callback.const onReset = () => {
uploadBatchPromises.length = 0;
};
tv.livenessDetection({
// ...
onReset: onReset,
// ...
});
const { IDCardSide, Errors } = TVWebSDK.Constants;
async function uploadIdCard({
image,
cardType,
cardSide,
apiClient,
outputEncryptionSettings,
}) {
const payload = {
label: `id_card.${cardType}.${cardSide}`,
};
if (outputEncryptionSettings && outputEncryptionSettings.key) {
return apiClient.httpClient.post(
"/v1/images",
{
base64: image.encrypted.hex,
...payload,
}, // additional payload
{ "X-TV-Key": outputEncryptionSettings.key } // additional header
);
}
return apiClient.uploadImage({
file: image.blob,
...payload,
});
}
async function uploadQRImage({
image,
apiClient,
rawString,
outputEncryptionSettings,
}) {
const payload = {
label: "qr_code",
metadata: JSON.stringify({
raw: rawString,
}),
};
if (outputEncryptionSettings && outputEncryptionSettings.key) {
return apiClient.httpClient.post(
"/v1/images",
{
base64: image.encrypted.hex,
...payload,
}, // additional payload
{ "X-TV-Key": outputEncryptionSettings.key } // additional header
);
}
return apiClient.uploadImage({
file: image.blob,
...payload,
});
}
async function checkIdSanity({ imageId, cardType, apiClient }) {
return apiClient.requestVerifyIDCard({
card_type: cardType,
image1: { id: imageId },
});
}
async function detectIDTampering({
apiClient,
cardType,
frontCardId,
isCapturingFrontSide,
backCardId,
recordedVideos,
qrImageId,
}) {
const uploadedVideos = recordedVideos.filter((i) => i.id !== null);
const pendingVideos = recordedVideos.filter(
(i) => i.id === null && i.frames.length > 0
);
const finalUploadResult = await Promise.all(
pendingVideos.map((i) =>
apiClient.fileService.request({ frames: i.frames })
)
);
const payload = {
card_type: cardType,
image: { id: frontCardId }, // this must be front side
image2: isCapturingFrontSide ? undefined : { id: backCardId },
videos: [
...uploadedVideos.map((i) => ({ id: i.id })),
...finalUploadResult.map((i) => ({ id: i.data.file_id })),
],
};
if (qrImageId) {
payload.qr1_images = [
{
id: qrImageId,
},
];
}
return apiClient.detectIDTampering(payload);
}
async function readIdCard({
cardType,
apiClient,
frontCardId,
backCardId,
isCapturingFrontSide,
qrImageId,
}) {
const payload = {
card_type: cardType,
image1: isCapturingFrontSide ? { id: frontCardId } : { id: backCardId },
};
if (qrImageId) {
payload.qr1_images = [
{
id: qrImageId,
},
];
}
return apiClient.readIDCard(payload);
}
export default async function handleApiCheck({
image,
frontCardId: existingFrontCardId,
qrImageId: existingQRImageId,
onError,
recordedVideos,
apiClient,
cardType,
cardSide,
qrScannedResult,
outputEncryptionSettings,
}) {
if (cardSide === IDCardSide.BACK && !existingFrontCardId) {
onError(Errors.missing_front_id_card);
return;
}
const uploadPromises = [
uploadIdCard({
image,
cardType,
cardSide,
apiClient,
outputEncryptionSettings,
}),
];
if (qrScannedResult && qrScannedResult.image && qrScannedResult.result) {
uploadPromises.push(
uploadQRImage({
image: qrScannedResult.image,
apiClient,
rawString: qrScannedResult.result,
outputEncryptionSettings,
})
);
}
const [uploadIdCardResult, uploadQrResult] = await Promise.all(
uploadPromises
);
if (uploadIdCardResult.errors) {
onError({ ...Errors.upload_error, details: uploadIdCardResult.errors });
return;
}
const imageId = uploadIdCardResult.data.image_id;
const qrImageId = uploadQrResult?.data?.image_id || existingQRImageId;
let frontCardId;
let backCardId;
const isCapturingFrontSide = cardSide === IDCardSide.FRONT;
if (isCapturingFrontSide) {
frontCardId = imageId;
} else {
frontCardId = existingFrontCardId;
backCardId = imageId;
}
const sanityResult = await checkIdSanity({ imageId, cardType, apiClient });
if (sanityResult.errors) {
onError({ ...Errors.sanity_check_error, details: sanityResult.errors });
return;
}
const readIdCardResult = await readIdCard({
cardType,
apiClient,
frontCardId,
backCardId,
isCapturingFrontSide,
qrImageId,
});
if (readIdCardResult.errors) {
onError({ ...Errors.read_id_card_error, details: readIdCardResult.errors });
return;
}
const detectIDTamperingResult = await detectIDTampering({
apiClient,
cardType,
frontCardId,
isCapturingFrontSide,
backCardId,
recordedVideos,
qrImageId,
});
if (detectIDTamperingResult.errors) {
onError({
...Errors.detect_id_tampering_error,
details: detectIDTamperingResult.errors,
});
return;
}
return {
sanityResult: sanityResult.data,
tamperingInfo: detectIDTamperingResult.data,
cardInfo: readIdCardResult.data,
};
}
If client uses outputEncryptionSettings
prop in Read ID Card, Liveness Detection and Face Authentication, SDK will encrypt images and frames with the given key, and return the encrypted data on SDK done.
For Images
The encrypted image in return via the encrypted
field, as described in the SDK Results sections for Read ID Card and and Liveness Detection.
When client receives the encrypted image, they can upload image as described above, and they MUST add the X-TV-Key
header with the same value of SDK's outputEncryptionSettings.key
to decrypt the image on the server side.
For Frames (available from SDK version 5.18.0)
The data that need to be encrypted in each frame are base64
and metadata
(currently only Flash liveness frames have metadata
field, Active and Passive liveness frames only have base64
). If encryption is enabled, the values of base64
and metadata
will be the encrypted data.
When client receives the encrypted frames, they can upload frames, and they MUST add the X-TV-Key
header with the same value of SDK's outputEncryptionSettings.key
to decrypt the frames on the server side.
Only available from SDK version 5.21.0
The SDK Adapter helps you integrate the whole Ekyc flow faster without having to worry about the details of the APIs. You can extend our adapter class, and overwrite the interfaces with whichever method you want to call our services (Whether it is direct calls to our APIs or through your own backend). After overwriting the interfaces, you can pass the SDK Adapter instance as a prop when you start the Ekyc full flow.
Use cases
The default case is to call to TS server directly, as illustrated below:
You can also overwrite the interfaces to make the SDK Adapter call your own backend instead of calling our APIs directly, as illustrated below:
There are 2 main configurations that can be set up by client's key settings (please contact us for more information about these setups):
Properties for the Ekyc Fullflow are as follows:
Required | Description | Note | |
---|---|---|---|
sdkAdapter | yes | SDK Adapter instance | |
flow | no | Desired ekyc flow | Default: full flow TVWebSDK.Constants.EkycFlow.FULL_FLOW . You can pass in TVWebSDK.Constants.EkycFlow.ID_CARD for ID card only, or TVWebSDK.Constants.EkycFlow.FACE for face only. |
logCredentials | no | Config for tracking user's behavior while using | |
readIDCardProps | no | Props for read ID card, check here. | Skip these props onStepDone, onError , because the fullflow adapter will handle it. |
livenessDetectionProps | no | Props for liveness detection, check here. | Skip these props apiCheck, apiCredentials, onLivenessDetectionDone, onError , because the fullflow adapter will handle it. |
onEkycDone | no | The callback will return all service results following this format | |
onError | no | Error callback |
Tvweb-sdk provides default implementations for the interfaces. You can overwrite the interfaces with your own implementation. Below are the interfaces, the output format, and the required return fields of each interface.
Service | Output Format | Required return fields |
---|---|---|
clientSettings | Same output as here | Must return as is |
uploadImage | Same output as here | Must return as is |
uploadFiles | Same output as here | Must return as is |
requestVerifyIDCard | Custom return format with API result | data.card_sanity.verdict ,data.status ,errors |
detectIDTampering | Custom return format with API result | data.card_tampering.verdict ,data.status ,errors |
readIDCard | Custom return format with API result | data.card_information ,data.status ,errors |
requestVerifyPortrait | Custom return format with API result | data.portrait_sanity.verdict ,data.status ,errors |
verifyFaceLiveness | Custom return format with API result | data.is_live ,data.status ,errors |
compareFaces | Custom return format with API result | data.compare_faces ,data.status ,errors |
searchFaces | Custom return format with API result | data.faces ,errors |
indexFaces | Custom return format with API result | Must return as is |
Note: You will have to override all interfaces in order to use the Adapter properly.
However if you wish to only override some, you will have to pass in your credentials, including accessKey
, secretKey
, apiUrl
.
With this, SDK Adapter will use the default implementations for the ones you don't override, and use your custom interfaces for the ones you do.
SDK Adapter allows you to customize the messages in popup if encounter failure during the Ekyc flow.
You don't have to pass in the message
prop if not needed, we also have the default popup content.
But if you wish to customize the popup content, please follow the return format as below:
{
result, // the return data from the corresponding API
message: { // optional
error: {
en: '', // custom english message for failed case
vi: '', // custom vietnamese message for failed case
},
success: {
en: '', // custom english message for successful case
vi: '', // custom vietnamese message for successful case
},
}
}
// Example for requestVerifyIDCard
{
result: {
data: {
card_sanity: {
verdict: 'PASS',
},
status: 'SUCCESS',
},
},
message: {
error: {
en: 'Verify card sanity failed',
vi: 'Kiểm tra sanity thất bại',
},
success: {
en: 'Verify card sanity successfully',
vi: 'Kiểm tra sanity thành công',
},
},
}
Note: In the result
prop, it is best to return as is to the SDK Adapter. But if you don't want to expose all data from APIs, SDK Adapter will only need some custom fields.
Please refer to the interfaces table to know which fields are needed for each API.
You must return the required fileds in the same format as the corresponding API for the SDK Adapter to work properly.
The SDK adapter will return the response after the Ekyc flow is done with the following format:
{
frontCard: // see Card result section below
backCard: // see Card result section below
faceLiveness: // see Face liveness result section below
compareFaces: // see Compare faces result section below
searchFaces: // see Search faces result section below
indexFaces: // see Index faces result section below
}
Note: Please check the Response
section of each service here for more details of the returned data.
{
stepNumber: number,
cardSide: string,
cardType: string,
image: {
blob,
encrypted,
detectedCard,
},
recordedVideos: [],
apiResult: {
qrImageId: string,
frontCardId: string,
backCardId: string,
sanityResult: {}, // data field from response of Request verify ID card
tamperingInfo: {}, // data field from response of Request detect ID card tampering
cardInfo: {}, // data field from response of Read ID card info
}
}
{
frontalFaces: [
{
id: string,
blob
}, ...
],
steps: [
{
name: string,
id: string,
blob
}, ...
],
capturedFrames: [],
verifyFacePortraitResult: {}, // data field from response of Request verify portrait
verifyFaceLivenessResult: {}, // data field from response of Verify face liveness
}
Data field from response of Compare faces
Data field from response of Search faces
Data field from response of Index faces
apiCredentials
. With apiCredentials
, the Adapter will handle everything.const accessKey = 'YOUR ACCESS KEY';
const secretKey = 'YOUR SECRET KEY';
const apiUrl = 'YOUR API URL';
class MyAdapter extends TVWebSDK.SDKAdapter {}
const adapter = new MyAdapter(accessKey, secretKey, apiUrl);
// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
tv.ekycFlow({
sdkAdapter: adapter,
livenessDetectionProps: {...},
readIDCardProps: {...}
onDoneEkyc: (result) => console.log(result),
onError: (e) => console.log(e),
});
apiClient
to overwrite the interfaces. You can check out the apis here.// create apiClient instance with your access key and secret key, pass in X-Request-Id header
const apiClient = new trustvisionSdk.default(
'accessKey',
'secretKey',
'apiUrl',
{ httpHeaders: { 'x-request-id': 'xRequestId' } }
);
// Extend the SDKAdapter class and overwrite the interfaces
class MyAdapter extends TVWebSDK.SDKAdapter {
async clientSettings() {
const result = await apiClient.clientSettings();
return result;
}
async uploadImage(params) {
const result = await apiClient.uploadImage(params);
return result;
}
async uploadFiles(params) {
const result = await apiClient.fileService.request(params);
return result;
}
async requestVerifyIDCard(params) {
const result = await apiClient.requestVerifyIDCard(params);
const customResult = { // only return the required fields
data: {
card_sanity: {
verdict: result?.data?.card_sanity?.verdict,
},
status: result?.data?.status,
},
};
return {
result: customResult,
message: { // custom popup message
error: { en: 'requestVerifyIDCard error en', vi: 'requestVerifyIDCard error vi' },
success: { en: 'requestVerifyIDCard success en', vi: 'requestVerifyIDCard success vi' },
},
};
}
async detectIDTampering(params) {
const result = await apiClient.detectIDTampering(params);
return { result };
}
async readIDCard(params) {
const result = await apiClient.readIDCard(params);
return { result };
}
async requestVerifyPortrait(params) {
const result = await apiClient.requestVerifyPortrait(params);
return { result };
}
async verifyFaceLiveness(params) {
const result = await apiClient.verifyFaceLiveness(params);
return { result };
}
async compareFaces(params) {
const result = await apiClient.compareFaces(params);
return { result };
}
async searchFaces(params) {
const result = await apiClient.searchFaces(params);
return { result };
}
async indexFaces(params) {
const result = await apiClient.indexFaces(params);
return { result };
}
}
// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
const adapter = new MyAdapter();
tv.ekycFlow({
sdkAdapter: adapter,
livenessDetectionProps: {...},
readIDCardProps: {...}
onDoneEkyc: (result) => console.log(result),
onError: (e) => console.log(e),
});
class MyAdapter extends TVWebSDK.SDKAdapter {
async clientSettings() {
const result = await bankBackend.getClientSettings();
return result;
}
async uploadImage(params) {
const result = await bankBackend.uploadImage();
return result;
}
async requestVerifyIDCard(params) {
const result = await bankBackend.requestVerifyIDCard(params);
const customResult = { // must return the required fields
data: {
card_sanity: {
verdict: 'good',
},
status: 'success',
},
};
return {
result: customResult,
message: { // custom popup message
error: { en: 'requestVerifyIDCard error en', vi: 'requestVerifyIDCard error vi' },
success: { en: 'requestVerifyIDCard success en', vi: 'requestVerifyIDCard success vi' },
},
};
}
... // similar for other interfaces.
}
// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
tv.ekycFlow({
sdkAdapter: adapter,
livenessDetectionProps: {...},
readIDCardProps: {...}
onDoneEkyc: (result) => console.log(result),
onError: (e) => console.log(e),
});