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/esm/index.js CHANGED
@@ -12758,16 +12758,13 @@ function isMobileDevice() {
12758
12758
  }
12759
12759
  return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
12760
12760
  }
12761
- function isSafeBrowser() {
12762
- return versionVerify() !== "1.0.0.0";
12763
- }
12764
12761
 
12765
12762
  // src/plugins/recorder.ts
12766
12763
  var proctoringId;
12767
12764
  function setRecorderProctoringId(id) {
12768
12765
  proctoringId = id;
12769
12766
  }
12770
- function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false, recorderOpts) {
12767
+ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
12771
12768
  let resolvePromise;
12772
12769
  let onBufferSizeInterval;
12773
12770
  let lastEvent;
@@ -12775,7 +12772,6 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12775
12772
  bufferSize = 0;
12776
12773
  let startTime;
12777
12774
  let duration = 0;
12778
- let chunkIndex = 0;
12779
12775
  let recorderOptions = {
12780
12776
  // eslint-disable-next-line no-useless-escape
12781
12777
  mimeType: "video/webm",
@@ -12810,10 +12806,6 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12810
12806
  mediaRecorder2.ondataavailable = (e3) => {
12811
12807
  bufferSize = bufferSize + e3.data.size;
12812
12808
  if (e3.data.size > 0) {
12813
- if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
12814
- recorderOpts.onChunkAvailable(e3.data, chunkIndex);
12815
- chunkIndex++;
12816
- }
12817
12809
  buffer.push(e3.data);
12818
12810
  }
12819
12811
  };
@@ -12845,13 +12837,7 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12845
12837
  };
12846
12838
  try {
12847
12839
  console.log("State antes do start:", recorder2.state);
12848
- chunkIndex = 0;
12849
- if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
12850
- recorder2.start(recorderOpts.timeslice);
12851
- } else {
12852
- recorder2.start(1e4);
12853
- }
12854
- bufferSize = 0;
12840
+ recorder2.start(1e4);
12855
12841
  startTime = new Date(Date.now());
12856
12842
  } catch (e3) {
12857
12843
  console.error("Recorder erro ao chamar start event:", e3);
@@ -12905,6 +12891,9 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
12905
12891
  console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
12906
12892
  resolve();
12907
12893
  }
12894
+ stream.getTracks().forEach((el) => {
12895
+ el.stop();
12896
+ });
12908
12897
  });
12909
12898
  }
12910
12899
  function pauseRecording() {
@@ -13312,617 +13301,11 @@ var VolumeMeter = class {
13312
13301
  }
13313
13302
  };
13314
13303
 
