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 +34 -5
- package/index.js +34 -5
- package/new-flow/chunk/ChunkStorageService.d.ts +1 -0
- package/package.json +1 -1
- package/unpkg/easyproctor.min.js +1 -1
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(
|
|
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
|
-
|
|
13434
|
-
|
|
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
|
|
13555
|
-
_ChunkStorageService.DB_VERSION =
|
|
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(
|
|
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
|
-
|
|
31531
|
-
|
|
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
|
|
31652
|
-
_ChunkStorageService.DB_VERSION =
|
|
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
package/unpkg/easyproctor.min.js
CHANGED
|
@@ -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
|