easyproctor-hml 2.7.7 → 2.7.8

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
@@ -13317,6 +13317,16 @@ var _ChunkStorageService = class _ChunkStorageService {
13317
13317
  constructor() {
13318
13318
  this.db = null;
13319
13319
  }
13320
+ /**
13321
+ * WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
13322
+ * ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
13323
+ * round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
13324
+ */
13325
+ static detachedArrayBufferCopy(src) {
13326
+ const next = new Uint8Array(src.byteLength);
13327
+ next.set(new Uint8Array(src));
13328
+ return next.buffer;
13329
+ }
13320
13330
  /**
13321
13331
  * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
13322
13332
  */
@@ -13356,10 +13366,18 @@ var _ChunkStorageService = class _ChunkStorageService {
13356
13366
  */
13357
13367
  async saveChunk(chunk) {
13358
13368
  const db = await this.connect();
13369
+ const record = {
13370
+ proctoringId: chunk.proctoringId,
13371
+ chunkIndex: chunk.chunkIndex,
13372
+ arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
13373
+ timestamp: chunk.timestamp,
13374
+ uploaded: chunk.uploaded,
13375
+ mimeType: chunk.mimeType
13376
+ };
13359
13377
  return new Promise((resolve, reject) => {
13360
13378
  const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13361
13379
  const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13362
- const request = store.add(chunk);
13380
+ const request = store.add(record);
13363
13381
  request.onsuccess = () => {
13364
13382
  resolve(request.result);
13365
13383
  };
@@ -13430,8 +13448,16 @@ var _ChunkStorageService = class _ChunkStorageService {
13430
13448
  resolve();
13431
13449
  return;
13432
13450
  }
13433
- chunk.uploaded = 1;
13434
- const putRequest = store.put(chunk);
13451
+ const updated = {
13452
+ id: chunk.id,
13453
+ proctoringId: chunk.proctoringId,
13454
+ chunkIndex: chunk.chunkIndex,
13455
+ arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
13456
+ timestamp: chunk.timestamp,
13457
+ uploaded: 1,
13458
+ mimeType: chunk.mimeType
13459
+ };
13460
+ const putRequest = store.put(updated);
13435
13461
  putRequest.onsuccess = () => resolve();
13436
13462
  putRequest.onerror = () => {
13437
13463
  var _a2;
@@ -13551,8 +13577,8 @@ var _ChunkStorageService = class _ChunkStorageService {
13551
13577
  }
13552
13578
  };
13553
13579
  _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
13554
- /** v2: índices numéricos; v3: payload em ArrayBuffer (Safari/iOS com Blob no IDB) */
13555
- _ChunkStorageService.DB_VERSION = 3;
13580
+ /** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
13581
+ _ChunkStorageService.DB_VERSION = 4;
13556
13582
  _ChunkStorageService.STORE_NAME = "chunks";
13557
13583
  var ChunkStorageService = _ChunkStorageService;
13558
13584
 
@@ -13713,6 +13739,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13713
13739
  */
13714
13740
  async processQueue(isFinal = false) {
13715
13741
  var _a2, _b, _c2, _d, _e3, _f;
13742
+ console.log(`[BackgroundUpload] processQueue init`);
13716
13743
  if (this.isProcessing) return;
13717
13744
  this.isProcessing = true;
13718
13745
  try {
@@ -13725,8 +13752,10 @@ var BackgroundUploadService = class _BackgroundUploadService {
13725
13752
  return;
13726
13753
  }
13727
13754
  }
13755
+ console.log(`[BackgroundUpload] processQueue syncOffset ok`);
13728
13756
  const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
13729
13757
  const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
13758
+ console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
13730
13759
  if (pendingChunks.length === 0 && !isFinal) {
13731
13760
  this.isProcessing = false;
13732
13761
  return;
package/index.js CHANGED
@@ -31414,6 +31414,16 @@ var _ChunkStorageService = class _ChunkStorageService {
31414
31414
  constructor() {
31415
31415
  this.db = null;
31416
31416
  }
31417
+ /**
31418
+ * WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
31419
+ * ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
31420
+ * round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
31421
+ */
31422
+ static detachedArrayBufferCopy(src) {
31423
+ const next = new Uint8Array(src.byteLength);
31424
+ next.set(new Uint8Array(src));
31425
+ return next.buffer;
31426
+ }
31417
31427
  /**
31418
31428
  * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
31419
31429
  */
@@ -31453,10 +31463,18 @@ var _ChunkStorageService = class _ChunkStorageService {
31453
31463
  */
31454
31464
  async saveChunk(chunk) {
31455
31465
  const db = await this.connect();
31466
+ const record = {
31467
+ proctoringId: chunk.proctoringId,
31468
+ chunkIndex: chunk.chunkIndex,
31469
+ arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
31470
+ timestamp: chunk.timestamp,
31471
+ uploaded: chunk.uploaded,
31472
+ mimeType: chunk.mimeType
31473
+ };
31456
31474
  return new Promise((resolve, reject) => {
31457
31475
  const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31458
31476
  const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31459
- const request = store.add(chunk);
31477
+ const request = store.add(record);
31460
31478
  request.onsuccess = () => {
31461
31479
  resolve(request.result);
31462
31480
  };
@@ -31527,8 +31545,16 @@ var _ChunkStorageService = class _ChunkStorageService {
31527
31545
  resolve();
31528
31546
  return;
31529
31547
  }
31530
- chunk.uploaded = 1;
31531
- const putRequest = store.put(chunk);
31548
+ const updated = {
31549
+ id: chunk.id,
31550
+ proctoringId: chunk.proctoringId,
31551
+ chunkIndex: chunk.chunkIndex,
31552
+ arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
31553
+ timestamp: chunk.timestamp,
31554
+ uploaded: 1,
31555
+ mimeType: chunk.mimeType
31556
+ };
31557
+ const putRequest = store.put(updated);
31532
31558
  putRequest.onsuccess = () => resolve();
31533
31559
  putRequest.onerror = () => {
31534
31560
  var _a2;
@@ -31648,8 +31674,8 @@ var _ChunkStorageService = class _ChunkStorageService {
31648
31674
  }
31649
31675
  };
31650
31676
  _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
31651
- /** v2: índices numéricos; v3: payload em ArrayBuffer (Safari/iOS com Blob no IDB) */
31652
- _ChunkStorageService.DB_VERSION = 3;
31677
+ /** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
31678
+ _ChunkStorageService.DB_VERSION = 4;
31653
31679
  _ChunkStorageService.STORE_NAME = "chunks";
31654
31680
  var ChunkStorageService = _ChunkStorageService;
31655
31681
 
@@ -31810,6 +31836,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31810
31836
  */
31811
31837
  async processQueue(isFinal = false) {
31812
31838
  var _a2, _b, _c2, _d, _e3, _f;
31839
+ console.log(`[BackgroundUpload] processQueue init`);
31813
31840
  if (this.isProcessing) return;
31814
31841
  this.isProcessing = true;
31815
31842
  try {
@@ -31822,8 +31849,10 @@ var BackgroundUploadService = class _BackgroundUploadService {
31822
31849
  return;
31823
31850
  }
31824
31851
  }
31852
+ console.log(`[BackgroundUpload] processQueue syncOffset ok`);
31825
31853
  const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
31826
31854
  const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
31855
+ console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
31827
31856
  if (pendingChunks.length === 0 && !isFinal) {
31828
31857
  this.isProcessing = false;
31829
31858
  return;
@@ -12,6 +12,7 @@ export declare class ChunkStorageService {
12
12
  private static readonly DB_VERSION;
13
13
  private static readonly STORE_NAME;
14
14
  private db;
15
+ private static detachedArrayBufferCopy;
15
16
  connect(): Promise<IDBDatabase>;
16
17
  saveChunk(chunk: Omit<VideoChunk, "id">): Promise<number>;
17
18
  getPendingChunks(proctoringId: string): Promise<VideoChunk[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easyproctor-hml",
3
- "version": "2.7.7",
3
+ "version": "2.7.8",
4
4
  "description": "Modulo web de gravação do EasyProctor",
5
5
  "main": "./index.js",
6
6
  "module": "./esm/index.js",
@@ -77,7 +77,7 @@ 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 pe.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,g=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 m={zIndex:"2",position:"absolute",border:"1px dashed #fff"};Object.assign(f.style,{...m,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=g-20+"px",Object.assign(u.style,{...m,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,g)=>d.chunkIndex-g.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,g)=>d.chunkIndex-g.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(m=>m.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(m=>{let u=i,b=u+m.arrayBuffer.byteLength-1;return i+=m.arrayBuffer.byteLength,{chunk:m,start:u,end:b}}),s=[],a=null,c=0,l=n[0]?.mimeType??r[r.length-1]?.mimeType??"video/webm";for(let m of o){if(this.currentOffset>m.end)continue;let u=Math.max(0,this.currentOffset-m.start),b=m.chunk.arrayBuffer.slice(u);s.push(b),a=m.chunk.id,c=m.chunk.chunkIndex}if(s.length===0&&!t){this.isProcessing=!1;return}let h=e.concatArrayBuffers(s),d=h.byteLength,g;if(t)g=i;else if(d=Math.floor(h.byteLength/this.GCS_CHUNK_SIZE)*this.GCS_CHUNK_SIZE,d===0){console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk..."),this.isProcessing=!1;return}let f=d<h.byteLength?h.slice(0,d):h;try{await this.uploadData(f,l,c,g);for(let m of o)m.chunk.uploaded===0&&m.end<this.currentOffset&&(await this.chunkStorage.markAsUploaded(m.chunk.id),this.retryCount.delete(m.chunk.id),this.onChunkUploaded?.(m.chunk.id,m.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${m.chunk.chunkIndex} marcado como enviado.`));if(this.config.cleanAfterUpload){let u=o.filter(b=>b.chunk.uploaded===1||b.chunk.uploaded===0&&b.end<this.currentOffset).reduce((b,_)=>b+_.chunk.arrayBuffer.byteLength,0);await this.chunkStorage.clearUploadedChunks(this.proctoringId),u>0&&(this.totalBytesPurged+=u,this.saveSessionState(),console.log(`[BackgroundUpload] ${u} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`))}t&&this.clearSessionState()}catch(m){console.error("[BackgroundUpload] Falha no upload:",m),this.onUploadError?.(a||0,m)}}catch(r){console.error("[BackgroundUpload] Erro ao processar fila:",r)}finally{this.isProcessing=!1}}}static concatArrayBuffers(t){if(t.length===0)return new ArrayBuffer(0);let r=t.reduce((o,s)=>o+s.byteLength,0),n=new Uint8Array(r),i=0;for(let o of t)n.set(new Uint8Array(o),i),i+=o.byteLength;return n.buffer.byteLength===r?n.buffer:n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}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),f=await fetch(g,{method:"POST",headers:{"x-goog-resumable":"start","Content-Type":r}});if(!f.ok)throw new Error(`Falha ao iniciar: ${f.status}`);if(this.sessionUrl=f.headers.get("Location"),!this.sessionUrl)throw new Error("Location header ausente");try{let u=new URL(this.sessionUrl).pathname.split("/"),b=u[1],_=decodeURIComponent(u.slice(2).join("/"));if(u.includes("b")&&u.includes("o")){let k=u.indexOf("b")+1,S=u.indexOf("o")+1;b=u[k],_=decodeURIComponent(u.slice(S).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${b}, Objeto: ${_}`)}catch{console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`)}this.currentOffset=0,this.saveSessionState()}let s=this.currentOffset,a=s+t.byteLength-1,c=i!==void 0?i.toString():"*",l=t.byteLength===0&&i!==void 0?`bytes */${c}`:`bytes ${s}-${a}/${c}`;console.log(`[BackgroundUpload] Enviando ${t.byteLength>0?"dados":"finaliza\xE7\xE3o"}: ${l} (Size: ${t.byteLength})`);let h=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":l},body:t.byteLength>0?t:null});if(console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${h.status}`),h.status!==200&&h.status!==201&&h.status!==308){let g=await h.text();throw console.error(`[BackgroundUpload] Erro GCS: ${g}`),new Error(`Status HTTP inesperado: ${h.status}`)}let d=h.headers.get("Range");if(d){let g=parseInt(d.split("-")[1],10);this.currentOffset=g+1}else this.currentOffset+=t.byteLength;this.saveSessionState(),pe.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,g=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 m={zIndex:"2",position:"absolute",border:"1px dashed #fff"};Object.assign(f.style,{...m,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=g-20+"px",Object.assign(u.style,{...m,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=4}static{this.STORE_NAME="chunks"}static detachedArrayBufferCopy(t){let r=new Uint8Array(t.byteLength);return r.set(new Uint8Array(t)),r.buffer}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(),n={proctoringId:t.proctoringId,chunkIndex:t.chunkIndex,arrayBuffer:e.detachedArrayBufferCopy(t.arrayBuffer),timestamp:t.timestamp,uploaded:t.uploaded,mimeType:t.mimeType};return new Promise((i,o)=>{let c=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME).add(n);c.onsuccess=()=>{i(c.result)},c.onerror=()=>{o(new Error(`Erro ao salvar chunk no IndexedDB: ${c.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,g)=>d.chunkIndex-g.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,g)=>d.chunkIndex-g.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}let l={id:c.id,proctoringId:c.proctoringId,chunkIndex:c.chunkIndex,arrayBuffer:e.detachedArrayBufferCopy(c.arrayBuffer),timestamp:c.timestamp,uploaded:1,mimeType:c.mimeType},h=s.put(l);h.onsuccess=()=>n(),h.onerror=()=>i(new Error(`Erro ao marcar chunk como enviado: ${h.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(console.log("[BackgroundUpload] processQueue init"),!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}console.log("[BackgroundUpload] processQueue syncOffset ok");let r=await this.chunkStorage.getAllChunks(this.proctoringId),n=r.filter(m=>m.uploaded===0);if(console.log("[BackgroundUpload] processQueue getAllChunks ok"),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(m=>{let u=i,b=u+m.arrayBuffer.byteLength-1;return i+=m.arrayBuffer.byteLength,{chunk:m,start:u,end:b}}),s=[],a=null,c=0,l=n[0]?.mimeType??r[r.length-1]?.mimeType??"video/webm";for(let m of o){if(this.currentOffset>m.end)continue;let u=Math.max(0,this.currentOffset-m.start),b=m.chunk.arrayBuffer.slice(u);s.push(b),a=m.chunk.id,c=m.chunk.chunkIndex}if(s.length===0&&!t){this.isProcessing=!1;return}let h=e.concatArrayBuffers(s),d=h.byteLength,g;if(t)g=i;else if(d=Math.floor(h.byteLength/this.GCS_CHUNK_SIZE)*this.GCS_CHUNK_SIZE,d===0){console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk..."),this.isProcessing=!1;return}let f=d<h.byteLength?h.slice(0,d):h;try{await this.uploadData(f,l,c,g);for(let m of o)m.chunk.uploaded===0&&m.end<this.currentOffset&&(await this.chunkStorage.markAsUploaded(m.chunk.id),this.retryCount.delete(m.chunk.id),this.onChunkUploaded?.(m.chunk.id,m.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${m.chunk.chunkIndex} marcado como enviado.`));if(this.config.cleanAfterUpload){let u=o.filter(b=>b.chunk.uploaded===1||b.chunk.uploaded===0&&b.end<this.currentOffset).reduce((b,_)=>b+_.chunk.arrayBuffer.byteLength,0);await this.chunkStorage.clearUploadedChunks(this.proctoringId),u>0&&(this.totalBytesPurged+=u,this.saveSessionState(),console.log(`[BackgroundUpload] ${u} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`))}t&&this.clearSessionState()}catch(m){console.error("[BackgroundUpload] Falha no upload:",m),this.onUploadError?.(a||0,m)}}catch(r){console.error("[BackgroundUpload] Erro ao processar fila:",r)}finally{this.isProcessing=!1}}}static concatArrayBuffers(t){if(t.length===0)return new ArrayBuffer(0);let r=t.reduce((o,s)=>o+s.byteLength,0),n=new Uint8Array(r),i=0;for(let o of t)n.set(new Uint8Array(o),i),i+=o.byteLength;return n.buffer.byteLength===r?n.buffer:n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}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),f=await fetch(g,{method:"POST",headers:{"x-goog-resumable":"start","Content-Type":r}});if(!f.ok)throw new Error(`Falha ao iniciar: ${f.status}`);if(this.sessionUrl=f.headers.get("Location"),!this.sessionUrl)throw new Error("Location header ausente");try{let u=new URL(this.sessionUrl).pathname.split("/"),b=u[1],_=decodeURIComponent(u.slice(2).join("/"));if(u.includes("b")&&u.includes("o")){let k=u.indexOf("b")+1,S=u.indexOf("o")+1;b=u[k],_=decodeURIComponent(u.slice(S).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${b}, Objeto: ${_}`)}catch{console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`)}this.currentOffset=0,this.saveSessionState()}let s=this.currentOffset,a=s+t.byteLength-1,c=i!==void 0?i.toString():"*",l=t.byteLength===0&&i!==void 0?`bytes */${c}`:`bytes ${s}-${a}/${c}`;console.log(`[BackgroundUpload] Enviando ${t.byteLength>0?"dados":"finaliza\xE7\xE3o"}: ${l} (Size: ${t.byteLength})`);let h=await fetch(this.sessionUrl,{method:"PUT",headers:{"Content-Range":l},body:t.byteLength>0?t:null});if(console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${h.status}`),h.status!==200&&h.status!==201&&h.status!==308){let g=await h.text();throw console.error(`[BackgroundUpload] Erro GCS: ${g}`),new Error(`Status HTTP inesperado: ${h.status}`)}let d=h.headers.get("Range");if(d){let g=parseInt(d.split("-")[1],10);this.currentOffset=g+1}else this.currentOffset+=t.byteLength;this.saveSessionState(),pe.registerUploadFile(this.proctoringId,`GCS Stream Upload
81
81
  Size: ${t.byteLength}
82
82
  Range: ${s}-${a}
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&&pe.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&&pe.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&&pe.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 pe.registerAnotherStream(this.proctoringId,`Maybe have another stream active