nervoscan-js-sdk 1.0.4-0 → 1.0.4

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
@@ -1,23 +1,25 @@
1
1
  ![nervotec.png](nervotec.png)
2
2
 
3
+ # NervoScan JS SDK
4
+
3
5
  [![npm version](https://img.shields.io/npm/v/nervoscan-js-sdk)](https://www.npmjs.com/package/nervoscan-js-sdk)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/)
4
8
 
5
9
  ## Overview
6
10
 
7
- The **NervoScan JS SDK** is a lightweight JavaScript library that enables seamless integration with the NervoScan backend services. This SDK handles authentication, video scan submission, and structured error handling allowing developers to build React or other web-based clients effortlessly on top of NervoScan's contactless health analysis platform.
8
-
9
- It provides a singleton `Client` interface and a full suite of custom error classes for advanced control and debugging.
11
+ The **NervoScan JS SDK** is a comprehensive JavaScript/TypeScript library that enables seamless integration with the NervoScan contactless health analysis platform. This SDK provides real-time video streaming capabilities, live health metric results, and robust error handling for building sophisticated health monitoring applications.
10
12
 
11
- ---
13
+ ### Key Features
12
14
 
13
- ## Features
14
-
15
- - Authenticated communication with the NervoScan backend
16
- - Video scan submission and result retrieval
17
- - Fully type-safe implementation (written in TypeScript)
18
- - Structured custom error classes for robust error handling
19
- - Easy integration into any React, Vite, or JS-based frontend
20
- - Singleton pattern for consistent client state management
15
+ - 🎥 **Real-time Video Streaming** - Stream video directly from webcam using WebRTC
16
+ - 📊 **Live Results** - Receive health metrics in real-time via Firebase
17
+ - 🔄 **Dual Processing Modes** - Support for both batch video upload and live streaming
18
+ - 🏗️ **Multiple Backend Types** - Server and serverless deployment options
19
+ - 🛡️ **Type-Safe** - Full TypeScript support with comprehensive type definitions
20
+ - 🎯 **Smart Error Handling** - Detailed error classes for face positioning and scan quality
21
+ - **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
22
+ - 📦 **Multiple Module Formats** - ESM, CommonJS, and UMD builds included
21
23
 
22
24
  ---
23
25
 
@@ -27,35 +29,71 @@ It provides a singleton `Client` interface and a full suite of custom error clas
27
29
  npm install nervoscan-js-sdk
28
30
  ```
29
31
 
32
+ Or with yarn:
33
+ ```bash
34
+ yarn add nervoscan-js-sdk
35
+ ```
36
+
30
37
  ---
31
38
 
32
- ## Usage
39
+ ## Quick Start
33
40
 
34
- ```ts
35
- import { Client, Errors } from 'nervoscan-js-sdk';
41
+ ### Basic Video Upload
42
+
43
+ ```typescript
44
+ import { Client } from 'nervoscan-js-sdk';
36
45
 
37
- // Get the singleton instance
38
46
  const client = Client.getInstance();
39
47
 
40
- async function submitScan(videoBlob: Blob) {
41
- try {
42
- // Initialize with credentials
43
- await client.initialize('username', 'password');
44
-
45
- // Upload video and get job ID
46
- const jobID = await client.uploadVideo(videoBlob);
47
- console.log('Scan submitted. Job ID:', jobID);
48
-
49
- // Check results
50
- const results = await client.getResults(jobID);
51
- console.log('Scan results:', results);
52
- } catch (error) {
53
- if (error instanceof Errors.InvalidPasswordError) {
54
- console.error('Password incorrect.');
55
- } else {
56
- console.error('Unexpected error:', error);
57
- }
58
- }
48
+ // Initialize with credentials
49
+ await client.initialize('username', 'password');
50
+
51
+ // Upload a video file
52
+ const videoBlob = new Blob([videoData], { type: 'video/mp4' });
53
+ const jobId = await client.uploadVideo(videoBlob);
54
+
55
+ console.log('Video uploaded! Job ID:', jobId);
56
+ ```
57
+
58
+ ### Real-time Streaming with Live Results
59
+
60
+ ```typescript
61
+ import { Client } from 'nervoscan-js-sdk';
62
+
63
+ const client = Client.getInstance();
64
+
65
+ async function startHealthMonitoring() {
66
+ // Initialize client
67
+ await client.initialize('username', 'password');
68
+
69
+ // Set up result callbacks
70
+ client.setOnWindowResults((results) => {
71
+ console.log('New window results:', results);
72
+ // Update UI with real-time metrics
73
+ });
74
+
75
+ client.setOnFinalResults((results) => {
76
+ console.log('Final averaged results:', results);
77
+ // Display final health report
78
+ });
79
+
80
+ client.setOnError((error) => {
81
+ console.error('Scan error:', error);
82
+ // Handle scanning errors
83
+ });
84
+
85
+ // Get webcam stream
86
+ const stream = await navigator.mediaDevices.getUserMedia({
87
+ video: { width: 1280, height: 720 }
88
+ });
89
+
90
+ // Initialize streaming
91
+ const videoElement = document.getElementById('video') as HTMLVideoElement;
92
+ client.initializeStreaming(stream, videoElement);
93
+
94
+ // Start streaming
95
+ const jobId = await client.startStreaming();
96
+ console.log('Streaming started! Job ID:', jobId);
59
97
  }
60
98
  ```
61
99
 
@@ -63,73 +101,260 @@ async function submitScan(videoBlob: Blob) {
63
101
 
64
102
  ## API Reference
65
103
 
66
- ### `Client` Class
104
+ ### Client Class
105
+
106
+ The `Client` class follows a singleton pattern to ensure consistent state management across your application.
67
107
 
68
- #### Singleton Access
69
- ```ts
70
- Client.getInstance(): Client
108
+ #### Getting the Instance
109
+
110
+ ```typescript
111
+ const client = Client.getInstance();
71
112
  ```
72
- Returns the singleton instance of the Client class.
73
113
 
74
- #### Methods
114
+ #### Core Methods
115
+
116
+ ##### `initialize(username: string, password: string, serverType?: string): void`
117
+ Initializes the client with authentication credentials.
118
+
119
+ - `username` - Your NervoScan account username
120
+ - `password` - Your NervoScan account password
121
+ - `serverType` - Backend type: `'server'` (default) or `'serverless'`
122
+
123
+ ##### `uploadVideo(videoBlob: Blob): Promise<string>`
124
+ Uploads a video for processing and returns the job ID.
125
+
126
+ - `videoBlob` - Video file as a Blob object
127
+ - Returns: Promise resolving to the job ID string
128
+
129
+ ##### `initializeStreaming(videoStream: MediaStream, videoElement: HTMLVideoElement): void`
130
+ Sets up real-time video streaming.
131
+
132
+ - `videoStream` - MediaStream from getUserMedia
133
+ - `videoElement` - HTML video element for preview
134
+
135
+ ##### `startStreaming(): Promise<string>`
136
+ Begins streaming video to the server for real-time analysis.
137
+
138
+ - Returns: Promise resolving to the job ID string
139
+
140
+ ##### `stopStreaming(): void`
141
+ Stops the active streaming session.
142
+
143
+ #### Callback Methods
75
144
 
76
- - `initialize(username: string, password: string): void`
77
- Initializes the client with user credentials.
145
+ ##### `setOnWindowResults(callback: (results: any) => void): void`
146
+ Sets callback for receiving real-time window-based results.
78
147
 
79
- - `uploadVideo(videoBlob: Blob): Promise<string>`
80
- Uploads a video scan and returns the job ID.
148
+ ##### `setOnFinalResults(callback: (results: any) => void): void`
149
+ Sets callback for receiving final averaged results.
81
150
 
82
- - `checkResults(jobID: string): Promise<string>`
83
- Checks if results are available for the given job ID.
151
+ ##### `setOnError(callback: (error: any) => void): void`
152
+ Sets callback for handling scan errors.
84
153
 
85
- - `getResults(jobID: string): Promise<any>`
86
- Fetches scan results for the given job ID.
154
+ ##### `setOnDisconnection(callback: () => void): void`
155
+ Sets callback for handling connection loss.
156
+
157
+ #### Deprecated Methods
158
+
159
+ ⚠️ The following methods are deprecated and will be removed in future versions:
160
+
161
+ - `checkResults(jobID: string)` - Use callback methods instead
162
+ - `getResults(jobID: string)` - Use callback methods instead
87
163
 
88
164
  ---
89
165
 
90
166
  ## Error Handling
91
167
 
92
- The SDK provides custom error classes for granular error control:
93
-
94
- ```ts
95
- import { Errors } from 'nervoscan-js-sdk';
168
+ The SDK provides comprehensive error classes for granular error control:
96
169
 
170
+ ### Authentication Errors
171
+ ```typescript
97
172
  try {
98
173
  await client.initialize('username', 'password');
99
- } catch (err) {
100
- if (err instanceof Errors.NotInitializedError) {
101
- console.error('Client not initialized.');
174
+ } catch (error) {
175
+ if (error instanceof Errors.InvalidUsernameError) {
176
+ console.error('Invalid username');
177
+ } else if (error instanceof Errors.InvalidPasswordError) {
178
+ console.error('Invalid password');
102
179
  }
103
180
  }
104
181
  ```
105
182
 
106
- ### Available Errors
183
+ ### Scan Quality Errors
184
+ ```typescript
185
+ client.setOnError((error) => {
186
+ if (error instanceof Errors.FaceTooFarError) {
187
+ showMessage('Please move closer to the camera');
188
+ } else if (error instanceof Errors.FaceTooCloseError) {
189
+ showMessage('Please move further from the camera');
190
+ } else if (error instanceof Errors.FaceNotCenteredError) {
191
+ showMessage('Please center your face in the frame');
192
+ } else if (error instanceof Errors.LowFPSError) {
193
+ showMessage('Poor video quality - check your lighting');
194
+ }
195
+ });
196
+ ```
107
197
 
108
- - `NotInitializedError` - Client not properly initialized
109
- - `EmptyVideoError` - Video blob is empty
110
- - `VideoTypeError` - Invalid video type, not a blob
111
- - `InvalidUsernameError` - Invalid username provided
112
- - `InvalidPasswordError` - Invalid password provided
113
- - `InvalidAccessTokenError` - Invalid or expired access token
114
- - `NoScansAvailableError` - No scans available in current plan
115
- - `NoScanDataError` - No scan data available for the job ID
198
+ ### Complete Error Reference
199
+
200
+ | Error Class | Description |
201
+ |-------------|-------------|
202
+ | `NotInitializedError` | Client not initialized |
203
+ | `EmptyVideoError` | Video blob is empty |
204
+ | `VideoTypeError` | Invalid video type |
205
+ | `InvalidUsernameError` | Invalid username |
206
+ | `InvalidPasswordError` | Invalid password |
207
+ | `InvalidAccessTokenError` | Invalid or expired token |
208
+ | `NoScansAvailableError` | No scans left in plan |
209
+ | `NoScanDataError` | No data for job ID |
210
+ | `InvalidServerTypeError` | Invalid server type |
211
+ | `LowFPSError` | Video FPS too low |
212
+ | `FaceNotCenteredError` | Face not centered |
213
+ | `FaceTooFarError` | Face too far away |
214
+ | `FaceTooCloseError` | Face too close |
215
+ | `FaceLookingLeftError` | Face turned left |
216
+ | `FaceLookingRightError` | Face turned right |
217
+ | `UnhandledScanError` | Other scan errors |
116
218
 
117
219
  ---
118
220
 
119
- ## Requirements
221
+ ## Real-World Examples
222
+
223
+ ### React Component with Live Monitoring
224
+
225
+ ```typescript
226
+ import React, { useEffect, useRef, useState } from 'react';
227
+ import { Client, Errors } from 'nervoscan-js-sdk';
228
+
229
+ function HealthMonitor() {
230
+ const videoRef = useRef<HTMLVideoElement>(null);
231
+ const [isScanning, setIsScanning] = useState(false);
232
+ const [heartRate, setHeartRate] = useState<number | null>(null);
233
+ const [message, setMessage] = useState('');
234
+
235
+ const client = Client.getInstance();
236
+
237
+ useEffect(() => {
238
+ // Setup callbacks
239
+ client.setOnWindowResults((results) => {
240
+ setHeartRate(results.heartRate);
241
+ });
242
+
243
+ client.setOnError((error) => {
244
+ if (error instanceof Errors.FaceNotCenteredError) {
245
+ setMessage('Center your face in the frame');
246
+ }
247
+ });
248
+
249
+ return () => {
250
+ client.stopStreaming();
251
+ };
252
+ }, []);
253
+
254
+ const startScan = async () => {
255
+ try {
256
+ await client.initialize('user@example.com', 'password');
257
+
258
+ const stream = await navigator.mediaDevices.getUserMedia({
259
+ video: { facingMode: 'user' }
260
+ });
261
+
262
+ if (videoRef.current) {
263
+ client.initializeStreaming(stream, videoRef.current);
264
+ await client.startStreaming();
265
+ setIsScanning(true);
266
+ }
267
+ } catch (error) {
268
+ console.error('Failed to start scan:', error);
269
+ }
270
+ };
271
+
272
+ return (
273
+ <div>
274
+ <video ref={videoRef} autoPlay playsInline />
275
+ {heartRate && <p>Heart Rate: {heartRate} BPM</p>}
276
+ <p>{message}</p>
277
+ <button onClick={startScan} disabled={isScanning}>
278
+ {isScanning ? 'Scanning...' : 'Start Scan'}
279
+ </button>
280
+ </div>
281
+ );
282
+ }
283
+ ```
284
+
285
+ ### Vue 3 Composition API Example
286
+
287
+ ```typescript
288
+ <template>
289
+ <div>
290
+ <video ref="videoEl" autoplay playsinline></video>
291
+ <div v-if="metrics">
292
+ <p>Heart Rate: {{ metrics.heartRate }} BPM</p>
293
+ <p>Stress Level: {{ metrics.stressLevel }}</p>
294
+ </div>
295
+ </div>
296
+ </template>
120
297
 
121
- - Node.js v14 or higher
122
- - Modern browser (Chrome/Safari) for webcam support
123
- - NervoScan backend access (credentials required)
298
+ <script setup lang="ts">
299
+ import { ref, onMounted, onUnmounted } from 'vue';
300
+ import { Client } from 'nervoscan-js-sdk';
301
+
302
+ const videoEl = ref<HTMLVideoElement>();
303
+ const metrics = ref(null);
304
+
305
+ const client = Client.getInstance();
306
+
307
+ onMounted(async () => {
308
+ await client.initialize(import.meta.env.VITE_NERVO_USER,
309
+ import.meta.env.VITE_NERVO_PASS);
310
+
311
+ client.setOnFinalResults((results) => {
312
+ metrics.value = results;
313
+ });
314
+
315
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
316
+ if (videoEl.value) {
317
+ client.initializeStreaming(stream, videoEl.value);
318
+ await client.startStreaming();
319
+ }
320
+ });
321
+
322
+ onUnmounted(() => {
323
+ client.stopStreaming();
324
+ });
325
+ </script>
326
+ ```
124
327
 
125
328
  ---
126
329
 
127
- ## License
330
+ ## Requirements
128
331
 
129
- MIT License
332
+ - **Node.js**: v14 or higher
333
+ - **Browser Support**: Chrome 80+, Safari 14+, Firefox 78+, Edge 80+
334
+ - **Network**: Stable internet connection for streaming
335
+ - **Camera**: HD webcam (720p or higher recommended)
336
+ - **NervoScan Account**: Valid credentials with active subscription
130
337
 
131
338
  ---
132
339
 
133
- ## Maintainers
340
+ ## TypeScript Support
341
+
342
+ The SDK is written in TypeScript and includes comprehensive type definitions:
343
+
344
+ ```typescript
345
+ import { Client, Errors, NervoscanError } from 'nervoscan-js-sdk';
346
+
347
+ // All methods are fully typed
348
+ const client: Client = Client.getInstance();
349
+
350
+ // Error types are available
351
+ function handleError(error: unknown) {
352
+ if (error instanceof NervoscanError) {
353
+ // Handle NervoScan specific errors
354
+ }
355
+ }
356
+ ```
357
+
358
+ ---
134
359
 
135
- Built and maintained by the NervoScan development team.
360
+ Built and maintained by the NervoScan team
@@ -1581,7 +1581,7 @@ FIREBASE: `))}restoreState_(){this.tryAuth(),this.tryAppCheck();for(const e of t
1581
1581
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1582
1582
  * See the License for the specific language governing permissions and
1583
1583
  * limitations under the License.
1584
- */function R_(n){Ed(ad),rn(new yt("database",(e,{instanceIdentifier:t})=>{const s=e.getProvider("app").getImmediate(),r=e.getProvider("auth-internal"),i=e.getProvider("app-check-internal");return v_(s,r,i,t)},"PUBLIC").setMultipleInstances(!0)),$e(Qr,Jr,n),$e(Qr,Jr,"esm2017")}fe.prototype.simpleListen=function(n,e){this.sendRequest("q",{p:n},e)};fe.prototype.echo=function(n,e){this.sendRequest("echo",{d:n},e)};R_();const Mn=n=>n==="server"?"https://backend.nervoscan.com":"https://backendv2-dev.nervoscan.com",Ne=class Ne{constructor(){k(this,"socket");k(this,"backendUrl",Mn("server"));k(this,"videoStream");k(this,"videoElement");k(this,"isConnected",!1);k(this,"isStreaming",!1);k(this,"jobId");k(this,"onDisconnection");k(this,"playHandler",()=>{if(!this.videoElement)return;const e=document.createElement("canvas");e.width=this.videoElement.videoWidth,e.height=this.videoElement.videoHeight,this.captureAndSendFrame(e,this.videoElement)});k(this,"stopStreaming",()=>{this.isStreaming=!1,this.removePlayEventListener()})}static getInstance(){return Ne.instance||(Ne.instance=new Ne),Ne.instance}setupWebSocketConnection(){this.socket||(this.socket=Gt(this.backendUrl,{transports:["websocket","polling"]}),this.socket.on("connect",()=>{this.isConnected=!0}),this.socket.on("connect_error",e=>this.handleSocketError(e)),this.socket.on("disconnect",e=>this.handleSocketDisconnection(e)))}handleSocketError(e){console.error("Connection error:",e)}handleSocketDisconnection(e){console.warn("Disconnected from WebSocket server:",e)}initializeStreaming(e,t){this.setupWebSocketConnection(),this.videoStream=e,this.videoElement=t,this.videoElement.srcObject=e}startStreaming(e){var t;this.jobId=e,(t=this.socket)==null||t.emit("register",e),this.sendInitialRequest(e),this.isStreaming=!0,this.startVideoStream(),this.addDisconnectionListener()}async sendInitialRequest(e){await I.post(`${this.backendUrl}/react`,{username:e,uuid:e,device_id:"1234567",face_detector:"BlazefaceFacemesh",motion_tracking:!0,skin_segmentor:"HSVandYCrCbSkinSegmentation",extreme_pixel_removal:!1,gamma_value:1,eularian_color_magnification:!1},{headers:{"Content-Type":"application/json"}})}startVideoStream(){this.videoElement?(this.videoElement.addEventListener("play",this.playHandler),!this.videoElement.paused&&!this.videoElement.ended&&this.playHandler(),this.videoElement.play().then(()=>{}).catch(e=>console.error("Error starting video stream:",e))):console.error("Video stream not found when trying to start streaming")}captureAndSendFrame(e,t){const s=e.getContext("2d");if(!s)return;const r=()=>{this.isStreaming&&(!t.paused&&!t.ended&&(s.drawImage(t,0,0,640,480),this.sendFrameToBackend(e)),setTimeout(r,33))};r()}sendFrameToBackend(e){e.toBlob(t=>{if(!t)return;const s=new FileReader;s.onloadend=()=>{var r;if(this.isConnected&&this.isStreaming){const i=s.result;(r=this.socket)==null||r.emit("frame",i)}},s.readAsArrayBuffer(t)},"image/jpeg",.7)}removePlayEventListener(){this.videoElement&&this.videoElement.removeEventListener("play",this.playHandler)}setOnDisconnection(e){this.onDisconnection=e}addDisconnectionListener(){const e=Jt(),t=Qt(e,`${this.jobId}/disconnect`),s=ks(t,r=>{r.val()!=null&&(this.stopStreaming(),this.onDisconnection&&this.onDisconnection(),s())})}};k(Ne,"instance");let En=Ne;class uc{constructor(e,t){k(this,"backendUrl",Mn("server"));k(this,"username");k(this,"password");k(this,"streamingManager",En.getInstance());this.username=e,this.password=t}}class I_ extends uc{constructor(t,s){super(t,s);k(this,"streamingManager",En.getInstance());this.backendUrl=Mn("server")}async getToken(){try{return(await I.post(this.backendUrl.concat("/clientlogin"),{username:this.username,password:this.password})).data.access_token}catch(t){T.handleErrorResponse(t)}}async getAPIKey(t){t||T.throwKnownError(R.InvalidAccessTokenError);try{return(await I.post(this.backendUrl.concat("/clientgeturl"),{host_name:"www.nervoscan.com"},{headers:{Authorization:`Bearer ${t}`}})).data.url.split("?api_key=")[1]}catch(s){T.handleErrorResponse(s)}}async uploadVideoToServer(t,s,r){(!s||!r)&&T.throwKnownError(R.InvalidAccessTokenError);try{const i=new FormData;return i.append("file",t,"recording.webm"),i.append("data",JSON.stringify({uuid:r,meta_data:{}})),(await I.post(this.backendUrl.concat("/submitscan"),i,{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${s}`}})).data}catch(i){throw console.error("Error uploading video:",i),i.response?(console.error("Error response:",i.response.data),console.error("Error status:",i.response.status),console.error("Error headers:",i.response.headers)):i.request?console.error("Error request:",i.request):console.error("Error message:",i.message),i}}async getResults(t,s){var r;s||T.throwKnownError(R.InvalidAccessTokenError);try{const i=await I.post(this.backendUrl.concat("/getcustomerinfo"),{mode:"selection",api_keys:[t]},{headers:{Authorization:`Bearer ${s}`}});return((r=i.data)==null?void 0:r.success)===!1&&T.throwKnownError(R.NoScanDataError),i.data}catch(i){console.error("Error getting results:",i),T.handleErrorResponse(i)}}async checkResults(t,s){try{s||T.throwKnownError(R.InvalidAccessTokenError);const o=(await I.post(this.backendUrl.concat("/check"),{api_key:t},{headers:{Authorization:`Bearer ${s}`}})).data.job_status;return o==="Completed"||o==="Failed"?o:"Pending"}catch(r){T.handleErrorResponse(r)}}}class N_ extends uc{constructor(e,t){super(e,t),this.backendUrl=Mn("serverless")}async getToken(){try{const e=await I.post(this.backendUrl.concat("/user/login"),{username:this.username,password:this.password});if(e.data.success===!1)T.throwKnownError(R.InvalidUsernameError);else return JSON.parse(e.data.body).idToken}catch(e){console.error("error printed in serverless repository",e),T.handleErrorResponse(e)}}async getAPIKey(e){e||T.throwKnownError(R.InvalidAccessTokenError);try{return(await I.post(this.backendUrl.concat("/get-redirection-url"),{host_name:"www.nervoscan.com",username:this.username},{headers:{Authorization:`Bearer ${e}`}})).data.url.split("?api_key=")[1]}catch(t){T.handleErrorResponse(t)}}async uploadVideoToServer(e,t,s){(!t||!s)&&T.throwKnownError(R.InvalidAccessTokenError);try{const r=await this.generatePreSignedUrl(s);return r||T.throwKnownError(R.InvalidAccessTokenError),await this.uploadVideoToS3(e,r)}catch(r){throw console.error("Error uploading video:",r),r.response?(console.error("Error response:",r.response.data),console.error("Error status:",r.response.status),console.error("Error headers:",r.response.headers)):r.request?console.error("Error request:",r.request):console.error("Error message:",r.message),r}}getResults(e,t){return console.log("not implemented"),Promise.resolve(void 0)}async checkResults(e,t){try{t||T.throwKnownError(R.InvalidAccessTokenError);const i=(await I.post(this.backendUrl.concat("/check"),{api_key:e},{headers:{Authorization:`Bearer ${t}`}})).data.job_status;return i==="Completed"||i==="Failed"?i:"Pending"}catch(s){T.handleErrorResponse(s)}}async generatePreSignedUrl(e){try{return(await I.get(this.backendUrl.concat("/generate-presigned-url?job_id="+e))).data.upload_url}catch(t){T.handleErrorResponse(t)}}async uploadVideoToS3(e,t){try{return(await I.put(t,e,{headers:{"Content-Type":"video/mp4"}})).data}catch(s){T.handleErrorResponse(s)}}}var A_="firebase",k_="11.6.1";/**
1584
+ */function R_(n){Ed(ad),rn(new yt("database",(e,{instanceIdentifier:t})=>{const s=e.getProvider("app").getImmediate(),r=e.getProvider("auth-internal"),i=e.getProvider("app-check-internal");return v_(s,r,i,t)},"PUBLIC").setMultipleInstances(!0)),$e(Qr,Jr,n),$e(Qr,Jr,"esm2017")}fe.prototype.simpleListen=function(n,e){this.sendRequest("q",{p:n},e)};fe.prototype.echo=function(n,e){this.sendRequest("echo",{d:n},e)};R_();const Mn=n=>n==="server"?"https://backend.nervoscan.com":"https://backendv2-dev.nervoscan.com",Ne=class Ne{constructor(){k(this,"socket");k(this,"backendUrl",Mn("server"));k(this,"videoStream");k(this,"videoElement");k(this,"isConnected",!1);k(this,"isStreaming",!1);k(this,"jobId");k(this,"onDisconnection");k(this,"playHandler",()=>{if(!this.videoElement)return;const e=document.createElement("canvas");e.width=this.videoElement.videoWidth,e.height=this.videoElement.videoHeight,this.captureAndSendFrame(e,this.videoElement)});k(this,"stopStreaming",()=>{this.isStreaming=!1,this.removePlayEventListener()})}static getInstance(){return Ne.instance||(Ne.instance=new Ne),Ne.instance}setupWebSocketConnection(){this.socket||(console.log("Setting up WebSocket connection:",this.backendUrl),this.socket=Gt(this.backendUrl,{transports:["websocket","polling"]}),this.socket.on("connect",()=>{this.isConnected=!0}),this.socket.on("connect_error",e=>this.handleSocketError(e)),this.socket.on("disconnect",e=>this.handleSocketDisconnection(e)))}handleSocketError(e){console.error("Connection error:",e)}handleSocketDisconnection(e){console.warn("Disconnected from WebSocket server:",e)}initializeStreaming(e,t){console.log("Initializing streaming"),this.setupWebSocketConnection(),this.videoStream=e,this.videoElement=t,this.videoElement.srcObject=e}startStreaming(e){var t;this.jobId=e,(t=this.socket)==null||t.emit("register",e),this.sendInitialRequest(e),this.isStreaming=!0,this.startVideoStream(),this.addDisconnectionListener()}async sendInitialRequest(e){await I.post(`${this.backendUrl}/react`,{username:e,uuid:e,device_id:"1234567",face_detector:"BlazefaceFacemesh",motion_tracking:!0,skin_segmentor:"HSVandYCrCbSkinSegmentation",extreme_pixel_removal:!1,gamma_value:1,eularian_color_magnification:!1},{headers:{"Content-Type":"application/json"}})}startVideoStream(){this.videoElement?(this.videoElement.addEventListener("play",this.playHandler),!this.videoElement.paused&&!this.videoElement.ended&&this.playHandler(),this.videoElement.play().then(()=>{}).catch(e=>console.error("Error starting video stream:",e))):console.error("Video stream not found when trying to start streaming")}captureAndSendFrame(e,t){const s=e.getContext("2d");if(!s)return;const r=()=>{this.isStreaming&&(!t.paused&&!t.ended&&(s.drawImage(t,0,0,640,480),this.sendFrameToBackend(e)),setTimeout(r,33))};r()}sendFrameToBackend(e){e.toBlob(t=>{if(!t)return;const s=new FileReader;s.onloadend=()=>{var r;if(this.isConnected&&this.isStreaming){const i=s.result;(r=this.socket)==null||r.emit("frame",i)}},s.readAsArrayBuffer(t)},"image/jpeg",.7)}removePlayEventListener(){this.videoElement&&this.videoElement.removeEventListener("play",this.playHandler)}setOnDisconnection(e){this.onDisconnection=e}addDisconnectionListener(){const e=Jt(),t=Qt(e,`${this.jobId}/disconnect`),s=ks(t,r=>{r.val()!=null&&(this.stopStreaming(),this.onDisconnection&&this.onDisconnection(),s())})}};k(Ne,"instance");let En=Ne;class uc{constructor(e,t){k(this,"backendUrl",Mn("server"));k(this,"username");k(this,"password");k(this,"streamingManager",En.getInstance());this.username=e,this.password=t}}class I_ extends uc{constructor(t,s){super(t,s);k(this,"streamingManager",En.getInstance());this.backendUrl=Mn("server")}async getToken(){try{return(await I.post(this.backendUrl.concat("/clientlogin"),{username:this.username,password:this.password})).data.access_token}catch(t){T.handleErrorResponse(t)}}async getAPIKey(t){t||T.throwKnownError(R.InvalidAccessTokenError);try{return(await I.post(this.backendUrl.concat("/clientgeturl"),{host_name:"www.nervoscan.com"},{headers:{Authorization:`Bearer ${t}`}})).data.url.split("?api_key=")[1]}catch(s){T.handleErrorResponse(s)}}async uploadVideoToServer(t,s,r){(!s||!r)&&T.throwKnownError(R.InvalidAccessTokenError);try{const i=new FormData;return i.append("file",t,"recording.webm"),i.append("data",JSON.stringify({uuid:r,meta_data:{}})),(await I.post(this.backendUrl.concat("/submitscan"),i,{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${s}`}})).data}catch(i){throw console.error("Error uploading video:",i),i.response?(console.error("Error response:",i.response.data),console.error("Error status:",i.response.status),console.error("Error headers:",i.response.headers)):i.request?console.error("Error request:",i.request):console.error("Error message:",i.message),i}}async getResults(t,s){var r;s||T.throwKnownError(R.InvalidAccessTokenError);try{const i=await I.post(this.backendUrl.concat("/getcustomerinfo"),{mode:"selection",api_keys:[t]},{headers:{Authorization:`Bearer ${s}`}});return((r=i.data)==null?void 0:r.success)===!1&&T.throwKnownError(R.NoScanDataError),i.data}catch(i){console.error("Error getting results:",i),T.handleErrorResponse(i)}}async checkResults(t,s){try{s||T.throwKnownError(R.InvalidAccessTokenError);const o=(await I.post(this.backendUrl.concat("/check"),{api_key:t},{headers:{Authorization:`Bearer ${s}`}})).data.job_status;return o==="Completed"||o==="Failed"?o:"Pending"}catch(r){T.handleErrorResponse(r)}}}class N_ extends uc{constructor(e,t){super(e,t),this.backendUrl=Mn("serverless")}async getToken(){try{const e=await I.post(this.backendUrl.concat("/user/login"),{username:this.username,password:this.password});if(e.data.success===!1)T.throwKnownError(R.InvalidUsernameError);else return JSON.parse(e.data.body).idToken}catch(e){console.error("error printed in serverless repository",e),T.handleErrorResponse(e)}}async getAPIKey(e){e||T.throwKnownError(R.InvalidAccessTokenError);try{return(await I.post(this.backendUrl.concat("/get-redirection-url"),{host_name:"www.nervoscan.com",username:this.username},{headers:{Authorization:`Bearer ${e}`}})).data.url.split("?api_key=")[1]}catch(t){T.handleErrorResponse(t)}}async uploadVideoToServer(e,t,s){(!t||!s)&&T.throwKnownError(R.InvalidAccessTokenError);try{const r=await this.generatePreSignedUrl(s);return r||T.throwKnownError(R.InvalidAccessTokenError),await this.uploadVideoToS3(e,r)}catch(r){throw console.error("Error uploading video:",r),r.response?(console.error("Error response:",r.response.data),console.error("Error status:",r.response.status),console.error("Error headers:",r.response.headers)):r.request?console.error("Error request:",r.request):console.error("Error message:",r.message),r}}getResults(e,t){return console.log("not implemented"),Promise.resolve(void 0)}async checkResults(e,t){try{t||T.throwKnownError(R.InvalidAccessTokenError);const i=(await I.post(this.backendUrl.concat("/check"),{api_key:e},{headers:{Authorization:`Bearer ${t}`}})).data.job_status;return i==="Completed"||i==="Failed"?i:"Pending"}catch(s){T.handleErrorResponse(s)}}async generatePreSignedUrl(e){try{return(await I.get(this.backendUrl.concat("/generate-presigned-url?job_id="+e))).data.upload_url}catch(t){T.handleErrorResponse(t)}}async uploadVideoToS3(e,t){try{return(await I.put(t,e,{headers:{"Content-Type":"video/mp4"}})).data}catch(s){T.handleErrorResponse(s)}}}var A_="firebase",k_="11.6.1";/**
1585
1585
  * @license
1586
1586
  * Copyright 2020 Google LLC
1587
1587
  *
@@ -1596,4 +1596,4 @@ FIREBASE: `))}restoreState_(){this.tryAuth(),this.tryAppCheck();for(const e of t
1596
1596
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1597
1597
  * See the License for the specific language governing permissions and
1598
1598
  * limitations under the License.
1599
- */$e(A_,k_,"app");const Ae=class Ae{constructor(){k(this,"initialized",!1);k(this,"accessToken");k(this,"backendRepository");k(this,"onWindowResults");k(this,"onFinalResults");k(this,"onError");k(this,"firebaseHandlers",[]);zo({apiKey:"AIzaSyAoOpQqLrc6wQLJI5KRE2Vt7QV1cplsZv8",authDomain:"nervowebreact.firebaseapp.com",databaseURL:"https://nervowebreact-default-rtdb.asia-southeast1.firebasedatabase.app",projectId:"nervowebreact",storageBucket:"nervowebreact.firebasestorage.app",messagingSenderId:"1025695568936",appId:"1:1025695568936:web:36f269a1eb74e1fdda511d",measurementId:"G-L3105FG92S"})}static getInstance(){return Ae.instance||(Ae.instance=new Ae),Ae.instance}initialize(e,t,s="server"){this.initialized=!0,s==="server"?this.backendRepository=new I_(e,t):s==="serverless"?this.backendRepository=new N_(e,t):T.throwKnownError(R.InvalidServerTypeError)}async uploadVideo(e){var r,i,o;this.initialized||T.throwKnownError(R.NotInitializedError),e instanceof Blob||T.throwKnownError(R.VideoTypeError),(e===null||e.size===0)&&T.throwKnownError(R.EmptyVideoError);const t=await((r=this.backendRepository)==null?void 0:r.getToken()),s=await((i=this.backendRepository)==null?void 0:i.getAPIKey(t));return(o=this.backendRepository)==null||o.uploadVideoToServer(e,t,s),s&&this.addfirebaseListeners(s),s}async checkResults(e){var t,s;console.warn("Warning: checkResults() is deprecated and will be removed in a future version. Use setOnWindowResults() and setOnFinalResults() instead!"),this.initialized||T.throwKnownError(R.NotInitializedError);try{return this.accessToken||(this.accessToken=await((t=this.backendRepository)==null?void 0:t.getToken())),await((s=this.backendRepository)==null?void 0:s.checkResults(e,this.accessToken))}catch(r){T.handleErrorResponse(r)}}async getResults(e){var s,r;return console.warn("Warning: getResults() is deprecated and will be removed in a future version. Use setOnWindowResults() and setOnFinalResults() instead!"),this.initialized||T.throwKnownError(R.NotInitializedError),this.accessToken||(this.accessToken=await((s=this.backendRepository)==null?void 0:s.getToken())),await((r=this.backendRepository)==null?void 0:r.getResults(e,this.accessToken))}setOnWindowResults(e){this.onWindowResults=e}setOnFinalResults(e){this.onFinalResults=e}setOnError(e){this.onError=e}setOnDisconnection(e){var t;(t=this.backendRepository)==null||t.streamingManager.setOnDisconnection(e)}async addfirebaseListeners(e){this.addWindowListener(e),this.addFinalListener(e),this.addErrorListener(e)}addWindowListener(e){const t=Jt(),s=Qt(t,`${e}`),r=g_(s,i=>{i.val()!==null&&i.key&&/^\d+$/.test(i.key)&&this.onWindowResults&&this.onWindowResults(i.val())});this.firebaseHandlers.push(r)}removefirebaseListeners(){this.firebaseHandlers.forEach(e=>{e()}),this.firebaseHandlers=[]}addFinalListener(e){const t=Jt(),s=Qt(t,`/${e}/averaged_results/`),r=ks(s,i=>{i.val()!==null&&(this.onFinalResults&&this.onFinalResults(i.val()),this.removefirebaseListeners())});this.firebaseHandlers.push(r)}addErrorListener(e){const t=Jt(),s=Qt(t,`/${e}/error/`),r=ks(s,i=>{if(i.val()===null)return;const o=T.handleScanError(i.val());this.onError?this.onError(o):T.throwKnownError(R.ErrorCallbackNotSetError),this.removefirebaseListeners()});this.firebaseHandlers.push(r)}initializeStreaming(e,t){var s;(s=this.backendRepository)==null||s.streamingManager.initializeStreaming(e,t)}async startStreaming(){var s,r,i;this.initialized||T.throwKnownError(R.NotInitializedError);const e=await((s=this.backendRepository)==null?void 0:s.getToken()),t=await((r=this.backendRepository)==null?void 0:r.getAPIKey(e));if(t)return(i=this.backendRepository)==null||i.streamingManager.startStreaming(t),this.addfirebaseListeners(t),t;T.throwKnownError(R.InvalidAccessTokenError)}stopStreaming(){var e;(e=this.backendRepository)==null||e.streamingManager.stopStreaming()}};k(Ae,"instance");let xs=Ae;exports.Client=xs;exports.EmptyVideoError=Oi;exports.ErrorCallbackNotSetError=Ki;exports.Errors=q;exports.FPSCalculationError=Wi;exports.FaceLookingLeftError=zi;exports.FaceLookingRightError=ji;exports.FaceNotCenteredError=Hi;exports.FaceTooCloseError=$i;exports.FaceTooFarError=Vi;exports.InvalidAccessTokenError=Fi;exports.InvalidPasswordError=Di;exports.InvalidServerTypeError=Bi;exports.InvalidUsernameError=Pi;exports.LowFPSError=qi;exports.NervoscanError=U;exports.NoScanDataError=Mi;exports.NoScansAvailableError=Li;exports.NotImplementedError=Ui;exports.NotInitializedError=ki;exports.UnhandledScanError=Ps;exports.VideoTypeError=xi;
1599
+ */$e(A_,k_,"app");const Ae=class Ae{constructor(){k(this,"initialized",!1);k(this,"accessToken");k(this,"backendRepository");k(this,"onWindowResults");k(this,"onFinalResults");k(this,"onError");k(this,"firebaseHandlers",[]);console.log("Initializing Client"),zo({apiKey:"AIzaSyAoOpQqLrc6wQLJI5KRE2Vt7QV1cplsZv8",authDomain:"nervowebreact.firebaseapp.com",databaseURL:"https://nervowebreact-default-rtdb.asia-southeast1.firebasedatabase.app",projectId:"nervowebreact",storageBucket:"nervowebreact.firebasestorage.app",messagingSenderId:"1025695568936",appId:"1:1025695568936:web:36f269a1eb74e1fdda511d",measurementId:"G-L3105FG92S"})}static getInstance(){return Ae.instance||(Ae.instance=new Ae),Ae.instance}initialize(e,t,s="server"){this.initialized=!0,s==="server"?this.backendRepository=new I_(e,t):s==="serverless"?this.backendRepository=new N_(e,t):T.throwKnownError(R.InvalidServerTypeError)}async uploadVideo(e){var r,i,o;this.initialized||T.throwKnownError(R.NotInitializedError),e instanceof Blob||T.throwKnownError(R.VideoTypeError),(e===null||e.size===0)&&T.throwKnownError(R.EmptyVideoError);const t=await((r=this.backendRepository)==null?void 0:r.getToken()),s=await((i=this.backendRepository)==null?void 0:i.getAPIKey(t));return(o=this.backendRepository)==null||o.uploadVideoToServer(e,t,s),s&&this.addfirebaseListeners(s),s}async checkResults(e){var t,s;console.warn("Warning: checkResults() is deprecated and will be removed in a future version. Use setOnWindowResults() and setOnFinalResults() instead!"),this.initialized||T.throwKnownError(R.NotInitializedError);try{return this.accessToken||(this.accessToken=await((t=this.backendRepository)==null?void 0:t.getToken())),await((s=this.backendRepository)==null?void 0:s.checkResults(e,this.accessToken))}catch(r){T.handleErrorResponse(r)}}async getResults(e){var s,r;return console.warn("Warning: getResults() is deprecated and will be removed in a future version. Use setOnWindowResults() and setOnFinalResults() instead!"),this.initialized||T.throwKnownError(R.NotInitializedError),this.accessToken||(this.accessToken=await((s=this.backendRepository)==null?void 0:s.getToken())),await((r=this.backendRepository)==null?void 0:r.getResults(e,this.accessToken))}setOnWindowResults(e){this.onWindowResults=e}setOnFinalResults(e){this.onFinalResults=e}setOnError(e){this.onError=e}setOnDisconnection(e){var t;(t=this.backendRepository)==null||t.streamingManager.setOnDisconnection(e)}initializeStreaming(e,t){var s;(s=this.backendRepository)==null||s.streamingManager.initializeStreaming(e,t)}async startStreaming(){var s,r,i;this.initialized||T.throwKnownError(R.NotInitializedError);const e=await((s=this.backendRepository)==null?void 0:s.getToken()),t=await((r=this.backendRepository)==null?void 0:r.getAPIKey(e));if(t)return(i=this.backendRepository)==null||i.streamingManager.startStreaming(t),this.addfirebaseListeners(t),t;T.throwKnownError(R.InvalidAccessTokenError)}stopStreaming(){var e;(e=this.backendRepository)==null||e.streamingManager.stopStreaming()}async addfirebaseListeners(e){this.addWindowListener(e),this.addFinalListener(e),this.addErrorListener(e)}addWindowListener(e){const t=Jt(),s=Qt(t,`${e}`),r=g_(s,i=>{i.val()!==null&&i.key&&/^\d+$/.test(i.key)&&this.onWindowResults&&this.onWindowResults(i.val())});this.firebaseHandlers.push(r)}removefirebaseListeners(){this.firebaseHandlers.forEach(e=>{e()}),this.firebaseHandlers=[]}addFinalListener(e){const t=Jt(),s=Qt(t,`/${e}/averaged_results/`),r=ks(s,i=>{i.val()!==null&&(this.onFinalResults&&this.onFinalResults(i.val()),this.removefirebaseListeners())});this.firebaseHandlers.push(r)}addErrorListener(e){const t=Jt(),s=Qt(t,`/${e}/error/`),r=ks(s,i=>{if(i.val()===null)return;const o=T.handleScanError(i.val());this.onError?this.onError(o):T.throwKnownError(R.ErrorCallbackNotSetError),this.removefirebaseListeners()});this.firebaseHandlers.push(r)}};k(Ae,"instance");let xs=Ae;exports.Client=xs;exports.EmptyVideoError=Oi;exports.ErrorCallbackNotSetError=Ki;exports.Errors=q;exports.FPSCalculationError=Wi;exports.FaceLookingLeftError=zi;exports.FaceLookingRightError=ji;exports.FaceNotCenteredError=Hi;exports.FaceTooCloseError=$i;exports.FaceTooFarError=Vi;exports.InvalidAccessTokenError=Fi;exports.InvalidPasswordError=Di;exports.InvalidServerTypeError=Bi;exports.InvalidUsernameError=Pi;exports.LowFPSError=qi;exports.NervoscanError=U;exports.NoScanDataError=Mi;exports.NoScansAvailableError=Li;exports.NotImplementedError=Ui;exports.NotInitializedError=ki;exports.UnhandledScanError=Ps;exports.VideoTypeError=xi;
@@ -12710,7 +12710,7 @@ const Mn = (n) => n === "server" ? "https://backend.nervoscan.com" : "https://ba
12710
12710
  return Ne.instance || (Ne.instance = new Ne()), Ne.instance;
12711
12711
  }
12712
12712
  setupWebSocketConnection() {
12713
- this.socket || (this.socket = Gt(this.backendUrl, { transports: ["websocket", "polling"] }), this.socket.on("connect", () => {
12713
+ this.socket || (console.log("Setting up WebSocket connection:", this.backendUrl), this.socket = Gt(this.backendUrl, { transports: ["websocket", "polling"] }), this.socket.on("connect", () => {
12714
12714
  this.isConnected = !0;
12715
12715
  }), this.socket.on("connect_error", (e) => this.handleSocketError(e)), this.socket.on("disconnect", (e) => this.handleSocketDisconnection(e)));
12716
12716
  }
@@ -12721,7 +12721,7 @@ const Mn = (n) => n === "server" ? "https://backend.nervoscan.com" : "https://ba
12721
12721
  console.warn("Disconnected from WebSocket server:", e);
12722
12722
  }
12723
12723
  initializeStreaming(e, t) {
12724
- this.setupWebSocketConnection(), this.videoStream = e, this.videoElement = t, this.videoElement.srcObject = e;
12724
+ console.log("Initializing streaming"), this.setupWebSocketConnection(), this.videoStream = e, this.videoElement = t, this.videoElement.srcObject = e;
12725
12725
  }
12726
12726
  startStreaming(e) {
12727
12727
  var t;
@@ -12971,7 +12971,7 @@ const Ae = class Ae {
12971
12971
  k(this, "onFinalResults");
12972
12972
  k(this, "onError");
12973
12973
  k(this, "firebaseHandlers", []);
12974
- Io({
12974
+ console.log("Initializing Client"), Io({
12975
12975
  apiKey: "AIzaSyAoOpQqLrc6wQLJI5KRE2Vt7QV1cplsZv8",
12976
12976
  authDomain: "nervowebreact.firebaseapp.com",
12977
12977
  databaseURL: "https://nervowebreact-default-rtdb.asia-southeast1.firebasedatabase.app",
@@ -13073,39 +13073,14 @@ const Ae = class Ae {
13073
13073
  setOnError(e) {
13074
13074
  this.onError = e;
13075
13075
  }
13076
+ /**
13077
+ * Sets a callback function to handle disconnection.
13078
+ * @param callback - Function to be called when the connection is lost. Takes no parameters.
13079
+ */
13076
13080
  setOnDisconnection(e) {
13077
13081
  var t;
13078
13082
  (t = this.backendRepository) == null || t.streamingManager.setOnDisconnection(e);
13079
13083
  }
13080
- async addfirebaseListeners(e) {
13081
- this.addWindowListener(e), this.addFinalListener(e), this.addErrorListener(e);
13082
- }
13083
- addWindowListener(e) {
13084
- const t = Jt(), s = Qt(t, `${e}`), i = g_(s, (r) => {
13085
- r.val() !== null && r.key && /^\d+$/.test(r.key) && this.onWindowResults && this.onWindowResults(r.val());
13086
- });
13087
- this.firebaseHandlers.push(i);
13088
- }
13089
- removefirebaseListeners() {
13090
- this.firebaseHandlers.forEach((e) => {
13091
- e();
13092
- }), this.firebaseHandlers = [];
13093
- }
13094
- addFinalListener(e) {
13095
- const t = Jt(), s = Qt(t, `/${e}/averaged_results/`), i = ks(s, (r) => {
13096
- r.val() !== null && (this.onFinalResults && this.onFinalResults(r.val()), this.removefirebaseListeners());
13097
- });
13098
- this.firebaseHandlers.push(i);
13099
- }
13100
- addErrorListener(e) {
13101
- const t = Jt(), s = Qt(t, `/${e}/error/`), i = ks(s, (r) => {
13102
- if (r.val() === null)
13103
- return;
13104
- const o = T.handleScanError(r.val());
13105
- this.onError ? this.onError(o) : T.throwKnownError(R.ErrorCallbackNotSetError), this.removefirebaseListeners();
13106
- });
13107
- this.firebaseHandlers.push(i);
13108
- }
13109
13084
  /**
13110
13085
  * Initializes the streaming manager.
13111
13086
  * @param videoStream - The video stream to stream
@@ -13138,6 +13113,35 @@ const Ae = class Ae {
13138
13113
  var e;
13139
13114
  (e = this.backendRepository) == null || e.streamingManager.stopStreaming();
13140
13115
  }
13116
+ async addfirebaseListeners(e) {
13117
+ this.addWindowListener(e), this.addFinalListener(e), this.addErrorListener(e);
13118
+ }
13119
+ addWindowListener(e) {
13120
+ const t = Jt(), s = Qt(t, `${e}`), i = g_(s, (r) => {
13121
+ r.val() !== null && r.key && /^\d+$/.test(r.key) && this.onWindowResults && this.onWindowResults(r.val());
13122
+ });
13123
+ this.firebaseHandlers.push(i);
13124
+ }
13125
+ removefirebaseListeners() {
13126
+ this.firebaseHandlers.forEach((e) => {
13127
+ e();
13128
+ }), this.firebaseHandlers = [];
13129
+ }
13130
+ addFinalListener(e) {
13131
+ const t = Jt(), s = Qt(t, `/${e}/averaged_results/`), i = ks(s, (r) => {
13132
+ r.val() !== null && (this.onFinalResults && this.onFinalResults(r.val()), this.removefirebaseListeners());
13133
+ });
13134
+ this.firebaseHandlers.push(i);
13135
+ }
13136
+ addErrorListener(e) {
13137
+ const t = Jt(), s = Qt(t, `/${e}/error/`), i = ks(s, (r) => {
13138
+ if (r.val() === null)
13139
+ return;
13140
+ const o = T.handleScanError(r.val());
13141
+ this.onError ? this.onError(o) : T.throwKnownError(R.ErrorCallbackNotSetError), this.removefirebaseListeners();
13142
+ });
13143
+ this.firebaseHandlers.push(i);
13144
+ }
13141
13145
  };
13142
13146
  k(Ae, "instance");
13143
13147
  let Nr = Ae;