easyproctor-hml 2.7.2 → 2.7.3

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/esm/index.js CHANGED
@@ -13476,8 +13476,13 @@ var _ChunkStorageService = class _ChunkStorageService {
13476
13476
  async clearAllChunks(proctoringId2) {
13477
13477
  const db = await this.connect();
13478
13478
  return new Promise((resolve, reject) => {
13479
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13480
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13479
+ const transaction = db.transaction(
13480
+ _ChunkStorageService.STORE_NAME,
13481
+ "readwrite"
13482
+ );
13483
+ const store = transaction.objectStore(
13484
+ _ChunkStorageService.STORE_NAME
13485
+ );
13481
13486
  const index = store.index("proctoringId");
13482
13487
  const range = IDBKeyRange.only(proctoringId2);
13483
13488
  const request = index.openCursor(range);
@@ -13486,13 +13491,34 @@ var _ChunkStorageService = class _ChunkStorageService {
13486
13491
  if (cursor) {
13487
13492
  cursor.delete();
13488
13493
  cursor.continue();
13489
- } else {
13490
- resolve();
13491
13494
  }
13492
13495
  };
13493
13496
  request.onerror = () => {
13494
13497
  var _a2;
13495
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13498
+ reject(
13499
+ new Error(
13500
+ `Erro ao limpar chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`
13501
+ )
13502
+ );
13503
+ };
13504
+ transaction.oncomplete = () => {
13505
+ resolve();
13506
+ };
13507
+ transaction.onerror = () => {
13508
+ var _a2;
13509
+ reject(
13510
+ new Error(
13511
+ `Transaction error: ${(_a2 = transaction.error) == null ? void 0 : _a2.message}`
13512
+ )
13513
+ );
13514
+ };
13515
+ transaction.onabort = () => {
13516
+ var _a2;
13517
+ reject(
13518
+ new Error(
13519
+ `Transaction aborted: ${(_a2 = transaction.error) == null ? void 0 : _a2.message}`
13520
+ )
13521
+ );
13496
13522
  };
13497
13523
  });
13498
13524
  }
@@ -14429,6 +14455,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14429
14455
  }
14430
14456
  this.removeLifecycleListeners();
14431
14457
  this.persistSessionState("FINISHED");
14458
+ trackers.registerError(this.proctoringId, `Finalizando flush e parada do background upload`);
14432
14459
  }
14433
14460
  }