13315
- // src/new-flow/chunk/ChunkStorageService.ts
13316
- var _ChunkStorageService = class _ChunkStorageService {
13317
- constructor() {
13318
- this.db = null;
13319
- }
13320
- /**
13321
- * Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
13322
- */
13323
- async connect() {
13324
- if (this.db) return this.db;
13325
- return new Promise((resolve, reject) => {
13326
- const request = window.indexedDB.open(
13327
- _ChunkStorageService.DB_NAME,
13328
- _ChunkStorageService.DB_VERSION
13329
- );
13330
- request.onerror = () => {
13331
- reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
13332
- };
13333
- request.onupgradeneeded = () => {
13334
- const db = request.result;
13335
- if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
13336
- db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
13337
- }
13338
- const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
13339
- keyPath: "id",
13340
- autoIncrement: true
13341
- });
13342
- store.createIndex("proctoringId", "proctoringId", { unique: false });
13343
- store.createIndex("uploaded", "uploaded", { unique: false });
13344
- store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
13345
- unique: false
13346
- });
13347
- };
13348
- request.onsuccess = () => {
13349
- this.db = request.result;
13350
- resolve(this.db);
13351
- };
13352
- });
13353
- }
13354
- /**
13355
- * Salva um chunk de vídeo no IndexedDB.
13356
- */
13357
- async saveChunk(chunk) {
13358
- const db = await this.connect();
13359
- return new Promise((resolve, reject) => {
13360
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13361
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13362
- const request = store.add(chunk);
13363
- request.onsuccess = () => {
13364
- resolve(request.result);
13365
- };
13366
- request.onerror = () => {
13367
- var _a2;
13368
- reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13369
- };
13370
- });
13371
- }
13372
- /**
13373
- * Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
13374
- */
13375
- async getPendingChunks(proctoringId2) {
13376
- const db = await this.connect();
13377
- return new Promise((resolve, reject) => {
13378
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13379
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13380
- const index = store.index("proctoringId_uploaded");
13381
- const range = IDBKeyRange.only([proctoringId2, 0]);
13382
- const request = index.getAll(range);
13383
- request.onsuccess = () => {
13384
- const chunks = request.result.sort(
13385
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
13386
- );
13387
- resolve(chunks);
13388
- };
13389
- request.onerror = () => {
13390
- var _a2;
13391
- reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13392
- };
13393
- });
13394
- }
13395
- /**
13396
- * Retorna todos os chunks (enviados ou não) de um proctoringId específico.
13397
- */
13398
- async getAllChunks(proctoringId2) {
13399
- const db = await this.connect();
13400
- return new Promise((resolve, reject) => {
13401
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13402
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13403
- const index = store.index("proctoringId");
13404
- const range = IDBKeyRange.only(proctoringId2);
13405
- const request = index.getAll(range);
13406
- request.onsuccess = () => {
13407
- const chunks = request.result.sort(
13408
- (a3, b3) => a3.chunkIndex - b3.chunkIndex
13409
- );
13410
- resolve(chunks);
13411
- };
13412
- request.onerror = () => {
13413
- var _a2;
13414
- reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13415
- };
13416
- });
13417
- }
13418
- /**
13419
- * Marca um chunk como enviado (uploaded = 1).
13420
- */
13421
- async markAsUploaded(chunkId) {
13422
- const db = await this.connect();
13423
- return new Promise((resolve, reject) => {
13424
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13425
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13426
- const getRequest = store.get(chunkId);
13427
- getRequest.onsuccess = () => {
13428
- const chunk = getRequest.result;
13429
- if (!chunk) {
13430
- resolve();
13431
- return;
13432
- }
13433
- chunk.uploaded = 1;
13434
- const putRequest = store.put(chunk);
13435
- putRequest.onsuccess = () => resolve();
13436
- putRequest.onerror = () => {
13437
- var _a2;
13438
- return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
13439
- };
13440
- };
13441
- getRequest.onerror = () => {
13442
- var _a2;
13443
- return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
13444
- };
13445
- });
13446
- }
13447
- /**
13448
- * Remove todos os chunks já enviados de um proctoringId para liberar espaço.
13449
- */
13450
- async clearUploadedChunks(proctoringId2) {
13451
- const db = await this.connect();
13452
- return new Promise((resolve, reject) => {
13453
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13454
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13455
- const index = store.index("proctoringId_uploaded");
13456
- const range = IDBKeyRange.only([proctoringId2, 1]);
13457
- const request = index.openCursor(range);
13458
- request.onsuccess = () => {
13459
- const cursor = request.result;
13460
- if (cursor) {
13461
- cursor.delete();
13462
- cursor.continue();
13463
- } else {
13464
- resolve();
13465
- }
13466
- };
13467
- request.onerror = () => {
13468
- var _a2;
13469
- return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13470
- };
13471
- });
13472
- }
13473
- /**
13474
- * Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
13475
- */
13476
- async clearAllChunks(proctoringId2) {
13477
- const db = await this.connect();
13478
- return new Promise((resolve, reject) => {
13479
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
13480
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13481
- const index = store.index("proctoringId");
13482
- const range = IDBKeyRange.only(proctoringId2);
13483
- const request = index.openCursor(range);
13484
- request.onsuccess = () => {
13485
- const cursor = request.result;
13486
- if (cursor) {
13487
- cursor.delete();
13488
- cursor.continue();
13489
- } else {
13490
- resolve();
13491
- }
13492
- };
13493
- request.onerror = () => {
13494
- var _a2;
13495
- return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13496
- };
13497
- });
13498
- }
13499
- /**
13500
- * Verifica se existem chunks pendentes para qualquer proctoringId.
13501
- * Útil na recuperação pós-crash.
13502
- */
13503
- async hasAnyPendingChunks() {
13504
- const db = await this.connect();
13505
- return new Promise((resolve, reject) => {
13506
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13507
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13508
- const index = store.index("uploaded");
13509
- const range = IDBKeyRange.only(0);
13510
- const request = index.count(range);
13511
- request.onsuccess = () => {
13512
- resolve(request.result > 0);
13513
- };
13514
- request.onerror = () => {
13515
- var _a2;
13516
- return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13517
- };
13518
- });
13519
- }
13520
- /**
13521
- * Retorna todos os proctoringIds que possuem chunks pendentes.
13522
- * Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
13523
- */
13524
- async getPendingProctoringIds() {
13525
- const db = await this.connect();
13526
- return new Promise((resolve, reject) => {
13527
- const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
13528
- const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
13529
- const index = store.index("uploaded");
13530
- const range = IDBKeyRange.only(0);
13531
- const request = index.getAll(range);
13532
- request.onsuccess = () => {
13533
- const chunks = request.result;
13534
- const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
13535
- resolve(ids);
13536
- };
13537
- request.onerror = () => {
13538
- var _a2;
13539
- return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
13540
- };
13541
- });
13542
- }
13543
- /**
13544
- * Fecha a conexão com o banco.
13545
- */
13546
- close() {
13547
- if (this.db) {
13548
- this.db.close();
13549
- this.db = null;
13550
- }
13551
- }
13552
- };
13553
- _ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
13554
- /** Incrementado para v2 para recriar índices com tipos numéricos em vez de boolean */
13555
- _ChunkStorageService.DB_VERSION = 2;
13556
- _ChunkStorageService.STORE_NAME = "chunks";
13557
- var ChunkStorageService = _ChunkStorageService;
13558
-
13559
- // src/new-flow/chunk/BackgroundUploadService.ts
13560
- var DEFAULT_CONFIG = {
13561
- pollInterval: 5e3,
13562
- maxRetries: 5,
13563
- baseRetryDelay: 2e3,
13564
- cleanAfterUpload: true
13565
- };
13566
- var BackgroundUploadService = class _BackgroundUploadService {
13567
- constructor(proctoringId2, token, backend, chunkStorage, config) {
13568
- this.pollTimer = null;
13569
- this.isProcessing = false;
13570
- this.isRunning = false;
13571
- /** Mapa de chunkId -> número de tentativas já feitas */
13572
- this.retryCount = /* @__PURE__ */ new Map();
13573
- /** GCS Resumable Upload State */
13574
- this.sessionUrl = null;
13575
- this.currentOffset = 0;
13576
- this.totalBytesPurged = 0;
13577
- this.STORAGE_KEY_PREFIX = "ep_upload_session_";
13578
- this.GCS_CHUNK_SIZE = 256 * 1024;
13579
- this.proctoringId = proctoringId2.trim();
13580
- this.token = token;
13581
- this.backend = backend;
13582
- this.chunkStorage = chunkStorage;
13583
- this.config = { ...DEFAULT_CONFIG, ...config };
13584
- this.loadSessionState();
13585
- }
13586
- loadSessionState() {
13587
- try {
13588
- const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
13589
- if (stored) {
13590
- const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
13591
- this.sessionUrl = sessionUrl;
13592
- this.currentOffset = currentOffset;
13593
- this.totalBytesPurged = totalBytesPurged || 0;
13594
- }
13595
- } catch (e3) {
13596
- console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
13597
- }
13598
- }
13599
- saveSessionState() {
13600
- try {
13601
- localStorage.setItem(
13602
- `${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
13603
- JSON.stringify({
13604
- sessionUrl: this.sessionUrl,
13605
- currentOffset: this.currentOffset,
13606
- totalBytesPurged: this.totalBytesPurged
13607
- })
13608
- );
13609
- } catch (e3) {
13610
- console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
13611
- }
13612
- }
13613
- clearSessionState() {
13614
- try {
13615
- localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
13616
- this.sessionUrl = null;
13617
- this.currentOffset = 0;
13618
- this.totalBytesPurged = 0;
13619
- } catch (e3) {
13620
- console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
13621
- }
13622
- }
13623
- /**
13624
- * Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
13625
- * para enviar chunks pendentes.
13626
- */
13627
- start() {
13628
- if (this.isRunning) return;
13629
- this.isRunning = true;
13630
- console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
13631
- this.processQueue();
13632
- this.pollTimer = setInterval(() => {
13633
- this.processQueue(false);
13634
- }, this.config.pollInterval);
13635
- }
13636
- /**
13637
- * Para o serviço de upload em background.
13638
- */
13639
- stop() {
13640
- this.isRunning = false;
13641
- if (this.pollTimer) {
13642
- clearInterval(this.pollTimer);
13643
- this.pollTimer = null;
13644
- }
13645
- console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
13646
- }
13647
- /**
13648
- * Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
13649
- * Útil quando a gravação é finalizada.
13650
- */
13651
- async flush() {
13652
- console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
13653
- let waitAttempts = 0;
13654
- while (this.isProcessing && waitAttempts < 10) {
13655
- await this.sleep(1e3);
13656
- waitAttempts++;
13657
- }
13658
- let flushRetries = 0;
13659
- const maxFlushRetries = 3;
13660
- while (flushRetries < maxFlushRetries) {
13661
- try {
13662
- await this.processQueue(true);
13663
- console.log(`[BackgroundUpload] Flush completado com sucesso.`);
13664
- return;
13665
- } catch (error) {
13666
- flushRetries++;
13667
- console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
13668
- if (flushRetries < maxFlushRetries) {
13669
- await this.sleep(2e3);
13670
- }
13671
- }
13672
- }
13673
- throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
13674
- }
13675
- /**
13676
- * Sincroniza o offset local com o estado real no Google Cloud Storage.
13677
- */
13678
- async syncOffset() {
13679
- if (!this.sessionUrl) return 0;
13680
- try {
13681
- console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
13682
- const response = await fetch(this.sessionUrl, {
13683
- method: "PUT",
13684
- headers: {
13685
- "Content-Range": "bytes */*"
13686
- }
13687
- });
13688
- console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
13689
- if (response.status === 308) {
13690
- const range = response.headers.get("Range");
13691
- if (range) {
13692
- const lastByte = parseInt(range.split("-")[1], 10);
13693
- this.currentOffset = lastByte + 1;
13694
- this.saveSessionState();
13695
- console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
13696
- } else {
13697
- this.currentOffset = 0;
13698
- }
13699
- } else if (response.ok || response.status === 201) {
13700
- console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
13701
- this.currentOffset = -1;
13702
- } else {
13703
- console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
13704
- }
13705
- } catch (error) {
13706
- console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
13707
- }
13708
- return this.currentOffset;
13709
- }
13710
- /**
13711
- * Verifica e envia chunks pendentes para o backend.
13712
- * @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
13713
- */
13714
- async processQueue(isFinal = false) {
13715
- var _a2, _b;
13716
- if (this.isProcessing) return;
13717
- this.isProcessing = true;
13718
- try {
13719
- if (this.sessionUrl) {
13720
- await this.syncOffset();
13721
- if (this.currentOffset === -1) {
13722
- console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
13723
- this.clearSessionState();
13724
- this.isProcessing = false;
13725
- return;
13726
- }
13727
- }
13728
- const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
13729
- const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
13730
- if (pendingChunks.length === 0 && !isFinal) {
13731
- this.isProcessing = false;
13732
- return;
13733
- }
13734
- console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
13735
- let virtualStart = this.totalBytesPurged;
13736
- const chunksWithMeta = allChunks.map((c3) => {
13737
- const start = virtualStart;
13738
- const end = start + c3.blob.size - 1;
13739
- virtualStart += c3.blob.size;
13740
- return { chunk: c3, start, end };
13741
- });
13742
- let combinedBlobParts = [];
13743
- let lastProcessedChunkId = null;
13744
- let finalChunkIndex = 0;
13745
- let mimeType = pendingChunks[0].mimeType;
13746
- for (const meta of chunksWithMeta) {
13747
- if (this.currentOffset > meta.end) continue;
13748
- const sliceStart = Math.max(0, this.currentOffset - meta.start);
13749
- const chunkSlice = meta.chunk.blob.slice(sliceStart);
13750
- combinedBlobParts.push(chunkSlice);
13751
- lastProcessedChunkId = meta.chunk.id;
13752
- finalChunkIndex = meta.chunk.chunkIndex;
13753
- }
13754
- if (combinedBlobParts.length === 0 && !isFinal) {
13755
- this.isProcessing = false;
13756
- return;
13757
- }
13758
- let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
13759
- let sendableSize = fullBlob.size;
13760
- let totalSizeForHeader = void 0;
13761
- if (!isFinal) {
13762
- sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
13763
- if (sendableSize === 0) {
13764
- console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
13765
- this.isProcessing = false;
13766
- return;
13767
- }
13768
- } else {
13769
- totalSizeForHeader = virtualStart;
13770
- }
13771
- const blobToSend = fullBlob.slice(0, sendableSize);
13772
- try {
13773
- await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
13774
- for (const meta of chunksWithMeta) {
13775
- if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
13776
- await this.chunkStorage.markAsUploaded(meta.chunk.id);
13777
- this.retryCount.delete(meta.chunk.id);
13778
- (_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
13779
- console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
13780
- }
13781
- }
13782
- if (this.config.cleanAfterUpload) {
13783
- const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
13784
- const sizePurged = chunksToClear.reduce((acc, meta) => acc + meta.chunk.blob.size, 0);
13785
- await this.chunkStorage.clearUploadedChunks(this.proctoringId);
13786
- if (sizePurged > 0) {
13787
- this.totalBytesPurged += sizePurged;
13788
- this.saveSessionState();
13789
- console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
13790
- }
13791
- }
13792
- if (isFinal) {
13793
- this.clearSessionState();
13794
- }
13795
- } catch (error) {
13796
- console.error("[BackgroundUpload] Falha no upload:", error);
13797
- (_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
13798
- }
13799
- } catch (error) {
13800
- console.error("[BackgroundUpload] Erro ao processar fila:", error);
13801
- } finally {
13802
- this.isProcessing = false;
13803
- }
13804
- }
13805
- /**
13806
- * Faz o upload bruto de dados para a sessão GCS.
13807
- */
13808
- async uploadData(blob, mimeType, chunkIndex, totalSize) {
13809
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
13810
- if (!this.sessionUrl) {
13811
- const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
13812
- const startResponse = await fetch(initiateUrl, {
13813
- method: "POST",
13814
- headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
13815
- });
13816
- if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
13817
- this.sessionUrl = startResponse.headers.get("Location");
13818
- if (!this.sessionUrl) throw new Error("Location header ausente");
13819
- try {
13820
- const urlObj = new URL(this.sessionUrl);
13821
- const pathParts = urlObj.pathname.split("/");
13822
- let bucket = pathParts[1];
13823
- let object = decodeURIComponent(pathParts.slice(2).join("/"));
13824
- if (pathParts.includes("b") && pathParts.includes("o")) {
13825
- const bIdx = pathParts.indexOf("b") + 1;
13826
- const oIdx = pathParts.indexOf("o") + 1;
13827
- bucket = pathParts[bIdx];
13828
- object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
13829
- }
13830
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
13831
- } catch (e3) {
13832
- console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
13833
- }
13834
- this.currentOffset = 0;
13835
- this.saveSessionState();
13836
- } else {
13837
- console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
13838
- }
13839
- const start = this.currentOffset;
13840
- const end = start + blob.size - 1;
13841
- const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
13842
- const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
13843
- console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
13844
- const response = await fetch(this.sessionUrl, {
13845
- method: "PUT",
13846
- headers: { "Content-Range": contentRangeHeader },
13847
- body: blob.size > 0 ? blob : null
13848
- // Usa null para garantir corpo vazio se necessário
13849
- });
13850
- console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
13851
- if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
13852
- const errorText = await response.text();
13853
- console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
13854
- throw new Error(`Status HTTP inesperado: ${response.status}`);
13855
- }
13856
- const rangeHeader = response.headers.get("Range");
13857
- if (rangeHeader) {
13858
- const lastByte = parseInt(rangeHeader.split("-")[1], 10);
13859
- this.currentOffset = lastByte + 1;
13860
- } else {
13861
- this.currentOffset += blob.size;
13862
- }
13863
- this.saveSessionState();
13864
- trackers.registerUploadFile(
13865
- this.proctoringId,
13866
- `GCS Stream Upload
13867
- Size: ${blob.size}
13868
- Range: ${start}-${end}
13869
- Last Index: ${chunkIndex}`,
13870
- "CameraChunk"
13871
- );
13872
- }
13873
- /**
13874
- * Método estático para recuperação pós-crash.
13875
- * Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
13876
- * e tenta enviar.
13877
- */
13878
- static async recoverPendingUploads(backend, token) {
13879
- const chunkStorage = new ChunkStorageService();
13880
- const recoveredIds = [];
13881
- try {
13882
- const pendingIds = await chunkStorage.getPendingProctoringIds();
13883
- if (pendingIds.length === 0) {
13884
- console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
13885
- return recoveredIds;
13886
- }
13887
- console.log(
13888
- `[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
13889
- );
13890
- for (const proctoringId2 of pendingIds) {
13891
- try {
13892
- const service = new _BackgroundUploadService(
13893
- proctoringId2,
13894
- token,
13895
- backend,
13896
- chunkStorage,
13897
- { cleanAfterUpload: true }
13898
- );
13899
- await service.flush();
13900
- recoveredIds.push(proctoringId2);
13901
- console.log(
13902
- `[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
13903
- );
13904
- } catch (error) {
13905
- console.error(
13906
- `[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
13907
- error
13908
- );
13909
- }
13910
- }
13911
- } catch (error) {
13912
- console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
13913
- }
13914
- return recoveredIds;
13915
- }
13916
- sleep(ms2) {
13917
- return new Promise((resolve) => setTimeout(resolve, ms2));
13918
- }
13919
- };
13920
-
13921
13304
  // src/new-flow/recorders/CameraRecorder.ts
13922
13305
  var import_jszip_min = __toESM(require_jszip_min());
13923
13306
  var pkg = require_fix_webm_duration();
13924
13307
  var fixWebmDuration = pkg.default || pkg;
13925
- var _CameraRecorder = class _CameraRecorder {
13308
+ var CameraRecorder = class {
13926
13309
  constructor(options, videoOptions, paramsConfig, backend, backendToken) {
13927
13310
  this.blobs = [];
13928
13311
  this.paramsConfig = {
@@ -13975,13 +13358,6 @@ var _CameraRecorder = class _CameraRecorder {
13975
13358
  this.videoElement = null;
13976
13359
  this.duration = 0;
13977
13360
  this.stopped = false;
13978
- this.backgroundUpload = null;
13979
- this.chunkIndex = 0;
13980
- /** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
13981
- this.pendingChunkSaves = [];
13982
- // Handlers bound para poder remover os listeners depois
13983
- this.boundVisibilityHandler = null;
13984
- this.boundPageHideHandler = null;
13985
13361
  this.currentRetries = 0;
13986
13362
  this.packageCount = 0;
13987
13363
  this.failedUploads = 0;
@@ -13992,122 +13368,10 @@ var _CameraRecorder = class _CameraRecorder {
13992
13368
  this.backendToken = backendToken;
13993
13369
  paramsConfig && (this.paramsConfig = paramsConfig);
13994
13370
  }
13995
- /**
13996
- * Determina se o fluxo de chunks e lifecycle deve estar ativo.
13997
- * Retorna true se:
13998
- * 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
13999
- * 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
14000
- */
14001
- get isChunkEnabled() {
14002
- return false;
14003
- }
14004
13371
  setProctoringId(proctoringId2) {
14005
13372
  this.proctoringId = proctoringId2;
14006
13373
  this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
14007
13374
  setRecorderProctoringId(proctoringId2);
14008
- if (this.isChunkEnabled) {
14009
- this.chunkStorage = new ChunkStorageService();
14010
- if (this.backend && this.backendToken) {
14011
- this.backgroundUpload = new BackgroundUploadService(
14012
- this.proctoringId,
14013
- this.backendToken,
14014
- this.backend,
14015
- this.chunkStorage,
14016
- { pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
14017
- );
14018
- }
14019
- this.persistSessionState("IN_PROGRESS");
14020
- console.log(
14021
- `[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
14022
- );
14023
- } else {
14024
- console.log(
14025
- `[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
14026
- );
14027
- }
14028
- }
14029
- // ========================
14030
- // Session State Persistence (localStorage)
14031
- // ========================
14032
- persistSessionState(status) {
14033
- try {
14034
- const data = {
14035
- proctoringId: this.proctoringId,
14036
- status,
14037
- timestamp: Date.now()
14038
- };
14039
- localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
14040
- } catch (e3) {
14041
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
14042
- }
14043
- }
14044
- clearSessionState() {
14045
- try {
14046
- localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
14047
- } catch (e3) {
14048
- console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
14049
- }
14050
- }
14051
- /**
14052
- * Verifica se existe uma sessão ativa anterior no localStorage.
14053
- * Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
14054
- */
14055
- static checkForActiveSession() {
14056
- try {
14057
- const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
14058
- if (!raw) return null;
14059
- const data = JSON.parse(raw);
14060
- if (data.status === "IN_PROGRESS") return data;
14061
- return null;
14062
- } catch (e3) {
14063
- return null;
14064
- }
14065
- }
14066
- // ========================
14067
- // Page Lifecycle Management
14068
- // ========================
14069
- setupLifecycleListeners() {
14070
- this.boundVisibilityHandler = () => this.handleVisibilityChange();
14071
- this.boundPageHideHandler = () => this.handlePageHide();
14072
- document.addEventListener("visibilitychange", this.boundVisibilityHandler);
14073
- window.addEventListener("pagehide", this.boundPageHideHandler);
14074
- }
14075
- removeLifecycleListeners() {
14076
- if (this.boundVisibilityHandler) {
14077
- document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
14078
- this.boundVisibilityHandler = null;
14079
- }
14080
- if (this.boundPageHideHandler) {
14081
- window.removeEventListener("pagehide", this.boundPageHideHandler);
14082
- this.boundPageHideHandler = null;
14083
- }
14084
- }
14085
- handleVisibilityChange() {
14086
- var _a2;
14087
- if (document.visibilityState === "hidden") {
14088
- console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
14089
- this.persistSessionState("INTERRUPTED");
14090
- this.proctoringId && trackers.registerError(
14091
- this.proctoringId,
14092
- "Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
14093
- );
14094
- } else if (document.visibilityState === "visible") {
14095
- console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
14096
- this.persistSessionState("IN_PROGRESS");
14097
- this.proctoringId && trackers.registerError(
14098
- this.proctoringId,
14099
- "Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
14100
- );
14101
- (_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
14102
- }
14103
- }
14104
- handlePageHide() {
14105
- console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
14106
- this.persistSessionState("INTERRUPTED");
14107
- this.proctoringId && trackers.registerError(
14108
- this.proctoringId,
14109
- "Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
14110
- );
14111
13375
  }
14112
13376
  async initializeDetectors() {
14113
13377
  var _a2, _b, _c2;
@@ -14260,15 +13524,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14260
13524
  await new Promise((r3) => setTimeout(r3, 300));
14261
13525
  }
14262
13526
  async startRecording() {
14263
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
13527
+ var _a2, _b, _c2, _d, _e3, _f, _g;
14264
13528
  await this.startStream();
14265
13529
  await this.attachAndWarmup(this.cameraStream);
14266
- const recorderOpts = this.isChunkEnabled ? {
14267
- timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
14268
- onChunkAvailable: (blob, idx) => {
14269
- this.handleNewChunk(blob, idx);
14270
- }
14271
- } : {};
14272
13530
  const {
14273
13531
  startRecording,
14274
13532
  stopRecording,
@@ -14284,8 +13542,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14284
13542
  this.blobs,
14285
13543
  this.options.onBufferSizeError,
14286
13544
  (e3) => this.bufferError(e3),
14287
- false,
14288
- recorderOpts
13545
+ false
14289
13546
  );
14290
13547
  this.recordingStart = startRecording;
14291
13548
  this.recordingStop = stopRecording;
@@ -14295,18 +13552,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14295
13552
  this.getBufferSize = getBufferSize;
14296
13553
  this.getStartTime = getStartTime;
14297
13554
  this.getDuration = getDuration;
14298
- this.chunkIndex = 0;
14299
- if (this.isChunkEnabled) {
14300
- (_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
14301
- this.setupLifecycleListeners();
14302
- }
14303
13555
  try {
14304
13556
  await new Promise((r3) => setTimeout(r3, 500));
14305
13557
  await this.recordingStart();
14306
13558
  } catch (error) {
14307
13559
  console.log("Camera Recorder error", error);
14308
13560
  this.stopRecording();
14309
- const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
13561
+ const maxRetries = ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.maxRetries) || 3;
14310
13562
  if (this.currentRetries < maxRetries) {
14311
13563
  console.log("Camera Recorder retry", this.currentRetries);
14312
13564
  this.currentRetries++;
@@ -14316,13 +13568,13 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14316
13568
  }
14317
13569
  }
14318
13570
  this.stopped = false;
14319
- 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)) {
13571
+ 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)) {
14320
13572
  await this.initializeDetectors();
14321
13573
  }
14322
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
13574
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
14323
13575
  await this.faceDetection.enableCam(this.cameraStream);
14324
13576
  }
14325
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
13577
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
14326
13578
  await this.objectDetection.enableCam(this.cameraStream);
14327
13579
  }
14328
13580
  this.filesToUpload = [];
@@ -14381,50 +13633,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14381
13633
  await this.sendPackage();
14382
13634
  await this.filesToUpload.splice(0, this.filesToUpload.length);
14383
13635
  }
14384
- if (this.isChunkEnabled) {
14385
- if (this.backgroundUpload) {
14386
- try {
14387
- if (this.pendingChunkSaves.length > 0) {
14388
- console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
14389
- await Promise.all(this.pendingChunkSaves);
14390
- }
14391
- await this.backgroundUpload.flush();
14392
- } catch (e3) {
14393
- console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
14394
- }
14395
- this.backgroundUpload.stop();
14396
- }
14397
- this.removeLifecycleListeners();
14398
- this.persistSessionState("FINISHED");
14399
- }
14400
- }
14401
- /**
14402
- * Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
14403
- * Salva o chunk no IndexedDB para persistência e recuperação.
14404
- */
14405
- async handleNewChunk(blob, idx) {
14406
- if (!this.proctoringId || !this.chunkStorage) return;
14407
- const savePromise = (async () => {
14408
- var _a2;
14409
- try {
14410
- await this.chunkStorage.saveChunk({
14411
- proctoringId: this.proctoringId,
14412
- chunkIndex: this.chunkIndex,
14413
- blob,
14414
- timestamp: Date.now(),
14415
- uploaded: 0,
14416
- mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
14417
- });
14418
- this.chunkIndex++;
14419
- console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
14420
- } catch (error) {
14421
- console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
14422
- }
14423
- })();
14424
- this.pendingChunkSaves.push(savePromise);
14425
- savePromise.finally(() => {
14426
- this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
14427
- });
14428
13636
  }
14429
13637
  async pauseRecording() {
14430
13638
  await this.recordingPause();
@@ -14560,36 +13768,16 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14560
13768
  if (this.blobs != null)
14561
13769
  trackers.registerSaveOnSession(
14562
13770
  this.proctoringId,
14563
- `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
13771
+ `Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
14564
13772
  );
14565
13773
  const settings = this.cameraStream.getVideoTracks()[0].getSettings();
14566
13774
  const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
14567
13775
  if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
14568
- let videoFile;
14569
- if (this.isChunkEnabled) {
14570
- const isStable = await this.checkInternetStability();
14571
- if (isStable) {
14572
- } else {
14573
- if (this.backend && this.backendToken && this.proctoringId) {
14574
- const fileName = `EP_${this.proctoringId}_camera_0.webm`;
14575
- const objectName = `${this.proctoringId}/${fileName}`;
14576
- const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
14577
- if (isUploaded) {
14578
- this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
14579
- return;
14580
- }
14581
- }
14582
- }
14583
- }
14584
13776
  const rawBlob = new Blob(this.blobs, {
14585
13777
  type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
14586
13778
  });
14587
13779
  const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
14588
- videoFile = new File(
14589
- [fixedBlob],
14590
- `EP_${session.id}_camera_0.webm`,
14591
- { type: rawBlob.type }
14592
- );
13780
+ const arrayBuffer = await fixedBlob.arrayBuffer();
14593
13781
  session.addRecording({
14594
13782
  device: `Audio
14595
13783
  Sample Rate: ${settingsAudio.sampleRate}
@@ -14597,7 +13785,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14597
13785
 
14598
13786
  Video:
14599
13787
  ${JSON.stringify(this.recorderOptions)}`,
14600
- file: videoFile,
13788
+ arrayBuffer,
14601
13789
  origin: "Camera" /* Camera */
14602
13790
  });
14603
13791
  }
@@ -14612,28 +13800,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14612
13800
  });
14613
13801
  });
