@moveris/shared 0.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 ADDED
@@ -0,0 +1,25 @@
1
+ # @moveris/shared
2
+
3
+ Core business logic for Moveris Live SDK. This package contains:
4
+
5
+ - WebSocket client with reconnection logic
6
+ - Frame buffer management
7
+ - Authentication manager
8
+ - Type definitions
9
+ - Utility functions
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @moveris/shared
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import { WebSocketClient, FrameBuffer, AuthManager } from '@moveris/shared';
21
+ ```
22
+
23
+ ## API
24
+
25
+ See [API Documentation](../../docs/api-reference.md#shared-package) for complete API reference.
@@ -0,0 +1,193 @@
1
+ type MessageType = 'auth_required' | 'auth' | 'auth_success' | 'auth_error' | 'frame' | 'frame_received' | 'processing_complete' | 'error' | 'session_resume';
2
+ interface BaseMessage {
3
+ type: MessageType;
4
+ timestamp?: number;
5
+ }
6
+ interface AuthRequiredMessage extends BaseMessage {
7
+ type: 'auth_required';
8
+ }
9
+ interface AuthMessage extends BaseMessage {
10
+ type: 'auth';
11
+ token: string;
12
+ model_file_name: string;
13
+ }
14
+ interface AuthSuccessMessage extends BaseMessage {
15
+ type: 'auth_success';
16
+ request_id: string;
17
+ last_acked_frame?: number;
18
+ }
19
+ interface AuthErrorMessage extends BaseMessage {
20
+ type: 'auth_error';
21
+ error: string;
22
+ code?: string;
23
+ }
24
+ interface FrameMessage extends BaseMessage {
25
+ type: 'frame';
26
+ frame_data: string;
27
+ frame_number: number;
28
+ }
29
+ interface FrameReceivedMessage extends BaseMessage {
30
+ type: 'frame_received';
31
+ frame_number: number;
32
+ }
33
+ interface ProcessingCompleteMessage extends BaseMessage {
34
+ type: 'processing_complete';
35
+ request_id: string;
36
+ prediction: 'real' | 'spoof';
37
+ confidence: number;
38
+ }
39
+ interface ErrorMessage extends BaseMessage {
40
+ type: 'error';
41
+ error: string;
42
+ code: string;
43
+ }
44
+ interface SessionResumeMessage extends BaseMessage {
45
+ type: 'session_resume';
46
+ last_acked_frame: number;
47
+ }
48
+ type WebSocketMessage = AuthRequiredMessage | AuthMessage | AuthSuccessMessage | AuthErrorMessage | FrameMessage | FrameReceivedMessage | ProcessingCompleteMessage | ErrorMessage | SessionResumeMessage;
49
+
50
+ interface Frame {
51
+ number: number;
52
+ data: string;
53
+ timestamp: number;
54
+ size: number;
55
+ }
56
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
57
+ interface LivenessResult {
58
+ prediction: 'real' | 'spoof';
59
+ confidence: number;
60
+ requestId: string;
61
+ }
62
+ type OnResultCallback = (result: LivenessResult) => void;
63
+ type OnErrorCallback = (error: Error) => void;
64
+ type OnFrameAckCallback = (frameNumber: number) => void;
65
+ type OnConnectionStateChangeCallback = (state: ConnectionState) => void;
66
+ type OnProgressCallback = (current: number, total: number) => void;
67
+ interface LivenessCallbacks {
68
+ onResult?: OnResultCallback;
69
+ onError?: OnErrorCallback;
70
+ onFrameAck?: OnFrameAckCallback;
71
+ onConnectionStateChange?: OnConnectionStateChangeCallback;
72
+ onProgress?: OnProgressCallback;
73
+ }
74
+
75
+ interface WebSocketClientConfig {
76
+ url: string;
77
+ apiKey: string;
78
+ model: string;
79
+ onMessage?: (message: WebSocketMessage) => void;
80
+ onConnectionStateChange?: OnConnectionStateChangeCallback;
81
+ onError?: (error: Error) => void;
82
+ }
83
+ declare class WebSocketClient {
84
+ private ws;
85
+ private connectionManager;
86
+ private authManager;
87
+ private config;
88
+ private state;
89
+ constructor(config: WebSocketClientConfig);
90
+ connect(): Promise<void>;
91
+ disconnect(): void;
92
+ sendFrame(frameData: string, frameNumber: number): void;
93
+ getState(): ConnectionState;
94
+ private setState;
95
+ private handleMessage;
96
+ private handleError;
97
+ }
98
+
99
+ declare class AuthManager {
100
+ authenticate(ws: WebSocket, apiKey: string, model: string): Promise<string>;
101
+ }
102
+
103
+ interface ConnectionManagerConfig {
104
+ onReconnect: () => Promise<void>;
105
+ maxAttempts?: number;
106
+ }
107
+ declare class ConnectionManager {
108
+ private config;
109
+ private reconnectAttempts;
110
+ constructor(config: ConnectionManagerConfig);
111
+ handleDisconnect(): Promise<void>;
112
+ reset(): void;
113
+ }
114
+
115
+ declare class FrameBuffer {
116
+ private buffer;
117
+ private lastAcknowledged;
118
+ private currentMemoryUsage;
119
+ private readonly maxSize;
120
+ private readonly maxMemory;
121
+ constructor(maxSize?: number, maxMemory?: number);
122
+ add(frame: Frame): void;
123
+ acknowledge(frameNumber: number): void;
124
+ getUnacknowledged(): Frame[];
125
+ getLastAcknowledged(): number;
126
+ size(): number;
127
+ getMemoryUsage(): number;
128
+ clear(): void;
129
+ private remove;
130
+ private flushOldest;
131
+ }
132
+
133
+ declare class FrameQueue {
134
+ private queue;
135
+ enqueue(frame: Frame): void;
136
+ dequeue(): Frame | undefined;
137
+ peek(): Frame | undefined;
138
+ isEmpty(): boolean;
139
+ size(): number;
140
+ clear(): void;
141
+ toArray(): Frame[];
142
+ }
143
+
144
+ type ModelType = '50-frame' | '250-frame';
145
+ interface ModelConfig {
146
+ type: ModelType;
147
+ fileName: string;
148
+ frameCount: number;
149
+ }
150
+ declare const MODEL_CONFIGS: Record<ModelType, ModelConfig>;
151
+
152
+ declare const API_ENDPOINTS: {
153
+ readonly production: "wss://api.moveris.com/v1/liveness";
154
+ readonly staging: "wss://staging-api.moveris.com/v1/liveness";
155
+ readonly development: "ws://localhost:8080/v1/liveness";
156
+ };
157
+ declare const DEFAULT_ENDPOINT: "wss://api.moveris.com/v1/liveness";
158
+ declare const RECONNECTION_CONFIG: {
159
+ readonly maxAttempts: 10;
160
+ readonly initialDelay: 1000;
161
+ readonly maxDelay: 30000;
162
+ readonly backoffMultiplier: 2;
163
+ };
164
+ declare const FRAME_BUFFER_CONFIG: {
165
+ readonly maxSize: 10;
166
+ readonly maxMemory: number;
167
+ };
168
+ declare const AUTH_CONFIG: {
169
+ readonly timeout: 10000;
170
+ readonly apiKeyPattern: RegExp;
171
+ };
172
+ declare const FRAME_CONFIG: {
173
+ readonly targetFPS: 30;
174
+ readonly frameInterval: 33;
175
+ readonly jpegQuality: 0.8;
176
+ };
177
+
178
+ declare function validateApiKey(apiKey: string): boolean;
179
+ declare function validateFrameData(data: string): boolean;
180
+ declare function validateFrameNumber(frameNumber: number): boolean;
181
+
182
+ declare function encodeBase64(buffer: ArrayBuffer | Uint8Array): string;
183
+ declare function decodeBase64(base64: string): Uint8Array;
184
+
185
+ interface RetryOptions {
186
+ maxAttempts?: number;
187
+ initialDelay?: number;
188
+ maxDelay?: number;
189
+ backoffMultiplier?: number;
190
+ }
191
+ declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
192
+
193
+ export { API_ENDPOINTS, AUTH_CONFIG, type AuthErrorMessage, AuthManager, type AuthMessage, type AuthRequiredMessage, type AuthSuccessMessage, type BaseMessage, ConnectionManager, type ConnectionState, DEFAULT_ENDPOINT, type ErrorMessage, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type Frame, FrameBuffer, type FrameMessage, FrameQueue, type FrameReceivedMessage, type LivenessCallbacks, type LivenessResult, MODEL_CONFIGS, type MessageType, type ModelConfig, type ModelType, type OnConnectionStateChangeCallback, type OnErrorCallback, type OnFrameAckCallback, type OnProgressCallback, type OnResultCallback, type ProcessingCompleteMessage, RECONNECTION_CONFIG, type RetryOptions, type SessionResumeMessage, WebSocketClient, type WebSocketMessage, decodeBase64, encodeBase64, retryWithBackoff, validateApiKey, validateFrameData, validateFrameNumber };
@@ -0,0 +1,193 @@
1
+ type MessageType = 'auth_required' | 'auth' | 'auth_success' | 'auth_error' | 'frame' | 'frame_received' | 'processing_complete' | 'error' | 'session_resume';
2
+ interface BaseMessage {
3
+ type: MessageType;
4
+ timestamp?: number;
5
+ }
6
+ interface AuthRequiredMessage extends BaseMessage {
7
+ type: 'auth_required';
8
+ }
9
+ interface AuthMessage extends BaseMessage {
10
+ type: 'auth';
11
+ token: string;
12
+ model_file_name: string;
13
+ }
14
+ interface AuthSuccessMessage extends BaseMessage {
15
+ type: 'auth_success';
16
+ request_id: string;
17
+ last_acked_frame?: number;
18
+ }
19
+ interface AuthErrorMessage extends BaseMessage {
20
+ type: 'auth_error';
21
+ error: string;
22
+ code?: string;
23
+ }
24
+ interface FrameMessage extends BaseMessage {
25
+ type: 'frame';
26
+ frame_data: string;
27
+ frame_number: number;
28
+ }
29
+ interface FrameReceivedMessage extends BaseMessage {
30
+ type: 'frame_received';
31
+ frame_number: number;
32
+ }
33
+ interface ProcessingCompleteMessage extends BaseMessage {
34
+ type: 'processing_complete';
35
+ request_id: string;
36
+ prediction: 'real' | 'spoof';
37
+ confidence: number;
38
+ }
39
+ interface ErrorMessage extends BaseMessage {
40
+ type: 'error';
41
+ error: string;
42
+ code: string;
43
+ }
44
+ interface SessionResumeMessage extends BaseMessage {
45
+ type: 'session_resume';
46
+ last_acked_frame: number;
47
+ }
48
+ type WebSocketMessage = AuthRequiredMessage | AuthMessage | AuthSuccessMessage | AuthErrorMessage | FrameMessage | FrameReceivedMessage | ProcessingCompleteMessage | ErrorMessage | SessionResumeMessage;
49
+
50
+ interface Frame {
51
+ number: number;
52
+ data: string;
53
+ timestamp: number;
54
+ size: number;
55
+ }
56
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
57
+ interface LivenessResult {
58
+ prediction: 'real' | 'spoof';
59
+ confidence: number;
60
+ requestId: string;
61
+ }
62
+ type OnResultCallback = (result: LivenessResult) => void;
63
+ type OnErrorCallback = (error: Error) => void;
64
+ type OnFrameAckCallback = (frameNumber: number) => void;
65
+ type OnConnectionStateChangeCallback = (state: ConnectionState) => void;
66
+ type OnProgressCallback = (current: number, total: number) => void;
67
+ interface LivenessCallbacks {
68
+ onResult?: OnResultCallback;
69
+ onError?: OnErrorCallback;
70
+ onFrameAck?: OnFrameAckCallback;
71
+ onConnectionStateChange?: OnConnectionStateChangeCallback;
72
+ onProgress?: OnProgressCallback;
73
+ }
74
+
75
+ interface WebSocketClientConfig {
76
+ url: string;
77
+ apiKey: string;
78
+ model: string;
79
+ onMessage?: (message: WebSocketMessage) => void;
80
+ onConnectionStateChange?: OnConnectionStateChangeCallback;
81
+ onError?: (error: Error) => void;
82
+ }
83
+ declare class WebSocketClient {
84
+ private ws;
85
+ private connectionManager;
86
+ private authManager;
87
+ private config;
88
+ private state;
89
+ constructor(config: WebSocketClientConfig);
90
+ connect(): Promise<void>;
91
+ disconnect(): void;
92
+ sendFrame(frameData: string, frameNumber: number): void;
93
+ getState(): ConnectionState;
94
+ private setState;
95
+ private handleMessage;
96
+ private handleError;
97
+ }
98
+
99
+ declare class AuthManager {
100
+ authenticate(ws: WebSocket, apiKey: string, model: string): Promise<string>;
101
+ }
102
+
103
+ interface ConnectionManagerConfig {
104
+ onReconnect: () => Promise<void>;
105
+ maxAttempts?: number;
106
+ }
107
+ declare class ConnectionManager {
108
+ private config;
109
+ private reconnectAttempts;
110
+ constructor(config: ConnectionManagerConfig);
111
+ handleDisconnect(): Promise<void>;
112
+ reset(): void;
113
+ }
114
+
115
+ declare class FrameBuffer {
116
+ private buffer;
117
+ private lastAcknowledged;
118
+ private currentMemoryUsage;
119
+ private readonly maxSize;
120
+ private readonly maxMemory;
121
+ constructor(maxSize?: number, maxMemory?: number);
122
+ add(frame: Frame): void;
123
+ acknowledge(frameNumber: number): void;
124
+ getUnacknowledged(): Frame[];
125
+ getLastAcknowledged(): number;
126
+ size(): number;
127
+ getMemoryUsage(): number;
128
+ clear(): void;
129
+ private remove;
130
+ private flushOldest;
131
+ }
132
+
133
+ declare class FrameQueue {
134
+ private queue;
135
+ enqueue(frame: Frame): void;
136
+ dequeue(): Frame | undefined;
137
+ peek(): Frame | undefined;
138
+ isEmpty(): boolean;
139
+ size(): number;
140
+ clear(): void;
141
+ toArray(): Frame[];
142
+ }
143
+
144
+ type ModelType = '50-frame' | '250-frame';
145
+ interface ModelConfig {
146
+ type: ModelType;
147
+ fileName: string;
148
+ frameCount: number;
149
+ }
150
+ declare const MODEL_CONFIGS: Record<ModelType, ModelConfig>;
151
+
152
+ declare const API_ENDPOINTS: {
153
+ readonly production: "wss://api.moveris.com/v1/liveness";
154
+ readonly staging: "wss://staging-api.moveris.com/v1/liveness";
155
+ readonly development: "ws://localhost:8080/v1/liveness";
156
+ };
157
+ declare const DEFAULT_ENDPOINT: "wss://api.moveris.com/v1/liveness";
158
+ declare const RECONNECTION_CONFIG: {
159
+ readonly maxAttempts: 10;
160
+ readonly initialDelay: 1000;
161
+ readonly maxDelay: 30000;
162
+ readonly backoffMultiplier: 2;
163
+ };
164
+ declare const FRAME_BUFFER_CONFIG: {
165
+ readonly maxSize: 10;
166
+ readonly maxMemory: number;
167
+ };
168
+ declare const AUTH_CONFIG: {
169
+ readonly timeout: 10000;
170
+ readonly apiKeyPattern: RegExp;
171
+ };
172
+ declare const FRAME_CONFIG: {
173
+ readonly targetFPS: 30;
174
+ readonly frameInterval: 33;
175
+ readonly jpegQuality: 0.8;
176
+ };
177
+
178
+ declare function validateApiKey(apiKey: string): boolean;
179
+ declare function validateFrameData(data: string): boolean;
180
+ declare function validateFrameNumber(frameNumber: number): boolean;
181
+
182
+ declare function encodeBase64(buffer: ArrayBuffer | Uint8Array): string;
183
+ declare function decodeBase64(base64: string): Uint8Array;
184
+
185
+ interface RetryOptions {
186
+ maxAttempts?: number;
187
+ initialDelay?: number;
188
+ maxDelay?: number;
189
+ backoffMultiplier?: number;
190
+ }
191
+ declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
192
+
193
+ export { API_ENDPOINTS, AUTH_CONFIG, type AuthErrorMessage, AuthManager, type AuthMessage, type AuthRequiredMessage, type AuthSuccessMessage, type BaseMessage, ConnectionManager, type ConnectionState, DEFAULT_ENDPOINT, type ErrorMessage, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type Frame, FrameBuffer, type FrameMessage, FrameQueue, type FrameReceivedMessage, type LivenessCallbacks, type LivenessResult, MODEL_CONFIGS, type MessageType, type ModelConfig, type ModelType, type OnConnectionStateChangeCallback, type OnErrorCallback, type OnFrameAckCallback, type OnProgressCallback, type OnResultCallback, type ProcessingCompleteMessage, RECONNECTION_CONFIG, type RetryOptions, type SessionResumeMessage, WebSocketClient, type WebSocketMessage, decodeBase64, encodeBase64, retryWithBackoff, validateApiKey, validateFrameData, validateFrameNumber };
package/dist/index.js ADDED
@@ -0,0 +1,410 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ API_ENDPOINTS: () => API_ENDPOINTS,
24
+ AUTH_CONFIG: () => AUTH_CONFIG,
25
+ AuthManager: () => AuthManager,
26
+ ConnectionManager: () => ConnectionManager,
27
+ DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
28
+ FRAME_BUFFER_CONFIG: () => FRAME_BUFFER_CONFIG,
29
+ FRAME_CONFIG: () => FRAME_CONFIG,
30
+ FrameBuffer: () => FrameBuffer,
31
+ FrameQueue: () => FrameQueue,
32
+ RECONNECTION_CONFIG: () => RECONNECTION_CONFIG,
33
+ WebSocketClient: () => WebSocketClient,
34
+ decodeBase64: () => decodeBase64,
35
+ encodeBase64: () => encodeBase64,
36
+ retryWithBackoff: () => retryWithBackoff,
37
+ validateApiKey: () => validateApiKey,
38
+ validateFrameData: () => validateFrameData,
39
+ validateFrameNumber: () => validateFrameNumber
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/constants/config.ts
44
+ var API_ENDPOINTS = {
45
+ production: "wss://api.moveris.com/v1/liveness",
46
+ staging: "wss://staging-api.moveris.com/v1/liveness",
47
+ development: "ws://localhost:8080/v1/liveness"
48
+ };
49
+ var DEFAULT_ENDPOINT = API_ENDPOINTS.production;
50
+ var RECONNECTION_CONFIG = {
51
+ maxAttempts: 10,
52
+ initialDelay: 1e3,
53
+ // 1 second
54
+ maxDelay: 3e4,
55
+ // 30 seconds
56
+ backoffMultiplier: 2
57
+ };
58
+ var FRAME_BUFFER_CONFIG = {
59
+ maxSize: 10,
60
+ // Maximum un-acked frames
61
+ maxMemory: 4 * 1024 * 1024
62
+ // 4 MB
63
+ };
64
+ var AUTH_CONFIG = {
65
+ timeout: 1e4,
66
+ // 10 seconds
67
+ apiKeyPattern: /^mv_[a-zA-Z0-9]{32}$/
68
+ };
69
+ var FRAME_CONFIG = {
70
+ targetFPS: 30,
71
+ frameInterval: 33,
72
+ // milliseconds (1000 / 30)
73
+ jpegQuality: 0.8
74
+ // 80%
75
+ };
76
+
77
+ // src/utils/retry.ts
78
+ async function retryWithBackoff(fn, options = {}) {
79
+ const {
80
+ maxAttempts = RECONNECTION_CONFIG.maxAttempts,
81
+ initialDelay = RECONNECTION_CONFIG.initialDelay,
82
+ maxDelay = RECONNECTION_CONFIG.maxDelay,
83
+ backoffMultiplier = RECONNECTION_CONFIG.backoffMultiplier
84
+ } = options;
85
+ let lastError;
86
+ let delay = initialDelay;
87
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
88
+ try {
89
+ return await fn();
90
+ } catch (error) {
91
+ lastError = error;
92
+ if (attempt < maxAttempts - 1) {
93
+ await new Promise((resolve) => setTimeout(resolve, delay));
94
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
95
+ }
96
+ }
97
+ }
98
+ throw lastError;
99
+ }
100
+
101
+ // src/client/ConnectionManager.ts
102
+ var ConnectionManager = class {
103
+ constructor(config) {
104
+ this.reconnectAttempts = 0;
105
+ this.config = config;
106
+ }
107
+ async handleDisconnect() {
108
+ if (this.reconnectAttempts >= (this.config.maxAttempts ?? 10)) {
109
+ throw new Error("Max reconnection attempts reached");
110
+ }
111
+ try {
112
+ await retryWithBackoff(
113
+ async () => {
114
+ await this.config.onReconnect();
115
+ this.reconnectAttempts = 0;
116
+ },
117
+ {
118
+ maxAttempts: this.config.maxAttempts ?? 10
119
+ }
120
+ );
121
+ } catch (error) {
122
+ this.reconnectAttempts++;
123
+ throw error;
124
+ }
125
+ }
126
+ reset() {
127
+ this.reconnectAttempts = 0;
128
+ }
129
+ };
130
+
131
+ // src/utils/validators.ts
132
+ function validateApiKey(apiKey) {
133
+ if (!apiKey || typeof apiKey !== "string") {
134
+ return false;
135
+ }
136
+ return AUTH_CONFIG.apiKeyPattern.test(apiKey);
137
+ }
138
+ function validateFrameData(data) {
139
+ if (!data || typeof data !== "string") {
140
+ return false;
141
+ }
142
+ const base64Pattern = /^[A-Za-z0-9+/=]+$/;
143
+ return base64Pattern.test(data) && data.length > 0;
144
+ }
145
+ function validateFrameNumber(frameNumber) {
146
+ return Number.isInteger(frameNumber) && frameNumber > 0;
147
+ }
148
+
149
+ // src/types/models.ts
150
+ var MODEL_CONFIGS = {
151
+ "50-frame": {
152
+ type: "50-frame",
153
+ fileName: "model_50.h5",
154
+ frameCount: 50
155
+ },
156
+ "250-frame": {
157
+ type: "250-frame",
158
+ fileName: "model_250.h5",
159
+ frameCount: 250
160
+ }
161
+ };
162
+
163
+ // src/client/AuthManager.ts
164
+ var AuthManager = class {
165
+ async authenticate(ws, apiKey, model) {
166
+ if (!validateApiKey(apiKey)) {
167
+ throw new Error("Invalid API key format");
168
+ }
169
+ const modelType = model;
170
+ if (!MODEL_CONFIGS[modelType]) {
171
+ throw new Error(`Invalid model type: ${model}`);
172
+ }
173
+ return new Promise((resolve, reject) => {
174
+ const timeout = setTimeout(() => {
175
+ reject(new Error("Authentication timeout"));
176
+ }, AUTH_CONFIG.timeout);
177
+ const messageHandler = (event) => {
178
+ try {
179
+ const message = JSON.parse(event.data);
180
+ if (message.type === "auth_success") {
181
+ clearTimeout(timeout);
182
+ ws.removeEventListener("message", messageHandler);
183
+ resolve(message.request_id);
184
+ } else if (message.type === "auth_error") {
185
+ clearTimeout(timeout);
186
+ ws.removeEventListener("message", messageHandler);
187
+ reject(new Error(message.error));
188
+ }
189
+ } catch (error) {
190
+ }
191
+ };
192
+ ws.addEventListener("message", messageHandler);
193
+ ws.send(
194
+ JSON.stringify({
195
+ type: "auth",
196
+ token: apiKey,
197
+ model_file_name: MODEL_CONFIGS[modelType].fileName
198
+ })
199
+ );
200
+ });
201
+ }
202
+ };
203
+
204
+ // src/client/WebSocketClient.ts
205
+ var WebSocketClient = class {
206
+ constructor(config) {
207
+ this.ws = null;
208
+ this.state = "disconnected";
209
+ this.config = config;
210
+ this.connectionManager = new ConnectionManager({
211
+ onReconnect: () => this.connect()
212
+ });
213
+ this.authManager = new AuthManager();
214
+ }
215
+ async connect() {
216
+ if (this.state === "connected" || this.state === "connecting") {
217
+ return;
218
+ }
219
+ this.setState("connecting");
220
+ try {
221
+ this.ws = new WebSocket(this.config.url);
222
+ this.ws.onopen = async () => {
223
+ try {
224
+ await this.authManager.authenticate(this.ws, this.config.apiKey, this.config.model);
225
+ this.setState("connected");
226
+ } catch (error) {
227
+ this.handleError(error instanceof Error ? error : new Error("Authentication failed"));
228
+ }
229
+ };
230
+ this.ws.onmessage = (event) => {
231
+ try {
232
+ const message = JSON.parse(event.data);
233
+ this.handleMessage(message);
234
+ } catch (error) {
235
+ this.handleError(error instanceof Error ? error : new Error("Failed to parse message"));
236
+ }
237
+ };
238
+ this.ws.onerror = () => {
239
+ this.handleError(new Error("WebSocket error"));
240
+ };
241
+ this.ws.onclose = () => {
242
+ this.setState("disconnected");
243
+ this.connectionManager.handleDisconnect();
244
+ };
245
+ } catch (error) {
246
+ this.handleError(error instanceof Error ? error : new Error("Connection failed"));
247
+ }
248
+ }
249
+ disconnect() {
250
+ if (this.ws) {
251
+ this.ws.close();
252
+ this.ws = null;
253
+ }
254
+ this.setState("disconnected");
255
+ }
256
+ sendFrame(frameData, frameNumber) {
257
+ if (this.state !== "connected" || !this.ws) {
258
+ throw new Error("WebSocket not connected");
259
+ }
260
+ const message = {
261
+ type: "frame",
262
+ frame_data: frameData,
263
+ frame_number: frameNumber,
264
+ timestamp: Date.now()
265
+ };
266
+ this.ws.send(JSON.stringify(message));
267
+ }
268
+ getState() {
269
+ return this.state;
270
+ }
271
+ setState(newState) {
272
+ if (this.state !== newState) {
273
+ this.state = newState;
274
+ this.config.onConnectionStateChange?.(newState);
275
+ }
276
+ }
277
+ handleMessage(message) {
278
+ this.config.onMessage?.(message);
279
+ }
280
+ handleError(error) {
281
+ this.setState("error");
282
+ this.config.onError?.(error);
283
+ }
284
+ };
285
+
286
+ // src/buffer/FrameBuffer.ts
287
+ var FrameBuffer = class {
288
+ constructor(maxSize = FRAME_BUFFER_CONFIG.maxSize, maxMemory = FRAME_BUFFER_CONFIG.maxMemory) {
289
+ this.buffer = /* @__PURE__ */ new Map();
290
+ this.lastAcknowledged = 0;
291
+ this.currentMemoryUsage = 0;
292
+ this.maxSize = maxSize;
293
+ this.maxMemory = maxMemory;
294
+ }
295
+ add(frame) {
296
+ if (this.currentMemoryUsage + frame.size > this.maxMemory) {
297
+ this.flushOldest();
298
+ }
299
+ this.buffer.set(frame.number, frame);
300
+ this.currentMemoryUsage += frame.size;
301
+ if (this.buffer.size > this.maxSize) {
302
+ const oldest = Math.min(...this.buffer.keys());
303
+ this.remove(oldest);
304
+ }
305
+ }
306
+ acknowledge(frameNumber) {
307
+ this.lastAcknowledged = Math.max(this.lastAcknowledged, frameNumber);
308
+ for (const [num] of this.buffer) {
309
+ if (num <= frameNumber) {
310
+ this.remove(num);
311
+ }
312
+ }
313
+ }
314
+ getUnacknowledged() {
315
+ return Array.from(this.buffer.values()).filter((f) => f.number > this.lastAcknowledged).sort((a, b) => a.number - b.number);
316
+ }
317
+ getLastAcknowledged() {
318
+ return this.lastAcknowledged;
319
+ }
320
+ size() {
321
+ return this.buffer.size;
322
+ }
323
+ getMemoryUsage() {
324
+ return this.currentMemoryUsage;
325
+ }
326
+ clear() {
327
+ this.buffer.clear();
328
+ this.currentMemoryUsage = 0;
329
+ this.lastAcknowledged = 0;
330
+ }
331
+ remove(frameNumber) {
332
+ const frame = this.buffer.get(frameNumber);
333
+ if (frame) {
334
+ this.currentMemoryUsage -= frame.size;
335
+ this.buffer.delete(frameNumber);
336
+ }
337
+ }
338
+ flushOldest() {
339
+ if (this.buffer.size === 0) {
340
+ return;
341
+ }
342
+ const oldest = Math.min(...this.buffer.keys());
343
+ this.remove(oldest);
344
+ }
345
+ };
346
+
347
+ // src/buffer/FrameQueue.ts
348
+ var FrameQueue = class {
349
+ constructor() {
350
+ this.queue = [];
351
+ }
352
+ enqueue(frame) {
353
+ this.queue.push(frame);
354
+ }
355
+ dequeue() {
356
+ return this.queue.shift();
357
+ }
358
+ peek() {
359
+ return this.queue[0];
360
+ }
361
+ isEmpty() {
362
+ return this.queue.length === 0;
363
+ }
364
+ size() {
365
+ return this.queue.length;
366
+ }
367
+ clear() {
368
+ this.queue = [];
369
+ }
370
+ toArray() {
371
+ return [...this.queue];
372
+ }
373
+ };
374
+
375
+ // src/utils/encoders.ts
376
+ function encodeBase64(buffer) {
377
+ if (buffer instanceof ArrayBuffer) {
378
+ const bytes = new Uint8Array(buffer);
379
+ return btoa(String.fromCharCode(...bytes));
380
+ }
381
+ return btoa(String.fromCharCode(...buffer));
382
+ }
383
+ function decodeBase64(base64) {
384
+ const binaryString = atob(base64);
385
+ const bytes = new Uint8Array(binaryString.length);
386
+ for (let i = 0; i < binaryString.length; i++) {
387
+ bytes[i] = binaryString.charCodeAt(i);
388
+ }
389
+ return bytes;
390
+ }
391
+ // Annotate the CommonJS export names for ESM import in node:
392
+ 0 && (module.exports = {
393
+ API_ENDPOINTS,
394
+ AUTH_CONFIG,
395
+ AuthManager,
396
+ ConnectionManager,
397
+ DEFAULT_ENDPOINT,
398
+ FRAME_BUFFER_CONFIG,
399
+ FRAME_CONFIG,
400
+ FrameBuffer,
401
+ FrameQueue,
402
+ RECONNECTION_CONFIG,
403
+ WebSocketClient,
404
+ decodeBase64,
405
+ encodeBase64,
406
+ retryWithBackoff,
407
+ validateApiKey,
408
+ validateFrameData,
409
+ validateFrameNumber
410
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,367 @@
1
+ // src/constants/config.ts
2
+ var API_ENDPOINTS = {
3
+ production: "wss://api.moveris.com/v1/liveness",
4
+ staging: "wss://staging-api.moveris.com/v1/liveness",
5
+ development: "ws://localhost:8080/v1/liveness"
6
+ };
7
+ var DEFAULT_ENDPOINT = API_ENDPOINTS.production;
8
+ var RECONNECTION_CONFIG = {
9
+ maxAttempts: 10,
10
+ initialDelay: 1e3,
11
+ // 1 second
12
+ maxDelay: 3e4,
13
+ // 30 seconds
14
+ backoffMultiplier: 2
15
+ };
16
+ var FRAME_BUFFER_CONFIG = {
17
+ maxSize: 10,
18
+ // Maximum un-acked frames
19
+ maxMemory: 4 * 1024 * 1024
20
+ // 4 MB
21
+ };
22
+ var AUTH_CONFIG = {
23
+ timeout: 1e4,
24
+ // 10 seconds
25
+ apiKeyPattern: /^mv_[a-zA-Z0-9]{32}$/
26
+ };
27
+ var FRAME_CONFIG = {
28
+ targetFPS: 30,
29
+ frameInterval: 33,
30
+ // milliseconds (1000 / 30)
31
+ jpegQuality: 0.8
32
+ // 80%
33
+ };
34
+
35
+ // src/utils/retry.ts
36
+ async function retryWithBackoff(fn, options = {}) {
37
+ const {
38
+ maxAttempts = RECONNECTION_CONFIG.maxAttempts,
39
+ initialDelay = RECONNECTION_CONFIG.initialDelay,
40
+ maxDelay = RECONNECTION_CONFIG.maxDelay,
41
+ backoffMultiplier = RECONNECTION_CONFIG.backoffMultiplier
42
+ } = options;
43
+ let lastError;
44
+ let delay = initialDelay;
45
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
46
+ try {
47
+ return await fn();
48
+ } catch (error) {
49
+ lastError = error;
50
+ if (attempt < maxAttempts - 1) {
51
+ await new Promise((resolve) => setTimeout(resolve, delay));
52
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
53
+ }
54
+ }
55
+ }
56
+ throw lastError;
57
+ }
58
+
59
+ // src/client/ConnectionManager.ts
60
+ var ConnectionManager = class {
61
+ constructor(config) {
62
+ this.reconnectAttempts = 0;
63
+ this.config = config;
64
+ }
65
+ async handleDisconnect() {
66
+ if (this.reconnectAttempts >= (this.config.maxAttempts ?? 10)) {
67
+ throw new Error("Max reconnection attempts reached");
68
+ }
69
+ try {
70
+ await retryWithBackoff(
71
+ async () => {
72
+ await this.config.onReconnect();
73
+ this.reconnectAttempts = 0;
74
+ },
75
+ {
76
+ maxAttempts: this.config.maxAttempts ?? 10
77
+ }
78
+ );
79
+ } catch (error) {
80
+ this.reconnectAttempts++;
81
+ throw error;
82
+ }
83
+ }
84
+ reset() {
85
+ this.reconnectAttempts = 0;
86
+ }
87
+ };
88
+
89
+ // src/utils/validators.ts
90
+ function validateApiKey(apiKey) {
91
+ if (!apiKey || typeof apiKey !== "string") {
92
+ return false;
93
+ }
94
+ return AUTH_CONFIG.apiKeyPattern.test(apiKey);
95
+ }
96
+ function validateFrameData(data) {
97
+ if (!data || typeof data !== "string") {
98
+ return false;
99
+ }
100
+ const base64Pattern = /^[A-Za-z0-9+/=]+$/;
101
+ return base64Pattern.test(data) && data.length > 0;
102
+ }
103
+ function validateFrameNumber(frameNumber) {
104
+ return Number.isInteger(frameNumber) && frameNumber > 0;
105
+ }
106
+
107
+ // src/types/models.ts
108
+ var MODEL_CONFIGS = {
109
+ "50-frame": {
110
+ type: "50-frame",
111
+ fileName: "model_50.h5",
112
+ frameCount: 50
113
+ },
114
+ "250-frame": {
115
+ type: "250-frame",
116
+ fileName: "model_250.h5",
117
+ frameCount: 250
118
+ }
119
+ };
120
+
121
+ // src/client/AuthManager.ts
122
+ var AuthManager = class {
123
+ async authenticate(ws, apiKey, model) {
124
+ if (!validateApiKey(apiKey)) {
125
+ throw new Error("Invalid API key format");
126
+ }
127
+ const modelType = model;
128
+ if (!MODEL_CONFIGS[modelType]) {
129
+ throw new Error(`Invalid model type: ${model}`);
130
+ }
131
+ return new Promise((resolve, reject) => {
132
+ const timeout = setTimeout(() => {
133
+ reject(new Error("Authentication timeout"));
134
+ }, AUTH_CONFIG.timeout);
135
+ const messageHandler = (event) => {
136
+ try {
137
+ const message = JSON.parse(event.data);
138
+ if (message.type === "auth_success") {
139
+ clearTimeout(timeout);
140
+ ws.removeEventListener("message", messageHandler);
141
+ resolve(message.request_id);
142
+ } else if (message.type === "auth_error") {
143
+ clearTimeout(timeout);
144
+ ws.removeEventListener("message", messageHandler);
145
+ reject(new Error(message.error));
146
+ }
147
+ } catch (error) {
148
+ }
149
+ };
150
+ ws.addEventListener("message", messageHandler);
151
+ ws.send(
152
+ JSON.stringify({
153
+ type: "auth",
154
+ token: apiKey,
155
+ model_file_name: MODEL_CONFIGS[modelType].fileName
156
+ })
157
+ );
158
+ });
159
+ }
160
+ };
161
+
162
+ // src/client/WebSocketClient.ts
163
+ var WebSocketClient = class {
164
+ constructor(config) {
165
+ this.ws = null;
166
+ this.state = "disconnected";
167
+ this.config = config;
168
+ this.connectionManager = new ConnectionManager({
169
+ onReconnect: () => this.connect()
170
+ });
171
+ this.authManager = new AuthManager();
172
+ }
173
+ async connect() {
174
+ if (this.state === "connected" || this.state === "connecting") {
175
+ return;
176
+ }
177
+ this.setState("connecting");
178
+ try {
179
+ this.ws = new WebSocket(this.config.url);
180
+ this.ws.onopen = async () => {
181
+ try {
182
+ await this.authManager.authenticate(this.ws, this.config.apiKey, this.config.model);
183
+ this.setState("connected");
184
+ } catch (error) {
185
+ this.handleError(error instanceof Error ? error : new Error("Authentication failed"));
186
+ }
187
+ };
188
+ this.ws.onmessage = (event) => {
189
+ try {
190
+ const message = JSON.parse(event.data);
191
+ this.handleMessage(message);
192
+ } catch (error) {
193
+ this.handleError(error instanceof Error ? error : new Error("Failed to parse message"));
194
+ }
195
+ };
196
+ this.ws.onerror = () => {
197
+ this.handleError(new Error("WebSocket error"));
198
+ };
199
+ this.ws.onclose = () => {
200
+ this.setState("disconnected");
201
+ this.connectionManager.handleDisconnect();
202
+ };
203
+ } catch (error) {
204
+ this.handleError(error instanceof Error ? error : new Error("Connection failed"));
205
+ }
206
+ }
207
+ disconnect() {
208
+ if (this.ws) {
209
+ this.ws.close();
210
+ this.ws = null;
211
+ }
212
+ this.setState("disconnected");
213
+ }
214
+ sendFrame(frameData, frameNumber) {
215
+ if (this.state !== "connected" || !this.ws) {
216
+ throw new Error("WebSocket not connected");
217
+ }
218
+ const message = {
219
+ type: "frame",
220
+ frame_data: frameData,
221
+ frame_number: frameNumber,
222
+ timestamp: Date.now()
223
+ };
224
+ this.ws.send(JSON.stringify(message));
225
+ }
226
+ getState() {
227
+ return this.state;
228
+ }
229
+ setState(newState) {
230
+ if (this.state !== newState) {
231
+ this.state = newState;
232
+ this.config.onConnectionStateChange?.(newState);
233
+ }
234
+ }
235
+ handleMessage(message) {
236
+ this.config.onMessage?.(message);
237
+ }
238
+ handleError(error) {
239
+ this.setState("error");
240
+ this.config.onError?.(error);
241
+ }
242
+ };
243
+
244
+ // src/buffer/FrameBuffer.ts
245
+ var FrameBuffer = class {
246
+ constructor(maxSize = FRAME_BUFFER_CONFIG.maxSize, maxMemory = FRAME_BUFFER_CONFIG.maxMemory) {
247
+ this.buffer = /* @__PURE__ */ new Map();
248
+ this.lastAcknowledged = 0;
249
+ this.currentMemoryUsage = 0;
250
+ this.maxSize = maxSize;
251
+ this.maxMemory = maxMemory;
252
+ }
253
+ add(frame) {
254
+ if (this.currentMemoryUsage + frame.size > this.maxMemory) {
255
+ this.flushOldest();
256
+ }
257
+ this.buffer.set(frame.number, frame);
258
+ this.currentMemoryUsage += frame.size;
259
+ if (this.buffer.size > this.maxSize) {
260
+ const oldest = Math.min(...this.buffer.keys());
261
+ this.remove(oldest);
262
+ }
263
+ }
264
+ acknowledge(frameNumber) {
265
+ this.lastAcknowledged = Math.max(this.lastAcknowledged, frameNumber);
266
+ for (const [num] of this.buffer) {
267
+ if (num <= frameNumber) {
268
+ this.remove(num);
269
+ }
270
+ }
271
+ }
272
+ getUnacknowledged() {
273
+ return Array.from(this.buffer.values()).filter((f) => f.number > this.lastAcknowledged).sort((a, b) => a.number - b.number);
274
+ }
275
+ getLastAcknowledged() {
276
+ return this.lastAcknowledged;
277
+ }
278
+ size() {
279
+ return this.buffer.size;
280
+ }
281
+ getMemoryUsage() {
282
+ return this.currentMemoryUsage;
283
+ }
284
+ clear() {
285
+ this.buffer.clear();
286
+ this.currentMemoryUsage = 0;
287
+ this.lastAcknowledged = 0;
288
+ }
289
+ remove(frameNumber) {
290
+ const frame = this.buffer.get(frameNumber);
291
+ if (frame) {
292
+ this.currentMemoryUsage -= frame.size;
293
+ this.buffer.delete(frameNumber);
294
+ }
295
+ }
296
+ flushOldest() {
297
+ if (this.buffer.size === 0) {
298
+ return;
299
+ }
300
+ const oldest = Math.min(...this.buffer.keys());
301
+ this.remove(oldest);
302
+ }
303
+ };
304
+
305
+ // src/buffer/FrameQueue.ts
306
+ var FrameQueue = class {
307
+ constructor() {
308
+ this.queue = [];
309
+ }
310
+ enqueue(frame) {
311
+ this.queue.push(frame);
312
+ }
313
+ dequeue() {
314
+ return this.queue.shift();
315
+ }
316
+ peek() {
317
+ return this.queue[0];
318
+ }
319
+ isEmpty() {
320
+ return this.queue.length === 0;
321
+ }
322
+ size() {
323
+ return this.queue.length;
324
+ }
325
+ clear() {
326
+ this.queue = [];
327
+ }
328
+ toArray() {
329
+ return [...this.queue];
330
+ }
331
+ };
332
+
333
+ // src/utils/encoders.ts
334
+ function encodeBase64(buffer) {
335
+ if (buffer instanceof ArrayBuffer) {
336
+ const bytes = new Uint8Array(buffer);
337
+ return btoa(String.fromCharCode(...bytes));
338
+ }
339
+ return btoa(String.fromCharCode(...buffer));
340
+ }
341
+ function decodeBase64(base64) {
342
+ const binaryString = atob(base64);
343
+ const bytes = new Uint8Array(binaryString.length);
344
+ for (let i = 0; i < binaryString.length; i++) {
345
+ bytes[i] = binaryString.charCodeAt(i);
346
+ }
347
+ return bytes;
348
+ }
349
+ export {
350
+ API_ENDPOINTS,
351
+ AUTH_CONFIG,
352
+ AuthManager,
353
+ ConnectionManager,
354
+ DEFAULT_ENDPOINT,
355
+ FRAME_BUFFER_CONFIG,
356
+ FRAME_CONFIG,
357
+ FrameBuffer,
358
+ FrameQueue,
359
+ RECONNECTION_CONFIG,
360
+ WebSocketClient,
361
+ decodeBase64,
362
+ encodeBase64,
363
+ retryWithBackoff,
364
+ validateApiKey,
365
+ validateFrameData,
366
+ validateFrameNumber
367
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@moveris/shared",
3
+ "version": "0.0.1",
4
+ "description": "Core business logic for Moveris Live SDK",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "moveris",
20
+ "liveness",
21
+ "websocket",
22
+ "sdk"
23
+ ],
24
+ "author": "Jeyson Palacio",
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@types/jest": "^29.5.11",
28
+ "@types/node": "^20.11.5",
29
+ "@types/ws": "^8.5.10",
30
+ "jest": "^29.7.0",
31
+ "ts-jest": "^29.1.2",
32
+ "tsup": "^8.0.1",
33
+ "typescript": "^5.3.3",
34
+ "ws": "^8.16.0"
35
+ },
36
+ "dependencies": {},
37
+ "scripts": {
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.json",
39
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
40
+ "test": "jest --passWithNoTests",
41
+ "test:coverage": "jest --coverage --passWithNoTests",
42
+ "test:watch": "jest --watch",
43
+ "lint": "eslint src --ext .ts",
44
+ "type-check": "tsc --noEmit",
45
+ "clean": "rm -rf dist",
46
+ "publish:local": "npm run build && yalc publish --push",
47
+ "yalc:publish": "yalc publish",
48
+ "yalc:push": "yalc push",
49
+ "yalc:remove": "yalc remove"
50
+ }
51
+ }