easyproctor-hml 2.7.9 → 2.7.10

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
@@ -13729,7 +13729,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13729
13729
  * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
13730
13730
  */
13731
13731
  async processQueue(isFinal = false) {
13732
- var _a2, _b, _c2, _d, _e3, _f;
13732
+ var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
13733
13733
  console.log(`[BackgroundUpload] processQueue init`);
13734
13734
  if (this.isProcessing) return;
13735
13735
  this.isProcessing = true;
@@ -13751,6 +13751,26 @@ var BackgroundUploadService = class _BackgroundUploadService {
13751
13751
  this.isProcessing = false;
13752
13752
  return;
13753
13753
  }
13754
+ if (pendingChunks.length === 0 && isFinal) {
13755
+ const mimeTypeFallback = (_b = (_a2 = allChunks[allChunks.length - 1]) == null ? void 0 : _a2.mimeType) != null ? _b : "video/webm";
13756
+ try {
13757
+ if (!this.sessionUrl) {
13758
+ this.clearSessionState();
13759
+ return;
13760
+ }
13761
+ const totalBytes = this.currentOffset;
13762
+ await this.uploadData(new ArrayBuffer(0), mimeTypeFallback, 0, totalBytes);
13763
+ await this.chunkStorage.clearUploadedChunks(this.proctoringId);
13764
+ this.clearSessionState();
13765
+ } catch (error) {
13766
+ console.error(
13767
+ "[BackgroundUpload] Falha ao finalizar upload (flush sem pendentes):",
13768
+ error
13769
+ );
13770
+ (_c2 = this.onUploadError) == null ? void 0 : _c2.call(this, 0, error);
13771
+ }
13772
+ return;
13773
+ }
13754
13774
  console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
13755
13775
  let virtualStart = this.totalBytesPurged;
13756
13776
  const chunksWithMeta = allChunks.map((c3) => {
@@ -13762,7 +13782,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13762
13782
  const combinedBufferParts = [];
13763
13783
  let lastProcessedChunkId = null;
13764
13784
  let finalChunkIndex = 0;
13765
- const mimeType = (_d = (_c2 = (_a2 = pendingChunks[0]) == null ? void 0 : _a2.mimeType) != null ? _c2 : (_b = allChunks[allChunks.length - 1]) == null ? void 0 : _b.mimeType) != null ? _d : "video/webm";
13785
+ const mimeType = (_g = (_f = (_d = pendingChunks[0]) == null ? void 0 : _d.mimeType) != null ? _f : (_e3 = allChunks[allChunks.length - 1]) == null ? void 0 : _e3.mimeType) != null ? _g : "video/webm";
13766
13786
  console.log(`[BackgroundUpload] passo 1 ok`);
13767
13787
  for (const meta of chunksWithMeta) {
13768
13788
  if (this.currentOffset > meta.end) continue;
@@ -13789,7 +13809,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13789
13809
  return;
13790
13810
  }
13791
13811
  } else {
13792
- totalSizeForHeader = virtualStart;
13812
+ totalSizeForHeader = fullBuffer.byteLength > 0 ? virtualStart : Math.max(virtualStart, this.currentOffset);
13793
13813
  }
13794
13814
  console.log(`[BackgroundUpload] passo 4 ok`);
13795
13815
  const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
@@ -13808,7 +13828,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13808
13828
  await this.chunkStorage.deleteChunkIds(idsToDelete);
13809
13829
  for (const meta of fullySent) {
13810
13830
  this.retryCount.delete(meta.chunk.id);
13811
- (_e3 = this.onChunkUploaded) == null ? void 0 : _e3.call(this, meta.chunk.id, meta.chunk.chunkIndex);
13831
+ (_h = this.onChunkUploaded) == null ? void 0 : _h.call(this, meta.chunk.id, meta.chunk.chunkIndex);
13812
13832
  console.log(
13813
13833
  `[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`
13814
13834
  );
@@ -13831,7 +13851,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
13831
13851
  console.log(`[BackgroundUpload] passo 9 ok`);
13832
13852
  } catch (error) {
13833
13853
  console.error("[BackgroundUpload] Falha no upload:", error);
13834
- (_f = this.onUploadError) == null ? void 0 : _f.call(this, lastProcessedChunkId || 0, error);
13854
+ (_i3 = this.onUploadError) == null ? void 0 : _i3.call(this, lastProcessedChunkId || 0, error);
13835
13855
  }
13836
13856
  } catch (error) {
13837
13857
  console.error("[BackgroundUpload] Erro ao processar fila:", error);
package/index.js CHANGED
@@ -31826,7 +31826,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31826
31826
  * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
31827
31827
  */
31828
31828
  async processQueue(isFinal = false) {
31829
- var _a2, _b, _c2, _d, _e3, _f;
31829
+ var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
31830
31830
  console.log(`[BackgroundUpload] processQueue init`);
31831
31831
  if (this.isProcessing) return;
31832
31832
  this.isProcessing = true;
@@ -31848,6 +31848,26 @@ var BackgroundUploadService = class _BackgroundUploadService {
31848
31848
  this.isProcessing = false;
31849
31849
  return;
31850
31850
  }
31851
+ if (pendingChunks.length === 0 && isFinal) {
31852
+ const mimeTypeFallback = (_b = (_a2 = allChunks[allChunks.length - 1]) == null ? void 0 : _a2.mimeType) != null ? _b : "video/webm";
31853
+ try {
31854
+ if (!this.sessionUrl) {
31855
+ this.clearSessionState();
31856
+ return;
31857
+ }
31858
+ const totalBytes = this.currentOffset;
31859
+ await this.uploadData(new ArrayBuffer(0), mimeTypeFallback, 0, totalBytes);
31860
+ await this.chunkStorage.clearUploadedChunks(this.proctoringId);
31861
+ this.clearSessionState();
31862
+ } catch (error) {
31863
+ console.error(
31864
+ "[BackgroundUpload] Falha ao finalizar upload (flush sem pendentes):",
31865
+ error
31866
+ );
31867
+ (_c2 = this.onUploadError) == null ? void 0 : _c2.call(this, 0, error);
31868
+ }
31869
+ return;
31870
+ }
31851
31871
  console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
31852
31872
  let virtualStart = this.totalBytesPurged;
31853
31873
  const chunksWithMeta = allChunks.map((c3) => {
@@ -31859,7 +31879,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31859
31879
  const combinedBufferParts = [];
31860
31880
  let lastProcessedChunkId = null;
31861
31881
  let finalChunkIndex = 0;
31862
- const mimeType = (_d = (_c2 = (_a2 = pendingChunks[0]) == null ? void 0 : _a2.mimeType) != null ? _c2 : (_b = allChunks[allChunks.length - 1]) == null ? void 0 : _b.mimeType) != null ? _d : "video/webm";
31882
+ const mimeType = (_g = (_f = (_d = pendingChunks[0]) == null ? void 0 : _d.mimeType) != null ? _f : (_e3 = allChunks[allChunks.length - 1]) == null ? void 0 : _e3.mimeType) != null ? _g : "video/webm";
31863
31883
  console.log(`[BackgroundUpload] passo 1 ok`);
31864
31884
  for (const meta of chunksWithMeta) {
31865
31885
  if (this.currentOffset > meta.end) continue;
@@ -31886,7 +31906,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31886
31906
  return;
31887
31907
  }
31888
31908
  } else {
31889
- totalSizeForHeader = virtualStart;
31909
+ totalSizeForHeader = fullBuffer.byteLength > 0 ? virtualStart : Math.max(virtualStart, this.currentOffset);
31890
31910
  }
31891
31911
  console.log(`[BackgroundUpload] passo 4 ok`);
31892
31912
  const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
@@ -31905,7 +31925,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31905
31925
  await this.chunkStorage.deleteChunkIds(idsToDelete);
31906
31926
  for (const meta of fullySent) {
31907
31927
  this.retryCount.delete(meta.chunk.id);
31908
- (_e3 = this.onChunkUploaded) == null ? void 0 : _e3.call(this, meta.chunk.id, meta.chunk.chunkIndex);
31928
+ (_h = this.onChunkUploaded) == null ? void 0 : _h.call(this, meta.chunk.id, meta.chunk.chunkIndex);
31909
31929
  console.log(
31910
31930
  `[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`
31911
31931
  );
@@ -31928,7 +31948,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
31928
31948
  console.log(`[BackgroundUpload] passo 9 ok`);
31929
31949
  } catch (error) {
31930
31950
  console.error("[BackgroundUpload] Falha no upload:", error);
31931
- (_f = this.onUploadError) == null ? void 0 : _f.call(this, lastProcessedChunkId || 0, error);
31951
+ (_i3 = this.onUploadError) == null ? void 0 : _i3.call(this, lastProcessedChunkId || 0, error);
31932
31952
  }
31933
31953
  } catch (error) {
31934
31954
  console.error("[BackgroundUpload] Erro ao processar fila:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easyproctor-hml",
3
- "version": "2.7.9",
3
+ "version": "2.7.10",
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=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 deleteChunkIds(t){if(t.length===0)return;let r=await this.connect();return new Promise((n,i)=>{let o=r.transaction(e.STORE_NAME,"readwrite"),s=o.objectStore(e.STORE_NAME);o.oncomplete=()=>n(),o.onerror=()=>i(new Error(`Erro ao remover chunks em lote: ${o.error?.message??"unknown"}`)),o.onabort=()=>i(new Error(`Remo\xE7\xE3o em lote abortada: ${o.error?.message??"unknown"}`));for(let a of t)s.delete(a)})}async clearUploadedChunks(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.index("proctoringId_uploaded"),c=IDBKeyRange.only([t,1]),l=a.openKeyCursor(c);l.onsuccess=()=>{let h=l.result;h?(s.delete(h.primaryKey),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 s=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME),a=s.index("proctoringId"),c=IDBKeyRange.only(t),l=a.openKeyCursor(c);l.onsuccess=()=>{let h=l.result;h?(s.delete(h.primaryKey),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";console.log("[BackgroundUpload] passo 1 ok");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(console.log("[BackgroundUpload] passo 2 ok"),s.length===0&&!t){this.isProcessing=!1;return}let h=e.concatArrayBuffers(s);console.log("[BackgroundUpload] passo 3 ok");let 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}console.log("[BackgroundUpload] passo 4 ok");let f=d<h.byteLength?h.slice(0,d):h;try{await this.uploadData(f,l,c,g),console.log("[BackgroundUpload] passo 5 ok");let m=o.filter(v=>v.chunk.uploaded===0&&v.end<this.currentOffset&&v.chunk.id!=null),u=m.reduce((v,k)=>v+k.chunk.arrayBuffer.byteLength,0),b=m.map(v=>v.chunk.id);if(b.length>0){await this.chunkStorage.deleteChunkIds(b);for(let v of m)this.retryCount.delete(v.chunk.id),this.onChunkUploaded?.(v.chunk.id,v.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${v.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`)}console.log("[BackgroundUpload] passo 6 ok"),await this.chunkStorage.clearUploadedChunks(this.proctoringId),console.log("[BackgroundUpload] passo 7 ok"),this.config.cleanAfterUpload&&u>0&&(this.totalBytesPurged+=u,this.saveSessionState(),console.log(`[BackgroundUpload] ${u} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`)),console.log("[BackgroundUpload] passo 8 ok"),t&&this.clearSessionState(),console.log("[BackgroundUpload] passo 9 ok")}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],v=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],v=decodeURIComponent(u.slice(S).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${b}, Objeto: ${v}`)}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 deleteChunkIds(t){if(t.length===0)return;let r=await this.connect();return new Promise((n,i)=>{let o=r.transaction(e.STORE_NAME,"readwrite"),s=o.objectStore(e.STORE_NAME);o.oncomplete=()=>n(),o.onerror=()=>i(new Error(`Erro ao remover chunks em lote: ${o.error?.message??"unknown"}`)),o.onabort=()=>i(new Error(`Remo\xE7\xE3o em lote abortada: ${o.error?.message??"unknown"}`));for(let a of t)s.delete(a)})}async clearUploadedChunks(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.index("proctoringId_uploaded"),c=IDBKeyRange.only([t,1]),l=a.openKeyCursor(c);l.onsuccess=()=>{let h=l.result;h?(s.delete(h.primaryKey),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 s=r.transaction(e.STORE_NAME,"readwrite").objectStore(e.STORE_NAME),a=s.index("proctoringId"),c=IDBKeyRange.only(t),l=a.openKeyCursor(c);l.onsuccess=()=>{let h=l.result;h?(s.delete(h.primaryKey),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}if(n.length===0&&t){let m=r[r.length-1]?.mimeType??"video/webm";try{if(!this.sessionUrl){this.clearSessionState();return}let u=this.currentOffset;await this.uploadData(new ArrayBuffer(0),m,0,u),await this.chunkStorage.clearUploadedChunks(this.proctoringId),this.clearSessionState()}catch(u){console.error("[BackgroundUpload] Falha ao finalizar upload (flush sem pendentes):",u),this.onUploadError?.(0,u)}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";console.log("[BackgroundUpload] passo 1 ok");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(console.log("[BackgroundUpload] passo 2 ok"),s.length===0&&!t){this.isProcessing=!1;return}let h=e.concatArrayBuffers(s);console.log("[BackgroundUpload] passo 3 ok");let d=h.byteLength,g;if(t)g=h.byteLength>0?i:Math.max(i,this.currentOffset);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}console.log("[BackgroundUpload] passo 4 ok");let f=d<h.byteLength?h.slice(0,d):h;try{await this.uploadData(f,l,c,g),console.log("[BackgroundUpload] passo 5 ok");let m=o.filter(v=>v.chunk.uploaded===0&&v.end<this.currentOffset&&v.chunk.id!=null),u=m.reduce((v,k)=>v+k.chunk.arrayBuffer.byteLength,0),b=m.map(v=>v.chunk.id);if(b.length>0){await this.chunkStorage.deleteChunkIds(b);for(let v of m)this.retryCount.delete(v.chunk.id),this.onChunkUploaded?.(v.chunk.id,v.chunk.chunkIndex),console.log(`[BackgroundUpload] Chunk ${v.chunk.chunkIndex} removido do IndexedDB ap\xF3s upload.`)}console.log("[BackgroundUpload] passo 6 ok"),await this.chunkStorage.clearUploadedChunks(this.proctoringId),console.log("[BackgroundUpload] passo 7 ok"),this.config.cleanAfterUpload&&u>0&&(this.totalBytesPurged+=u,this.saveSessionState(),console.log(`[BackgroundUpload] ${u} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`)),console.log("[BackgroundUpload] passo 8 ok"),t&&this.clearSessionState(),console.log("[BackgroundUpload] passo 9 ok")}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],v=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],v=decodeURIComponent(u.slice(S).join("/"))}console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${b}, Objeto: ${v}`)}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