14614
13802
  }
14615
- /**
14616
- * Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
14617
- */
14618
- async checkInternetStability() {
14619
- var _a2;
14620
- if (!navigator.onLine) return false;
14621
- try {
14622
- const controller = new AbortController();
14623
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
14624
- const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
14625
- if (!baseUrl) return true;
14626
- const response = await fetch(`${baseUrl}/Client/health`, {
14627
- method: "GET",
14628
- signal: controller.signal
14629
- });
14630
- clearTimeout(timeoutId);
14631
- return response.status < 500;
14632
- } catch (e3) {
14633
- console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
14634
- return false;
14635
- }
14636
- }
14637
13803
  onNoiseDetected() {
14638
13804
  var _a2, _b, _c2;
14639
13805
  if (this.options.proctoringType === "REALTIME") return;
@@ -14658,14 +13824,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
14658
13824
  this.noiseWait++;
14659
13825
  }
14660
13826
  };
14661
- // ========================
14662
- // Chunk & Lifecycle
14663
- // ========================
14664
- /** Intervalo de cada chunk em ms (padrão: 60 segundos) */
14665
- _CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
14666
- /** Chave do localStorage para persistir estado da sessão */
14667
- _CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
14668
- var CameraRecorder = _CameraRecorder;
14669
13827
 