14434
14461
  /**
@@ -14611,7 +14638,12 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14611
14638
  const objectName = `${this.proctoringId}/${fileName}`;
14612
14639
  const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
14613
14640
  if (isUploaded) {
14614
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
14641
+ trackers.registerError(this.proctoringId, `Limpa o armazenamento local pois o v\xEDdeo j\xE1 est\xE1 no servidor`);
14642
+ try {
14643
+ this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
14644
+ } catch (e3) {
14645
+ trackers.registerError(this.proctoringId, `Erro ao limpar o armazenamento local: ${e3}`);
14646
+ }
14615
14647
  return;
14616
14648
  }
14617
14649
  }
package/index.js CHANGED
@@ -31573,8 +31573,13 @@ var _ChunkStorageService = class _ChunkStorageService {
31573
31573
  async clearAllChunks(proctoringId2) {
31574
31574
  const db = await this.connect();
31575
31575
  return new Promise((resolve, reject) => {
31576
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31577
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31576
+ const transaction = db.transaction(
31577
+ _ChunkStorageService.STORE_NAME,
31578
+ "readwrite"
31579
+ );
31580
+ const store = transaction.objectStore(
31581
+ _ChunkStorageService.STORE_NAME
31582
+ );
31578
31583
  const index = store.index("proctoringId");
31579
31584
  const range = IDBKeyRange.only(proctoringId2);
31580
31585
  const request = index.openCursor(range);
@@ -31583,13 +31588,34 @@ var _ChunkStorageService = class _ChunkStorageService {
31583
31588
  if (cursor) {
31584
31589
  cursor.delete();
31585
31590
  cursor.continue();
31586
- } else {
31587
- resolve();
31588
31591
  }
31589
31592
  };
31590
31593
  request.onerror = () => {
31591
31594
  var _a2;
31592
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31595
+ reject(
31596
+ new Error(
31597
+ `Erro ao limpar chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`
31598
+ )
31599
+ );
31600
+ };
31601
+ transaction.oncomplete = () => {
31602
+ resolve();
31603
+ };
31604
+ transaction.onerror = () => {
31605
+ var _a2;
31606
+ reject(
31607
+ new Error(
31608
+ `Transaction error: ${(_a2 = transaction.error) == null ? void 0 : _a2.message}`
31609
+ )
31610
+ );
31611
+ };
31612
+ transaction.onabort = () => {
31613
+ var _a2;
31614
+ reject(
31615
+ new Error(
31616
+ `Transaction aborted: ${(_a2 = transaction.error) == null ? void 0 : _a2.message}`
31617
+ )
31618
+ );
31593
31619
  };
31594
31620
  });
31595
31621
  }
@@ -32526,6 +32552,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32526
32552
  }
32527
32553
  this.removeLifecycleListeners();
32528
32554
  this.persistSessionState("FINISHED");
32555
+ trackers.registerError(this.proctoringId, `Finalizando flush e parada do background upload`);
32529
32556
  }
32530
32557
  }
32531
32558
  /**
@@ -32708,7 +32735,12 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32708
32735
  const objectName = `${this.proctoringId}/${fileName}`;
32709
32736
  const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
32710
32737
  if (isUploaded) {
32711
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
32738
+ trackers.registerError(this.proctoringId, `Limpa o armazenamento local pois o v\xEDdeo j\xE1 est\xE1 no servidor`);
32739
+ try {
32740
+ this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
32741
+ } catch (e3) {
32742
+ trackers.registerError(this.proctoringId, `Erro ao limpar o armazenamento local: ${e3}`);
32743
+ }
32712
32744
  return;
32713
32745
  }
32714
32746
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easyproctor-hml",
3
- "version": "2.7.2",
3
+ "version": "2.7.3",
4
4
  "description": "Modulo web de gravação do EasyProctor",
5
5
  "main": "./index.js",
6
6
  "module": "./esm/index.js",
@@ -77,14 +77,14 @@ Minimum version required to store current data is: `+o+`.
77
77
  File size: ${i.size}`),new Error("Failed to upload to AWS")}}async upload(t,r,n){let{file:i,onProgress:o}=t;try{let s=l=>{let h=l.loadedBytes/i.size*100;o&&o(Math.round(h))},a;if(n){if(this.imageBatchNum===this.contImages){let l=[];for(let d=this.imageBatchNum;d<this.imageBatchNum+20;d++)l.push({objectName:`${this.proctoringId}/${this.proctoringId}_${d+1}.jpg`,contentType:"image/jpeg"});(await this.backend.getSignedUrlImage(r,l)).map(d=>{this.imageUrlPackage.push(d)}),this.imageBatchNum+=20}}else a=await this.backend.getSignedUrl(r,i,this.proctoringId);let c=!1;return n?c=await Wt.request({url:this.imageUrlPackage[this.contImages],method:"PUT",headers:{"Content-Type":i.type,"x-ms-blob-type":"BlockBlob"},data:i,onUploadProgress:l=>{s({loadedBytes:l.loaded})}}).then(()=>!0).catch(()=>!1).finally(()=>{this.contImages++}):c=await Wt.request({url:a,method:"PUT",headers:{"Content-Type":i.type,"x-ms-blob-type":"BlockBlob"},data:i,onUploadProgress:l=>{s({loadedBytes:l.loaded})}}).then(()=>!0).catch(()=>!1),{storage:"upload",url:a,uploaded:c}}catch{throw de.registerError(this.proctoringId,`Failed to upload to AWS
78
78
  File name: ${i.name}
79
79
  File type: ${i.type}
80
- File size: ${i.size}`),new Error("Failed to upload to AWS")}}};var Yg="not_shared_first_screen",Zg="not_shared_screen",Ma="multiple_monitors_detected",Qg="proctoring_already_started",Na="proctoring_not_started";var e0="another_stream_active",t0="stream_under_minimum_permitted",r0="browser_not_supported",n0="token_missing",i0="credentials_missing";var o0="spy_scan_api_not_found",Ed="safe_browser_api_not_found";var s0="external_camera_not_started";var za=class extends Oi{constructor(t,r,n="videoPreviewFrameDetection",i="liveViewFrameDetection"){super("ObjectDetector","https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float16/1/efficientdet_lite0.tflite",t,r,n,i)}stopDetection(){super.stopDetection(),this.numPersonsSent>0&&this.handleOk("person_ok","person_detection_on_stream")}displayVideoDetections(t){for(let r of this.children)this.liveView.removeChild(r);this.children.splice(0);for(let r of t.detections){let n=window.innerWidth,i=window.innerHeight,o=this.video.offsetWidth,s=this.video.offsetHeight,a=o/n,c=s/i,l=o-r.boundingBox.width*a-r.boundingBox.originX*a,h=r.boundingBox.originY*c,d=(r.boundingBox.width-10)*a,m=r.boundingBox.height*c,f=document.createElement("p");f.innerText=r.categories[0].categoryName+" - with "+Math.round(parseFloat(r.categories[0].score)*100)+"% confidence.",f.style.right=l-20+"px",f.style.top=h+"px",f.style.width=d+"px";let g={zIndex:"2",position:"absolute",border:"1px dashed #fff"};Object.assign(f.style,{...g,margin:"0",fontSize:"9px",paddingBottom:"5px",paddingTop:"5px",color:"#fff",backgroundColor:"#007f8b"});let u=document.createElement("div");u.setAttribute("class","highlighter"),u.style.right=l-20+"px",u.style.top=h+"px",u.style.width=d+"px",u.style.height=m-20+"px",Object.assign(u.style,{...g,zIndex:"1",background:"rgba(0, 255, 0, 0.25)"}),this.liveView.appendChild(u),this.liveView.appendChild(f),this.children.push(u),this.children.push(f)}}verify(t){let r=t.detections.filter(i=>i.categories.some(o=>o.categoryName==="person")).length,n=t.detections.filter(i=>i.categories.some(o=>o.categoryName==="cell phone")).length;this.paramsConfig.videoBehaviourParameters?.detectPerson&&r!==this.numPersonsSent&&(r===0?(this.handleAlert("no_person_detected","person_detection_on_stream"),this.numPersonsSent=r):r>1?(this.handleAlert("multiple_persons_detected","person_detection_on_stream"),this.numPersonsSent=r):(this.handleOk("person_ok","person_detection_on_stream"),this.numPersonsSent=r)),this.paramsConfig.videoBehaviourParameters?.detectCellPhone&&n!==this.numCellphoneSent&&(n>0?(this.handleAlert("cellphone_detected","mobile_detection_on_stream"),this.numCellphoneSent=n):(this.handleOk("cellphone_ok","mobile_detection_on_stream"),this.numCellphoneSent=n))}};var gn=class{constructor(t){this.volume=null;this.animationFrameId=null;this.stream=t}async start(t={}){return new Promise((r,n)=>{try{this.audioContext=new AudioContext,this.analyser=this.audioContext.createAnalyser(),this.microphone=this.audioContext.createMediaStreamSource(this.stream),this.analyser.smoothingTimeConstant=.8,this.analyser.fftSize=1024,this.microphone.connect(this.analyser);let i=()=>{let o=new Uint8Array(this.analyser.frequencyBinCount);this.analyser.getByteFrequencyData(o);let a=o.reduce((c,l)=>c+l,0)/o.length;this.setVolume(a),t.setVolume&&t.setVolume(a),this.animationFrameId=requestAnimationFrame(i)};this.animationFrameId=requestAnimationFrame(i),r(!0)}catch(i){this.stop(),n(`Error: ${i}`)}})}stop(){this.animationFrameId!==null&&cancelAnimationFrame(this.animationFrameId),this.audioContext?.close(),this.microphone?.disconnect(),this.analyser?.disconnect()}getVolume(){return this.volume}setVolume(t){this.volume=t}};var Hi=class e{constructor(){this.db=null}static{this.DB_NAME="EasyProctorChunksDb"}static{this.DB_VERSION=3}static{this.STORE_NAME="chunks"}async connect(){return this.db?this.db:new Promise((t,r)=>{let n=window.indexedDB.open(e.DB_NAME,e.DB_VERSION);n.onerror=()=>{r(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."))},n.onupgradeneeded=()=>{let i=n.result;i.objectStoreNames.contains(e.STORE_NAME)&&i.deleteObjectStore(e.STORE_NAME);let o=i.createObjectStore(e.STORE_NAME,{keyPath:"id",autoIncrement:!0});o.createIndex("proctoringId","proctoringId",{unique:!1}),o.createIndex("uploaded","uploaded",{unique:!1}),o.createIndex("proctoringId_uploaded",["proctoringId","uploaded"],{unique:!1})},n.onsuccess=()=>{this.db=n.result,t(this.db)}})}async saveChunk(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).add(t);a.onsuccess=()=>{n(a.result)},a.onerror=()=>{i(new Error(`Erro ao salvar chunk no IndexedDB: ${a.error?.message}`))}})}async getPendingChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("proctoringId_uploaded"),c=IDBKeyRange.only([t,0]),l=a.getAll(c);l.onsuccess=()=>{let h=l.result.sort((d,m)=>d.chunkIndex-m.chunkIndex);n(h)},l.onerror=()=>{i(new Error(`Erro ao buscar chunks pendentes: ${l.error?.message}`))}})}async getAllChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("proctoringId"),c=IDBKeyRange.only(t),l=a.getAll(c);l.onsuccess=()=>{let h=l.result.sort((d,m)=>d.chunkIndex-m.chunkIndex);n(h)},l.onerror=()=>{i(new Error(`Erro ao buscar todos os chunks: ${l.error?.message}`))}})}async markAsUploaded(t){let r=await this.connect();return new Promise((n,i)=>{let s=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME),a=s.get(t);a.onsuccess=()=>{let c=a.result;if(!c){n();return}c.uploaded=1;let l=s.put(c);l.onsuccess=()=>n(),l.onerror=()=>i(new Error(`Erro ao marcar chunk como enviado: ${l.error?.message}`))},a.onerror=()=>i(new Error(`Erro ao buscar chunk para marcar: ${a.error?.message}`))})}async clearUploadedChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).index("proctoringId_uploaded"),c=IDBKeyRange.only([t,1]),l=a.openCursor(c);l.onsuccess=()=>{let h=l.result;h?(h.delete(),h.continue()):n()},l.onerror=()=>i(new Error(`Erro ao limpar chunks enviados: ${l.error?.message}`))})}async clearAllChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).index("proctoringId"),c=IDBKeyRange.only(t),l=a.openCursor(c);l.onsuccess=()=>{let h=l.result;h?(h.delete(),h.continue()):n()},l.onerror=()=>i(new Error(`Erro ao limpar todos os chunks: ${l.error?.message}`))})}async hasAnyPendingChunks(){let t=await this.connect();return new Promise((r,n)=>{let s=t.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("uploaded"),a=IDBKeyRange.only(0),c=s.count(a);c.onsuccess=()=>{r(c.result>0)},c.onerror=()=>n(new Error(`Erro ao verificar chunks pendentes: ${c.error?.message}`))})}async getPendingProctoringIds(){let t=await this.connect();return new Promise((r,n)=>{let s=t.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("uploaded"),a=IDBKeyRange.only(0),c=s.getAll(a);c.onsuccess=()=>{let l=c.result,h=[...new Set(l.map(d=>d.proctoringId))];r(h)},c.onerror=()=>n(new Error(`Erro ao buscar proctoringIds pendentes: ${c.error?.message}`))})}close(){this.db&&(this.db.close(),this.db=null)}};var C_={pollInterval:5e3,maxRetries:5,baseRetryDelay:2e3,cleanAfterUpload:!0},Wi=class e{constructor(t,r,n,i,o){this.pollTimer=null;this.isProcessing=!1;this.isRunning=!1;this.retryCount=new Map;this.sessionUrl=null;this.currentOffset=0;this.totalBytesPurged=0;this.STORAGE_KEY_PREFIX="ep_upload_session_";this.GCS_CHUNK_SIZE=256*1024;this.proctoringId=t.trim(),this.token=r,this.backend=n,this.chunkStorage=i,this.config={...C_,...o},this.loadSessionState()}loadSessionState(){try{let t=localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);if(t){let{sessionUrl:r,currentOffset:n,totalBytesPurged:i}=JSON.parse(t);this.sessionUrl=r,this.currentOffset=n,this.totalBytesPurged=i||0}}catch(t){console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:",t)}}saveSessionState(){try{localStorage.setItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,JSON.stringify({sessionUrl:this.sessionUrl,currentOffset:this.currentOffset,totalBytesPurged:this.totalBytesPurged}))}catch(t){console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:",t)}}clearSessionState(){try{localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`),this.sessionUrl=null,this.currentOffset=0,this.totalBytesPurged=0}catch(t){console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:",t)}}start(){this.isRunning||(this.isRunning=!0,console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`),this.processQueue(),this.pollTimer=setInterval(()=>{this.processQueue(!1)},this.config.pollInterval))}stop(){this.isRunning=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`)}async flush(){console.log("[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...");let t=0;for(;this.isProcessing&&t<10;)await this.sleep(1e3),t++;let r=0,n=3;for(;r<n;)try{await this.processQueue(!0),console.log("[BackgroundUpload] Flush completado com sucesso.");return}catch(i){r++,console.error(`[BackgroundUpload] Erro no flush (tentativa ${r}/${n}):`,i),r<n&&await this.sleep(2e3)}throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${n} tentativas.`)}async syncOffset(){if(!this.sessionUrl)return 0;try{console.log("[BackgroundUpload] Sincronizando offset com GCS...");let t=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":"bytes */*"}});if(console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${t.status}`),t.status===308){let r=t.headers.get("Range");if(r){let n=parseInt(r.split("-")[1],10);this.currentOffset=n+1,this.saveSessionState(),console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`)}else this.currentOffset=0}else t.ok||t.status===201?(console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO."),this.currentOffset=-1):console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${t.status}`)}catch(t){console.warn("[BackgroundUpload] Erro ao sincronizar offset:",t)}return this.currentOffset}async processQueue(t=!1){if(!this.isProcessing){this.isProcessing=!0;try{if(this.sessionUrl&&(await this.syncOffset(),this.currentOffset===-1)){console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor."),this.clearSessionState(),this.isProcessing=!1;return}let r=await this.chunkStorage.getAllChunks(this.proctoringId),n=r.filter(u=>u.uploaded===0);if(n.length===0&&!t){this.isProcessing=!1;return}console.log(`[BackgroundUpload] ${n.length} chunks pendentes encontrados. Modo final: ${t}`);let i=this.totalBytesPurged,o=r.map(u=>{let b=u.arrayBuffer.byteLength,v=i,k=v+b-1;return i+=b,{chunk:u,start:v,end:k}}),s=[],a=null,c=0,l=n[0].mimeType;for(let u of o){if(this.currentOffset>u.end)continue;let b=Math.max(0,this.currentOffset-u.start),v=new Uint8Array(u.chunk.arrayBuffer);s.push(v.subarray(b)),a=u.chunk.id,c=u.chunk.chunkIndex}if(s.length===0&&!t){this.isProcessing=!1;return}let h=s.reduce((u,b)=>u+b.length,0),d=new Uint8Array(h);{let u=0;for(let b of s)d.set(b,u),u+=b.length}let m=d.byteLength,f;if(t)f=i;else if(m=Math.floor(d.byteLength/this.GCS_CHUNK_SIZE)*this.GCS_CHUNK_SIZE,m===0){console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk..."),this.isProcessing=!1;return}let g=m===d.byteLength?d:d.subarray(0,m);try{await this.uploadData(g.byteLength>0?g:null,l,c,f);for(let u of o)u.chunk.uploaded===0&&u.end<this.currentOffset&&(await this.chunkStorage.markAsUploaded(u.chunk.id),this.retryCount.delete(u.chunk.id),this.onChunkUploaded?.(u.chunk.id,u.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${u.chunk.chunkIndex} marcado como enviado.`));if(this.config.cleanAfterUpload){let b=o.filter(v=>v.chunk.uploaded===1||v.chunk.uploaded===0&&v.end<this.currentOffset).reduce((v,k)=>v+k.chunk.arrayBuffer.byteLength,0);await this.chunkStorage.clearUploadedChunks(this.proctoringId),b>0&&(this.totalBytesPurged+=b,this.saveSessionState(),console.log(`[BackgroundUpload] ${b} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`))}t&&this.clearSessionState()}catch(u){console.error("[BackgroundUpload] Falha no upload:",u),this.onUploadError?.(a||0,u)}}catch(r){console.error("[BackgroundUpload] Erro ao processar fila:",r)}finally{this.isProcessing=!1}}}async uploadData(t,r,n,i){let o=`EP_${this.proctoringId}_camera_0.webm`;if(this.sessionUrl)console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);else{let g=await this.backend.initiateUpload(this.token,`${this.proctoringId}/${o}`,r),u=await fetch(g,{method:"POST",headers:{"x-goog-resumable":"start","Content-Type":r}});if(!u.ok)throw new Error(`Falha ao iniciar: ${u.status}`);if(this.sessionUrl=u.headers.get("Location"),!this.sessionUrl)throw new Error("Location header ausente");try{let v=new URL(this.sessionUrl).pathname.split("/"),k=v[1],S=decodeURIComponent(v.slice(2).join("/"));if(v.includes("b")&&v.includes("o")){let I=v.indexOf("b")+1,x=v.indexOf("o")+1;k=v[I],S=decodeURIComponent(v.slice(x).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${k}, Objeto: ${S}`)}catch{console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`)}this.currentOffset=0,this.saveSessionState()}let s=t?.byteLength??0,a=this.currentOffset,c=a+s-1,l=i!==void 0?i.toString():"*",h=s===0&&i!==void 0?`bytes */${l}`:`bytes ${a}-${c}/${l}`;console.log(`[BackgroundUpload] Enviando ${s>0?"dados":"finaliza\xE7\xE3o"}: ${h} (Size: ${s})`);let d=null;if(s>0&&t){let g=t.buffer;g instanceof ArrayBuffer?d=t.byteOffset===0&&t.byteLength===g.byteLength?g:g.slice(t.byteOffset,t.byteOffset+t.byteLength):(d=new ArrayBuffer(t.byteLength),new Uint8Array(d).set(t))}let m=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":h},body:d});if(console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${m.status}`),m.status!==200&&m.status!==201&&m.status!==308){let g=await m.text();throw console.error(`[BackgroundUpload] Erro GCS: ${g}`),new Error(`Status HTTP inesperado: ${m.status}`)}let f=m.headers.get("Range");if(f){let g=parseInt(f.split("-")[1],10);this.currentOffset=g+1}else this.currentOffset+=s;this.saveSessionState(),de.registerUploadFile(this.proctoringId,`GCS Stream Upload
80
+ File size: ${i.size}`),new Error("Failed to upload to AWS")}}};var Yg="not_shared_first_screen",Zg="not_shared_screen",Ma="multiple_monitors_detected",Qg="proctoring_already_started",Na="proctoring_not_started";var e0="another_stream_active",t0="stream_under_minimum_permitted",r0="browser_not_supported",n0="token_missing",i0="credentials_missing";var o0="spy_scan_api_not_found",Ed="safe_browser_api_not_found";var s0="external_camera_not_started";var za=class extends Oi{constructor(t,r,n="videoPreviewFrameDetection",i="liveViewFrameDetection"){super("ObjectDetector","https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float16/1/efficientdet_lite0.tflite",t,r,n,i)}stopDetection(){super.stopDetection(),this.numPersonsSent>0&&this.handleOk("person_ok","person_detection_on_stream")}displayVideoDetections(t){for(let r of this.children)this.liveView.removeChild(r);this.children.splice(0);for(let r of t.detections){let n=window.innerWidth,i=window.innerHeight,o=this.video.offsetWidth,s=this.video.offsetHeight,a=o/n,c=s/i,l=o-r.boundingBox.width*a-r.boundingBox.originX*a,h=r.boundingBox.originY*c,d=(r.boundingBox.width-10)*a,m=r.boundingBox.height*c,f=document.createElement("p");f.innerText=r.categories[0].categoryName+" - with "+Math.round(parseFloat(r.categories[0].score)*100)+"% confidence.",f.style.right=l-20+"px",f.style.top=h+"px",f.style.width=d+"px";let g={zIndex:"2",position:"absolute",border:"1px dashed #fff"};Object.assign(f.style,{...g,margin:"0",fontSize:"9px",paddingBottom:"5px",paddingTop:"5px",color:"#fff",backgroundColor:"#007f8b"});let u=document.createElement("div");u.setAttribute("class","highlighter"),u.style.right=l-20+"px",u.style.top=h+"px",u.style.width=d+"px",u.style.height=m-20+"px",Object.assign(u.style,{...g,zIndex:"1",background:"rgba(0, 255, 0, 0.25)"}),this.liveView.appendChild(u),this.liveView.appendChild(f),this.children.push(u),this.children.push(f)}}verify(t){let r=t.detections.filter(i=>i.categories.some(o=>o.categoryName==="person")).length,n=t.detections.filter(i=>i.categories.some(o=>o.categoryName==="cell phone")).length;this.paramsConfig.videoBehaviourParameters?.detectPerson&&r!==this.numPersonsSent&&(r===0?(this.handleAlert("no_person_detected","person_detection_on_stream"),this.numPersonsSent=r):r>1?(this.handleAlert("multiple_persons_detected","person_detection_on_stream"),this.numPersonsSent=r):(this.handleOk("person_ok","person_detection_on_stream"),this.numPersonsSent=r)),this.paramsConfig.videoBehaviourParameters?.detectCellPhone&&n!==this.numCellphoneSent&&(n>0?(this.handleAlert("cellphone_detected","mobile_detection_on_stream"),this.numCellphoneSent=n):(this.handleOk("cellphone_ok","mobile_detection_on_stream"),this.numCellphoneSent=n))}};var gn=class{constructor(t){this.volume=null;this.animationFrameId=null;this.stream=t}async start(t={}){return new Promise((r,n)=>{try{this.audioContext=new AudioContext,this.analyser=this.audioContext.createAnalyser(),this.microphone=this.audioContext.createMediaStreamSource(this.stream),this.analyser.smoothingTimeConstant=.8,this.analyser.fftSize=1024,this.microphone.connect(this.analyser);let i=()=>{let o=new Uint8Array(this.analyser.frequencyBinCount);this.analyser.getByteFrequencyData(o);let a=o.reduce((c,l)=>c+l,0)/o.length;this.setVolume(a),t.setVolume&&t.setVolume(a),this.animationFrameId=requestAnimationFrame(i)};this.animationFrameId=requestAnimationFrame(i),r(!0)}catch(i){this.stop(),n(`Error: ${i}`)}})}stop(){this.animationFrameId!==null&&cancelAnimationFrame(this.animationFrameId),this.audioContext?.close(),this.microphone?.disconnect(),this.analyser?.disconnect()}getVolume(){return this.volume}setVolume(t){this.volume=t}};var Hi=class e{constructor(){this.db=null}static{this.DB_NAME="EasyProctorChunksDb"}static{this.DB_VERSION=3}static{this.STORE_NAME="chunks"}async connect(){return this.db?this.db:new Promise((t,r)=>{let n=window.indexedDB.open(e.DB_NAME,e.DB_VERSION);n.onerror=()=>{r(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."))},n.onupgradeneeded=()=>{let i=n.result;i.objectStoreNames.contains(e.STORE_NAME)&&i.deleteObjectStore(e.STORE_NAME);let o=i.createObjectStore(e.STORE_NAME,{keyPath:"id",autoIncrement:!0});o.createIndex("proctoringId","proctoringId",{unique:!1}),o.createIndex("uploaded","uploaded",{unique:!1}),o.createIndex("proctoringId_uploaded",["proctoringId","uploaded"],{unique:!1})},n.onsuccess=()=>{this.db=n.result,t(this.db)}})}async saveChunk(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).add(t);a.onsuccess=()=>{n(a.result)},a.onerror=()=>{i(new Error(`Erro ao salvar chunk no IndexedDB: ${a.error?.message}`))}})}async getPendingChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("proctoringId_uploaded"),c=IDBKeyRange.only([t,0]),l=a.getAll(c);l.onsuccess=()=>{let h=l.result.sort((d,m)=>d.chunkIndex-m.chunkIndex);n(h)},l.onerror=()=>{i(new Error(`Erro ao buscar chunks pendentes: ${l.error?.message}`))}})}async getAllChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("proctoringId"),c=IDBKeyRange.only(t),l=a.getAll(c);l.onsuccess=()=>{let h=l.result.sort((d,m)=>d.chunkIndex-m.chunkIndex);n(h)},l.onerror=()=>{i(new Error(`Erro ao buscar todos os chunks: ${l.error?.message}`))}})}async markAsUploaded(t){let r=await this.connect();return new Promise((n,i)=>{let s=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME),a=s.get(t);a.onsuccess=()=>{let c=a.result;if(!c){n();return}c.uploaded=1;let l=s.put(c);l.onsuccess=()=>n(),l.onerror=()=>i(new Error(`Erro ao marcar chunk como enviado: ${l.error?.message}`))},a.onerror=()=>i(new Error(`Erro ao buscar chunk para marcar: ${a.error?.message}`))})}async clearUploadedChunks(t){let r=await this.connect();return new Promise((n,i)=>{let a=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).index("proctoringId_uploaded"),c=IDBKeyRange.only([t,1]),l=a.openCursor(c);l.onsuccess=()=>{let h=l.result;h?(h.delete(),h.continue()):n()},l.onerror=()=>i(new Error(`Erro ao limpar chunks enviados: ${l.error?.message}`))})}async clearAllChunks(t){let r=await this.connect();return new Promise((n,i)=>{let o=r.transaction(e.STORE_NAME,"readwrite"),a=o.objectStore(e.STORE_NAME).index("proctoringId"),c=IDBKeyRange.only(t),l=a.openCursor(c);l.onsuccess=()=>{let h=l.result;h&&(h.delete(),h.continue())},l.onerror=()=>{i(new Error(`Erro ao limpar chunks: ${l.error?.message}`))},o.oncomplete=()=>{n()},o.onerror=()=>{i(new Error(`Transaction error: ${o.error?.message}`))},o.onabort=()=>{i(new Error(`Transaction aborted: ${o.error?.message}`))}})}async hasAnyPendingChunks(){let t=await this.connect();return new Promise((r,n)=>{let s=t.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("uploaded"),a=IDBKeyRange.only(0),c=s.count(a);c.onsuccess=()=>{r(c.result>0)},c.onerror=()=>n(new Error(`Erro ao verificar chunks pendentes: ${c.error?.message}`))})}async getPendingProctoringIds(){let t=await this.connect();return new Promise((r,n)=>{let s=t.transaction(e.STORE_NAME,"readonly").objectStore(e.STORE_NAME).index("uploaded"),a=IDBKeyRange.only(0),c=s.getAll(a);c.onsuccess=()=>{let l=c.result,h=[...new Set(l.map(d=>d.proctoringId))];r(h)},c.onerror=()=>n(new Error(`Erro ao buscar proctoringIds pendentes: ${c.error?.message}`))})}close(){this.db&&(this.db.close(),this.db=null)}};var C_={pollInterval:5e3,maxRetries:5,baseRetryDelay:2e3,cleanAfterUpload:!0},Wi=class e{constructor(t,r,n,i,o){this.pollTimer=null;this.isProcessing=!1;this.isRunning=!1;this.retryCount=new Map;this.sessionUrl=null;this.currentOffset=0;this.totalBytesPurged=0;this.STORAGE_KEY_PREFIX="ep_upload_session_";this.GCS_CHUNK_SIZE=256*1024;this.proctoringId=t.trim(),this.token=r,this.backend=n,this.chunkStorage=i,this.config={...C_,...o},this.loadSessionState()}loadSessionState(){try{let t=localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);if(t){let{sessionUrl:r,currentOffset:n,totalBytesPurged:i}=JSON.parse(t);this.sessionUrl=r,this.currentOffset=n,this.totalBytesPurged=i||0}}catch(t){console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:",t)}}saveSessionState(){try{localStorage.setItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,JSON.stringify({sessionUrl:this.sessionUrl,currentOffset:this.currentOffset,totalBytesPurged:this.totalBytesPurged}))}catch(t){console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:",t)}}clearSessionState(){try{localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`),this.sessionUrl=null,this.currentOffset=0,this.totalBytesPurged=0}catch(t){console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:",t)}}start(){this.isRunning||(this.isRunning=!0,console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`),this.processQueue(),this.pollTimer=setInterval(()=>{this.processQueue(!1)},this.config.pollInterval))}stop(){this.isRunning=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`)}async flush(){console.log("[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...");let t=0;for(;this.isProcessing&&t<10;)await this.sleep(1e3),t++;let r=0,n=3;for(;r<n;)try{await this.processQueue(!0),console.log("[BackgroundUpload] Flush completado com sucesso.");return}catch(i){r++,console.error(`[BackgroundUpload] Erro no flush (tentativa ${r}/${n}):`,i),r<n&&await this.sleep(2e3)}throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${n} tentativas.`)}async syncOffset(){if(!this.sessionUrl)return 0;try{console.log("[BackgroundUpload] Sincronizando offset com GCS...");let t=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":"bytes */*"}});if(console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${t.status}`),t.status===308){let r=t.headers.get("Range");if(r){let n=parseInt(r.split("-")[1],10);this.currentOffset=n+1,this.saveSessionState(),console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`)}else this.currentOffset=0}else t.ok||t.status===201?(console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO."),this.currentOffset=-1):console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${t.status}`)}catch(t){console.warn("[BackgroundUpload] Erro ao sincronizar offset:",t)}return this.currentOffset}async processQueue(t=!1){if(!this.isProcessing){this.isProcessing=!0;try{if(this.sessionUrl&&(await this.syncOffset(),this.currentOffset===-1)){console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor."),this.clearSessionState(),this.isProcessing=!1;return}let r=await this.chunkStorage.getAllChunks(this.proctoringId),n=r.filter(u=>u.uploaded===0);if(n.length===0&&!t){this.isProcessing=!1;return}console.log(`[BackgroundUpload] ${n.length} chunks pendentes encontrados. Modo final: ${t}`);let i=this.totalBytesPurged,o=r.map(u=>{let b=u.arrayBuffer.byteLength,v=i,k=v+b-1;return i+=b,{chunk:u,start:v,end:k}}),s=[],a=null,c=0,l=n[0].mimeType;for(let u of o){if(this.currentOffset>u.end)continue;let b=Math.max(0,this.currentOffset-u.start),v=new Uint8Array(u.chunk.arrayBuffer);s.push(v.subarray(b)),a=u.chunk.id,c=u.chunk.chunkIndex}if(s.length===0&&!t){this.isProcessing=!1;return}let h=s.reduce((u,b)=>u+b.length,0),d=new Uint8Array(h);{let u=0;for(let b of s)d.set(b,u),u+=b.length}let m=d.byteLength,f;if(t)f=i;else if(m=Math.floor(d.byteLength/this.GCS_CHUNK_SIZE)*this.GCS_CHUNK_SIZE,m===0){console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk..."),this.isProcessing=!1;return}let g=m===d.byteLength?d:d.subarray(0,m);try{await this.uploadData(g.byteLength>0?g:null,l,c,f);for(let u of o)u.chunk.uploaded===0&&u.end<this.currentOffset&&(await this.chunkStorage.markAsUploaded(u.chunk.id),this.retryCount.delete(u.chunk.id),this.onChunkUploaded?.(u.chunk.id,u.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${u.chunk.chunkIndex} marcado como enviado.`));if(this.config.cleanAfterUpload){let b=o.filter(v=>v.chunk.uploaded===1||v.chunk.uploaded===0&&v.end<this.currentOffset).reduce((v,k)=>v+k.chunk.arrayBuffer.byteLength,0);await this.chunkStorage.clearUploadedChunks(this.proctoringId),b>0&&(this.totalBytesPurged+=b,this.saveSessionState(),console.log(`[BackgroundUpload] ${b} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`))}t&&this.clearSessionState()}catch(u){console.error("[BackgroundUpload] Falha no upload:",u),this.onUploadError?.(a||0,u)}}catch(r){console.error("[BackgroundUpload] Erro ao processar fila:",r)}finally{this.isProcessing=!1}}}async uploadData(t,r,n,i){let o=`EP_${this.proctoringId}_camera_0.webm`;if(this.sessionUrl)console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);else{let g=await this.backend.initiateUpload(this.token,`${this.proctoringId}/${o}`,r),u=await fetch(g,{method:"POST",headers:{"x-goog-resumable":"start","Content-Type":r}});if(!u.ok)throw new Error(`Falha ao iniciar: ${u.status}`);if(this.sessionUrl=u.headers.get("Location"),!this.sessionUrl)throw new Error("Location header ausente");try{let v=new URL(this.sessionUrl).pathname.split("/"),k=v[1],S=decodeURIComponent(v.slice(2).join("/"));if(v.includes("b")&&v.includes("o")){let I=v.indexOf("b")+1,x=v.indexOf("o")+1;k=v[I],S=decodeURIComponent(v.slice(x).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${k}, Objeto: ${S}`)}catch{console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`)}this.currentOffset=0,this.saveSessionState()}let s=t?.byteLength??0,a=this.currentOffset,c=a+s-1,l=i!==void 0?i.toString():"*",h=s===0&&i!==void 0?`bytes */${l}`:`bytes ${a}-${c}/${l}`;console.log(`[BackgroundUpload] Enviando ${s>0?"dados":"finaliza\xE7\xE3o"}: ${h} (Size: ${s})`);let d=null;if(s>0&&t){let g=t.buffer;g instanceof ArrayBuffer?d=t.byteOffset===0&&t.byteLength===g.byteLength?g:g.slice(t.byteOffset,t.byteOffset+t.byteLength):(d=new ArrayBuffer(t.byteLength),new Uint8Array(d).set(t))}let m=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":h},body:d});if(console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${m.status}`),m.status!==200&&m.status!==201&&m.status!==308){let g=await m.text();throw console.error(`[BackgroundUpload] Erro GCS: ${g}`),new Error(`Status HTTP inesperado: ${m.status}`)}let f=m.headers.get("Range");if(f){let g=parseInt(f.split("-")[1],10);this.currentOffset=g+1}else this.currentOffset+=s;this.saveSessionState(),de.registerUploadFile(this.proctoringId,`GCS Stream Upload
81
81
  Size: ${s}
82
82
  Range: ${a}-${c}
83
83
  Last Index: ${n}`,"CameraChunk")}static async recoverPendingUploads(t,r){let n=new Hi,i=[];try{let o=await n.getPendingProctoringIds();if(o.length===0)return console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o."),i;console.log(`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${o.length} sess\xE3o(\xF5es) com chunks pendentes.`);for(let s of o)try{await new e(s,r,t,n,{cleanAfterUpload:!0}).flush(),i.push(s),console.log(`[BackgroundUpload] Chunks da sess\xE3o ${s} recuperados com sucesso.`)}catch(a){console.error(`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${s}:`,a)}}catch(o){console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:",o)}return i}sleep(t){return new Promise(r=>setTimeout(r,t))}};var h0=fl(c0()),l0=xd(),A_=l0.default||l0,Zr=class e{constructor(t,r,n,i,o){this.blobs=[];this.paramsConfig={audioBehaviourParameters:{recordingBitrate:128,noiseLimit:40},imageBehaviourParameters:{useUploadImage:!0,uploadInterval:20,saveVideo:!0},videoBehaviourParameters:{detectPerson:!1,detectFace:!1,detectCellPhone:!1}};this.options={cameraId:void 0,microphoneId:void 0,onBufferSizeError:!1,onBufferSizeErrorCallback:t=>{},proctoringType:"IMAGE",onChangeDevicesCallback:t=>{},onRealtimeAlertsCallback:t=>{}};this.videoOptions={width:640,height:480,minWidth:0,minHeight:0};this.blobsRTC=[];this.imageCount=0;this.filesToUpload=[];this.pendingPackages=[];this.animationFrameId=null;this.isCanvasLoopActive=!1;this.hardwareStream=null;this.internalClonedStream=null;this.videoElement=null;this.duration=0;this.stopped=!1;this.backgroundUpload=null;this.chunkIndex=0;this.pendingChunkSaves=[];this.boundVisibilityHandler=null;this.boundPageHideHandler=null;this.currentRetries=0;this.packageCount=0;this.failedUploads=0;this.noiseWait=20;this.options=t,this.videoOptions=r,this.backend=i,this.backendToken=o,n&&(this.paramsConfig=n)}static{this.CHUNK_TIMESLICE_MS=6e4}static{this.LS_SESSION_KEY="ep_proctoring_session"}get isChunkEnabled(){return!!this.proctoringId&&this.options.proctoringType==="REALTIME"&&!Ua()}setProctoringId(t){this.proctoringId=t,this.proctoringId&&this.backend&&(this.upload=new Yr(this.proctoringId,this.backend)),La(t),this.isChunkEnabled?(this.chunkStorage=new Hi,this.backend&&this.backendToken&&(this.backgroundUpload=new Wi(this.proctoringId,this.backendToken,this.backend,this.chunkStorage,{pollInterval:5e3,maxRetries:5,cleanAfterUpload:!0})),this.persistSessionState("IN_PROGRESS"),console.log(`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${_r()})`)):console.log(`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`)}persistSessionState(t){try{let r={proctoringId:this.proctoringId,status:t,timestamp:Date.now()};localStorage.setItem(e.LS_SESSION_KEY,JSON.stringify(r))}catch(r){console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:",r)}}clearSessionState(){try{localStorage.removeItem(e.LS_SESSION_KEY)}catch(t){console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:",t)}}static checkForActiveSession(){try{let t=localStorage.getItem(e.LS_SESSION_KEY);if(!t)return null;let r=JSON.parse(t);return r.status==="IN_PROGRESS"?r:null}catch{return null}}setupLifecycleListeners(){this.boundVisibilityHandler=()=>this.handleVisibilityChange(),this.boundPageHideHandler=()=>this.handlePageHide(),document.addEventListener("visibilitychange",this.boundVisibilityHandler),window.addEventListener("pagehide",this.boundPageHideHandler)}removeLifecycleListeners(){this.boundVisibilityHandler&&(document.removeEventListener("visibilitychange",this.boundVisibilityHandler),this.boundVisibilityHandler=null),this.boundPageHideHandler&&(window.removeEventListener("pagehide",this.boundPageHideHandler),this.boundPageHideHandler=null)}handleVisibilityChange(){document.visibilityState==="hidden"?(console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida."),this.persistSessionState("INTERRUPTED"),this.proctoringId&&de.registerError(this.proctoringId,"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o.")):document.visibilityState==="visible"&&(console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o."),this.persistSessionState("IN_PROGRESS"),this.proctoringId&&de.registerError(this.proctoringId,"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."),this.onVisibilityRestored?.())}handlePageHide(){console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado."),this.persistSessionState("INTERRUPTED"),this.proctoringId&&de.registerError(this.proctoringId,"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada.")}async initializeDetectors(){lg(),(this.paramsConfig.videoBehaviourParameters?.detectPerson||this.paramsConfig.videoBehaviourParameters?.detectCellPhone)&&(this.objectDetection=new za({onRealtimeAlertsCallback:t=>{t.begin=Date.now()-(this.getStartTime()?.getTime()||0),t.end=Date.now()-(this.getStartTime()?.getTime()||0),this.options.onRealtimeAlertsCallback(t)}},this.paramsConfig),await this.objectDetection.initializeDetector()),this.paramsConfig.videoBehaviourParameters?.detectFace&&(this.faceDetection=new Ui({onRealtimeAlertsCallback:t=>{t.begin=Date.now()-(this.getStartTime()?.getTime()||0),t.end=Date.now()-(this.getStartTime()?.getTime()||0),this.options.onRealtimeAlertsCallback(t)}},this.paramsConfig),await this.faceDetection.initializeDetector())}configImageCapture(){this.video=document.createElement("video"),this.canvas=document.createElement("canvas"),this.video.srcObject=this.cameraStream,this.video.play(),this.video.muted=!0,screen.orientation?.type.includes("portrait")&&_r()?(console.log("configurando canvas em portrait"),this.canvas.width=this.videoOptions.height/2,this.canvas.height=this.videoOptions.width/2):(this.canvas.width=this.videoOptions.width/2,this.canvas.height=this.videoOptions.height/2)}async bufferError(t){console.log("buffer error Camera Recorder params ");let r=this.paramsConfig.videoBehaviourParameters?.retryEnabled||!1,n=this.paramsConfig.videoBehaviourParameters?.maxRetries||3;r&&this.currentRetries<n?(await this.recordingStop(),await this.startRecording(),this.currentRetries++,this.options.onBufferSizeErrorCallback&&this.options.onBufferSizeErrorCallback(this.cameraStream)):this.options.onBufferSizeErrorCallback&&this.options.onBufferSizeErrorCallback()}async startStream(){let{cameraId:t,microphoneId:r,onBufferSizeErrorCallback:n}=this.options,i={audio:{deviceId:r},video:{deviceId:t,width:this.videoOptions.width,height:this.videoOptions.height,frameRate:15}};try{this.hardwareStream=await navigator.mediaDevices.getUserMedia(i)}catch(h){throw h.toString()=="NotReadableError: Could not start video source"?"N\xE3o foi poss\xEDvel conectar a camera, ela pode estar sendo utilizada por outro programa":h}this.cameraStream=this.hardwareStream;let s=this.cameraStream.getVideoTracks()[0].getSettings(),{width:a=0,height:c=0}=s;if(screen.orientation?.type.includes("portrait")&&_r()&&this.videoOptions.width==c&&this.videoOptions.height==a&&([a,c]=[c,a]),this.videoOptions.minWidth>a||this.videoOptions.minHeight>c)throw t0;if(this.videoOptions.width!==a||this.videoOptions.height!==c)throw de.registerAnotherStream(this.proctoringId,`Maybe have another stream active
