ekyc-sdk-arm
Version:
Automated Recognition Module
676 lines (667 loc) • 25.1 kB
JavaScript
'use strict';
var FaceAPI = require('face-api.js');
var axios = require('axios');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var FaceAPI__namespace = /*#__PURE__*/_interopNamespace(FaceAPI);
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
var Question;
(function (Question) {
Question["FacingLeft"] = "FacingLeft";
Question["FacingRight"] = "FacingRight";
Question["MouthOpen"] = "MouthOpen";
Question["Smiling"] = "Smiling";
})(Question || (Question = {}));
var ProcessType;
(function (ProcessType) {
ProcessType["GET_RESULT"] = "GET_RESULT";
ProcessType["SAVE_TRANSACTION"] = "SAVE_TRANSACTION";
ProcessType["ERROR_API"] = "ERROR_API";
})(ProcessType || (ProcessType = {}));
var TypeCall;
(function (TypeCall) {
TypeCall["CONSUMER"] = "CONSUMER";
TypeCall["APIKEY"] = "APIKEY";
TypeCall["JWT"] = "JWT";
})(TypeCall || (TypeCall = {}));
class LivenessService {
AxiosCall;
Path;
ApiKey;
TypeCall;
constructor(api_key, path_api, type) {
this.Path = path_api;
this.ApiKey = api_key;
this.TypeCall = type;
var headers = {};
switch (type) {
case TypeCall.APIKEY:
headers = {
"x-api-key": this.ApiKey
};
break;
case TypeCall.CONSUMER:
headers = {
"x-consumer-custom-id": this.ApiKey
};
break;
case TypeCall.JWT:
headers = {
"Authorization": `Bearer ${this.ApiKey}`
};
break;
}
this.AxiosCall = axios__default["default"].create({
baseURL: this.Path,
headers: headers
});
}
async GetConfig() {
try {
const response = await this.AxiosCall.get("/v1/liveness/config").then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
throw new Error('different error than axios');
}
}
}
async CreateSession(param) {
try {
const response = await this.AxiosCall.post("/v1/liveness/session", param).then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
throw new Error('different error than axios');
}
}
}
async SaveTransaction(param) {
const paramFrom = new FormData();
paramFrom.append('image', param.image);
paramFrom.append('is_timeout', param.is_timeout.toString());
paramFrom.append('session_id', param.session_id);
paramFrom.append('question_no', param.question_no.toString());
paramFrom.append('round', param.round.toString());
paramFrom.append('question', param.question);
try {
const response = await this.AxiosCall.post("/v1/liveness/save_trx", paramFrom).then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
const err = error;
throw err;
}
}
}
SetPathAPI(path_api) {
this.Path = path_api;
this.AxiosCall = axios__default["default"].create({
baseURL: this.Path
});
}
GetPathAPI() {
return this.Path;
}
SetApiKey(api_key) {
this.ApiKey = api_key;
}
GetApiKey() {
return this.ApiKey;
}
SetTypeCall(type) {
this.TypeCall = type;
var headers = {};
switch (type) {
case TypeCall.APIKEY:
headers = {
"x-api-key": this.ApiKey
};
break;
case TypeCall.CONSUMER:
headers = {
"x-consumer-custom-id": this.ApiKey
};
break;
case TypeCall.JWT:
headers = {
"Authentication": `Bearer ${this.ApiKey}`
};
break;
}
this.AxiosCall = axios__default["default"].create({
baseURL: this.Path,
headers: headers
});
}
GetTypeCall() {
return this.TypeCall;
}
async Verify(request) {
const paramFrom = new FormData();
paramFrom.append('cust_uuid', request.cust_uuid);
paramFrom.append('source_image', request.source_image);
try {
const response = await this.AxiosCall.post("/v1/liveness/verify", paramFrom, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
throw new Error('different error than axios');
}
}
}
async GetResult(session_id) {
const paramFrom = new FormData();
paramFrom.append('session_id', session_id);
try {
const response = await this.AxiosCall.post("/v1/liveness/get_result", paramFrom, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
throw new Error('different error than axios');
}
}
}
async CheckStatusService(cust_uuid) {
try {
const response = await this.AxiosCall.get(`/v1/liveness/status?cust_uuid=${cust_uuid}`).then(res => res.data);
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error.response?.data;
}
else {
throw new Error('different error than axios');
}
}
}
}
class LivenessSDK {
ApiKey = "";
PathAPI = "http://localhost:8000";
Cust_UUID = "";
LivenessService;
MinConfident = 0.5;
Round = 0;
QuestionNo = 0;
SessionID = "";
ProcessStatus = false;
centerConfident = 0.1;
ConfigChannel = {
questions: 0,
question_list: [],
question_timeout_in_second: 0,
retry_proof: 0,
retry_verification: 0,
session_expired_in_second: 0,
};
SessionQuestions;
constructor(api_key, path_api = "", type, minConfident = 0.5) {
if (path_api != "") {
this.PathAPI = path_api;
}
this.ApiKey = api_key;
this.SessionQuestions = [];
this.LivenessService = new LivenessService(this.ApiKey, this.PathAPI, type);
this.MinConfident = minConfident;
}
async GetConfig() {
try {
const response = await this.LivenessService.GetConfig();
this.ConfigChannel = this.CheckFormatConfig(response);
return this.ConfigChannel;
}
catch (error) {
throw error;
}
}
GetConfidentConfig() {
return this.MinConfident;
}
CheckFormatConfig(config) {
if (config.questions * 1) {
config.questions *= 1;
}
if (config.question_timeout_in_second * 1) {
config.question_timeout_in_second *= 1;
}
if (config.retry_proof * 1) {
config.retry_proof *= 1;
}
if (config.retry_verification * 1) {
config.retry_verification *= 1;
}
if (config.session_expired_in_second * 1) {
config.session_expired_in_second *= 1;
}
return config;
}
async CreateSession(cust_uuid) {
this.Cust_UUID = cust_uuid;
const param = {
cust_uuid: this.Cust_UUID,
};
try {
const response = await this.LivenessService.CreateSession(param);
this.Round = response.round;
this.QuestionNo = 1;
this.ConfigChannel = this.CheckFormatConfig(response.config);
this.SessionID = response.session_id;
var tempQuestion = "";
var currentQuestion = "";
if (this.SessionQuestions.length != 0) {
tempQuestion = this.SessionQuestions.join(":");
currentQuestion = this.SessionQuestions[this.QuestionNo - 1];
}
for (let index = 0; index < 10; index++) {
this.SessionQuestions = [];
while (this.SessionQuestions.length < this.ConfigChannel.questions) {
const question_index = Math.floor(Math.random() * this.ConfigChannel.question_list.length);
if (this.SessionQuestions.includes(this.ConfigChannel.question_list[question_index]) ||
(this.SessionQuestions.length == 0 &&
this.ConfigChannel.question_list[question_index] ==
currentQuestion)) {
continue;
}
else {
this.SessionQuestions.push(this.ConfigChannel.question_list[question_index]);
}
}
if (this.SessionQuestions.join(":") != tempQuestion) {
break;
}
}
return response;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error;
}
else {
throw error;
}
}
}
GetQuestionSession() {
return this.SessionQuestions;
}
GetCurrentQuestion() {
var currentQuestion;
if (this.QuestionNo <= this.SessionQuestions.length) {
currentQuestion = {
questions: this.SessionQuestions,
question_no: this.QuestionNo,
current_question: this.SessionQuestions[this.QuestionNo - 1],
};
}
else {
currentQuestion = {
questions: this.SessionQuestions,
current_question: "",
question_no: this.QuestionNo,
};
}
return currentQuestion;
}
GetSessionID() {
return this.SessionID;
}
async SaveTransaction(image, is_timeout = false) {
var parameterRequest;
// const result = await this.ProcessImage(image);
const result = true;
if (is_timeout || result) {
parameterRequest = {
image: image,
is_timeout: is_timeout,
session_id: this.SessionID,
question_no: this.QuestionNo,
round: this.Round,
question: Question[this.SessionQuestions[this.QuestionNo - 1]],
};
var responseValue;
try {
const response = await this.LivenessService.SaveTransaction(parameterRequest);
if (response.condition_valid) {
if (this.QuestionNo === this.ConfigChannel.questions) {
const resultResponse = await this.LivenessService.GetResult(this.SessionID);
responseValue = response;
responseValue.is_timeout = false;
responseValue.session_status = true;
responseValue.session = {};
responseValue.result = resultResponse;
}
else {
const responseDetail = response;
responseValue = responseDetail;
responseValue.is_timeout = false;
responseValue.session_status = true;
responseValue.session = {};
responseValue.result = {};
this.QuestionNo += 1;
}
}
else {
const responseError = response;
responseValue = {
session_id: responseError.session_id,
condition_valid: responseError.condition_valid,
is_timeout: false,
session_status: true,
details: {},
session: {},
result: {},
};
const resultResponse = await this.LivenessService.GetResult(this.SessionID);
if (this.CheckPropertyInObj(responseError, "details", "reason")) {
switch (responseError.details.reason) {
case "session_expire":
responseValue.session_status = false;
break;
case "timeout":
responseValue.is_timeout = true;
break;
}
if (responseError.details.reason == "session_expire" ||
responseError.details.reason == "timeout") {
responseValue = {
session_id: responseError.session_id,
condition_valid: responseError.condition_valid,
is_timeout: false,
session_status: true,
details: {},
session: responseError.details,
result: resultResponse,
};
}
}
else {
responseValue = {
session_id: responseError.session_id,
condition_valid: responseError.condition_valid,
is_timeout: false,
session_status: true,
details: responseError.details,
session: {},
result: resultResponse,
};
}
}
return responseValue;
}
catch (error) {
if (axios__default["default"].isAxiosError(error)) {
throw error;
}
else {
const err = error;
if (err.error.type === "session_expire") {
try {
await this.LivenessService.GetResult(this.SessionID);
}
catch (error) {
throw error;
}
}
throw err;
}
}
}
else {
return false;
}
}
CheckPropertyInObj(obj, ...args) {
return args.reduce((obj, level) => obj && obj[level], obj);
}
async LoadModels(path) {
const MODEL_URL = path;
await Promise.all([
// FaceAPI.loadTinyFaceDetectorModel(MODEL_URL),
// FaceAPI.loadTinyYolov2Model(MODEL_URL),
// FaceAPI.loadAgeGenderModel(MODEL_URL),
FaceAPI__namespace.loadFaceLandmarkModel(MODEL_URL),
FaceAPI__namespace.loadFaceLandmarkTinyModel(MODEL_URL),
FaceAPI__namespace.loadFaceRecognitionModel(MODEL_URL),
FaceAPI__namespace.loadFaceExpressionModel(MODEL_URL),
FaceAPI__namespace.loadSsdMobilenetv1Model(MODEL_URL),
]);
}
GetMeanPosition(l) {
return l
.map((a) => [a.x, a.y])
.reduce((a, b) => [a[0] + b[0], a[1] + b[1]])
.map((a) => a / l.length);
}
GetTop(l) {
return l.map((a) => a.y).reduce((a, b) => Math.min(a, b));
}
async ProcessImage(image_file) {
var url_file;
if (typeof image_file == "string") {
url_file = image_file;
}
else {
url_file = URL.createObjectURL(image_file);
}
const image = await FaceAPI__namespace.fetchImage(url_file);
const options = new FaceAPI__namespace.SsdMobilenetv1Options({
minConfidence: this.MinConfident,
});
const result = await FaceAPI__namespace.detectSingleFace(image, options)
.withFaceLandmarks(true)
.withFaceDescriptor()
.withFaceExpressions();
var answer = [];
if (result) {
const eyeRight = this.GetMeanPosition(result.landmarks.getRightEye());
const eyeLeft = this.GetMeanPosition(result.landmarks.getLeftEye());
const nose = this.GetMeanPosition(result.landmarks.getNose());
const mouth = this.GetMeanPosition(result.landmarks.getMouth());
const jaw = this.GetTop(result.landmarks.getJawOutline());
const mouthUpper = result.landmarks.getMouth()[14].y;
const mouthLower = result.landmarks.getMouth()[18].y;
const mouthOffset = mouthLower - mouthUpper;
const rx = (jaw - mouth[1]) / result.detection.box.height;
const ry = (eyeLeft[0] + (eyeRight[0] - eyeLeft[0]) / 2 - nose[0]) /
result.detection.box.width;
if (ry < -0.075 && rx < 0.02) {
answer.push(Question.FacingLeft.toString());
}
if (ry > 0.065 && rx < 0.02) {
answer.push(Question.FacingRight.toString());
}
if (result.expressions.happy >= 0.8) {
answer.push(Question.Smiling.toString());
}
if (mouthOffset >= 15) {
answer.push(Question.MouthOpen.toString());
}
}
if (answer.length == 0) {
return false;
}
if (this.SessionQuestions.length <= this.QuestionNo) {
if (answer.includes(this.SessionQuestions[this.QuestionNo - 1])) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
SetAPIKey(api_key) {
this.ApiKey = api_key;
this.LivenessService.SetApiKey(this.ApiKey);
}
GetAPIKey() {
return this.ApiKey;
}
SetPathAPI(path_api) {
this.PathAPI = path_api;
this.LivenessService.SetPathAPI(path_api);
}
GetPathAPI() {
return this.PathAPI;
}
SetTypeCall(type) {
this.LivenessService.SetTypeCall(type);
}
GetTypeCall() {
return this.LivenessService.GetTypeCall();
}
setCenterConfident(centerConfident) {
this.centerConfident = centerConfident;
}
async DetectFace(image) {
var url_file;
if (typeof image == "string") {
url_file = image;
}
else {
url_file = URL.createObjectURL(image);
}
const imageBase = await FaceAPI__namespace.fetchImage(url_file);
const options = new FaceAPI__namespace.SsdMobilenetv1Options({
minConfidence: this.MinConfident,
});
const result = await FaceAPI__namespace.detectAllFaces(imageBase, options);
var response = {
num_faces: result.length,
detect_face: result.length > 0 ? true : false,
face_left: false,
face_right: false,
face_center: false,
open_month: false,
smiling: false,
ry: 0,
rx: 0,
};
if (result.length == 1) {
const options = new FaceAPI__namespace.SsdMobilenetv1Options({
minConfidence: this.MinConfident,
});
const processFace = await FaceAPI__namespace.detectSingleFace(imageBase, options)
.withFaceLandmarks(true)
.withFaceDescriptor()
.withFaceExpressions();
if (processFace) {
const eyeRight = this.GetMeanPosition(processFace.landmarks.getRightEye());
const eyeLeft = this.GetMeanPosition(processFace.landmarks.getLeftEye());
const nose = this.GetMeanPosition(processFace.landmarks.getNose());
const mouth = this.GetMeanPosition(processFace.landmarks.getMouth());
const jaw = this.GetTop(processFace.landmarks.getJawOutline());
const mouthUpper = processFace.landmarks.getMouth()[14].y;
const mouthLower = processFace.landmarks.getMouth()[18].y;
const mouthOffset = mouthLower - mouthUpper;
const rx = (jaw - mouth[1]) / processFace.detection.box.height;
const ry = (eyeLeft[0] + (eyeRight[0] - eyeLeft[0]) / 2 - nose[0]) /
processFace.detection.box.width;
response.ry = ry;
response.rx = rx;
const face_val = Number(ry.toFixed(2));
if (ry < -0.08 && rx < 0.015) {
response.face_left = true;
}
if (ry > 0.06 && rx < 0.015) {
response.face_right = true;
}
if (processFace.expressions.happy >= 0.8) {
response.smiling = true;
}
if (mouthOffset >= 15) {
response.open_month = true;
}
const start = processFace.detection.box.x;
const end = processFace.detection.box.y + processFace.detection.box.width;
const sizeXStart = start;
const sizeXEnd = processFace.alignedRect.imageWidth - end;
const spaceXSize = sizeXStart >= sizeXEnd ? sizeXStart - sizeXEnd : sizeXEnd - sizeXStart;
const startY = processFace.detection.box.y;
const endY = processFace.detection.box.y + processFace.detection.box.height;
const sizeYStart = startY;
const sizeYEnd = processFace.alignedRect.imageHeight - endY;
const spaceYSize = sizeYStart >= sizeYEnd ? sizeYStart - sizeYEnd : sizeYEnd - sizeYStart;
if (spaceYSize <= processFace.alignedRect.imageHeight * this.centerConfident &&
spaceXSize <= processFace.alignedRect.imageWidth * this.centerConfident &&
face_val > -0.06 && face_val < 0.07 && rx < -0.3) {
response.face_center = true;
}
}
}
return response;
}
async Verify(cust_uuid, source_image) {
var param;
param = {
cust_uuid: cust_uuid,
source_image: source_image,
};
try {
const response = await this.LivenessService.Verify(param);
return response;
}
catch (error) {
throw error;
}
}
async CheckServiceStatus(cust_uuid = "") {
const cust_uuid_param = cust_uuid != "" ? cust_uuid : this.Cust_UUID;
try {
const response = await this.LivenessService.CheckStatusService(cust_uuid_param);
return response;
}
catch (error) {
throw error;
}
}
}
exports.LivenessSDK = LivenessSDK;