santoid-sdk 1.0.7 → 2.0.1
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 +113 -29
- package/browser/liveness/index.js +1 -0
- package/liveness/index.d.ts +9 -0
- package/liveness/index.js +29 -0
- package/liveness/methods/livenessDetection.d.ts +6 -0
- package/{dist/esm/liveness/index.js → liveness/methods/livenessDetection.js} +2 -22
- package/liveness/methods/uploadIdentificationDocument.d.ts +91 -0
- package/liveness/methods/uploadIdentificationDocument.js +238 -0
- package/liveness/utils/index.d.ts +2 -0
- package/{dist/esm/liveness → liveness}/utils/index.js +10 -1
- package/{dist/esm/liveness → liveness}/utils/types.d.ts +2 -2
- package/{dist/node/liveness → liveness}/utils/types.js +1 -1
- package/{dist/esm/liveness → liveness}/utils/websocket.js +1 -1
- package/{dist/node → node}/auth/index.js +3 -6
- package/node/liveness/index.d.ts +9 -0
- package/node/liveness/index.js +29 -0
- package/node/liveness/methods/livenessDetection.d.ts +6 -0
- package/{dist/node/liveness/index.js → node/liveness/methods/livenessDetection.js} +2 -3
- package/node/liveness/methods/uploadIdentificationDocument.d.ts +91 -0
- package/node/liveness/methods/uploadIdentificationDocument.js +239 -0
- package/node/liveness/utils/index.d.ts +2 -0
- package/{dist/node → node}/liveness/utils/index.js +10 -1
- package/package.json +12 -29
- package/tsconfig.json +6 -2
- package/tsconfig.prod.json +26 -0
- package/{dist/utils → utils}/constants.d.ts +1 -1
- package/{dist/utils → utils}/constants.js +1 -1
- package/{dist/utils → utils}/types.d.ts +11 -8
- package/webpack.config.cjs +12 -2
- package/dist/browser/liveness/index.js +0 -1
- package/dist/esm/liveness/index.d.ts +0 -9
- package/dist/esm/liveness/utils/index.d.ts +0 -1
- package/dist/node/liveness/index.d.ts +0 -4
- package/dist/node/liveness/utils/index.d.ts +0 -1
- package/src/esm/liveness/index.ts +0 -156
- package/src/esm/liveness/utils/getNextFrame.ts +0 -76
- package/src/esm/liveness/utils/index.ts +0 -16
- package/src/esm/liveness/utils/types.ts +0 -7
- package/src/esm/liveness/utils/websocket.ts +0 -33
- package/src/index.ts +0 -0
- package/src/node/auth/index.ts +0 -43
- package/src/node/liveness/index.ts +0 -135
- package/src/node/liveness/utils/index.ts +0 -3
- package/src/node/liveness/utils/types.ts +0 -8
- package/src/node/liveness/utils/websocket.ts +0 -35
- package/src/utils/constants.ts +0 -3
- package/src/utils/types.ts +0 -46
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/index.js → index.js} +0 -0
- /package/{dist/esm/liveness → liveness}/utils/getNextFrame.d.ts +0 -0
- /package/{dist/esm/liveness → liveness}/utils/getNextFrame.js +0 -0
- /package/{dist/esm/liveness → liveness}/utils/websocket.d.ts +0 -0
- /package/{dist/node → node}/auth/index.d.ts +0 -0
- /package/{dist/node → node}/liveness/utils/types.d.ts +0 -0
- /package/{dist/esm → node}/liveness/utils/types.js +0 -0
- /package/{dist/node → node}/liveness/utils/websocket.d.ts +0 -0
- /package/{dist/node → node}/liveness/utils/websocket.js +0 -0
- /package/{dist/utils → utils}/types.js +0 -0
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
export interface builtInGetNextFrameReturn {
|
|
2
|
-
getNextFrame: () => Promise<Blob | null>
|
|
3
|
-
stopGetNextFrame: () => void
|
|
4
|
-
videoStream: MediaStream | undefined
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function builtInGetNextFrame (): Promise<builtInGetNextFrameReturn> {
|
|
8
|
-
let videoStream: MediaStream | undefined
|
|
9
|
-
let videoElement: HTMLVideoElement | undefined
|
|
10
|
-
let canvasElement: HTMLCanvasElement | undefined
|
|
11
|
-
let canvasContext: CanvasRenderingContext2D | null | undefined
|
|
12
|
-
|
|
13
|
-
async function getNextFrame (): Promise<Blob | null> {
|
|
14
|
-
if (canvasElement && videoElement) {
|
|
15
|
-
canvasElement.width = videoElement.videoWidth
|
|
16
|
-
canvasElement.height = videoElement.videoHeight
|
|
17
|
-
|
|
18
|
-
canvasContext?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height)
|
|
19
|
-
|
|
20
|
-
return await new Promise((resolve, reject) => {
|
|
21
|
-
if (!canvasElement) {
|
|
22
|
-
resolve(null)
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
canvasElement.toBlob((blob) => {
|
|
27
|
-
resolve(blob)
|
|
28
|
-
})
|
|
29
|
-
})
|
|
30
|
-
} else {
|
|
31
|
-
throw new Error('Cannot found video element')
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function stopGetNextFrame () {
|
|
36
|
-
if (videoElement) {
|
|
37
|
-
document.body.removeChild(videoElement)
|
|
38
|
-
videoElement = undefined
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (canvasElement) {
|
|
42
|
-
canvasElement = undefined
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (canvasContext) {
|
|
46
|
-
canvasContext = undefined
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
videoStream?.getTracks().forEach(track => { track.stop() })
|
|
50
|
-
videoStream = undefined
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!videoStream || !videoElement || !canvasElement) {
|
|
54
|
-
videoStream = await navigator.mediaDevices.getUserMedia({ video: true })
|
|
55
|
-
|
|
56
|
-
videoElement = document.createElement('video')
|
|
57
|
-
videoElement.setAttribute('autoplay', 'true')
|
|
58
|
-
|
|
59
|
-
videoElement.style.transform = 'scale(-1, 1)'
|
|
60
|
-
videoElement.style.position = 'absolute'
|
|
61
|
-
videoElement.style.left = '99999px'
|
|
62
|
-
|
|
63
|
-
document.body.appendChild(videoElement)
|
|
64
|
-
|
|
65
|
-
videoElement.srcObject = videoStream
|
|
66
|
-
|
|
67
|
-
canvasElement = document.createElement('canvas')
|
|
68
|
-
canvasContext = canvasElement.getContext('2d')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
getNextFrame,
|
|
73
|
-
stopGetNextFrame,
|
|
74
|
-
videoStream,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function decodeJwt (token: string) {
|
|
2
|
-
const base64Url = token.split('.')[1]
|
|
3
|
-
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
|
|
4
|
-
|
|
5
|
-
const jsonPayload = decodeURIComponent(
|
|
6
|
-
window
|
|
7
|
-
.atob(base64)
|
|
8
|
-
.split('')
|
|
9
|
-
.map((c) => {
|
|
10
|
-
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
11
|
-
})
|
|
12
|
-
.join(''),
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
return JSON.parse(jsonPayload)
|
|
16
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { decodeJwt } from '.'
|
|
2
|
-
import { WEBSOCKET_BASE_URL } from '../../../utils/constants'
|
|
3
|
-
import { type IDecodedToken, type TWebSDKOptions } from './types'
|
|
4
|
-
|
|
5
|
-
async function waitForWebSocketConnection (websocket: WebSocket) {
|
|
6
|
-
await new Promise<void>((resolve) => {
|
|
7
|
-
const intervalKey = setInterval(() => {
|
|
8
|
-
if ([3, 1].includes(websocket.readyState)) {
|
|
9
|
-
clearInterval(intervalKey)
|
|
10
|
-
resolve()
|
|
11
|
-
}
|
|
12
|
-
}, 5)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
if (websocket.readyState === 3) {
|
|
16
|
-
throw new Error('Cannot connect to server')
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function connectToWebSocket (options: TWebSDKOptions) {
|
|
21
|
-
const decodedToken: IDecodedToken = decodeJwt(options.token)
|
|
22
|
-
const query = `?x_endpoint_api_userinfo=${options.token}&domain=${options.domain ?? decodedToken.claims.domain}&track=${options.track}&customerRequestId=${options.customId}`
|
|
23
|
-
|
|
24
|
-
const websocket = new WebSocket(`${options.customWebSocketUrl ?? WEBSOCKET_BASE_URL}${query}`)
|
|
25
|
-
await waitForWebSocketConnection(websocket)
|
|
26
|
-
return websocket
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function disconnectFromWebSocket (websocket: WebSocket) {
|
|
30
|
-
if (![2, 3].includes(websocket.readyState)) {
|
|
31
|
-
websocket.close?.()
|
|
32
|
-
}
|
|
33
|
-
}
|
package/src/index.ts
DELETED
|
File without changes
|
package/src/node/auth/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import { API_GATEWAY_URL } from '../../utils/constants'
|
|
3
|
-
|
|
4
|
-
export interface IInitalizeOptions {
|
|
5
|
-
customApiUrl?: string
|
|
6
|
-
credentials?: string | Record<string, string>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function initialize (options?: IInitalizeOptions) {
|
|
10
|
-
let credentialsBase64
|
|
11
|
-
|
|
12
|
-
if (options?.credentials) {
|
|
13
|
-
if (typeof options?.credentials === 'string') {
|
|
14
|
-
credentialsBase64 = options?.credentials
|
|
15
|
-
} else if (typeof options?.credentials === 'object') {
|
|
16
|
-
credentialsBase64 = Buffer.from(JSON.stringify(options?.credentials)).toString('base64')
|
|
17
|
-
} else {
|
|
18
|
-
throw new Error('Invalid credentials')
|
|
19
|
-
}
|
|
20
|
-
} else {
|
|
21
|
-
if (process.env.SANTOID_SDK_CREDENTIALS) {
|
|
22
|
-
credentialsBase64 = fs.readFileSync(process.env.SANTOID_SDK_CREDENTIALS, { encoding: 'base64' })
|
|
23
|
-
} else {
|
|
24
|
-
throw new Error('Credentials not found')
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (credentialsBase64) {
|
|
29
|
-
const response = await fetch(options?.customApiUrl ?? API_GATEWAY_URL, {
|
|
30
|
-
method: 'POST',
|
|
31
|
-
body: JSON.stringify({ service_account_base64: credentialsBase64 }),
|
|
32
|
-
headers: { 'Content-Type': 'application/json' },
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const token = await response.json()
|
|
36
|
-
|
|
37
|
-
if (typeof token !== 'string') {
|
|
38
|
-
throw new Error('Cannot generate token')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return token
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import type WebSocket from 'ws'
|
|
2
|
-
import { type Data } from 'ws'
|
|
3
|
-
|
|
4
|
-
import { type TPositions, type IWebSocketMessage, type TNodeSDKOptions } from './utils/types'
|
|
5
|
-
import { SEND_FRAMES_INTERVAL_MS } from '../../utils/constants'
|
|
6
|
-
import { connectToWebSocket, disconnectFromWebSocket } from './utils/websocket'
|
|
7
|
-
|
|
8
|
-
async function onWebSocketMessage (options: TNodeSDKOptions, event: { data: string }) {
|
|
9
|
-
const message = event.data
|
|
10
|
-
|
|
11
|
-
let messageObj: IWebSocketMessage | undefined
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
messageObj = JSON.parse(message)
|
|
15
|
-
} catch (err) {
|
|
16
|
-
console.error('Cannot parse API message', err)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (messageObj?.next_step && !messageObj.error) {
|
|
20
|
-
await options.nextStepCallback?.(messageObj.next_step)
|
|
21
|
-
await options[`${messageObj.next_step}Callback`]?.()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (messageObj?.error) {
|
|
25
|
-
await options.errorCallback?.({ code: 'wrong-position' })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return messageObj
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function livenessDetection (options: TNodeSDKOptions) {
|
|
32
|
-
type FrameType = Data | null | undefined
|
|
33
|
-
|
|
34
|
-
let websocketClosedBySDK = false
|
|
35
|
-
let validationSuccess = false
|
|
36
|
-
let endCalled = false
|
|
37
|
-
let canSendNextBytes = true
|
|
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
|
-
|
|
50
|
-
async function stopLivenessDetection (options: TNodeSDKOptions, websocket?: WebSocket, intervalKey?: any) {
|
|
51
|
-
if (intervalKey) {
|
|
52
|
-
clearInterval(intervalKey)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (websocket) {
|
|
56
|
-
disconnectFromWebSocket(websocket)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
!endCalled && await options.endCallback?.()
|
|
60
|
-
endCalled = true
|
|
61
|
-
|
|
62
|
-
websocketClosedBySDK = true
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let sendFramesIntervalKey: any
|
|
66
|
-
let websocket: WebSocket | undefined
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
websocket = await connectToWebSocket(options)
|
|
70
|
-
|
|
71
|
-
await options.nextStepCallback?.('front')
|
|
72
|
-
await options.frontCallback?.()
|
|
73
|
-
|
|
74
|
-
sendFramesIntervalKey = setInterval(async () => {
|
|
75
|
-
try {
|
|
76
|
-
if (canSendNextBytes) {
|
|
77
|
-
const bytes = await options.getNextFrame()
|
|
78
|
-
|
|
79
|
-
if (bytes) {
|
|
80
|
-
websocket?.send(bytes)
|
|
81
|
-
lastSentBytes = bytes
|
|
82
|
-
canSendNextBytes = false
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
} catch (err) {
|
|
86
|
-
await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
|
|
87
|
-
}
|
|
88
|
-
}, SEND_FRAMES_INTERVAL_MS)
|
|
89
|
-
|
|
90
|
-
websocket.on('message', async (data: string) => {
|
|
91
|
-
const parsedMessage = await onWebSocketMessage(options, { data: Buffer.from(data).toString() })
|
|
92
|
-
|
|
93
|
-
if (parsedMessage) {
|
|
94
|
-
canSendNextBytes = !!parsedMessage?.can_send_next_bytes
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (parsedMessage?.validation && currentStep) {
|
|
98
|
-
successFrames[currentStep] = lastSentBytes
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (parsedMessage?.next_step) {
|
|
102
|
-
currentStep = parsedMessage.next_step
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (parsedMessage?.success) {
|
|
106
|
-
successFrames.bottom = lastSentBytes
|
|
107
|
-
|
|
108
|
-
validationSuccess = true
|
|
109
|
-
await options.successCallback?.<FrameType>({ livenessId: parsedMessage.livenessId, successFrames })
|
|
110
|
-
await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
websocket.on('error', async (event) => {
|
|
115
|
-
!validationSuccess && options.errorCallback?.({ code: 'internal-error' })
|
|
116
|
-
await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
websocket.on('close', async () => {
|
|
120
|
-
if (!websocketClosedBySDK) {
|
|
121
|
-
!validationSuccess && options.errorCallback?.({ code: 'internal-error' })
|
|
122
|
-
await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
stopLivenessDetection: async () => { await stopLivenessDetection(options, websocket, sendFramesIntervalKey) },
|
|
128
|
-
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error('catch error', err)
|
|
131
|
-
!validationSuccess && options.errorCallback?.({ code: 'internal-error' })
|
|
132
|
-
await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
|
|
133
|
-
throw err
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import WebSocket from 'ws'
|
|
2
|
-
|
|
3
|
-
import { decodeJwt } from '.'
|
|
4
|
-
import { WEBSOCKET_BASE_URL } from '../../../utils/constants'
|
|
5
|
-
import { type IDecodedToken, type TNodeSDKOptions } from './types'
|
|
6
|
-
|
|
7
|
-
export async function waitForWebSocketConnection (websocket: WebSocket) {
|
|
8
|
-
await new Promise<void>((resolve) => {
|
|
9
|
-
const intervalKey = setInterval(() => {
|
|
10
|
-
if ([3, 1].includes(websocket.readyState)) {
|
|
11
|
-
clearInterval(intervalKey)
|
|
12
|
-
resolve()
|
|
13
|
-
}
|
|
14
|
-
}, 5)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
if (websocket.readyState === 3) {
|
|
18
|
-
throw new Error('Cannot connect to server')
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function connectToWebSocket (options: TNodeSDKOptions) {
|
|
23
|
-
const decodedToken: IDecodedToken = decodeJwt(options.token)
|
|
24
|
-
const query = `?x_endpoint_api_userinfo=${options.token}&domain=${options.domain ?? decodedToken.claims.domain}&track=${options.track}&customerRequestId=${options.customId}`
|
|
25
|
-
|
|
26
|
-
const websocket = new WebSocket(`${options.customWebSocketUrl ?? WEBSOCKET_BASE_URL}${query}`)
|
|
27
|
-
await waitForWebSocketConnection(websocket)
|
|
28
|
-
return websocket
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function disconnectFromWebSocket (websocket: WebSocket) {
|
|
32
|
-
if (![2, 3].includes(websocket.readyState)) {
|
|
33
|
-
websocket.close?.()
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/utils/constants.ts
DELETED
package/src/utils/types.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export type TCallbackFunction = (...args: any[]) => void | Promise<void>
|
|
2
|
-
|
|
3
|
-
export type TPositions = 'front' | 'right' | 'left' | 'up' | 'bottom'
|
|
4
|
-
|
|
5
|
-
export type TErrorCode = 'wrong-position' | 'internal-error'
|
|
6
|
-
|
|
7
|
-
export interface IDecodedToken {
|
|
8
|
-
claims: {
|
|
9
|
-
domain: string
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface ILivenessResults<FrameType> {
|
|
14
|
-
livenessId: string
|
|
15
|
-
successFrames: Record<TPositions, FrameType>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ISDKOptions {
|
|
19
|
-
token: string
|
|
20
|
-
track: string
|
|
21
|
-
customId?: string
|
|
22
|
-
domain?: string
|
|
23
|
-
|
|
24
|
-
nextStepCallback?: (step: TPositions) => void | Promise<void>
|
|
25
|
-
frontCallback?: TCallbackFunction
|
|
26
|
-
rightCallback?: TCallbackFunction
|
|
27
|
-
leftCallback?: TCallbackFunction
|
|
28
|
-
upCallback?: TCallbackFunction
|
|
29
|
-
bottomCallback?: TCallbackFunction
|
|
30
|
-
errorCallback?: (error: { code: TErrorCode }) => void | Promise<void>
|
|
31
|
-
successCallback?: <FrameType>(results: ILivenessResults<FrameType>) => void | Promise<void>
|
|
32
|
-
endCallback?: TCallbackFunction
|
|
33
|
-
|
|
34
|
-
customWebSocketUrl?: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface IWebSocketMessage {
|
|
38
|
-
can_send_next_bytes?: boolean
|
|
39
|
-
next_step?: TPositions
|
|
40
|
-
validation?: boolean
|
|
41
|
-
|
|
42
|
-
error?: boolean
|
|
43
|
-
|
|
44
|
-
success?: boolean
|
|
45
|
-
livenessId: string
|
|
46
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|