84
84
  Video Options: ${JSON.stringify(this.videoOptions,null,2)}
85
85
  Setting: ${JSON.stringify(s,null,2)}`),e0}async stopStream(){this.cameraStream&&this.cameraStream.getTracks().forEach(t=>t.stop()),this.internalClonedStream&&(this.internalClonedStream.getTracks().forEach(t=>t.stop()),this.internalClonedStream=null),this.hardwareStream&&(this.hardwareStream.getTracks().forEach(t=>t.stop()),this.hardwareStream=null)}async waitForVideoFlow(){return new Promise(t=>{let r=()=>{if(this.videoElement&&this.videoElement?.readyState>=3)return t();requestAnimationFrame(r)};r()})}async attachAndWarmup(t){this.videoElement=document.createElement("video"),this.videoElement.srcObject=t,this.videoElement.muted=!0,await this.videoElement.play().catch(r=>{}),await new Promise(r=>{if(this.videoElement&&this.videoElement?.readyState>=1)return r();this.videoElement&&(this.videoElement.onloadedmetadata=()=>r())}),console.log("CameraRecorder checking metadata ok"),await this.waitForVideoFlow(),console.log("CameraRecorder waiting for video flow ok"),await new Promise(r=>setTimeout(r,300))}async startRecording(){await this.startStream(),await this.attachAndWarmup(this.cameraStream);let t=this.isChunkEnabled?{timeslice:e.CHUNK_TIMESLICE_MS,onChunkAvailable:(h,d)=>{this.handleNewChunk(h,d)}}:{},{startRecording:r,stopRecording:n,pauseRecording:i,resumeRecording:o,recorderOptions:s,getBufferSize:a,getStartTime:c,getDuration:l}=Wo(this.cameraStream,this.blobs,this.options.onBufferSizeError,h=>this.bufferError(h),!1,t);this.recordingStart=r,this.recordingStop=n,this.recordingPause=i,this.recordingResume=o,this.recorderOptions=s,this.getBufferSize=a,this.getStartTime=c,this.getDuration=l,this.chunkIndex=0,this.isChunkEnabled&&(this.backgroundUpload?.start(),this.setupLifecycleListeners());try{await new Promise(h=>setTimeout(h,500)),await this.recordingStart()}catch(h){console.log("Camera Recorder error",h),this.stopRecording();let d=this.paramsConfig.videoBehaviourParameters?.maxRetries||3;if(this.currentRetries<d)console.log("Camera Recorder retry",this.currentRetries),this.currentRetries++,await this.startRecording();else throw h}this.stopped=!1,(this.paramsConfig.videoBehaviourParameters?.detectPerson||this.paramsConfig.videoBehaviourParameters?.detectCellPhone||this.paramsConfig.videoBehaviourParameters?.detectFace)&&await this.initializeDetectors(),this.paramsConfig.videoBehaviourParameters?.detectFace&&await this.faceDetection.enableCam(this.cameraStream),(this.paramsConfig.videoBehaviourParameters?.detectPerson||this.paramsConfig.videoBehaviourParameters?.detectCellPhone)&&await this.objectDetection.enableCam(this.cameraStream),this.filesToUpload=[],this.pendingPackages=[],this.options.proctoringType=="REALTIME"&&await this.startRealtimeCapture(),this.packageCount=0,console.log("Camera Recorder started OK")}async stopRecording(){console.log("Camera Recorder stopRecording"),this.stopped=!0,this.isCanvasLoopActive=!1,this.faceDetection&&this.faceDetection.detecting&&this.faceDetection.stopDetection(),this.objectDetection&&this.objectDetection.detecting&&this.objectDetection.stopDetection(),clearInterval(this.imageInterval),clearInterval(this.sendFrameInterval),this.volumeMeter&&this.volumeMeter.stop(),this.intervalNoiseDetection&&clearInterval(this.intervalNoiseDetection),this.recordingStop&&await this.recordingStop(),this.duration=this.getDuration?this.getDuration():0,await new Promise(t=>setTimeout(t,200));try{this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.cameraStream&&this.cameraStream.getTracks().forEach(t=>t.stop()),this.internalClonedStream&&(this.internalClonedStream.getTracks().forEach(t=>t.stop()),this.internalClonedStream=null),this.hardwareStream&&(this.hardwareStream.getTracks().forEach(t=>t.stop()),this.hardwareStream=null),this.videoElement&&(this.videoElement?.remove(),this.videoElement=null),this.video&&this.video?.remove(),this.canvas&&this.canvas?.remove()}catch{console.error("Erro ao parar os streams de m\xEDdia.")}if(this.options.proctoringType=="REALTIME"&&this.upload&&this.backendToken&&(this.pendingPackages.push(this.filesToUpload.slice(0,this.filesToUpload.length)),await this.sendPackage(),await this.filesToUpload.splice(0,this.filesToUpload.length)),this.isChunkEnabled){if(this.backgroundUpload){de.registerError(this.proctoringId,"Flush e parada do background upload");try{this.pendingChunkSaves.length>0&&(console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`),await Promise.all(this.pendingChunkSaves)),await this.backgroundUpload.flush(),this.backgroundUpload.stop()}catch(t){console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:",t),de.registerError(this.proctoringId,`Flush Chunks
86
- Error: ${t}`)}}this.removeLifecycleListeners(),this.persistSessionState("FINISHED")}}async handleNewChunk(t,r){if(!this.proctoringId||!this.chunkStorage)return;let n=(async()=>{try{let i=await t.arrayBuffer();await this.chunkStorage.saveChunk({proctoringId:this.proctoringId,chunkIndex:this.chunkIndex,arrayBuffer:i,timestamp:Date.now(),uploaded:0,mimeType:this.recorderOptions?.mimeType||"video/webm"}),this.chunkIndex++,console.log(`[CameraRecorder] Chunk ${this.chunkIndex-1} salvo no IndexedDB.`)}catch(i){de.registerError(this.proctoringId,`Save Chunk
87
- Error: ${i}`),console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:",i)}})();this.pendingChunkSaves.push(n),n.finally(()=>{this.pendingChunkSaves=this.pendingChunkSaves.filter(i=>i!==n)})}async pauseRecording(){await this.recordingPause()}async resumeRecording(){await this.recordingResume()}async getCurrentImageBase64(){return!this.video||!this.canvas?this.configImageCapture():this.video.srcObject!==this.cameraStream&&(this.video.srcObject=this.cameraStream,await this.video.play()),this.video.paused&&await this.video.play(),await new Promise(t=>{if(this.video.readyState>=2){t();return}let r=()=>{this.video.removeEventListener("loadedmetadata",r),setTimeout(()=>t(),50)};this.video.addEventListener("loadedmetadata",r),setTimeout(()=>{this.video.removeEventListener("loadedmetadata",r),t()},500)}),this.canvas.getContext("2d").drawImage(this.video,0,0,this.canvas.width,this.canvas.height),this.canvas.toDataURL("image/jpeg")}async captureFrame(){let t,r=this.paramsConfig.videoBehaviourParameters?.realtimePackageSize;this.canvas.getContext("2d").drawImage(this.video,0,0,this.canvas.width,this.canvas.height);let n=this.canvas.toDataURL("image/jpeg");if(this.proctoringId==null)return;r==this.imageCount&&(this.imageCount=0,this.pendingPackages.push(this.filesToUpload.slice(0,r)),this.sendPackage(),await this.filesToUpload.splice(0,r));let i=`${this.proctoringId}_${this.imageCount+1}.jpg`;t=await this.getFile(n,i,"image/jpeg"),t&&t.size>10&&r>0&&(this.filesToUpload.push(t),this.imageCount++)}async startRealtimeCapture(){this.configImageCapture(),this.imageCount=0,this.pendingPackages=[],await this.captureFrame(),this.imageInterval=setInterval(async()=>{await this.captureFrame()},this.paramsConfig.videoBehaviourParameters?.realtimeCaptureInterval*1e3)}async sendPackage(){let t=this.paramsConfig.videoBehaviourParameters?.realtimePackageSize,r=this.paramsConfig.videoBehaviourParameters?.realtimeCaptureInterval;if(this.upload&&this.backendToken&&this.pendingPackages.length>0){let n=[],i=0;for(let o of this.pendingPackages){let s=new h0.default;for(let h of o)s.file(h.name,h);let a=await s.generateAsync({type:"blob"}),c="realtime_package_"+t*r*this.packageCount+".zip",l=new File([a],c,{type:"application/zip"});try{await this.upload.uploadPackage({file:l},this.backendToken)==!0&&(this.packageCount++,this.failedUploads=0,n.push(i++))}catch{this.failedUploads++,this.failedUploads>=2&&this.options.onRealtimeAlertsCallback({status:"ALERT",description:"Realtime n\xE3o est\xE1 enviando pacotes",type:"error_upload_package",category:"error_upload_package",begin:0,end:0});break}}for(let o of n)await this.pendingPackages.splice(o,1)}}download(t){let r=URL.createObjectURL(t),n=document.createElement("a");document.body.appendChild(n),n.style.display="none",n.href=r,n.download=t.name,n.click(),window.URL.revokeObjectURL(r)}async saveOnSession(t){this.blobs!=null&&de.registerSaveOnSession(this.proctoringId,`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`);let r=this.cameraStream.getVideoTracks()[0].getSettings(),n=this.cameraStream.getAudioTracks()[0].getSettings();if(this.options.proctoringType=="VIDEO"||this.options.proctoringType=="REALTIME"||this.options.proctoringType=="IMAGE"&&this.paramsConfig.imageBehaviourParameters?.saveVideo){let i;if(this.isChunkEnabled&&!await this.checkInternetStability()){if(this.backend&&this.backendToken&&this.proctoringId){let c=`EP_${this.proctoringId}_camera_0.webm`,l=`${this.proctoringId}/${c}`;if(await this.backend.checkUpload(this.backendToken,l,"video/webm")){this.chunkStorage&&await this.chunkStorage.clearAllChunks(t.id);return}}}let o=new Blob(this.blobs,{type:this.recorderOptions?.mimeType||"video/webm"}),s=await A_(o,this.duration);i=new File([s],`EP_${t.id}_camera_0.webm`,{type:o.type}),t.addRecording({device:`Audio
86
+ Error: ${t}`)}}this.removeLifecycleListeners(),this.persistSessionState("FINISHED"),de.registerError(this.proctoringId,"Finalizando flush e parada do background upload")}}async handleNewChunk(t,r){if(!this.proctoringId||!this.chunkStorage)return;let n=(async()=>{try{let i=await t.arrayBuffer();await this.chunkStorage.saveChunk({proctoringId:this.proctoringId,chunkIndex:this.chunkIndex,arrayBuffer:i,timestamp:Date.now(),uploaded:0,mimeType:this.recorderOptions?.mimeType||"video/webm"}),this.chunkIndex++,console.log(`[CameraRecorder] Chunk ${this.chunkIndex-1} salvo no IndexedDB.`)}catch(i){de.registerError(this.proctoringId,`Save Chunk
87
+ Error: ${i}`),console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:",i)}})();this.pendingChunkSaves.push(n),n.finally(()=>{this.pendingChunkSaves=this.pendingChunkSaves.filter(i=>i!==n)})}async pauseRecording(){await this.recordingPause()}async resumeRecording(){await this.recordingResume()}async getCurrentImageBase64(){return!this.video||!this.canvas?this.configImageCapture():this.video.srcObject!==this.cameraStream&&(this.video.srcObject=this.cameraStream,await this.video.play()),this.video.paused&&await this.video.play(),await new Promise(t=>{if(this.video.readyState>=2){t();return}let r=()=>{this.video.removeEventListener("loadedmetadata",r),setTimeout(()=>t(),50)};this.video.addEventListener("loadedmetadata",r),setTimeout(()=>{this.video.removeEventListener("loadedmetadata",r),t()},500)}),this.canvas.getContext("2d").drawImage(this.video,0,0,this.canvas.width,this.canvas.height),this.canvas.toDataURL("image/jpeg")}async captureFrame(){let t,r=this.paramsConfig.videoBehaviourParameters?.realtimePackageSize;this.canvas.getContext("2d").drawImage(this.video,0,0,this.canvas.width,this.canvas.height);let n=this.canvas.toDataURL("image/jpeg");if(this.proctoringId==null)return;r==this.imageCount&&(this.imageCount=0,this.pendingPackages.push(this.filesToUpload.slice(0,r)),this.sendPackage(),await this.filesToUpload.splice(0,r));let i=`${this.proctoringId}_${this.imageCount+1}.jpg`;t=await this.getFile(n,i,"image/jpeg"),t&&t.size>10&&r>0&&(this.filesToUpload.push(t),this.imageCount++)}async startRealtimeCapture(){this.configImageCapture(),this.imageCount=0,this.pendingPackages=[],await this.captureFrame(),this.imageInterval=setInterval(async()=>{await this.captureFrame()},this.paramsConfig.videoBehaviourParameters?.realtimeCaptureInterval*1e3)}async sendPackage(){let t=this.paramsConfig.videoBehaviourParameters?.realtimePackageSize,r=this.paramsConfig.videoBehaviourParameters?.realtimeCaptureInterval;if(this.upload&&this.backendToken&&this.pendingPackages.length>0){let n=[],i=0;for(let o of this.pendingPackages){let s=new h0.default;for(let h of o)s.file(h.name,h);let a=await s.generateAsync({type:"blob"}),c="realtime_package_"+t*r*this.packageCount+".zip",l=new File([a],c,{type:"application/zip"});try{await this.upload.uploadPackage({file:l},this.backendToken)==!0&&(this.packageCount++,this.failedUploads=0,n.push(i++))}catch{this.failedUploads++,this.failedUploads>=2&&this.options.onRealtimeAlertsCallback({status:"ALERT",description:"Realtime n\xE3o est\xE1 enviando pacotes",type:"error_upload_package",category:"error_upload_package",begin:0,end:0});break}}for(let o of n)await this.pendingPackages.splice(o,1)}}download(t){let r=URL.createObjectURL(t),n=document.createElement("a");document.body.appendChild(n),n.style.display="none",n.href=r,n.download=t.name,n.click(),window.URL.revokeObjectURL(r)}async saveOnSession(t){this.blobs!=null&&de.registerSaveOnSession(this.proctoringId,`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`);let r=this.cameraStream.getVideoTracks()[0].getSettings(),n=this.cameraStream.getAudioTracks()[0].getSettings();if(this.options.proctoringType=="VIDEO"||this.options.proctoringType=="REALTIME"||this.options.proctoringType=="IMAGE"&&this.paramsConfig.imageBehaviourParameters?.saveVideo){let i;if(this.isChunkEnabled&&!await this.checkInternetStability()){if(this.backend&&this.backendToken&&this.proctoringId){let c=`EP_${this.proctoringId}_camera_0.webm`,l=`${this.proctoringId}/${c}`;if(await this.backend.checkUpload(this.backendToken,l,"video/webm")){de.registerError(this.proctoringId,"Limpa o armazenamento local pois o v\xEDdeo j\xE1 est\xE1 no servidor");try{this.chunkStorage&&await this.chunkStorage.clearAllChunks(t.id)}catch(d){de.registerError(this.proctoringId,`Erro ao limpar o armazenamento local: ${d}`)}return}}}let o=new Blob(this.blobs,{type:this.recorderOptions?.mimeType||"video/webm"}),s=await A_(o,this.duration);i=new File([s],`EP_${t.id}_camera_0.webm`,{type:o.type}),t.addRecording({device:`Audio
88
88
  Sample Rate: ${n.sampleRate}
89
89
  Sample Size: ${n.sampleSize}
90
90