santoid-sdk 1.0.0

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.
Files changed (46) hide show
  1. package/.eslintrc.js +39 -0
  2. package/LICENSE.md +177 -0
  3. package/README.md +126 -0
  4. package/dist/browser/liveness/index.js +1 -0
  5. package/dist/esm/liveness/index.d.ts +9 -0
  6. package/dist/esm/liveness/index.js +132 -0
  7. package/dist/esm/liveness/utils/getNextFrame.d.ts +6 -0
  8. package/dist/esm/liveness/utils/getNextFrame.js +55 -0
  9. package/dist/esm/liveness/utils/index.d.ts +1 -0
  10. package/dist/esm/liveness/utils/index.js +16 -0
  11. package/dist/esm/liveness/utils/types.d.ts +5 -0
  12. package/dist/esm/liveness/utils/types.js +17 -0
  13. package/dist/esm/liveness/utils/websocket.d.ts +3 -0
  14. package/dist/esm/liveness/utils/websocket.js +34 -0
  15. package/dist/index.d.ts +0 -0
  16. package/dist/index.js +0 -0
  17. package/dist/node/auth/index.d.ts +5 -0
  18. package/dist/node/auth/index.js +44 -0
  19. package/dist/node/liveness/index.d.ts +4 -0
  20. package/dist/node/liveness/index.js +98 -0
  21. package/dist/node/liveness/utils/index.d.ts +1 -0
  22. package/dist/node/liveness/utils/index.js +7 -0
  23. package/dist/node/liveness/utils/types.d.ts +6 -0
  24. package/dist/node/liveness/utils/types.js +17 -0
  25. package/dist/node/liveness/utils/websocket.d.ts +5 -0
  26. package/dist/node/liveness/utils/websocket.js +39 -0
  27. package/dist/utils/constants.d.ts +3 -0
  28. package/dist/utils/constants.js +6 -0
  29. package/dist/utils/types.d.ts +31 -0
  30. package/dist/utils/types.js +2 -0
  31. package/package.json +56 -0
  32. package/src/esm/liveness/index.ts +134 -0
  33. package/src/esm/liveness/utils/getNextFrame.ts +71 -0
  34. package/src/esm/liveness/utils/index.ts +16 -0
  35. package/src/esm/liveness/utils/types.ts +7 -0
  36. package/src/esm/liveness/utils/websocket.ts +33 -0
  37. package/src/index.ts +0 -0
  38. package/src/node/auth/index.ts +43 -0
  39. package/src/node/liveness/index.ts +112 -0
  40. package/src/node/liveness/utils/index.ts +3 -0
  41. package/src/node/liveness/utils/types.ts +8 -0
  42. package/src/node/liveness/utils/websocket.ts +35 -0
  43. package/src/utils/constants.ts +3 -0
  44. package/src/utils/types.ts +36 -0
  45. package/tsconfig.json +15 -0
  46. package/webpack.config.cjs +56 -0
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initialize = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const constants_1 = require("../../utils/constants");
9
+ async function initialize(options) {
10
+ var _a;
11
+ let credentialsBase64;
12
+ if (options === null || options === void 0 ? void 0 : options.credentials) {
13
+ if (typeof (options === null || options === void 0 ? void 0 : options.credentials) === 'string') {
14
+ credentialsBase64 = options === null || options === void 0 ? void 0 : options.credentials;
15
+ }
16
+ else if (typeof (options === null || options === void 0 ? void 0 : options.credentials) === 'object') {
17
+ credentialsBase64 = Buffer.from(JSON.stringify(options === null || options === void 0 ? void 0 : options.credentials)).toString('base64');
18
+ }
19
+ else {
20
+ throw new Error('Invalid credentials');
21
+ }
22
+ }
23
+ else {
24
+ if (process.env.SANTOID_SDK_CREDENTIALS) {
25
+ credentialsBase64 = fs_1.default.readFileSync(process.env.SANTOID_SDK_CREDENTIALS, { encoding: 'base64' });
26
+ }
27
+ else {
28
+ throw new Error('Credentials not found');
29
+ }
30
+ }
31
+ if (credentialsBase64) {
32
+ const response = await fetch((_a = options === null || options === void 0 ? void 0 : options.customApiUrl) !== null && _a !== void 0 ? _a : constants_1.API_GATEWAY_URL, {
33
+ method: 'POST',
34
+ body: JSON.stringify({ service_account_base64: credentialsBase64 }),
35
+ headers: { 'Content-Type': 'application/json' },
36
+ });
37
+ const token = await response.json();
38
+ if (typeof token !== 'string') {
39
+ throw new Error('Cannot generate token');
40
+ }
41
+ return token;
42
+ }
43
+ }
44
+ exports.initialize = initialize;
@@ -0,0 +1,4 @@
1
+ import { type TNodeSDKOptions } from './utils/types';
2
+ export declare function livenessDetection(options: TNodeSDKOptions): Promise<{
3
+ stopLivenessDetection: () => Promise<void>;
4
+ }>;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.livenessDetection = void 0;
4
+ const constants_1 = require("../../utils/constants");
5
+ const websocket_1 = require("./utils/websocket");
6
+ async function onWebSocketMessage(options, event) {
7
+ var _a, _b, _c;
8
+ const message = event.data;
9
+ let messageObj;
10
+ try {
11
+ messageObj = JSON.parse(message);
12
+ }
13
+ catch (err) {
14
+ console.error('Cannot parse API message', err);
15
+ }
16
+ if ((messageObj === null || messageObj === void 0 ? void 0 : messageObj.next_step) && !messageObj.error) {
17
+ await ((_a = options.nextStepCallback) === null || _a === void 0 ? void 0 : _a.call(options, messageObj.next_step));
18
+ await ((_b = options[`${messageObj.next_step}Callback`]) === null || _b === void 0 ? void 0 : _b.call(options));
19
+ }
20
+ if (messageObj === null || messageObj === void 0 ? void 0 : messageObj.error) {
21
+ await ((_c = options.errorCallback) === null || _c === void 0 ? void 0 : _c.call(options, { code: 'wrong-position' }));
22
+ }
23
+ return messageObj;
24
+ }
25
+ async function livenessDetection(options) {
26
+ var _a, _b, _c;
27
+ let websocketClosedBySDK = false;
28
+ let validationSuccess = false;
29
+ let endCalled = false;
30
+ async function stopLivenessDetection(options, websocket, intervalKey) {
31
+ var _a;
32
+ if (intervalKey) {
33
+ clearInterval(intervalKey);
34
+ }
35
+ if (websocket) {
36
+ (0, websocket_1.disconnectFromWebSocket)(websocket);
37
+ }
38
+ !endCalled && await ((_a = options.endCallback) === null || _a === void 0 ? void 0 : _a.call(options));
39
+ endCalled = true;
40
+ websocketClosedBySDK = true;
41
+ }
42
+ let sendFramesIntervalKey;
43
+ let websocket;
44
+ try {
45
+ websocket = await (0, websocket_1.connectToWebSocket)(options);
46
+ await ((_a = options.nextStepCallback) === null || _a === void 0 ? void 0 : _a.call(options, 'front'));
47
+ await ((_b = options.frontCallback) === null || _b === void 0 ? void 0 : _b.call(options));
48
+ let lastMessage;
49
+ sendFramesIntervalKey = setInterval(async () => {
50
+ try {
51
+ if (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.can_send_next_bytes) {
52
+ const bytes = await options.getNextFrame();
53
+ if (bytes) {
54
+ websocket === null || websocket === void 0 ? void 0 : websocket.send(bytes);
55
+ }
56
+ }
57
+ }
58
+ catch (err) {
59
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
60
+ }
61
+ }, constants_1.SEND_FRAMES_INTERVAL_MS);
62
+ websocket.on('message', async (data) => {
63
+ var _a;
64
+ const parsedMessage = await onWebSocketMessage(options, { data: Buffer.from(data).toString() });
65
+ if (parsedMessage) {
66
+ lastMessage = parsedMessage;
67
+ }
68
+ if (parsedMessage === null || parsedMessage === void 0 ? void 0 : parsedMessage.success) {
69
+ validationSuccess = true;
70
+ await ((_a = options.successCallback) === null || _a === void 0 ? void 0 : _a.call(options));
71
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
72
+ }
73
+ });
74
+ websocket.on('error', async (event) => {
75
+ var _a;
76
+ !validationSuccess && ((_a = options.errorCallback) === null || _a === void 0 ? void 0 : _a.call(options, { code: 'internal-error' }));
77
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
78
+ });
79
+ websocket.on('close', async () => {
80
+ var _a;
81
+ if (!websocketClosedBySDK) {
82
+ !validationSuccess && ((_a = options.errorCallback) === null || _a === void 0 ? void 0 : _a.call(options, { code: 'internal-error' }));
83
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
84
+ }
85
+ });
86
+ lastMessage = { can_send_next_bytes: true };
87
+ return {
88
+ stopLivenessDetection: async () => { await stopLivenessDetection(options, websocket, sendFramesIntervalKey); },
89
+ };
90
+ }
91
+ catch (err) {
92
+ console.error('catch error', err);
93
+ !validationSuccess && ((_c = options.errorCallback) === null || _c === void 0 ? void 0 : _c.call(options, { code: 'internal-error' }));
94
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey);
95
+ throw err;
96
+ }
97
+ }
98
+ exports.livenessDetection = livenessDetection;
@@ -0,0 +1 @@
1
+ export declare function decodeJwt(token: string): any;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeJwt = void 0;
4
+ function decodeJwt(token) {
5
+ return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
6
+ }
7
+ exports.decodeJwt = decodeJwt;
@@ -0,0 +1,6 @@
1
+ import { type Data } from 'ws';
2
+ import { type ISDKOptions } from '../../../utils/types';
3
+ export type TNodeSDKOptions = ISDKOptions & {
4
+ getNextFrame: () => Promise<Data | null>;
5
+ };
6
+ export * from '../../../utils/types';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../../../utils/types"), exports);
@@ -0,0 +1,5 @@
1
+ import WebSocket from 'ws';
2
+ import { type TNodeSDKOptions } from './types';
3
+ export declare function waitForWebSocketConnection(websocket: WebSocket): Promise<void>;
4
+ export declare function connectToWebSocket(options: TNodeSDKOptions): Promise<WebSocket>;
5
+ export declare function disconnectFromWebSocket(websocket: WebSocket): void;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.disconnectFromWebSocket = exports.connectToWebSocket = exports.waitForWebSocketConnection = void 0;
7
+ const ws_1 = __importDefault(require("ws"));
8
+ const _1 = require(".");
9
+ const constants_1 = require("../../../utils/constants");
10
+ async function waitForWebSocketConnection(websocket) {
11
+ await new Promise((resolve) => {
12
+ const intervalKey = setInterval(() => {
13
+ if ([3, 1].includes(websocket.readyState)) {
14
+ clearInterval(intervalKey);
15
+ resolve();
16
+ }
17
+ }, 5);
18
+ });
19
+ if (websocket.readyState === 3) {
20
+ throw new Error('Cannot connect to server');
21
+ }
22
+ }
23
+ exports.waitForWebSocketConnection = waitForWebSocketConnection;
24
+ async function connectToWebSocket(options) {
25
+ var _a;
26
+ const decodedToken = (0, _1.decodeJwt)(options.token);
27
+ const query = `?x_endpoint_api_userinfo=${options.token}&domain=${decodedToken.claims.domain}&track=${options.track}&customerRequestId=${options.customId}`;
28
+ const websocket = new ws_1.default(`${(_a = options.customWebSocketUrl) !== null && _a !== void 0 ? _a : constants_1.WEBSOCKET_BASE_URL}${query}`);
29
+ await waitForWebSocketConnection(websocket);
30
+ return websocket;
31
+ }
32
+ exports.connectToWebSocket = connectToWebSocket;
33
+ function disconnectFromWebSocket(websocket) {
34
+ var _a;
35
+ if (![2, 3].includes(websocket.readyState)) {
36
+ (_a = websocket.close) === null || _a === void 0 ? void 0 : _a.call(websocket);
37
+ }
38
+ }
39
+ exports.disconnectFromWebSocket = disconnectFromWebSocket;
@@ -0,0 +1,3 @@
1
+ export declare const WEBSOCKET_BASE_URL = "ws://santoid-liveness-w4ynos5dlq-ue.a.run.app/ws";
2
+ export declare const API_GATEWAY_URL = "https://api.santoid.com.br/api/v1/service-account/refresh-token";
3
+ export declare const SEND_FRAMES_INTERVAL_MS = 100;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEND_FRAMES_INTERVAL_MS = exports.API_GATEWAY_URL = exports.WEBSOCKET_BASE_URL = void 0;
4
+ exports.WEBSOCKET_BASE_URL = 'ws://santoid-liveness-w4ynos5dlq-ue.a.run.app/ws';
5
+ exports.API_GATEWAY_URL = 'https://api.santoid.com.br/api/v1/service-account/refresh-token';
6
+ exports.SEND_FRAMES_INTERVAL_MS = 100;
@@ -0,0 +1,31 @@
1
+ export type TCallbackFunction = (...args: any[]) => void | Promise<void>;
2
+ export type TPositions = 'front' | 'right' | 'left' | 'up' | 'bottom';
3
+ export type TErrorCode = 'wrong-position' | 'internal-error';
4
+ export interface IDecodedToken {
5
+ claims: {
6
+ domain: string;
7
+ };
8
+ }
9
+ export interface ISDKOptions {
10
+ token: string;
11
+ track: string;
12
+ customId?: string;
13
+ nextStepCallback?: (step: TPositions) => void | Promise<void>;
14
+ frontCallback?: TCallbackFunction;
15
+ rightCallback?: TCallbackFunction;
16
+ leftCallback?: TCallbackFunction;
17
+ upCallback?: TCallbackFunction;
18
+ bottomCallback?: TCallbackFunction;
19
+ errorCallback?: (error: {
20
+ code: TErrorCode;
21
+ }) => void | Promise<void>;
22
+ successCallback?: TCallbackFunction;
23
+ endCallback?: TCallbackFunction;
24
+ customWebSocketUrl?: string;
25
+ }
26
+ export interface IWebSocketMessage {
27
+ success?: boolean;
28
+ error?: boolean;
29
+ can_send_next_bytes?: boolean;
30
+ next_step?: TPositions;
31
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "santoid-sdk",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/esm/liveness/index.js",
6
+ "types": "dist/esm/liveness/index.d.ts",
7
+ "scripts": {
8
+ "expose:cmd": "mv -v dist/* ./",
9
+ "dev:webpack": "webpack --mode=development --watch --progress",
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"
13
+ },
14
+ "exports": {
15
+ ".": "./dist",
16
+ "./liveness": "./dist/esm/liveness/index.js",
17
+ "./node/liveness": "./dist/node/liveness/index.js",
18
+ "./node/auth": "./dist/node/auth/index.js"
19
+ },
20
+ "typesVersions": {
21
+ "*": {
22
+ "liveness": [
23
+ "dist/esm/liveness/index.d.ts"
24
+ ],
25
+ "node/liveness": [
26
+ "dist/node/liveness/index.d.ts"
27
+ ],
28
+ "node/auth": [
29
+ "dist/node/auth/index.d.ts"
30
+ ]
31
+ }
32
+ },
33
+ "keywords": [],
34
+ "author": "SantoDigital",
35
+ "license": "Apache-2.0",
36
+ "devDependencies": {
37
+ "@types/node": "^20.6.2",
38
+ "@types/ws": "^8.5.6",
39
+ "@typescript-eslint/eslint-plugin": "^6.4.0",
40
+ "clean-webpack-plugin": "^4.0.0",
41
+ "eslint": "^8.0.1",
42
+ "eslint-config-standard-with-typescript": "^39.0.0",
43
+ "eslint-plugin-import": "^2.25.2",
44
+ "eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
45
+ "eslint-plugin-promise": "^6.0.0",
46
+ "ts-loader": "^9.4.4",
47
+ "ts-node": "^10.9.1",
48
+ "typescript": "*",
49
+ "webpack": "^5.88.2",
50
+ "webpack-cli": "^5.1.4",
51
+ "webpack-node-externals": "^3.0.0"
52
+ },
53
+ "dependencies": {
54
+ "ws": "^8.14.2"
55
+ }
56
+ }
@@ -0,0 +1,134 @@
1
+ import { type IWebSocketMessage, type TWebSDKOptions } from './utils/types'
2
+ import { SEND_FRAMES_INTERVAL_MS } from '../../utils/constants'
3
+ import { builtInGetNextFrame, type builtInGetNextFrameReturn } from './utils/getNextFrame'
4
+
5
+ import { connectToWebSocket, disconnectFromWebSocket } from './utils/websocket'
6
+
7
+ export * from './utils/types'
8
+
9
+ async function onWebSocketMessage (options: TWebSDKOptions, event: MessageEvent) {
10
+ const message = event.data
11
+
12
+ let messageObj: IWebSocketMessage | undefined
13
+
14
+ try {
15
+ messageObj = JSON.parse(message)
16
+ } catch (err) {
17
+ console.error('Cannot parse API message', err)
18
+ }
19
+
20
+ if (messageObj?.next_step && !messageObj.error) {
21
+ await options.nextStepCallback?.(messageObj.next_step)
22
+ await options[`${messageObj.next_step}Callback`]?.()
23
+ }
24
+
25
+ if (messageObj?.error) {
26
+ await options.errorCallback?.({ code: 'wrong-position' })
27
+ }
28
+
29
+ return messageObj
30
+ }
31
+
32
+ export async function livenessDetection (options: TWebSDKOptions) {
33
+ let websocketClosedBySDK = false
34
+ let builtInGetNextFrameReturn: builtInGetNextFrameReturn | undefined
35
+ let validationSuccess = false
36
+ let endCalled = false
37
+
38
+ async function stopLivenessDetection (options: TWebSDKOptions, websocket?: WebSocket, intervalKey?: any) {
39
+ if (intervalKey) {
40
+ clearInterval(intervalKey)
41
+ }
42
+
43
+ if (websocket) {
44
+ disconnectFromWebSocket(websocket)
45
+ }
46
+
47
+ if (builtInGetNextFrameReturn) {
48
+ builtInGetNextFrameReturn.stopGetNextFrame()
49
+ }
50
+
51
+ !endCalled && await options.endCallback?.()
52
+ endCalled = true
53
+
54
+ websocketClosedBySDK = true
55
+ }
56
+
57
+ let sendFramesIntervalKey: any
58
+ let websocket: WebSocket | undefined
59
+
60
+ try {
61
+ websocket = await connectToWebSocket(options)
62
+
63
+ await options.nextStepCallback?.('front')
64
+ await options.frontCallback?.()
65
+
66
+ let lastMessage: IWebSocketMessage | undefined
67
+
68
+ if (!options.getNextFrame && !builtInGetNextFrameReturn) {
69
+ builtInGetNextFrameReturn = await builtInGetNextFrame()
70
+ }
71
+
72
+ sendFramesIntervalKey = setInterval(async () => {
73
+ try {
74
+ if (lastMessage?.can_send_next_bytes) {
75
+ let bytes
76
+
77
+ if (options.getNextFrame) {
78
+ bytes = await options.getNextFrame()
79
+ } else {
80
+ bytes = await builtInGetNextFrameReturn?.getNextFrame()
81
+ }
82
+
83
+ if (bytes) {
84
+ websocket?.send(bytes)
85
+ }
86
+ }
87
+ } catch (err) {
88
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
89
+ }
90
+ }, SEND_FRAMES_INTERVAL_MS)
91
+
92
+ websocket.addEventListener('message', async (event) => {
93
+ const parsedMessage = await onWebSocketMessage(options, event)
94
+
95
+ if (parsedMessage) {
96
+ lastMessage = parsedMessage
97
+ }
98
+
99
+ if (parsedMessage?.success) {
100
+ validationSuccess = true
101
+ await options.successCallback?.()
102
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
103
+ }
104
+ })
105
+
106
+ websocket.addEventListener('error', async () => {
107
+ !validationSuccess && options.errorCallback?.({ code: 'internal-error' })
108
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
109
+ })
110
+
111
+ websocket.addEventListener('close', async () => {
112
+ if (!websocketClosedBySDK) {
113
+ !validationSuccess && options.errorCallback?.({ code: 'internal-error' })
114
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
115
+ }
116
+ })
117
+
118
+ lastMessage = { can_send_next_bytes: true }
119
+
120
+ return {
121
+ stopLivenessDetection: async () => { await stopLivenessDetection(options, websocket, sendFramesIntervalKey) },
122
+ videoStream: builtInGetNextFrameReturn?.videoStream,
123
+ }
124
+ } catch (err) {
125
+ !validationSuccess && options.errorCallback?.({ code: 'internal-error' })
126
+ await stopLivenessDetection(options, websocket, sendFramesIntervalKey)
127
+ throw err
128
+ }
129
+ }
130
+
131
+ // Class for use in browsers
132
+ export default class SantoiDSDK {
133
+ static livenessDetection = livenessDetection
134
+ }
@@ -0,0 +1,71 @@
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
+
12
+ async function getNextFrame (): Promise<Blob | null> {
13
+ if (canvasElement && videoElement) {
14
+ const ctx = canvasElement.getContext('2d')
15
+
16
+ canvasElement.width = videoElement.videoWidth
17
+ canvasElement.height = videoElement.videoHeight
18
+
19
+ ctx?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height)
20
+
21
+ return await new Promise((resolve, reject) => {
22
+ if (!canvasElement) {
23
+ resolve(null)
24
+ return
25
+ }
26
+
27
+ canvasElement.toBlob((blob) => {
28
+ resolve(blob)
29
+ })
30
+ })
31
+ } else {
32
+ throw new Error('Cannot found video element')
33
+ }
34
+ }
35
+
36
+ function stopGetNextFrame () {
37
+ if (videoElement) {
38
+ document.body.removeChild(videoElement)
39
+ videoElement = undefined
40
+ }
41
+
42
+ if (canvasElement) {
43
+ canvasElement = undefined
44
+ }
45
+
46
+ videoStream?.getTracks().forEach(track => { track.stop() })
47
+ videoStream = undefined
48
+ }
49
+
50
+ if (!videoStream || !videoElement || !canvasElement) {
51
+ videoStream = await navigator.mediaDevices.getUserMedia({ video: true })
52
+
53
+ videoElement = document.createElement('video')
54
+ videoElement.setAttribute('autoplay', 'true')
55
+
56
+ videoElement.style.position = 'absolute'
57
+ videoElement.style.left = '99999px'
58
+
59
+ document.body.appendChild(videoElement)
60
+
61
+ videoElement.srcObject = videoStream
62
+
63
+ canvasElement = document.createElement('canvas')
64
+ }
65
+
66
+ return {
67
+ getNextFrame,
68
+ stopGetNextFrame,
69
+ videoStream,
70
+ }
71
+ }
@@ -0,0 +1,16 @@
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
+ }
@@ -0,0 +1,7 @@
1
+ import { type ISDKOptions } from '../../../utils/types'
2
+
3
+ export type TWebSDKOptions = ISDKOptions & {
4
+ getNextFrame?: () => Promise<Blob | null>
5
+ }
6
+
7
+ export * from '../../../utils/types'
@@ -0,0 +1,33 @@
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=${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 ADDED
File without changes
@@ -0,0 +1,43 @@
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
+ }