santoid-sdk 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,7 +91,8 @@ async function startLivenessDetection () {
91
91
  errorCallback: ({ errorCode }) => {},
92
92
 
93
93
  // (Opcional) Função executada em caso de sucesso
94
- successCallback: () => {},
94
+ // O ID da solicitação pode ser recebido pelos parâmetros, como no exemplo abaixo
95
+ successCallback: ({ livenessId }) => {},
95
96
 
96
97
  // (Opcional) Função executada quando a validação é finalizada
97
98
  endCallback: () => {},
@@ -147,7 +148,7 @@ Ao importar a biblioteca via tag script, a função **livenessDetection** ficar
147
148
  <body>
148
149
  <button onclick="startLiveness()">Iniciar</button>
149
150
 
150
- <script src="https://cdn.jsdelivr.net/npm/santoid-sdk@1.0.4/dist/browser/liveness/index.js"></script>
151
+ <script src="https://cdn.jsdelivr.net/npm/santoid-sdk@1.0.6/dist/browser/liveness/index.js"></script>
151
152
 
152
153
  <script>
153
154
  async function startLiveness () {
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("SantoiDSDK",[],t):"object"==typeof exports?exports.SantoiDSDK=t():e.SantoiDSDK=t()}(this,(()=>(()=>{"use strict";var e={401:function(e,t,o){var n=this&&this.__createBinding||(Object.create?function(e,t,o,n){void 0===n&&(n=o);var a=Object.getOwnPropertyDescriptor(t,o);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[o]}}),Object.defineProperty(e,n,a)}:function(e,t,o,n){void 0===n&&(n=o),e[n]=t[o]}),a=this&&this.__exportStar||function(e,t){for(var o in e)"default"===o||Object.prototype.hasOwnProperty.call(t,o)||n(t,e,o)};Object.defineProperty(t,"__esModule",{value:!0}),t.livenessDetection=void 0;const r=o(553),i=o(793),c=o(692);async function l(e){var t,o,n;let a,l,s,d=!1,u=!1,v=!1,p=!0;async function f(e,t,o){var n;o&&clearInterval(o),t&&(0,c.disconnectFromWebSocket)(t),a&&a.stopGetNextFrame(),!v&&await(null===(n=e.endCallback)||void 0===n?void 0:n.call(e)),v=!0,d=!0}try{return s=await(0,c.connectToWebSocket)(e),await(null===(t=e.nextStepCallback)||void 0===t?void 0:t.call(e,"front")),await(null===(o=e.frontCallback)||void 0===o?void 0:o.call(e)),e.getNextFrame||a||(a=await(0,i.builtInGetNextFrame)()),l=setInterval((async()=>{try{if(p){let t;t=e.getNextFrame?await e.getNextFrame():await(null==a?void 0:a.getNextFrame()),t&&(null==s||s.send(t),p=!1)}}catch(t){await f(e,s,l)}}),r.SEND_FRAMES_INTERVAL_MS),s.addEventListener("message",(async t=>{var o;const n=await async function(e,t){var o,n,a;const r=t.data;let i;try{i=JSON.parse(r)}catch(e){console.error("Cannot parse API message",e)}return(null==i?void 0:i.next_step)&&!i.error&&(await(null===(o=e.nextStepCallback)||void 0===o?void 0:o.call(e,i.next_step)),await(null===(n=e[`${i.next_step}Callback`])||void 0===n?void 0:n.call(e))),(null==i?void 0:i.error)&&await(null===(a=e.errorCallback)||void 0===a?void 0:a.call(e,{code:"wrong-position"})),i}(e,t);n&&(p=!!(null==n?void 0:n.can_send_next_bytes)),(null==n?void 0:n.success)&&(u=!0,await(null===(o=e.successCallback)||void 0===o?void 0:o.call(e)),await f(e,s,l))})),s.addEventListener("error",(async()=>{var t;!u&&(null===(t=e.errorCallback)||void 0===t||t.call(e,{code:"internal-error"})),await f(e,s,l)})),s.addEventListener("close",(async()=>{var t;d||(!u&&(null===(t=e.errorCallback)||void 0===t||t.call(e,{code:"internal-error"})),await f(e,s,l))})),{stopLivenessDetection:async()=>{await f(e,s,l)},videoStream:null==a?void 0:a.videoStream}}catch(t){throw!u&&(null===(n=e.errorCallback)||void 0===n||n.call(e,{code:"internal-error"})),await f(e,s,l),t}}a(o(722),t),t.livenessDetection=l;class s{}s.livenessDetection=l,t.default=s},793:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.builtInGetNextFrame=void 0,t.builtInGetNextFrame=async function(){let e,t,o,n;return e&&t&&o||(e=await navigator.mediaDevices.getUserMedia({video:!0}),t=document.createElement("video"),t.setAttribute("autoplay","true"),t.style.transform="scale(-1, 1)",t.style.position="absolute",t.style.left="99999px",document.body.appendChild(t),t.srcObject=e,o=document.createElement("canvas"),n=o.getContext("2d")),{getNextFrame:async function(){if(o&&t)return o.width=t.videoWidth,o.height=t.videoHeight,null==n||n.drawImage(t,0,0,o.width,o.height),await new Promise(((e,t)=>{o?o.toBlob((t=>{e(t)})):e(null)}));throw new Error("Cannot found video element")},stopGetNextFrame:function(){t&&(document.body.removeChild(t),t=void 0),o&&(o=void 0),n&&(n=void 0),null==e||e.getTracks().forEach((e=>{e.stop()})),e=void 0},videoStream:e}}},268:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.decodeJwt=void 0,t.decodeJwt=function(e){const t=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(window.atob(t).split("").map((e=>"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2))).join(""));return JSON.parse(o)}},722:function(e,t,o){var n=this&&this.__createBinding||(Object.create?function(e,t,o,n){void 0===n&&(n=o);var a=Object.getOwnPropertyDescriptor(t,o);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[o]}}),Object.defineProperty(e,n,a)}:function(e,t,o,n){void 0===n&&(n=o),e[n]=t[o]}),a=this&&this.__exportStar||function(e,t){for(var o in e)"default"===o||Object.prototype.hasOwnProperty.call(t,o)||n(t,e,o)};Object.defineProperty(t,"__esModule",{value:!0}),a(o(307),t)},692:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.disconnectFromWebSocket=t.connectToWebSocket=void 0;const n=o(268),a=o(553);t.connectToWebSocket=async function(e){var t,o;const r=(0,n.decodeJwt)(e.token),i=`?x_endpoint_api_userinfo=${e.token}&domain=${null!==(t=e.domain)&&void 0!==t?t:r.claims.domain}&track=${e.track}&customerRequestId=${e.customId}`,c=new WebSocket(`${null!==(o=e.customWebSocketUrl)&&void 0!==o?o:a.WEBSOCKET_BASE_URL}${i}`);return await async function(e){if(await new Promise((t=>{const o=setInterval((()=>{[3,1].includes(e.readyState)&&(clearInterval(o),t())}),5)})),3===e.readyState)throw new Error("Cannot connect to server")}(c),c},t.disconnectFromWebSocket=function(e){var t;[2,3].includes(e.readyState)||null===(t=e.close)||void 0===t||t.call(e)}},553:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SEND_FRAMES_INTERVAL_MS=t.API_GATEWAY_URL=t.WEBSOCKET_BASE_URL=void 0,t.WEBSOCKET_BASE_URL="wss://santoid-liveness-w4ynos5dlq-ue.a.run.app/api/v1/liveness/ws",t.API_GATEWAY_URL="https://api.santoid.com.br/api/v1/service-account/refresh-token",t.SEND_FRAMES_INTERVAL_MS=100},307:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0})}},t={},o=function o(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n].call(r.exports,r,r.exports,o),r.exports}(401);return o.default})()));
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("SantoiDSDK",[],t):"object"==typeof exports?exports.SantoiDSDK=t():e.SantoiDSDK=t()}(this,(()=>(()=>{"use strict";var e={401:function(e,t,o){var n=this&&this.__createBinding||(Object.create?function(e,t,o,n){void 0===n&&(n=o);var a=Object.getOwnPropertyDescriptor(t,o);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[o]}}),Object.defineProperty(e,n,a)}:function(e,t,o,n){void 0===n&&(n=o),e[n]=t[o]}),a=this&&this.__exportStar||function(e,t){for(var o in e)"default"===o||Object.prototype.hasOwnProperty.call(t,o)||n(t,e,o)};Object.defineProperty(t,"__esModule",{value:!0}),t.livenessDetection=void 0;const r=o(553),i=o(793),l=o(692);async function c(e){var t,o,n;let a,c=!1,s=!1,d=!1,u=!0;const v={front:void 0,right:void 0,left:void 0,up:void 0,bottom:void 0};let p,f,_,b="front";async function m(e,t,o){var n;o&&clearInterval(o),t&&(0,l.disconnectFromWebSocket)(t),a&&a.stopGetNextFrame(),!d&&await(null===(n=e.endCallback)||void 0===n?void 0:n.call(e)),d=!0,c=!0}try{return _=await(0,l.connectToWebSocket)(e),await(null===(t=e.nextStepCallback)||void 0===t?void 0:t.call(e,"front")),await(null===(o=e.frontCallback)||void 0===o?void 0:o.call(e)),e.getNextFrame||a||(a=await(0,i.builtInGetNextFrame)()),f=setInterval((async()=>{try{if(u){let t;t=e.getNextFrame?await e.getNextFrame():await(null==a?void 0:a.getNextFrame()),t&&(null==_||_.send(t),p=t,u=!1)}}catch(t){await m(e,_,f)}}),r.SEND_FRAMES_INTERVAL_MS),_.addEventListener("message",(async t=>{var o;const n=await async function(e,t){var o,n,a;const r=t.data;let i;try{i=JSON.parse(r)}catch(e){console.error("Cannot parse API message",e)}return(null==i?void 0:i.next_step)&&!i.error&&(await(null===(o=e.nextStepCallback)||void 0===o?void 0:o.call(e,i.next_step)),await(null===(n=e[`${i.next_step}Callback`])||void 0===n?void 0:n.call(e))),(null==i?void 0:i.error)&&await(null===(a=e.errorCallback)||void 0===a?void 0:a.call(e,{code:"wrong-position"})),i}(e,t);n&&(u=!!(null==n?void 0:n.can_send_next_bytes)),(null==n?void 0:n.validation)&&b&&(v[b]=p),(null==n?void 0:n.next_step)&&(b=n.next_step),(null==n?void 0:n.success)&&(v.bottom=p,s=!0,await(null===(o=e.successCallback)||void 0===o?void 0:o.call(e,{livenessId:n.livenessId,successFrames:v})),await m(e,_,f))})),_.addEventListener("error",(async()=>{var t;!s&&(null===(t=e.errorCallback)||void 0===t||t.call(e,{code:"internal-error"})),await m(e,_,f)})),_.addEventListener("close",(async()=>{var t;c||(!s&&(null===(t=e.errorCallback)||void 0===t||t.call(e,{code:"internal-error"})),await m(e,_,f))})),{stopLivenessDetection:async()=>{await m(e,_,f)},videoStream:null==a?void 0:a.videoStream}}catch(t){throw!s&&(null===(n=e.errorCallback)||void 0===n||n.call(e,{code:"internal-error"})),await m(e,_,f),t}}a(o(722),t),t.livenessDetection=c;class s{}s.livenessDetection=c,t.default=s},793:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.builtInGetNextFrame=void 0,t.builtInGetNextFrame=async function(){let e,t,o,n;return e&&t&&o||(e=await navigator.mediaDevices.getUserMedia({video:!0}),t=document.createElement("video"),t.setAttribute("autoplay","true"),t.style.transform="scale(-1, 1)",t.style.position="absolute",t.style.left="99999px",document.body.appendChild(t),t.srcObject=e,o=document.createElement("canvas"),n=o.getContext("2d")),{getNextFrame:async function(){if(o&&t)return o.width=t.videoWidth,o.height=t.videoHeight,null==n||n.drawImage(t,0,0,o.width,o.height),await new Promise(((e,t)=>{o?o.toBlob((t=>{e(t)})):e(null)}));throw new Error("Cannot found video element")},stopGetNextFrame:function(){t&&(document.body.removeChild(t),t=void 0),o&&(o=void 0),n&&(n=void 0),null==e||e.getTracks().forEach((e=>{e.stop()})),e=void 0},videoStream:e}}},268:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.decodeJwt=void 0,t.decodeJwt=function(e){const t=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(window.atob(t).split("").map((e=>"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2))).join(""));return JSON.parse(o)}},722:function(e,t,o){var n=this&&this.__createBinding||(Object.create?function(e,t,o,n){void 0===n&&(n=o);var a=Object.getOwnPropertyDescriptor(t,o);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[o]}}),Object.defineProperty(e,n,a)}:function(e,t,o,n){void 0===n&&(n=o),e[n]=t[o]}),a=this&&this.__exportStar||function(e,t){for(var o in e)"default"===o||Object.prototype.hasOwnProperty.call(t,o)||n(t,e,o)};Object.defineProperty(t,"__esModule",{value:!0}),a(o(307),t)},692:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.disconnectFromWebSocket=t.connectToWebSocket=void 0;const n=o(268),a=o(553);t.connectToWebSocket=async function(e){var t,o;const r=(0,n.decodeJwt)(e.token),i=`?x_endpoint_api_userinfo=${e.token}&domain=${null!==(t=e.domain)&&void 0!==t?t:r.claims.domain}&track=${e.track}&customerRequestId=${e.customId}`,l=new WebSocket(`${null!==(o=e.customWebSocketUrl)&&void 0!==o?o:a.WEBSOCKET_BASE_URL}${i}`);return await async function(e){if(await new Promise((t=>{const o=setInterval((()=>{[3,1].includes(e.readyState)&&(clearInterval(o),t())}),5)})),3===e.readyState)throw new Error("Cannot connect to server")}(l),l},t.disconnectFromWebSocket=function(e){var t;[2,3].includes(e.readyState)||null===(t=e.close)||void 0===t||t.call(e)}},553:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SEND_FRAMES_INTERVAL_MS=t.API_GATEWAY_URL=t.WEBSOCKET_BASE_URL=void 0,t.WEBSOCKET_BASE_URL="wss://santoid-liveness-w4ynos5dlq-ue.a.run.app/api/v1/liveness/ws",t.API_GATEWAY_URL="https://api.santoid.com.br/api/v1/service-account/refresh-token",t.SEND_FRAMES_INTERVAL_MS=100},307:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0})}},t={},o=function o(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n].call(r.exports,r,r.exports,o),r.exports}(401);return o.default})()));
@@ -45,6 +45,15 @@ async function livenessDetection(options) {
45
45
  let validationSuccess = false;
46
46
  let endCalled = false;
47
47
  let canSendNextBytes = true;
48
+ const successFrames = {
49
+ front: undefined,
50
+ right: undefined,
51
+ left: undefined,
52
+ up: undefined,
53
+ bottom: undefined,
54
+ };
55
+ let lastSentBytes;
56
+ let currentStep = 'front';
48
57
  async function stopLivenessDetection(options, websocket, intervalKey) {
49
58
  var _a;
50
59
  if (intervalKey) {
@@ -81,6 +90,7 @@ async function livenessDetection(options) {
81
90
  }
82
91
  if (bytes) {
83
92
  websocket === null || websocket === void 0 ? void 0 : websocket.send(bytes);
93
+ lastSentBytes = bytes;
84
94
  canSendNextBytes = false;
85
95
  }
86
96
  }
@@ -95,9 +105,16 @@ async function livenessDetection(options) {
95
105
  if (parsedMessage) {
96
106
  canSendNextBytes = !!(parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.can_send_next_bytes);
97
107
  }
108
+ if ((parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.validation) && currentStep) {
109
+ successFrames[currentStep] = lastSentBytes;
110
+ }
111
+ if (parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.next_step) {
112
+ currentStep = parsedMessage.next_step;
113
+ }
98
114
  if (parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.success) {
115
+ successFrames.bottom = lastSentBytes;
99
116
  validationSuccess = true;
100
- await ((_a = options.successCallback) === null || _a === void 0 ? void 0 : _a.call(options));
117
+ await ((_a = options.successCallback) === null || _a === void 0 ? void 0 : _a.call(options, { livenessId: parsedMessage.livenessId, successFrames }));
101
118
  await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
102
119
  }
103
120
  });
@@ -28,6 +28,15 @@ async function livenessDetection(options) {
28
28
  let validationSuccess = false;
29
29
  let endCalled = false;
30
30
  let canSendNextBytes = true;
31
+ const successFrames = {
32
+ front: undefined,
33
+ right: undefined,
34
+ left: undefined,
35
+ up: undefined,
36
+ bottom: undefined,
37
+ };
38
+ let lastSentBytes;
39
+ let currentStep = 'front';
31
40
  async function stopLivenessDetection(options, websocket, intervalKey) {
32
41
  var _a;
33
42
  if (intervalKey) {
@@ -52,6 +61,7 @@ async function livenessDetection(options) {
52
61
  const bytes = await options.getNextFrame();
53
62
  if (bytes) {
54
63
  websocket === null || websocket === void 0 ? void 0 : websocket.send(bytes);
64
+ lastSentBytes = bytes;
55
65
  canSendNextBytes = false;
56
66
  }
57
67
  }
@@ -66,9 +76,16 @@ async function livenessDetection(options) {
66
76
  if (parsedMessage) {
67
77
  canSendNextBytes = !!(parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.can_send_next_bytes);
68
78
  }
79
+ if ((parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.validation) && currentStep) {
80
+ successFrames[currentStep] = lastSentBytes;
81
+ }
82
+ if (parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.next_step) {
83
+ currentStep = parsedMessage.next_step;
84
+ }
69
85
  if (parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.success) {
86
+ successFrames.bottom = lastSentBytes;
70
87
  validationSuccess = true;
71
- await ((_a = options.successCallback) === null || _a === void 0 ? void 0 : _a.call(options));
88
+ await ((_a = options.successCallback) === null || _a === void 0 ? void 0 : _a.call(options, { livenessId: parsedMessage.livenessId, successFrames }));
72
89
  await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
73
90
  }
74
91
  });
@@ -6,6 +6,10 @@ export interface IDecodedToken {
6
6
  domain: string;
7
7
  };
8
8
  }
9
+ interface ILivenessResults<FrameType> {
10
+ livenessId: string;
11
+ successFrames: Record<TPositions, FrameType>;
12
+ }
9
13
  export interface ISDKOptions {
10
14
  token: string;
11
15
  track: string;
@@ -20,13 +24,16 @@ export interface ISDKOptions {
20
24
  errorCallback?: (error: {
21
25
  code: TErrorCode;
22
26
  }) => void | Promise<void>;
23
- successCallback?: TCallbackFunction;
27
+ successCallback?: <FrameType>(results: ILivenessResults<FrameType>) => void | Promise<void>;
24
28
  endCallback?: TCallbackFunction;
25
29
  customWebSocketUrl?: string;
26
30
  }
27
31
  export interface IWebSocketMessage {
28
- success?: boolean;
29
- error?: boolean;
30
32
  can_send_next_bytes?: boolean;
31
33
  next_step?: TPositions;
34
+ validation?: boolean;
35
+ error?: boolean;
36
+ success?: boolean;
37
+ livenessId: string;
32
38
  }
39
+ export {};
@@ -0,0 +1,17 @@
1
+ const config = {
2
+ testEnvironment: 'jsdom',
3
+ transform: {
4
+ '^.+\\.ts?$': 'ts-jest',
5
+ },
6
+ collectCoverageFrom: [
7
+ '<rootDir>/src/**/*.ts',
8
+ ],
9
+ coveragePathIgnorePatterns: [
10
+ 'types.ts',
11
+ ],
12
+ setupFiles: [
13
+ 'jest-canvas-mock',
14
+ ],
15
+ }
16
+
17
+ module.exports = config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "santoid-sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "",
5
5
  "main": "dist/esm/liveness/index.js",
6
6
  "types": "dist/esm/liveness/index.d.ts",
@@ -8,11 +8,13 @@
8
8
  "expose:cmd": "mv -v dist/* ./",
9
9
  "dev:webpack": "webpack --mode=development --watch --progress",
10
10
  "build:browser-node": "webpack --mode=production",
11
- "build:typescript": "rm -rf ./dist/esm && tsc",
12
- "build": "npm run build:typescript && npm run build:browser-node"
11
+ "build:typescript": "rm -rf ./dist && tsc",
12
+ "build": "npm run build:typescript && npm run build:browser-node",
13
+ "test": "jest",
14
+ "test:coverage": "jest --coverage"
13
15
  },
14
16
  "exports": {
15
- ".": "./dist",
17
+ ".": "./dist/index.js",
16
18
  "./liveness": "./dist/esm/liveness/index.js",
17
19
  "./node/liveness": "./dist/node/liveness/index.js",
18
20
  "./node/auth": "./dist/node/auth/index.js"
@@ -34,6 +36,7 @@
34
36
  "author": "SantoDigital",
35
37
  "license": "Apache-2.0",
36
38
  "devDependencies": {
39
+ "@types/jest": "^29.5.5",
37
40
  "@types/node": "^20.6.2",
38
41
  "@types/ws": "^8.5.6",
39
42
  "@typescript-eslint/eslint-plugin": "^6.4.0",
@@ -43,6 +46,10 @@
43
46
  "eslint-plugin-import": "^2.25.2",
44
47
  "eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
45
48
  "eslint-plugin-promise": "^6.0.0",
49
+ "jest": "^29.7.0",
50
+ "jest-canvas-mock": "^2.5.2",
51
+ "jest-environment-jsdom": "^29.7.0",
52
+ "ts-jest": "^29.1.1",
46
53
  "ts-loader": "^9.4.4",
47
54
  "ts-node": "^10.9.1",
48
55
  "typescript": "*",
@@ -1,4 +1,4 @@
1
- import { type IWebSocketMessage, type TWebSDKOptions } from './utils/types'
1
+ import { type TPositions, type IWebSocketMessage, type TWebSDKOptions } from './utils/types'
2
2
  import { SEND_FRAMES_INTERVAL_MS } from '../../utils/constants'
3
3
  import { builtInGetNextFrame, type builtInGetNextFrameReturn } from './utils/getNextFrame'
4
4
 
@@ -30,12 +30,25 @@ async function onWebSocketMessage (options: TWebSDKOptions, event: MessageEvent)
30
30
  }
31
31
 
32
32
  export async function livenessDetection (options: TWebSDKOptions) {
33
+ type FrameType = Blob | null | undefined
34
+
33
35
  let websocketClosedBySDK = false
34
36
  let builtInGetNextFrameReturn: builtInGetNextFrameReturn | undefined
35
37
  let validationSuccess = false
36
38
  let endCalled = false
37
39
  let canSendNextBytes = true
38
40
 
41
+ const successFrames: Record<TPositions, FrameType> = {
42
+ front: undefined,
43
+ right: undefined,
44
+ left: undefined,
45
+ up: undefined,
46
+ bottom: undefined,
47
+ }
48
+
49
+ let lastSentBytes: FrameType
50
+ let currentStep: TPositions = 'front'
51
+
39
52
  async function stopLivenessDetection (options: TWebSDKOptions, websocket?: WebSocket, intervalKey?: any) {
40
53
  if (intervalKey) {
41
54
  clearInterval(intervalKey)
@@ -81,6 +94,7 @@ export async function livenessDetection (options: TWebSDKOptions) {
81
94
 
82
95
  if (bytes) {
83
96
  websocket?.send(bytes)
97
+ lastSentBytes = bytes
84
98
  canSendNextBytes = false
85
99
  }
86
100
  }
@@ -96,9 +110,19 @@ export async function livenessDetection (options: TWebSDKOptions) {
96
110
  canSendNextBytes = !!parsedMessage?.can_send_next_bytes
97
111
  }
98
112
 
113
+ if (parsedMessage?.validation && currentStep) {
114
+ successFrames[currentStep] = lastSentBytes
115
+ }
116
+
117
+ if (parsedMessage?.next_step) {
118
+ currentStep = parsedMessage.next_step
119
+ }
120
+
99
121
  if (parsedMessage?.success) {
122
+ successFrames.bottom = lastSentBytes
123
+
100
124
  validationSuccess = true
101
- await options.successCallback?.()
125
+ await options.successCallback?.<FrameType>({ livenessId: parsedMessage.livenessId, successFrames })
102
126
  await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
103
127
  }
104
128
  })
@@ -1,6 +1,7 @@
1
1
  import type WebSocket from 'ws'
2
+ import { type Data } from 'ws'
2
3
 
3
- import { type IWebSocketMessage, type TNodeSDKOptions } from './utils/types'
4
+ import { type TPositions, type IWebSocketMessage, type TNodeSDKOptions } from './utils/types'
4
5
  import { SEND_FRAMES_INTERVAL_MS } from '../../utils/constants'
5
6
  import { connectToWebSocket, disconnectFromWebSocket } from './utils/websocket'
6
7
 
@@ -28,11 +29,24 @@ async function onWebSocketMessage (options: TNodeSDKOptions, event: { data: stri
28
29
  }
29
30
 
30
31
  export async function livenessDetection (options: TNodeSDKOptions) {
32
+ type FrameType = Data | null | undefined
33
+
31
34
  let websocketClosedBySDK = false
32
35
  let validationSuccess = false
33
36
  let endCalled = false
34
37
  let canSendNextBytes = true
35
38
 
39
+ const successFrames: Record<TPositions, FrameType> = {
40
+ front: undefined,
41
+ right: undefined,
42
+ left: undefined,
43
+ up: undefined,
44
+ bottom: undefined,
45
+ }
46
+
47
+ let lastSentBytes: FrameType
48
+ let currentStep: TPositions = 'front'
49
+
36
50
  async function stopLivenessDetection (options: TNodeSDKOptions, websocket?: WebSocket, intervalKey?: any) {
37
51
  if (intervalKey) {
38
52
  clearInterval(intervalKey)
@@ -64,6 +78,7 @@ export async function livenessDetection (options: TNodeSDKOptions) {
64
78
 
65
79
  if (bytes) {
66
80
  websocket?.send(bytes)
81
+ lastSentBytes = bytes
67
82
  canSendNextBytes = false
68
83
  }
69
84
  }
@@ -79,9 +94,19 @@ export async function livenessDetection (options: TNodeSDKOptions) {
79
94
  canSendNextBytes = !!parsedMessage?.can_send_next_bytes
80
95
  }
81
96
 
97
+ if (parsedMessage?.validation && currentStep) {
98
+ successFrames[currentStep] = lastSentBytes
99
+ }
100
+
101
+ if (parsedMessage?.next_step) {
102
+ currentStep = parsedMessage.next_step
103
+ }
104
+
82
105
  if (parsedMessage?.success) {
106
+ successFrames.bottom = lastSentBytes
107
+
83
108
  validationSuccess = true
84
- await options.successCallback?.()
109
+ await options.successCallback?.<FrameType>({ livenessId: parsedMessage.livenessId, successFrames })
85
110
  await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
86
111
  }
87
112
  })
@@ -10,6 +10,11 @@ export interface IDecodedToken {
10
10
  }
11
11
  }
12
12
 
13
+ interface ILivenessResults<FrameType> {
14
+ livenessId: string
15
+ successFrames: Record<TPositions, FrameType>
16
+ }
17
+
13
18
  export interface ISDKOptions {
14
19
  token: string
15
20
  track: string
@@ -23,15 +28,19 @@ export interface ISDKOptions {
23
28
  upCallback?: TCallbackFunction
24
29
  bottomCallback?: TCallbackFunction
25
30
  errorCallback?: (error: { code: TErrorCode }) => void | Promise<void>
26
- successCallback?: TCallbackFunction
31
+ successCallback?: <FrameType>(results: ILivenessResults<FrameType>) => void | Promise<void>
27
32
  endCallback?: TCallbackFunction
28
33
 
29
34
  customWebSocketUrl?: string
30
35
  }
31
36
 
32
37
  export interface IWebSocketMessage {
33
- success?: boolean
34
- error?: boolean
35
38
  can_send_next_bytes?: boolean
36
39
  next_step?: TPositions
40
+ validation?: boolean
41
+
42
+ error?: boolean
43
+
44
+ success?: boolean
45
+ livenessId: string
37
46
  }
package/tsconfig.json CHANGED
@@ -5,11 +5,12 @@
5
5
  "target": "es2017",
6
6
  "moduleResolution": "node",
7
7
  "outDir": "dist",
8
+ "rootDir": "src",
8
9
  "baseUrl": "./",
9
10
  "declaration": true,
10
11
  "esModuleInterop": true,
11
12
  "strictNullChecks": true,
12
13
  },
13
14
 
14
- "include": ["**/*.ts", "./.eslintrc.js", "./webpack.config.cjs"],
15
+ "include": ["src/**/*.ts", "./.eslintrc.js", "./webpack.config.cjs", "./jest.config.cjs"],
15
16
  }