14670
13828
  // src/new-flow/checkers/DeviceCheckerUI.ts
14671
13829
  var DeviceCheckerUI = class {
@@ -16582,46 +15740,60 @@ var ProctoringUploader = class {
16582
15740
  globalOnProgres(100);
16583
15741
  }
16584
15742
  }
15743
+ toFile(rec) {
15744
+ const suffix = rec.origin === "Screen" /* Screen */ ? "screen" : rec.origin === "Camera" /* Camera */ ? "camera" : "audio";
15745
+ const name = `EP_${this.proctoringId}_${suffix}_0.webm`;
15746
+ return new File([rec.arrayBuffer], name, { type: "video/webm" });
15747
+ }
15748
+ getFileType(origin2) {
15749
+ switch (origin2) {
15750
+ case "Camera" /* Camera */:
15751
+ return "Camera";
15752
+ case "Screen" /* Screen */:
15753
+ return "Screen";
15754
+ case "Mic" /* Mic */:
15755
+ return "Audio";
15756
+ }
15757
+ }
16585
15758
  async uploadFile(rec, token, onProgress) {
15759
+ const file = this.toFile(rec);
16586
15760
  for await (const uploadService of this.uploadServices) {
16587
15761
  const result = await uploadService.upload(
16588
15762
  {
16589
- file: rec.file,
15763
+ file,
16590
15764
  onProgress: (progress) => {
16591
15765
  if (onProgress) onProgress(progress);
16592
15766
  }
16593
15767
  },
16594
15768
  token
16595
- ).catch(
16596
- async (e3) => {
16597
- console.log("Upload File Error", e3), trackers.registerError(this.proctoringId, `Upload File
16598
- Name: ${rec.file.name}
15769
+ ).catch(async (e3) => {
15770
+ console.log("Upload File Error", e3), trackers.registerError(
15771
+ this.proctoringId,
15772
+ `Upload File
15773
+ Name: ${file.name}
16599
15774
  Error: ${e3.message}
16600
- Size: ${e3.error}`);
16601
- await uploadService.upload(
16602
- {
16603
- file: rec.file,
16604
- onProgress: (progress) => {
16605
- if (onProgress) onProgress(progress);
16606
- }
16607
- },
16608
- token
16609
- );
16610
- }
16611
- );
15775
+ Size: ${e3.error}`
15776
+ );
15777
+ await uploadService.upload(
15778
+ {
15779
+ file,
15780
+ onProgress: (progress) => {
15781
+ if (onProgress) onProgress(progress);
15782
+ }
15783
+ },
15784
+ token
15785
+ );
15786
+ });
16612
15787
  if (result) {
16613
- let fileType = "";
16614
- if (rec.file.name.search("camera") !== -1) {
16615
- fileType = "Camera";
16616
- } else if (rec.file.name.search("screen") !== -1) {
16617
- fileType = "Screen";
16618
- } else if (rec.file.name.search("audio") !== -1) {
16619
- fileType = "Audio";
16620
- }
16621
- trackers.registerUploadFile(this.proctoringId, `Upload File
16622
- Name: ${rec.file.name}
16623
- Type: ${rec.file.type}
16624
- Size: ${rec.file.size}`, fileType);
15788
+ const fileType = this.getFileType(rec.origin);
15789
+ trackers.registerUploadFile(
15790
+ this.proctoringId,
15791
+ `Upload File
15792
+ Name: ${file.name}
15793
+ Type: ${file.type}
15794
+ Size: ${file.size}`,
15795
+ fileType
15796
+ );
16625
15797
  return result;
16626
15798
  }
16627
15799
  }
@@ -19431,14 +18603,10 @@ var ScreenRecorder = class {
19431
18603
  type: "video/webm"
19432
18604
  });
