easyproctor-hml 2.7.11 → 2.7.13

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/index.js CHANGED
@@ -30855,16 +30855,13 @@ function isMobileDevice() {
30855
30855
  }
30856
30856
  return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
30857
30857
  }
30858
- function isSafeBrowser() {
30859
- return versionVerify() !== "1.0.0.0";
30860
- }
30861
30858
 
30862
30859
  // src/plugins/recorder.ts
30863
30860
  var proctoringId;
30864
30861
  function setRecorderProctoringId(id) {
30865
30862
  proctoringId = id;
30866
30863
  }
30867
- function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false, recorderOpts) {
30864
+ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
30868
30865
  let resolvePromise;
30869
30866
  let onBufferSizeInterval;
30870
30867
  let lastEvent;
@@ -30872,7 +30869,6 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
30872
30869
  bufferSize = 0;
30873
30870
  let startTime;
30874
30871
  let duration = 0;
30875
- let chunkIndex = 0;
30876
30872
  let recorderOptions = {
30877
30873
  // eslint-disable-next-line no-useless-escape
30878
30874
  mimeType: "video/webm",
@@ -30907,10 +30903,6 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
30907
30903
  mediaRecorder2.ondataavailable = (e3) => {
30908
30904
  bufferSize = bufferSize + e3.data.size;
30909
30905
  if (e3.data.size > 0) {
30910
- if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
30911
- recorderOpts.onChunkAvailable(e3.data, chunkIndex);
30912
- chunkIndex++;
30913
- }
30914
30906
  buffer.push(e3.data);
30915
30907
  }
30916
30908
  };
@@ -30942,13 +30934,7 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
30942
30934
  };
30943
30935
  try {
30944
30936
  console.log("State antes do start:", recorder2.state);
30945
- chunkIndex = 0;
30946
- if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
30947
- recorder2.start(recorderOpts.timeslice);
30948
- } else {
30949
- recorder2.start(1e4);
30950
- }
30951
- bufferSize = 0;
30937
+ recorder2.start(1e4);
30952
30938
  startTime = new Date(Date.now());
30953
30939
  } catch (e3) {
30954
30940
  console.error("Recorder erro ao chamar start event:", e3);
@@ -31002,6 +30988,9 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
31002
30988
  console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
31003
30989
  resolve();
31004
30990
  }
30991
+ stream4.getTracks().forEach((el) => {
30992
+ el.stop();
30993
+ });
31005
30994
  });
31006
30995
  }
31007
30996
  function pauseRecording() {
@@ -31409,617 +31398,11 @@ var VolumeMeter = class {
31409
31398
  }
31410
31399
  };
31411
31400
 