19433
18605
  const fixedBlob = await fixWebmDuration2(rawBlob, this.duration);
19434
- const file = new File(
19435
- [fixedBlob],
19436
- `EP_${session.id}_screen_0.webm`,
19437
- { type: rawBlob.type }
19438
- );
18606
+ const arrayBuffer = await fixedBlob.arrayBuffer();
19439
18607
  session.addRecording({
19440
18608
  device: "",
19441
- file,
18609
+ arrayBuffer,
19442
18610
  origin: "Screen" /* Screen */
19443
18611
  });
19444
18612
  }
@@ -23638,20 +22806,6 @@ var Proctoring = class {
23638
22806
  } catch (error) {
23639
22807
  throw SAFE_BROWSER_API_NOT_FOUND;
23640
22808
  }
23641
- this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
23642
- console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
23643
- this.onVisibilityRestoredCallback();
23644
- };
23645
- if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
23646
- try {
23647
- await BackgroundUploadService.recoverPendingUploads(
23648
- this.backend,
23649
- this.context.token
23650
- );
23651
- } catch (e3) {
23652
- console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
23653
- }
23654
- }
23655
22809
  try {
23656
22810
  console.log("Starting recorders");
23657
22811
  await this.recorder.startAll();
@@ -23732,7 +22886,9 @@ Error: ${error}`
23732
22886
  this.appChecker && await this.appChecker.disconnectWebSocket();
23733
22887
  await this.recorder.saveAllOnSession();
23734
22888
  await this.sendPendingRealtimeAlerts();
22889
+ trackers.registerError(this.proctoringId, `finish this.repository.save starting`);
23735
22890
  await this.repository.save(this.proctoringSession);
22891
+ trackers.registerError(this.proctoringId, `finish this.repository.save finished`);
23736
22892
  let uploader;
23737
22893
  let uploaderServices;
23738
22894
  if (versionVerify() !== "1.0.0.0") {