31412
- // src/new-flow/chunk/ChunkStorageService.ts
31413
- var _ChunkStorageService = class _ChunkStorageService {
31414
- constructor() {
31415
- this.db = null;
31416
- }
31417
- /**
31418
- * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
31419
- */
31420
- async connect() {
31421
- if (this.db) return this.db;
31422
- return new Promise((resolve, reject) => {
31423
- const request = window.indexedDB.open(
31424
- _ChunkStorageService.DB_NAME,
31425
- _ChunkStorageService.DB_VERSION
31426
- );
31427
- request.onerror = () => {
31428
- reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
31429
- };
31430
- request.onupgradeneeded = () => {
31431
- const db = request.result;
31432
- if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
31433
- db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
31434
- }
31435
- const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
31436
- keyPath: "id",
31437
- autoIncrement: true
31438
- });
31439
- store.createIndex("proctoringId", "proctoringId", { unique: false });
31440
- store.createIndex("uploaded", "uploaded", { unique: false });
31441
- store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
31442
- unique: false
31443
- });
31444
- };
31445
- request.onsuccess = () => {
31446
- this.db = request.result;
31447
- resolve(this.db);
31448
- };
31449
- });
31450
- }
31451
- /**
31452
- * Salva um chunk de vídeo no IndexedDB.
31453
- */
31454
- async saveChunk(chunk) {
31455
- const db = await this.connect();
31456
- return new Promise((resolve, reject) => {
31457
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31458
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31459
- const request = store.add(chunk);
31460
- request.onsuccess = () => {
31461
- resolve(request.result);
31462
- };
31463
- request.onerror = () => {
31464
- var _a2;
31465
- reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31466
- };
31467
- });
31468
- }
31469
- /**
31470
- * Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
31471
- */
31472
- async getPendingChunks(proctoringId2) {
31473
- const db = await this.connect();
31474
- return new Promise((resolve, reject) => {
31475
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31476
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31477
- const index = store.index("proctoringId_uploaded");
31478
- const range = IDBKeyRange.only([proctoringId2, 0]);
31479
- const request = index.getAll(range);
31480
- request.onsuccess = () => {
31481
- const chunks = request.result.sort(
31482
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
31483
- );
31484
- resolve(chunks);
31485
- };
31486
- request.onerror = () => {
31487
- var _a2;
31488
- reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31489
- };
31490
- });
31491
- }
31492
- /**
31493
- * Retorna todos os chunks (enviados ou não) de um proctoringId específico.
31494
- */
31495
- async getAllChunks(proctoringId2) {
31496
- const db = await this.connect();
31497
- return new Promise((resolve, reject) => {
31498
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31499
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31500
- const index = store.index("proctoringId");
31501
- const range = IDBKeyRange.only(proctoringId2);
31502
- const request = index.getAll(range);
31503
- request.onsuccess = () => {
31504
- const chunks = request.result.sort(
31505
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
31506
- );
31507
- resolve(chunks);
31508
- };
31509
- request.onerror = () => {
31510
- var _a2;
31511
- reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31512
- };
31513
- });
31514
- }
31515
- /**
31516
- * Marca um chunk como enviado (uploaded = 1).
31517
- */
31518
- async markAsUploaded(chunkId) {
31519
- const db = await this.connect();
31520
- return new Promise((resolve, reject) => {
31521
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31522
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31523
- const getRequest = store.get(chunkId);
31524
- getRequest.onsuccess = () => {
31525
- const chunk = getRequest.result;
31526
- if (!chunk) {
31527
- resolve();
31528
- return;
31529
- }
31530
- chunk.uploaded = 1;
31531
- const putRequest = store.put(chunk);
31532
- putRequest.onsuccess = () => resolve();
31533
- putRequest.onerror = () => {
31534
- var _a2;
31535
- return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
31536
- };
31537
- };
31538
- getRequest.onerror = () => {
31539
- var _a2;
31540
- return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
31541
- };
31542
- });
31543
- }
31544
- /**
31545
- * Remove todos os chunks já enviados de um proctoringId para liberar espaço.
31546
- */
31547
- async clearUploadedChunks(proctoringId2) {
31548
- const db = await this.connect();
31549
- return new Promise((resolve, reject) => {
31550
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31551
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31552
- const index = store.index("proctoringId_uploaded");
31553
- const range = IDBKeyRange.only([proctoringId2, 1]);
31554
- const request = index.openCursor(range);
31555
- request.onsuccess = () => {
31556
- const cursor = request.result;
31557
- if (cursor) {
31558
- cursor.delete();
31559
- cursor.continue();
31560
- } else {
31561
- resolve();
31562
- }
31563
- };
31564
- request.onerror = () => {
31565
- var _a2;
31566
- return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31567
- };
31568
- });
31569
- }
31570
- /**
31571
- * Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
31572
- */
31573
- async clearAllChunks(proctoringId2) {
31574
- const db = await this.connect();
31575
- return new Promise((resolve, reject) => {
31576
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
31577
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31578
- const index = store.index("proctoringId");
31579
- const range = IDBKeyRange.only(proctoringId2);
31580
- const request = index.openCursor(range);
31581
- request.onsuccess = () => {
31582
- const cursor = request.result;
31583
- if (cursor) {
31584
- cursor.delete();
31585
- cursor.continue();
31586
- } else {
31587
- resolve();
31588
- }
31589
- };
31590
- request.onerror = () => {
31591
- var _a2;
31592
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31593
- };
31594
- });
31595
- }
31596
- /**
31597
- * Verifica se existem chunks pendentes para qualquer proctoringId.
31598
- * Útil na recuperação pós-crash.
31599
- */
31600
- async hasAnyPendingChunks() {
31601
- const db = await this.connect();
31602
- return new Promise((resolve, reject) => {
31603
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31604
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31605
- const index = store.index("uploaded");
31606
- const range = IDBKeyRange.only(0);
31607
- const request = index.count(range);
31608
- request.onsuccess = () => {
31609
- resolve(request.result > 0);
31610
- };
31611
- request.onerror = () => {
31612
- var _a2;
31613
- return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31614
- };
31615
- });
31616
- }
31617
- /**
31618
- * Retorna todos os proctoringIds que possuem chunks pendentes.
31619
- * Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
31620
- */
31621
- async getPendingProctoringIds() {
31622
- const db = await this.connect();
31623
- return new Promise((resolve, reject) => {
31624
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
31625
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
31626
- const index = store.index("uploaded");
31627
- const range = IDBKeyRange.only(0);
31628
- const request = index.getAll(range);
31629
- request.onsuccess = () => {
31630
- const chunks = request.result;
31631
- const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
31632
- resolve(ids);
31633
- };
31634
- request.onerror = () => {
31635
- var _a2;
31636
- return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
31637
- };
31638
- });
31639
- }
31640
- /**
31641
- * Fecha a conexão com o banco.
31642
- */
31643
- close() {
31644
- if (this.db) {
31645
- this.db.close();
31646
- this.db = null;
31647
- }
31648
- }
31649
- };
31650
- _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
31651
- /** Incrementado para v2 para recriar índices com tipos numéricos em vez de boolean */
31652
- _ChunkStorageService.DB_VERSION = 2;
31653
- _ChunkStorageService.STORE_NAME = "chunks";
31654
- var ChunkStorageService = _ChunkStorageService;
31655
-
31656
- // src/new-flow/chunk/BackgroundUploadService.ts
31657
- var DEFAULT_CONFIG = {
31658
- pollInterval: 5e3,
31659
- maxRetries: 5,
31660
- baseRetryDelay: 2e3,
31661
- cleanAfterUpload: true
31662
- };
31663
- var BackgroundUploadService = class _BackgroundUploadService {
31664
- constructor(proctoringId2, token, backend, chunkStorage, config) {
31665
- this.pollTimer = null;
31666
- this.isProcessing = false;
31667
- this.isRunning = false;
31668
- /** Mapa de chunkId -> número de tentativas já feitas */
31669
- this.retryCount = /* @__PURE__ */ new Map();
31670
- /** GCS Resumable Upload State */
31671
- this.sessionUrl = null;
31672
- this.currentOffset = 0;
31673
- this.totalBytesPurged = 0;
31674
- this.STORAGE_KEY_PREFIX = "ep_upload_session_";
31675
- this.GCS_CHUNK_SIZE = 256 * 1024;
31676
- this.proctoringId = proctoringId2.trim();
31677
- this.token = token;
31678
- this.backend = backend;
31679
- this.chunkStorage = chunkStorage;
31680
- this.config = { ...DEFAULT_CONFIG, ...config };
31681
- this.loadSessionState();
31682
- }
31683
- loadSessionState() {
31684
- try {
31685
- const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
31686
- if (stored) {
31687
- const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
31688
- this.sessionUrl = sessionUrl;
31689
- this.currentOffset = currentOffset;
31690
- this.totalBytesPurged = totalBytesPurged || 0;
31691
- }
31692
- } catch (e3) {
31693
- console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
31694
- }
31695
- }
31696
- saveSessionState() {
31697
- try {
31698
- localStorage.setItem(
31699
- `${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
31700
- JSON.stringify({
31701
- sessionUrl: this.sessionUrl,
31702
- currentOffset: this.currentOffset,
31703
- totalBytesPurged: this.totalBytesPurged
31704
- })
31705
- );
31706
- } catch (e3) {
31707
- console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
31708
- }
31709
- }
31710
- clearSessionState() {
31711
- try {
31712
- localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
31713
- this.sessionUrl = null;
31714
- this.currentOffset = 0;
31715
- this.totalBytesPurged = 0;
31716
- } catch (e3) {
31717
- console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
31718
- }
31719
- }
31720
- /**
31721
- * Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
31722
- * para enviar chunks pendentes.
31723
- */
31724
- start() {
31725
- if (this.isRunning) return;
31726
- this.isRunning = true;
31727
- console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
31728
- this.processQueue();
31729
- this.pollTimer = setInterval(() => {
31730
- this.processQueue(false);
31731
- }, this.config.pollInterval);
31732
- }
31733
- /**
31734
- * Para o serviço de upload em background.
31735
- */
31736
- stop() {
31737
- this.isRunning = false;
31738
- if (this.pollTimer) {
31739
- clearInterval(this.pollTimer);
31740
- this.pollTimer = null;
31741
- }
31742
- console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
31743
- }
31744
- /**
31745
- * Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
31746
- * Útil quando a gravação é finalizada.
31747
- */
31748
- async flush() {
31749
- console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
31750
- let waitAttempts = 0;
31751
- while (this.isProcessing && waitAttempts < 10) {
31752
- await this.sleep(1e3);
31753
- waitAttempts++;
31754
- }
31755
- let flushRetries = 0;
31756
- const maxFlushRetries = 3;
31757
- while (flushRetries < maxFlushRetries) {
31758
- try {
31759
- await this.processQueue(true);
31760
- console.log(`[BackgroundUpload] Flush completado com sucesso.`);
31761
- return;
31762
- } catch (error) {
31763
- flushRetries++;
31764
- console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
31765
- if (flushRetries < maxFlushRetries) {
31766
- await this.sleep(2e3);
31767
- }
31768
- }
31769
- }
31770
- throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
31771
- }
31772
- /**
31773
- * Sincroniza o offset local com o estado real no Google Cloud Storage.
31774
- */
31775
- async syncOffset() {
31776
- if (!this.sessionUrl) return 0;
31777
- try {
31778
- console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
31779
- const response = await fetch(this.sessionUrl, {
31780
- method: "PUT",
31781
- headers: {
31782
- "Content-Range": "bytes */*"
31783
- }
31784
- });
31785
- console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
31786
- if (response.status === 308) {
31787
- const range = response.headers.get("Range");
31788
- if (range) {
31789
- const lastByte = parseInt(range.split("-")[1], 10);
31790
- this.currentOffset = lastByte + 1;
31791
- this.saveSessionState();
31792
- console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
31793
- } else {
31794
- this.currentOffset = 0;
31795
- }
31796
- } else if (response.ok || response.status === 201) {
31797
- console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
31798
- this.currentOffset = -1;
31799
- } else {
31800
- console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
31801
- }
31802
- } catch (error) {
31803
- console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
31804
- }
31805
- return this.currentOffset;
31806
- }
31807
- /**
31808
- * Verifica e envia chunks pendentes para o backend.
31809
- * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
31810
- */
31811
- async processQueue(isFinal = false) {
31812
- var _a2, _b;
31813
- if (this.isProcessing) return;
31814
- this.isProcessing = true;
31815
- try {
31816
- if (this.sessionUrl) {
31817
- await this.syncOffset();
31818
- if (this.currentOffset === -1) {
31819
- console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
31820
- this.clearSessionState();
31821
- this.isProcessing = false;
31822
- return;
31823
- }
31824
- }
31825
- const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
31826
- const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
31827
- if (pendingChunks.length === 0 && !isFinal) {
31828
- this.isProcessing = false;
31829
- return;
31830
- }
31831
- console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
31832
- let virtualStart = this.totalBytesPurged;
31833
- const chunksWithMeta = allChunks.map((c3) => {
31834
- const start = virtualStart;
31835
- const end = start + c3.blob.size - 1;
31836
- virtualStart += c3.blob.size;
31837
- return { chunk: c3, start, end };
31838
- });
31839
- let combinedBlobParts = [];
31840
- let lastProcessedChunkId = null;
31841
- let finalChunkIndex = 0;
31842
- let mimeType = pendingChunks[0].mimeType;
31843
- for (const meta of chunksWithMeta) {
31844
- if (this.currentOffset > meta.end) continue;
31845
- const sliceStart = Math.max(0, this.currentOffset - meta.start);
31846
- const chunkSlice = meta.chunk.blob.slice(sliceStart);
31847
- combinedBlobParts.push(chunkSlice);
31848
- lastProcessedChunkId = meta.chunk.id;
31849
- finalChunkIndex = meta.chunk.chunkIndex;
31850
- }
31851
- if (combinedBlobParts.length === 0 && !isFinal) {
31852
- this.isProcessing = false;
31853
- return;
31854
- }
31855
- let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
31856
- let sendableSize = fullBlob.size;
31857
- let totalSizeForHeader = void 0;
31858
- if (!isFinal) {
31859
- sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
31860
- if (sendableSize === 0) {
31861
- console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
31862
- this.isProcessing = false;
31863
- return;
31864
- }
31865
- } else {
31866
- totalSizeForHeader = virtualStart;
31867
- }
31868
- const blobToSend = fullBlob.slice(0, sendableSize);
31869
- try {
31870
- await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
31871
- for (const meta of chunksWithMeta) {
31872
- if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
31873
- await this.chunkStorage.markAsUploaded(meta.chunk.id);
31874
- this.retryCount.delete(meta.chunk.id);
31875
- (_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
31876
- console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
31877
- }
31878
- }
31879
- if (this.config.cleanAfterUpload) {
31880
- const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
31881
- const sizePurged = chunksToClear.reduce((acc, meta) => acc + meta.chunk.blob.size, 0);
31882
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
31883
- if (sizePurged > 0) {
31884
- this.totalBytesPurged += sizePurged;
31885
- this.saveSessionState();
31886
- console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
31887
- }
31888
- }
31889
- if (isFinal) {
31890
- this.clearSessionState();
31891
- }
31892
- } catch (error) {
31893
- console.error("[BackgroundUpload] Falha no upload:", error);
31894
- (_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
31895
- }
31896
- } catch (error) {
31897
- console.error("[BackgroundUpload] Erro ao processar fila:", error);
31898
- } finally {
31899
- this.isProcessing = false;
31900
- }
31901
- }
31902
- /**
31903
- * Faz o upload bruto de dados para a sessão GCS.
31904
- */
31905
- async uploadData(blob, mimeType, chunkIndex, totalSize) {
31906
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
31907
- if (!this.sessionUrl) {
31908
- const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
31909
- const startResponse = await fetch(initiateUrl, {
31910
- method: "POST",
31911
- headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
31912
- });
31913
- if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
31914
- this.sessionUrl = startResponse.headers.get("Location");
31915
- if (!this.sessionUrl) throw new Error("Location header ausente");
31916
- try {
31917
- const urlObj = new URL(this.sessionUrl);
31918
- const pathParts = urlObj.pathname.split("/");
31919
- let bucket = pathParts[1];
31920
- let object = decodeURIComponent(pathParts.slice(2).join("/"));
31921
- if (pathParts.includes("b") && pathParts.includes("o")) {
31922
- const bIdx = pathParts.indexOf("b") + 1;
31923
- const oIdx = pathParts.indexOf("o") + 1;
31924
- bucket = pathParts[bIdx];
31925
- object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
31926
- }
31927
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
31928
- } catch (e3) {
31929
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
31930
- }
31931
- this.currentOffset = 0;
31932
- this.saveSessionState();
31933
- } else {
31934
- console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
31935
- }
31936
- const start = this.currentOffset;
31937
- const end = start + blob.size - 1;
31938
- const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
31939
- const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
31940
- console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
31941
- const response = await fetch(this.sessionUrl, {
31942
- method: "PUT",
31943
- headers: { "Content-Range": contentRangeHeader },
31944
- body: blob.size > 0 ? blob : null
31945
- // Usa null para garantir corpo vazio se necessário
31946
- });
31947
- console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
31948
- if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
31949
- const errorText = await response.text();
31950
- console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
31951
- throw new Error(`Status HTTP inesperado: ${response.status}`);
31952
- }
31953
- const rangeHeader = response.headers.get("Range");
31954
- if (rangeHeader) {
31955
- const lastByte = parseInt(rangeHeader.split("-")[1], 10);
31956
- this.currentOffset = lastByte + 1;
31957
- } else {
31958
- this.currentOffset += blob.size;
31959
- }
31960
- this.saveSessionState();
31961
- trackers.registerUploadFile(
31962
- this.proctoringId,
31963
- `GCS Stream Upload
31964
- Size: ${blob.size}
31965
- Range: ${start}-${end}
31966
- Last Index: ${chunkIndex}`,
31967
- "CameraChunk"
31968
- );
31969
- }
31970
- /**
31971
- * Método estático para recuperação pós-crash.
31972
- * Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
31973
- * e tenta enviar.
31974
- */
31975
- static async recoverPendingUploads(backend, token) {
31976
- const chunkStorage = new ChunkStorageService();
31977
- const recoveredIds = [];
31978
- try {
31979
- const pendingIds = await chunkStorage.getPendingProctoringIds();
31980
- if (pendingIds.length === 0) {
31981
- console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
31982
- return recoveredIds;
31983
- }
31984
- console.log(
31985
- `[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
31986
- );
31987
- for (const proctoringId2 of pendingIds) {
31988
- try {
31989
- const service = new _BackgroundUploadService(
31990
- proctoringId2,
31991
- token,
31992
- backend,
31993
- chunkStorage,
31994
- { cleanAfterUpload: true }
31995
- );
31996
- await service.flush();
31997
- recoveredIds.push(proctoringId2);
31998
- console.log(
31999
- `[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
32000
- );
32001
- } catch (error) {
32002
- console.error(
32003
- `[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
32004
- error
32005
- );
32006
- }
32007
- }
32008
- } catch (error) {
32009
- console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
32010
- }
32011
- return recoveredIds;
32012
- }
32013
- sleep(ms2) {
32014
- return new Promise((resolve) => setTimeout(resolve, ms2));
32015
- }
32016
- };
32017
-
32018
31401
  // src/new-flow/recorders/CameraRecorder.ts
32019
31402
  var import_jszip_min = __toESM(require_jszip_min());
32020
31403
  var pkg = require_fix_webm_duration();
32021
31404
  var fixWebmDuration = pkg.default || pkg;
32022
- var _CameraRecorder = class _CameraRecorder {
31405
+ var CameraRecorder = class {
32023
31406
  constructor(options, videoOptions, paramsConfig, backend, backendToken) {
32024
31407
  this.blobs = [];
32025
31408
  this.paramsConfig = {
@@ -32072,13 +31455,6 @@ var _CameraRecorder = class _CameraRecorder {
32072
31455
  this.videoElement = null;
32073
31456
  this.duration = 0;
32074
31457
  this.stopped = false;
32075
- this.backgroundUpload = null;
32076
- this.chunkIndex = 0;
32077
- /** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
32078
- this.pendingChunkSaves = [];
32079
- // Handlers bound para poder remover os listeners depois
32080
- this.boundVisibilityHandler = null;
32081
- this.boundPageHideHandler = null;
32082
31458
  this.currentRetries = 0;
32083
31459
  this.packageCount = 0;
32084
31460
  this.failedUploads = 0;
@@ -32089,122 +31465,10 @@ var _CameraRecorder = class _CameraRecorder {
32089
31465
  this.backendToken = backendToken;
32090
31466
  paramsConfig && (this.paramsConfig = paramsConfig);
32091
31467
  }
32092
- /**
32093
- * Determina se o fluxo de chunks e lifecycle deve estar ativo.
32094
- * Retorna true se:
32095
- * 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
32096
- * 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
32097
- */
32098
- get isChunkEnabled() {
32099
- return false;
32100
- }
32101
31468
  setProctoringId(proctoringId2) {
32102
31469
  this.proctoringId = proctoringId2;
32103
31470
  this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
32104
31471
  setRecorderProctoringId(proctoringId2);
32105
- if (this.isChunkEnabled) {
32106
- this.chunkStorage = new ChunkStorageService();
32107
- if (this.backend && this.backendToken) {
32108
- this.backgroundUpload = new BackgroundUploadService(
32109
- this.proctoringId,
32110
- this.backendToken,
32111
- this.backend,
32112
- this.chunkStorage,
32113
- { pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
32114
- );
32115
- }
32116
- this.persistSessionState("IN_PROGRESS");
32117
- console.log(
32118
- `[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
32119
- );
32120
- } else {
32121
- console.log(
32122
- `[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
32123
- );
32124
- }
32125
- }
32126
- // ========================
32127
- // Session State Persistence (localStorage)
32128
- // ========================
32129
- persistSessionState(status) {
32130
- try {
32131
- const data = {
32132
- proctoringId: this.proctoringId,
32133
- status,
32134
- timestamp: Date.now()
32135
- };
32136
- localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
32137
- } catch (e3) {
32138
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
32139
- }
32140
- }
32141
- clearSessionState() {
32142
- try {
32143
- localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
32144
- } catch (e3) {
32145
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
32146
- }
32147
- }
32148
- /**
32149
- * Verifica se existe uma sessão ativa anterior no localStorage.
32150
- * Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
32151
- */
32152
- static checkForActiveSession() {
32153
- try {
32154
- const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
32155
- if (!raw) return null;
32156
- const data = JSON.parse(raw);
32157
- if (data.status === "IN_PROGRESS") return data;
32158
- return null;
32159
- } catch {
32160
- return null;
32161
- }
32162
- }
32163
- // ========================
32164
- // Page Lifecycle Management
32165
- // ========================
32166
- setupLifecycleListeners() {
32167
- this.boundVisibilityHandler = () => this.handleVisibilityChange();
32168
- this.boundPageHideHandler = () => this.handlePageHide();
32169
- document.addEventListener("visibilitychange", this.boundVisibilityHandler);
32170
- window.addEventListener("pagehide", this.boundPageHideHandler);
32171
- }
32172
- removeLifecycleListeners() {
32173
- if (this.boundVisibilityHandler) {
32174
- document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
32175
- this.boundVisibilityHandler = null;
32176
- }
32177
- if (this.boundPageHideHandler) {
32178
- window.removeEventListener("pagehide", this.boundPageHideHandler);
32179
- this.boundPageHideHandler = null;
32180
- }
32181
- }
32182
- handleVisibilityChange() {
32183
- var _a2;
32184
- if (document.visibilityState === "hidden") {
32185
- console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
32186
- this.persistSessionState("INTERRUPTED");
32187
- this.proctoringId && trackers.registerError(
32188
- this.proctoringId,
32189
- "Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
32190
- );
32191
- } else if (document.visibilityState === "visible") {
32192
- console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
32193
- this.persistSessionState("IN_PROGRESS");
32194
- this.proctoringId && trackers.registerError(
32195
- this.proctoringId,
32196
- "Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
32197
- );
32198
- (_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
32199
- }
32200
- }
32201
- handlePageHide() {
32202
- console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
32203
- this.persistSessionState("INTERRUPTED");
32204
- this.proctoringId && trackers.registerError(
32205
- this.proctoringId,
32206
- "Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
32207
- );
32208
31472
  }
32209
31473
  async initializeDetectors() {
32210
31474
  var _a2, _b, _c2;
@@ -32357,15 +31621,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32357
31621
  await new Promise((r3) => setTimeout(r3, 300));
32358
31622
  }
32359
31623
  async startRecording() {
32360
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
31624
+ var _a2, _b, _c2, _d, _e3, _f, _g;
32361
31625
  await this.startStream();
32362
31626
  await this.attachAndWarmup(this.cameraStream);
32363
- const recorderOpts = this.isChunkEnabled ? {
32364
- timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
32365
- onChunkAvailable: (blob, idx) => {
32366
- this.handleNewChunk(blob, idx);
32367
- }
32368
- } : {};
32369
31627
  const {
32370
31628
  startRecording,
32371
31629
  stopRecording,
@@ -32381,8 +31639,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32381
31639
  this.blobs,
32382
31640
  this.options.onBufferSizeError,
32383
31641
  (e3) => this.bufferError(e3),
32384
- false,
32385
- recorderOpts
31642
+ false
32386
31643
  );
32387
31644
  this.recordingStart = startRecording;
32388
31645
  this.recordingStop = stopRecording;
@@ -32392,18 +31649,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32392
31649
  this.getBufferSize = getBufferSize;
32393
31650
  this.getStartTime = getStartTime;
32394
31651
  this.getDuration = getDuration;
32395
- this.chunkIndex = 0;
32396
- if (this.isChunkEnabled) {
32397
- (_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
32398
- this.setupLifecycleListeners();
32399
- }
32400
31652
  try {
32401
31653
  await new Promise((r3) => setTimeout(r3, 500));
32402
31654
  await this.recordingStart();
32403
31655
  } catch (error) {
32404
31656
  console.log("Camera Recorder error", error);
32405
31657
  this.stopRecording();
32406
- const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
31658
+ const maxRetries = ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.maxRetries) || 3;
32407
31659
  if (this.currentRetries < maxRetries) {
32408
31660
  console.log("Camera Recorder retry", this.currentRetries);
32409
31661
  this.currentRetries++;
@@ -32413,13 +31665,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32413
31665
  }
32414
31666
  }
32415
31667
  this.stopped = false;
32416
- if (((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectPerson) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectCellPhone) || ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace)) {
31668
+ if (((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.detectPerson) || ((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectCellPhone) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectFace)) {
32417
31669
  await this.initializeDetectors();
32418
31670
  }
32419
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
31671
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
32420
31672
  await this.faceDetection.enableCam(this.cameraStream);
32421
31673
  }
32422
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
31674
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
32423
31675
  await this.objectDetection.enableCam(this.cameraStream);
32424
31676
  }
32425
31677
  this.filesToUpload = [];
@@ -32478,50 +31730,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32478
31730
  await this.sendPackage();
32479
31731
  await this.filesToUpload.splice(0, this.filesToUpload.length);
32480
31732
  }
32481
- if (this.isChunkEnabled) {
32482
- if (this.backgroundUpload) {
32483
- try {
32484
- if (this.pendingChunkSaves.length > 0) {
32485
- console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
32486
- await Promise.all(this.pendingChunkSaves);
32487
- }
32488
- await this.backgroundUpload.flush();
32489
- } catch (e3) {
32490
- console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
32491
- }
32492
- this.backgroundUpload.stop();
32493
- }
32494
- this.removeLifecycleListeners();
32495
- this.persistSessionState("FINISHED");
32496
- }
32497
- }
32498
- /**
32499
- * Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
32500
- * Salva o chunk no IndexedDB para persistência e recuperação.
32501
- */
32502
- async handleNewChunk(blob, idx) {
32503
- if (!this.proctoringId || !this.chunkStorage) return;
32504
- const savePromise = (async () => {
32505
- var _a2;
32506
- try {
32507
- await this.chunkStorage.saveChunk({
32508
- proctoringId: this.proctoringId,
32509
- chunkIndex: this.chunkIndex,
32510
- blob,
32511
- timestamp: Date.now(),
32512
- uploaded: 0,
32513
- mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
32514
- });
32515
- this.chunkIndex++;
32516
- console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
32517
- } catch (error) {
32518
- console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
32519
- }
32520
- })();
32521
- this.pendingChunkSaves.push(savePromise);
32522
- savePromise.finally(() => {
32523
- this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
32524
- });
32525
31733
  }
32526
31734
  async pauseRecording() {
32527
31735
  await this.recordingPause();
@@ -32657,36 +31865,16 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32657
31865
  if (this.blobs != null)
32658
31866
  trackers.registerSaveOnSession(
32659
31867
  this.proctoringId,
32660
- `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
31868
+ `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
32661
31869
  );
32662
31870
  const settings = this.cameraStream.getVideoTracks()[0].getSettings();
32663
31871
  const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
32664
31872
  if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
32665
- let videoFile;
32666
- if (this.isChunkEnabled) {
32667
- const isStable = await this.checkInternetStability();
32668
- if (isStable) {
32669
- } else {
32670
- if (this.backend && this.backendToken && this.proctoringId) {
32671
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
32672
- const objectName = `${this.proctoringId}/${fileName}`;
32673
- const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
32674
- if (isUploaded) {
32675
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
32676
- return;
32677
- }
32678
- }
32679
- }
32680
- }
32681
31873
  const rawBlob = new Blob(this.blobs, {
32682
31874
  type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
32683
31875
  });
32684
31876
  const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
32685
- videoFile = new File(
32686
- [fixedBlob],
32687
- `EP_${session.id}_camera_0.webm`,
32688
- { type: rawBlob.type }
32689
- );
31877
+ const arrayBuffer = await fixedBlob.arrayBuffer();
32690
31878
  session.addRecording({
32691
31879
  device: `Audio
32692
31880
  Sample Rate: ${settingsAudio.sampleRate}
@@ -32694,7 +31882,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32694
31882
 
32695
31883
  Video:
32696
31884
  ${JSON.stringify(this.recorderOptions)}`,
32697
- file: videoFile,
31885
+ arrayBuffer,
32698
31886
  origin: "Camera" /* Camera */
32699
31887
  });
32700
31888
  }
@@ -32709,28 +31897,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32709
31897
  });
32710
31898
  });
32711
31899
  }
32712
- /**
32713
- * Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
32714
- */
32715
- async checkInternetStability() {
32716
- var _a2;
32717
- if (!navigator.onLine) return false;
32718
- try {
32719
- const controller = new AbortController();
32720
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
32721
- const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
32722
- if (!baseUrl) return true;
32723
- const response = await fetch(`${baseUrl}/Client/health`, {
32724
- method: "GET",
32725
- signal: controller.signal
32726
- });
32727
- clearTimeout(timeoutId);
32728
- return response.status < 500;
32729
- } catch (e3) {
32730
- console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
32731
- return false;
32732
- }
32733
- }
32734
31900
  onNoiseDetected() {
32735
31901
  var _a2, _b, _c2;
32736
31902
  if (this.options.proctoringType === "REALTIME") return;
@@ -32755,14 +31921,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
32755
31921
  this.noiseWait++;
32756
31922
  }
32757
31923
  };
32758
- // ========================
32759
- // Chunk & Lifecycle
32760
- // ========================
32761
- /** Intervalo de cada chunk em ms (padrão: 60 segundos) */
32762
- _CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
32763
- /** Chave do localStorage para persistir estado da sessão */
32764
- _CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
32765
- var CameraRecorder = _CameraRecorder;
32766
31924
 
32767
31925
  // src/new-flow/checkers/DeviceCheckerUI.ts
32768
31926
  var DeviceCheckerUI = class {
@@ -34679,46 +33837,60 @@ var ProctoringUploader = class {
34679
33837
  globalOnProgres(100);
34680
33838
  }
34681
33839
  }
33840
+ toFile(rec) {
33841
+ const suffix = rec.origin === "Screen" /* Screen */ ? "screen" : rec.origin === "Camera" /* Camera */ ? "camera" : "audio";
33842
+ const name = `EP_${this.proctoringId}_${suffix}_0.webm`;
33843
+ return new File([rec.arrayBuffer], name, { type: "video/webm" });
33844
+ }
33845
+ getFileType(origin2) {
33846
+ switch (origin2) {
33847
+ case "Camera" /* Camera */:
33848
+ return "Camera";
33849
+ case "Screen" /* Screen */:
33850
+ return "Screen";
33851
+ case "Mic" /* Mic */:
33852
+ return "Audio";
33853
+ }
33854
+ }
34682
33855
  async uploadFile(rec, token, onProgress) {
33856
+ const file = this.toFile(rec);
34683
33857
  for await (const uploadService of this.uploadServices) {
34684
33858
  const result = await uploadService.upload(
34685
33859
  {
34686
- file: rec.file,
33860
+ file,
34687
33861
  onProgress: (progress) => {
34688
33862
  if (onProgress) onProgress(progress);
34689
33863
  }
34690
33864
  },
34691
33865
  token
34692
- ).catch(
34693
- async (e3) => {
34694
- console.log("Upload File Error", e3), trackers.registerError(this.proctoringId, `Upload File
34695
- Name: ${rec.file.name}
33866
+ ).catch(async (e3) => {
33867
+ console.log("Upload File Error", e3), trackers.registerError(
33868
+ this.proctoringId,
33869
+ `Upload File
33870
+ Name: ${file.name}
34696
33871
  Error: ${e3.message}
34697
- Size: ${e3.error}`);
34698
- await uploadService.upload(
34699
- {
34700
- file: rec.file,
34701
- onProgress: (progress) => {
34702
- if (onProgress) onProgress(progress);
34703
- }
34704
- },
34705
- token
34706
- );
34707
- }
34708
- );
33872
+ Size: ${e3.error}`
33873
+ );
33874
+ await uploadService.upload(
33875
+ {
33876
+ file,
33877
+ onProgress: (progress) => {
33878
+ if (onProgress) onProgress(progress);
33879
+ }
33880
+ },
33881
+ token
33882
+ );
33883
+ });
34709
33884
  if (result) {
34710
- let fileType = "";
34711
- if (rec.file.name.search("camera") !== -1) {
34712
- fileType = "Camera";
34713
- } else if (rec.file.name.search("screen") !== -1) {
34714
- fileType = "Screen";
34715
- } else if (rec.file.name.search("audio") !== -1) {
34716
- fileType = "Audio";
34717
- }
34718
- trackers.registerUploadFile(this.proctoringId, `Upload File
34719
- Name: ${rec.file.name}
34720
- Type: ${rec.file.type}
34721
- Size: ${rec.file.size}`, fileType);
33885
+ const fileType = this.getFileType(rec.origin);
33886
+ trackers.registerUploadFile(
33887
+ this.proctoringId,
33888
+ `Upload File
33889
+ Name: ${file.name}
33890
+ Type: ${file.type}
33891
+ Size: ${file.size}`,
33892
+ fileType
33893
+ );
34722
33894
  return result;
34723
33895
  }
34724
33896
  }
@@ -37528,14 +36700,10 @@ var ScreenRecorder = class {
37528
36700
  type: "video/webm"
37529
36701
  });
37530
36702
  const fixedBlob = await fixWebmDuration2(rawBlob, this.duration);
37531
- const file = new File(
37532
- [fixedBlob],
37533
- `EP_${session.id}_screen_0.webm`,
37534
- { type: rawBlob.type }
37535
- );
36703
+ const arrayBuffer = await fixedBlob.arrayBuffer();
37536
36704
  session.addRecording({
37537
36705
  device: "",
37538
- file,
36706
+ arrayBuffer,
37539
36707
  origin: "Screen" /* Screen */
37540
36708
  });
37541
36709
  }
@@ -38887,20 +38055,6 @@ var Proctoring = class {
38887
38055
  } catch (error) {
38888
38056
  throw SAFE_BROWSER_API_NOT_FOUND;
38889
38057
  }
38890
- this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
38891
- console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
38892
- this.onVisibilityRestoredCallback();
38893
- };
38894
- if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
38895
- try {
38896
- await BackgroundUploadService.recoverPendingUploads(
38897
- this.backend,
38898
- this.context.token
38899
- );
38900
- } catch (e3) {
38901
- console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
38902
- }
38903
- }
38904
38058
  try {
38905
38059
  console.log("Starting recorders");
38906
38060
  await this.recorder.startAll();
@@ -38981,7 +38135,9 @@ Error: ${error}`
38981
38135
  this.appChecker && await this.appChecker.disconnectWebSocket();
38982
38136
  await this.recorder.saveAllOnSession();
38983
38137
  await this.sendPendingRealtimeAlerts();
38138
+ trackers.registerError(this.proctoringId, `finish this.repository.save starting`);
38984
38139
  await this.repository.save(this.proctoringSession);
38140
+ trackers.registerError(this.proctoringId, `finish this.repository.save finished`);
38985
38141
  let uploader;
38986
38142
  let uploaderServices;
38987
38143
  if (versionVerify() !== "1.0.0.0") {