easyproctor-hml 2.7.13 → 2.8.0
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 +1078 -85
- package/extension/extensionEasyCatcher.d.ts +11 -0
- package/extension/{extension.d.ts → extensionEasyProctor.d.ts} +1 -1
- package/index.js +1078 -85
- package/new-flow/checkers/DeviceCheckerUI.d.ts +11 -0
- package/new-flow/chunk/BackgroundUploadService.d.ts +38 -0
- package/new-flow/chunk/ChunkStorageService.d.ts +25 -0
- package/new-flow/recorders/CameraRecorder.d.ts +23 -2
- package/package.json +1 -1
- package/proctoring/options/ProctoringOptions.d.ts +1 -0
- package/proctoring/proctoring.d.ts +5 -0
- package/proctoring/useProctoring.d.ts +2 -0
- package/unpkg/easyproctor.min.js +44 -41
package/index.js
CHANGED
|
@@ -26625,6 +26625,9 @@ var FaceDetection = class extends BaseDetection {
|
|
|
26625
26625
|
if (this.emmitedFaceAlert) {
|
|
26626
26626
|
this.handleOk("face_stop", "face_detection_on_stream");
|
|
26627
26627
|
}
|
|
26628
|
+
this.numFacesSent = -1;
|
|
26629
|
+
this.emmitedPositionAlert = false;
|
|
26630
|
+
this.emmitedFaceAlert = false;
|
|
26628
26631
|
}
|
|
26629
26632
|
// displayVideoDetections(result: { detections: any; }) {
|
|
26630
26633
|
// // console.log(result);
|
|
@@ -30796,7 +30799,8 @@ var getDefaultProctoringOptions = {
|
|
|
30796
30799
|
useSpyScan: false,
|
|
30797
30800
|
useExternalCamera: false,
|
|
30798
30801
|
useChallenge: false,
|
|
30799
|
-
screenRecorderOptions: { width: 1280, height: 720 }
|
|
30802
|
+
screenRecorderOptions: { width: 1280, height: 720 },
|
|
30803
|
+
auto: false
|
|
30800
30804
|
};
|
|
30801
30805
|
|
|
30802
30806
|
// src/proctoring/options/ProctoringVideoOptions.ts
|
|
@@ -30855,6 +30859,9 @@ function isMobileDevice() {
|
|
|
30855
30859
|
}
|
|
30856
30860
|
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
30857
30861
|
}
|
|
30862
|
+
function isSafeBrowser() {
|
|
30863
|
+
return versionVerify() !== "1.0.0.0";
|
|
30864
|
+
}
|
|
30858
30865
|
|
|
30859
30866
|
// src/plugins/recorder.ts
|
|
30860
30867
|
var proctoringId;
|
|
@@ -31348,61 +31355,623 @@ var ObjectDetection = class extends BaseDetection {
|
|
|
31348
31355
|
}
|
|
31349
31356
|
};
|
|
31350
31357
|
|
|
31351
|
-
// src/new-flow/recorders/
|
|
31352
|
-
var
|
|
31353
|
-
|
|
31354
|
-
|
|
31355
|
-
|
|
31356
|
-
|
|
31358
|
+
// src/new-flow/recorders/CameraRecorder.ts
|
|
31359
|
+
var import_jszip_min = __toESM(require_jszip_min());
|
|
31360
|
+
|
|
31361
|
+
// src/new-flow/chunk/ChunkStorageService.ts
|
|
31362
|
+
var _ChunkStorageService = class _ChunkStorageService {
|
|
31363
|
+
constructor() {
|
|
31364
|
+
this.db = null;
|
|
31357
31365
|
}
|
|
31358
|
-
|
|
31366
|
+
/**
|
|
31367
|
+
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
31368
|
+
*/
|
|
31369
|
+
async connect() {
|
|
31370
|
+
if (this.db) return this.db;
|
|
31359
31371
|
return new Promise((resolve, reject) => {
|
|
31360
|
-
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
|
|
31369
|
-
|
|
31370
|
-
|
|
31371
|
-
|
|
31372
|
-
|
|
31373
|
-
|
|
31374
|
-
|
|
31372
|
+
const request = window.indexedDB.open(
|
|
31373
|
+
_ChunkStorageService.DB_NAME,
|
|
31374
|
+
_ChunkStorageService.DB_VERSION
|
|
31375
|
+
);
|
|
31376
|
+
request.onerror = () => {
|
|
31377
|
+
reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
|
|
31378
|
+
};
|
|
31379
|
+
request.onupgradeneeded = () => {
|
|
31380
|
+
const db = request.result;
|
|
31381
|
+
if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
|
|
31382
|
+
db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
|
|
31383
|
+
}
|
|
31384
|
+
const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
|
|
31385
|
+
keyPath: "id",
|
|
31386
|
+
autoIncrement: true
|
|
31387
|
+
});
|
|
31388
|
+
store.createIndex("proctoringId", "proctoringId", { unique: false });
|
|
31389
|
+
store.createIndex("uploaded", "uploaded", { unique: false });
|
|
31390
|
+
store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
|
|
31391
|
+
unique: false
|
|
31392
|
+
});
|
|
31393
|
+
};
|
|
31394
|
+
request.onsuccess = () => {
|
|
31395
|
+
this.db = request.result;
|
|
31396
|
+
resolve(this.db);
|
|
31397
|
+
};
|
|
31398
|
+
});
|
|
31399
|
+
}
|
|
31400
|
+
/**
|
|
31401
|
+
* Salva um chunk de vídeo no IndexedDB.
|
|
31402
|
+
*/
|
|
31403
|
+
async saveChunk(chunk) {
|
|
31404
|
+
const db = await this.connect();
|
|
31405
|
+
return new Promise((resolve, reject) => {
|
|
31406
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31407
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31408
|
+
const request = store.add(chunk);
|
|
31409
|
+
request.onsuccess = () => {
|
|
31410
|
+
resolve(request.result);
|
|
31411
|
+
};
|
|
31412
|
+
request.onerror = () => {
|
|
31413
|
+
var _a2;
|
|
31414
|
+
reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31415
|
+
};
|
|
31416
|
+
});
|
|
31417
|
+
}
|
|
31418
|
+
/**
|
|
31419
|
+
* Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
|
|
31420
|
+
*/
|
|
31421
|
+
async getPendingChunks(proctoringId2) {
|
|
31422
|
+
const db = await this.connect();
|
|
31423
|
+
return new Promise((resolve, reject) => {
|
|
31424
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31425
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31426
|
+
const index = store.index("proctoringId_uploaded");
|
|
31427
|
+
const range = IDBKeyRange.only([proctoringId2, 0]);
|
|
31428
|
+
const request = index.getAll(range);
|
|
31429
|
+
request.onsuccess = () => {
|
|
31430
|
+
const chunks = request.result.sort(
|
|
31431
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31432
|
+
);
|
|
31433
|
+
resolve(chunks);
|
|
31434
|
+
};
|
|
31435
|
+
request.onerror = () => {
|
|
31436
|
+
var _a2;
|
|
31437
|
+
reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31438
|
+
};
|
|
31439
|
+
});
|
|
31440
|
+
}
|
|
31441
|
+
/**
|
|
31442
|
+
* Retorna todos os chunks (enviados ou não) de um proctoringId específico.
|
|
31443
|
+
*/
|
|
31444
|
+
async getAllChunks(proctoringId2) {
|
|
31445
|
+
const db = await this.connect();
|
|
31446
|
+
return new Promise((resolve, reject) => {
|
|
31447
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31448
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31449
|
+
const index = store.index("proctoringId");
|
|
31450
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
31451
|
+
const request = index.getAll(range);
|
|
31452
|
+
request.onsuccess = () => {
|
|
31453
|
+
const chunks = request.result.sort(
|
|
31454
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31455
|
+
);
|
|
31456
|
+
resolve(chunks);
|
|
31457
|
+
};
|
|
31458
|
+
request.onerror = () => {
|
|
31459
|
+
var _a2;
|
|
31460
|
+
reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31461
|
+
};
|
|
31462
|
+
});
|
|
31463
|
+
}
|
|
31464
|
+
/**
|
|
31465
|
+
* Marca um chunk como enviado (uploaded = 1).
|
|
31466
|
+
*/
|
|
31467
|
+
async markAsUploaded(chunkId) {
|
|
31468
|
+
const db = await this.connect();
|
|
31469
|
+
return new Promise((resolve, reject) => {
|
|
31470
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31471
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31472
|
+
const getRequest = store.get(chunkId);
|
|
31473
|
+
getRequest.onsuccess = () => {
|
|
31474
|
+
const chunk = getRequest.result;
|
|
31475
|
+
if (!chunk) {
|
|
31476
|
+
resolve();
|
|
31477
|
+
return;
|
|
31478
|
+
}
|
|
31479
|
+
chunk.uploaded = 1;
|
|
31480
|
+
const putRequest = store.put(chunk);
|
|
31481
|
+
putRequest.onsuccess = () => resolve();
|
|
31482
|
+
putRequest.onerror = () => {
|
|
31483
|
+
var _a2;
|
|
31484
|
+
return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
|
|
31375
31485
|
};
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31486
|
+
};
|
|
31487
|
+
getRequest.onerror = () => {
|
|
31488
|
+
var _a2;
|
|
31489
|
+
return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
|
|
31490
|
+
};
|
|
31491
|
+
});
|
|
31492
|
+
}
|
|
31493
|
+
/**
|
|
31494
|
+
* Remove todos os chunks já enviados de um proctoringId para liberar espaço.
|
|
31495
|
+
*/
|
|
31496
|
+
async clearUploadedChunks(proctoringId2) {
|
|
31497
|
+
const db = await this.connect();
|
|
31498
|
+
return new Promise((resolve, reject) => {
|
|
31499
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31500
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31501
|
+
const index = store.index("proctoringId_uploaded");
|
|
31502
|
+
const range = IDBKeyRange.only([proctoringId2, 1]);
|
|
31503
|
+
const request = index.openCursor(range);
|
|
31504
|
+
request.onsuccess = () => {
|
|
31505
|
+
const cursor = request.result;
|
|
31506
|
+
if (cursor) {
|
|
31507
|
+
cursor.delete();
|
|
31508
|
+
cursor.continue();
|
|
31509
|
+
} else {
|
|
31510
|
+
resolve();
|
|
31511
|
+
}
|
|
31512
|
+
};
|
|
31513
|
+
request.onerror = () => {
|
|
31514
|
+
var _a2;
|
|
31515
|
+
return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31516
|
+
};
|
|
31517
|
+
});
|
|
31518
|
+
}
|
|
31519
|
+
/**
|
|
31520
|
+
* Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
|
|
31521
|
+
*/
|
|
31522
|
+
async clearAllChunks(proctoringId2) {
|
|
31523
|
+
const db = await this.connect();
|
|
31524
|
+
return new Promise((resolve, reject) => {
|
|
31525
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31526
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31527
|
+
const index = store.index("proctoringId");
|
|
31528
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
31529
|
+
const request = index.openCursor(range);
|
|
31530
|
+
request.onsuccess = () => {
|
|
31531
|
+
const cursor = request.result;
|
|
31532
|
+
if (cursor) {
|
|
31533
|
+
cursor.delete();
|
|
31534
|
+
cursor.continue();
|
|
31535
|
+
} else {
|
|
31536
|
+
resolve();
|
|
31537
|
+
}
|
|
31538
|
+
};
|
|
31539
|
+
request.onerror = () => {
|
|
31540
|
+
var _a2;
|
|
31541
|
+
return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31542
|
+
};
|
|
31543
|
+
});
|
|
31544
|
+
}
|
|
31545
|
+
/**
|
|
31546
|
+
* Verifica se existem chunks pendentes para qualquer proctoringId.
|
|
31547
|
+
* Útil na recuperação pós-crash.
|
|
31548
|
+
*/
|
|
31549
|
+
async hasAnyPendingChunks() {
|
|
31550
|
+
const db = await this.connect();
|
|
31551
|
+
return new Promise((resolve, reject) => {
|
|
31552
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31553
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31554
|
+
const index = store.index("uploaded");
|
|
31555
|
+
const range = IDBKeyRange.only(0);
|
|
31556
|
+
const request = index.count(range);
|
|
31557
|
+
request.onsuccess = () => {
|
|
31558
|
+
resolve(request.result > 0);
|
|
31559
|
+
};
|
|
31560
|
+
request.onerror = () => {
|
|
31561
|
+
var _a2;
|
|
31562
|
+
return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31563
|
+
};
|
|
31382
31564
|
});
|
|
31383
31565
|
}
|
|
31566
|
+
/**
|
|
31567
|
+
* Retorna todos os proctoringIds que possuem chunks pendentes.
|
|
31568
|
+
* Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
|
|
31569
|
+
*/
|
|
31570
|
+
async getPendingProctoringIds() {
|
|
31571
|
+
const db = await this.connect();
|
|
31572
|
+
return new Promise((resolve, reject) => {
|
|
31573
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31574
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31575
|
+
const index = store.index("uploaded");
|
|
31576
|
+
const range = IDBKeyRange.only(0);
|
|
31577
|
+
const request = index.getAll(range);
|
|
31578
|
+
request.onsuccess = () => {
|
|
31579
|
+
const chunks = request.result;
|
|
31580
|
+
const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
|
|
31581
|
+
resolve(ids);
|
|
31582
|
+
};
|
|
31583
|
+
request.onerror = () => {
|
|
31584
|
+
var _a2;
|
|
31585
|
+
return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31586
|
+
};
|
|
31587
|
+
});
|
|
31588
|
+
}
|
|
31589
|
+
/**
|
|
31590
|
+
* Fecha a conexão com o banco.
|
|
31591
|
+
*/
|
|
31592
|
+
close() {
|
|
31593
|
+
if (this.db) {
|
|
31594
|
+
this.db.close();
|
|
31595
|
+
this.db = null;
|
|
31596
|
+
}
|
|
31597
|
+
}
|
|
31598
|
+
};
|
|
31599
|
+
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
31600
|
+
/** v2: índices uploaded numéricos; v3: campo arrayBuffer em vez de blob */
|
|
31601
|
+
_ChunkStorageService.DB_VERSION = 3;
|
|
31602
|
+
_ChunkStorageService.STORE_NAME = "chunks";
|
|
31603
|
+
var ChunkStorageService = _ChunkStorageService;
|
|
31604
|
+
|
|
31605
|
+
// src/new-flow/chunk/BackgroundUploadService.ts
|
|
31606
|
+
var DEFAULT_CONFIG = {
|
|
31607
|
+
pollInterval: 5e3,
|
|
31608
|
+
maxRetries: 5,
|
|
31609
|
+
baseRetryDelay: 2e3,
|
|
31610
|
+
cleanAfterUpload: true
|
|
31611
|
+
};
|
|
31612
|
+
var BackgroundUploadService = class _BackgroundUploadService {
|
|
31613
|
+
constructor(proctoringId2, token, backend, chunkStorage, config) {
|
|
31614
|
+
this.pollTimer = null;
|
|
31615
|
+
this.isProcessing = false;
|
|
31616
|
+
this.isRunning = false;
|
|
31617
|
+
/** Mapa de chunkId -> número de tentativas já feitas */
|
|
31618
|
+
this.retryCount = /* @__PURE__ */ new Map();
|
|
31619
|
+
/** GCS Resumable Upload State */
|
|
31620
|
+
this.sessionUrl = null;
|
|
31621
|
+
this.currentOffset = 0;
|
|
31622
|
+
this.totalBytesPurged = 0;
|
|
31623
|
+
this.STORAGE_KEY_PREFIX = "ep_upload_session_";
|
|
31624
|
+
this.GCS_CHUNK_SIZE = 256 * 1024;
|
|
31625
|
+
this.proctoringId = proctoringId2.trim();
|
|
31626
|
+
this.token = token;
|
|
31627
|
+
this.backend = backend;
|
|
31628
|
+
this.chunkStorage = chunkStorage;
|
|
31629
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31630
|
+
this.loadSessionState();
|
|
31631
|
+
}
|
|
31632
|
+
loadSessionState() {
|
|
31633
|
+
try {
|
|
31634
|
+
const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31635
|
+
if (stored) {
|
|
31636
|
+
const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
|
|
31637
|
+
this.sessionUrl = sessionUrl;
|
|
31638
|
+
this.currentOffset = currentOffset;
|
|
31639
|
+
this.totalBytesPurged = totalBytesPurged || 0;
|
|
31640
|
+
}
|
|
31641
|
+
} catch (e3) {
|
|
31642
|
+
console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
|
|
31643
|
+
}
|
|
31644
|
+
}
|
|
31645
|
+
saveSessionState() {
|
|
31646
|
+
try {
|
|
31647
|
+
localStorage.setItem(
|
|
31648
|
+
`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
|
|
31649
|
+
JSON.stringify({
|
|
31650
|
+
sessionUrl: this.sessionUrl,
|
|
31651
|
+
currentOffset: this.currentOffset,
|
|
31652
|
+
totalBytesPurged: this.totalBytesPurged
|
|
31653
|
+
})
|
|
31654
|
+
);
|
|
31655
|
+
} catch (e3) {
|
|
31656
|
+
console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
|
|
31657
|
+
}
|
|
31658
|
+
}
|
|
31659
|
+
clearSessionState() {
|
|
31660
|
+
try {
|
|
31661
|
+
localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31662
|
+
this.sessionUrl = null;
|
|
31663
|
+
this.currentOffset = 0;
|
|
31664
|
+
this.totalBytesPurged = 0;
|
|
31665
|
+
} catch (e3) {
|
|
31666
|
+
console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
|
|
31667
|
+
}
|
|
31668
|
+
}
|
|
31669
|
+
/**
|
|
31670
|
+
* Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
|
|
31671
|
+
* para enviar chunks pendentes.
|
|
31672
|
+
*/
|
|
31673
|
+
start() {
|
|
31674
|
+
if (this.isRunning) return;
|
|
31675
|
+
this.isRunning = true;
|
|
31676
|
+
console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
|
|
31677
|
+
this.processQueue();
|
|
31678
|
+
this.pollTimer = setInterval(() => {
|
|
31679
|
+
this.processQueue(false);
|
|
31680
|
+
}, this.config.pollInterval);
|
|
31681
|
+
}
|
|
31682
|
+
/**
|
|
31683
|
+
* Para o serviço de upload em background.
|
|
31684
|
+
*/
|
|
31384
31685
|
stop() {
|
|
31385
|
-
|
|
31386
|
-
if (this.
|
|
31387
|
-
|
|
31686
|
+
this.isRunning = false;
|
|
31687
|
+
if (this.pollTimer) {
|
|
31688
|
+
clearInterval(this.pollTimer);
|
|
31689
|
+
this.pollTimer = null;
|
|
31388
31690
|
}
|
|
31389
|
-
(
|
|
31390
|
-
(_b = this.microphone) == null ? void 0 : _b.disconnect();
|
|
31391
|
-
(_c2 = this.analyser) == null ? void 0 : _c2.disconnect();
|
|
31691
|
+
console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
|
|
31392
31692
|
}
|
|
31393
|
-
|
|
31394
|
-
|
|
31693
|
+
/**
|
|
31694
|
+
* Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
|
|
31695
|
+
* Útil quando a gravação é finalizada.
|
|
31696
|
+
*/
|
|
31697
|
+
async flush() {
|
|
31698
|
+
console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
|
|
31699
|
+
let waitAttempts = 0;
|
|
31700
|
+
while (this.isProcessing && waitAttempts < 10) {
|
|
31701
|
+
await this.sleep(1e3);
|
|
31702
|
+
waitAttempts++;
|
|
31703
|
+
}
|
|
31704
|
+
let flushRetries = 0;
|
|
31705
|
+
const maxFlushRetries = 3;
|
|
31706
|
+
while (flushRetries < maxFlushRetries) {
|
|
31707
|
+
try {
|
|
31708
|
+
await this.processQueue(true);
|
|
31709
|
+
console.log(`[BackgroundUpload] Flush completado com sucesso.`);
|
|
31710
|
+
return;
|
|
31711
|
+
} catch (error) {
|
|
31712
|
+
flushRetries++;
|
|
31713
|
+
console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
|
|
31714
|
+
if (flushRetries < maxFlushRetries) {
|
|
31715
|
+
await this.sleep(2e3);
|
|
31716
|
+
}
|
|
31717
|
+
}
|
|
31718
|
+
}
|
|
31719
|
+
throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
|
|
31395
31720
|
}
|
|
31396
|
-
|
|
31397
|
-
|
|
31721
|
+
/**
|
|
31722
|
+
* Sincroniza o offset local com o estado real no Google Cloud Storage.
|
|
31723
|
+
*/
|
|
31724
|
+
async syncOffset() {
|
|
31725
|
+
if (!this.sessionUrl) return 0;
|
|
31726
|
+
try {
|
|
31727
|
+
console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
|
|
31728
|
+
const response = await fetch(this.sessionUrl, {
|
|
31729
|
+
method: "PUT",
|
|
31730
|
+
headers: {
|
|
31731
|
+
"Content-Range": "bytes */*"
|
|
31732
|
+
}
|
|
31733
|
+
});
|
|
31734
|
+
console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
|
|
31735
|
+
if (response.status === 308) {
|
|
31736
|
+
const range = response.headers.get("Range");
|
|
31737
|
+
if (range) {
|
|
31738
|
+
const lastByte = parseInt(range.split("-")[1], 10);
|
|
31739
|
+
this.currentOffset = lastByte + 1;
|
|
31740
|
+
this.saveSessionState();
|
|
31741
|
+
console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
|
|
31742
|
+
} else {
|
|
31743
|
+
this.currentOffset = 0;
|
|
31744
|
+
}
|
|
31745
|
+
} else if (response.ok || response.status === 201) {
|
|
31746
|
+
console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
|
|
31747
|
+
this.currentOffset = -1;
|
|
31748
|
+
} else {
|
|
31749
|
+
console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
|
|
31750
|
+
}
|
|
31751
|
+
} catch (error) {
|
|
31752
|
+
console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
|
|
31753
|
+
}
|
|
31754
|
+
return this.currentOffset;
|
|
31755
|
+
}
|
|
31756
|
+
/**
|
|
31757
|
+
* Verifica e envia chunks pendentes para o backend.
|
|
31758
|
+
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
31759
|
+
*/
|
|
31760
|
+
async processQueue(isFinal = false) {
|
|
31761
|
+
var _a2, _b;
|
|
31762
|
+
if (this.isProcessing) return;
|
|
31763
|
+
this.isProcessing = true;
|
|
31764
|
+
try {
|
|
31765
|
+
if (this.sessionUrl) {
|
|
31766
|
+
await this.syncOffset();
|
|
31767
|
+
if (this.currentOffset === -1) {
|
|
31768
|
+
console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
|
|
31769
|
+
this.clearSessionState();
|
|
31770
|
+
this.isProcessing = false;
|
|
31771
|
+
return;
|
|
31772
|
+
}
|
|
31773
|
+
}
|
|
31774
|
+
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
31775
|
+
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
31776
|
+
if (pendingChunks.length === 0 && !isFinal) {
|
|
31777
|
+
this.isProcessing = false;
|
|
31778
|
+
return;
|
|
31779
|
+
}
|
|
31780
|
+
console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
|
|
31781
|
+
let virtualStart = this.totalBytesPurged;
|
|
31782
|
+
const chunksWithMeta = allChunks.map((c3) => {
|
|
31783
|
+
const start = virtualStart;
|
|
31784
|
+
const byteLength = c3.arrayBuffer.byteLength;
|
|
31785
|
+
const end = start + byteLength - 1;
|
|
31786
|
+
virtualStart += byteLength;
|
|
31787
|
+
return { chunk: c3, start, end };
|
|
31788
|
+
});
|
|
31789
|
+
let combinedBlobParts = [];
|
|
31790
|
+
let lastProcessedChunkId = null;
|
|
31791
|
+
let finalChunkIndex = 0;
|
|
31792
|
+
let mimeType = pendingChunks[0].mimeType;
|
|
31793
|
+
for (const meta of chunksWithMeta) {
|
|
31794
|
+
if (this.currentOffset > meta.end) continue;
|
|
31795
|
+
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
31796
|
+
const sliceBuf = meta.chunk.arrayBuffer.slice(sliceStart);
|
|
31797
|
+
combinedBlobParts.push(new Blob([sliceBuf]));
|
|
31798
|
+
lastProcessedChunkId = meta.chunk.id;
|
|
31799
|
+
finalChunkIndex = meta.chunk.chunkIndex;
|
|
31800
|
+
}
|
|
31801
|
+
if (combinedBlobParts.length === 0 && !isFinal) {
|
|
31802
|
+
this.isProcessing = false;
|
|
31803
|
+
return;
|
|
31804
|
+
}
|
|
31805
|
+
let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
|
|
31806
|
+
let sendableSize = fullBlob.size;
|
|
31807
|
+
let totalSizeForHeader = void 0;
|
|
31808
|
+
if (!isFinal) {
|
|
31809
|
+
sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
31810
|
+
if (sendableSize === 0) {
|
|
31811
|
+
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
31812
|
+
this.isProcessing = false;
|
|
31813
|
+
return;
|
|
31814
|
+
}
|
|
31815
|
+
} else {
|
|
31816
|
+
totalSizeForHeader = virtualStart;
|
|
31817
|
+
}
|
|
31818
|
+
const blobToSend = fullBlob.slice(0, sendableSize);
|
|
31819
|
+
try {
|
|
31820
|
+
await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
31821
|
+
for (const meta of chunksWithMeta) {
|
|
31822
|
+
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
31823
|
+
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
31824
|
+
this.retryCount.delete(meta.chunk.id);
|
|
31825
|
+
(_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
31826
|
+
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
31827
|
+
}
|
|
31828
|
+
}
|
|
31829
|
+
if (this.config.cleanAfterUpload) {
|
|
31830
|
+
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
31831
|
+
const sizePurged = chunksToClear.reduce(
|
|
31832
|
+
(acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
|
|
31833
|
+
0
|
|
31834
|
+
);
|
|
31835
|
+
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
31836
|
+
if (sizePurged > 0) {
|
|
31837
|
+
this.totalBytesPurged += sizePurged;
|
|
31838
|
+
this.saveSessionState();
|
|
31839
|
+
console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
|
|
31840
|
+
}
|
|
31841
|
+
}
|
|
31842
|
+
if (isFinal) {
|
|
31843
|
+
this.clearSessionState();
|
|
31844
|
+
}
|
|
31845
|
+
} catch (error) {
|
|
31846
|
+
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
31847
|
+
(_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
|
|
31848
|
+
}
|
|
31849
|
+
} catch (error) {
|
|
31850
|
+
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
31851
|
+
} finally {
|
|
31852
|
+
this.isProcessing = false;
|
|
31853
|
+
}
|
|
31854
|
+
}
|
|
31855
|
+
/**
|
|
31856
|
+
* Faz o upload bruto de dados para a sessão GCS.
|
|
31857
|
+
*/
|
|
31858
|
+
async uploadData(blob, mimeType, chunkIndex, totalSize) {
|
|
31859
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
31860
|
+
if (!this.sessionUrl) {
|
|
31861
|
+
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
31862
|
+
const startResponse = await fetch(initiateUrl, {
|
|
31863
|
+
method: "POST",
|
|
31864
|
+
headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
|
|
31865
|
+
});
|
|
31866
|
+
if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
|
|
31867
|
+
this.sessionUrl = startResponse.headers.get("Location");
|
|
31868
|
+
if (!this.sessionUrl) throw new Error("Location header ausente");
|
|
31869
|
+
try {
|
|
31870
|
+
const urlObj = new URL(this.sessionUrl);
|
|
31871
|
+
const pathParts = urlObj.pathname.split("/");
|
|
31872
|
+
let bucket = pathParts[1];
|
|
31873
|
+
let object = decodeURIComponent(pathParts.slice(2).join("/"));
|
|
31874
|
+
if (pathParts.includes("b") && pathParts.includes("o")) {
|
|
31875
|
+
const bIdx = pathParts.indexOf("b") + 1;
|
|
31876
|
+
const oIdx = pathParts.indexOf("o") + 1;
|
|
31877
|
+
bucket = pathParts[bIdx];
|
|
31878
|
+
object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
|
|
31879
|
+
}
|
|
31880
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
|
|
31881
|
+
} catch (e3) {
|
|
31882
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
|
|
31883
|
+
}
|
|
31884
|
+
this.currentOffset = 0;
|
|
31885
|
+
this.saveSessionState();
|
|
31886
|
+
} else {
|
|
31887
|
+
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
31888
|
+
}
|
|
31889
|
+
const start = this.currentOffset;
|
|
31890
|
+
const end = start + blob.size - 1;
|
|
31891
|
+
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
31892
|
+
const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
31893
|
+
console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
|
|
31894
|
+
const response = await fetch(this.sessionUrl, {
|
|
31895
|
+
method: "PUT",
|
|
31896
|
+
headers: { "Content-Range": contentRangeHeader },
|
|
31897
|
+
body: blob.size > 0 ? blob : null
|
|
31898
|
+
// Usa null para garantir corpo vazio se necessário
|
|
31899
|
+
});
|
|
31900
|
+
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
31901
|
+
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
31902
|
+
const errorText = await response.text();
|
|
31903
|
+
console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
|
|
31904
|
+
throw new Error(`Status HTTP inesperado: ${response.status}`);
|
|
31905
|
+
}
|
|
31906
|
+
const rangeHeader = response.headers.get("Range");
|
|
31907
|
+
if (rangeHeader) {
|
|
31908
|
+
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
31909
|
+
this.currentOffset = lastByte + 1;
|
|
31910
|
+
} else {
|
|
31911
|
+
this.currentOffset += blob.size;
|
|
31912
|
+
}
|
|
31913
|
+
this.saveSessionState();
|
|
31914
|
+
trackers.registerUploadFile(
|
|
31915
|
+
this.proctoringId,
|
|
31916
|
+
`GCS Stream Upload
|
|
31917
|
+
Size: ${blob.size}
|
|
31918
|
+
Range: ${start}-${end}
|
|
31919
|
+
Last Index: ${chunkIndex}`,
|
|
31920
|
+
"CameraChunk"
|
|
31921
|
+
);
|
|
31922
|
+
}
|
|
31923
|
+
/**
|
|
31924
|
+
* Método estático para recuperação pós-crash.
|
|
31925
|
+
* Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
|
|
31926
|
+
* e tenta enviar.
|
|
31927
|
+
*/
|
|
31928
|
+
static async recoverPendingUploads(backend, token) {
|
|
31929
|
+
const chunkStorage = new ChunkStorageService();
|
|
31930
|
+
const recoveredIds = [];
|
|
31931
|
+
try {
|
|
31932
|
+
const pendingIds = await chunkStorage.getPendingProctoringIds();
|
|
31933
|
+
if (pendingIds.length === 0) {
|
|
31934
|
+
console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
|
|
31935
|
+
return recoveredIds;
|
|
31936
|
+
}
|
|
31937
|
+
console.log(
|
|
31938
|
+
`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
|
|
31939
|
+
);
|
|
31940
|
+
for (const proctoringId2 of pendingIds) {
|
|
31941
|
+
try {
|
|
31942
|
+
const service = new _BackgroundUploadService(
|
|
31943
|
+
proctoringId2,
|
|
31944
|
+
token,
|
|
31945
|
+
backend,
|
|
31946
|
+
chunkStorage,
|
|
31947
|
+
{ cleanAfterUpload: true }
|
|
31948
|
+
);
|
|
31949
|
+
await service.flush();
|
|
31950
|
+
recoveredIds.push(proctoringId2);
|
|
31951
|
+
console.log(
|
|
31952
|
+
`[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
|
|
31953
|
+
);
|
|
31954
|
+
} catch (error) {
|
|
31955
|
+
console.error(
|
|
31956
|
+
`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
|
|
31957
|
+
error
|
|
31958
|
+
);
|
|
31959
|
+
}
|
|
31960
|
+
}
|
|
31961
|
+
} catch (error) {
|
|
31962
|
+
console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
|
|
31963
|
+
}
|
|
31964
|
+
return recoveredIds;
|
|
31965
|
+
}
|
|
31966
|
+
sleep(ms2) {
|
|
31967
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
31398
31968
|
}
|
|
31399
31969
|
};
|
|
31400
31970
|
|
|
31401
31971
|
// src/new-flow/recorders/CameraRecorder.ts
|
|
31402
|
-
var import_jszip_min = __toESM(require_jszip_min());
|
|
31403
31972
|
var pkg = require_fix_webm_duration();
|
|
31404
31973
|
var fixWebmDuration = pkg.default || pkg;
|
|
31405
|
-
var
|
|
31974
|
+
var _CameraRecorder = class _CameraRecorder {
|
|
31406
31975
|
constructor(options, videoOptions, paramsConfig, backend, backendToken) {
|
|
31407
31976
|
this.blobs = [];
|
|
31408
31977
|
this.paramsConfig = {
|
|
@@ -31455,20 +32024,138 @@ var CameraRecorder = class {
|
|
|
31455
32024
|
this.videoElement = null;
|
|
31456
32025
|
this.duration = 0;
|
|
31457
32026
|
this.stopped = false;
|
|
32027
|
+
this.backgroundUpload = null;
|
|
32028
|
+
this.chunkIndex = 0;
|
|
32029
|
+
/** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
|
|
32030
|
+
this.pendingChunkSaves = [];
|
|
32031
|
+
// Handlers bound para poder remover os listeners depois
|
|
32032
|
+
this.boundVisibilityHandler = null;
|
|
32033
|
+
this.boundPageHideHandler = null;
|
|
31458
32034
|
this.currentRetries = 0;
|
|
31459
32035
|
this.packageCount = 0;
|
|
31460
32036
|
this.failedUploads = 0;
|
|
31461
|
-
this.noiseWait = 20;
|
|
31462
32037
|
this.options = options;
|
|
31463
32038
|
this.videoOptions = videoOptions;
|
|
31464
32039
|
this.backend = backend;
|
|
31465
32040
|
this.backendToken = backendToken;
|
|
31466
32041
|
paramsConfig && (this.paramsConfig = paramsConfig);
|
|
31467
32042
|
}
|
|
32043
|
+
/**
|
|
32044
|
+
* Determina se o fluxo de chunks e lifecycle deve estar ativo.
|
|
32045
|
+
* Retorna true se:
|
|
32046
|
+
* 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
|
|
32047
|
+
* 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
|
|
32048
|
+
*/
|
|
32049
|
+
get isChunkEnabled() {
|
|
32050
|
+
return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
|
|
32051
|
+
}
|
|
31468
32052
|
setProctoringId(proctoringId2) {
|
|
31469
32053
|
this.proctoringId = proctoringId2;
|
|
31470
32054
|
this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
|
|
31471
32055
|
setRecorderProctoringId(proctoringId2);
|
|
32056
|
+
if (this.isChunkEnabled) {
|
|
32057
|
+
this.chunkStorage = new ChunkStorageService();
|
|
32058
|
+
if (this.backend && this.backendToken) {
|
|
32059
|
+
this.backgroundUpload = new BackgroundUploadService(
|
|
32060
|
+
this.proctoringId,
|
|
32061
|
+
this.backendToken,
|
|
32062
|
+
this.backend,
|
|
32063
|
+
this.chunkStorage,
|
|
32064
|
+
{ pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
|
|
32065
|
+
);
|
|
32066
|
+
}
|
|
32067
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32068
|
+
console.log(
|
|
32069
|
+
`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
|
|
32070
|
+
);
|
|
32071
|
+
} else {
|
|
32072
|
+
console.log(
|
|
32073
|
+
`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
|
|
32074
|
+
);
|
|
32075
|
+
}
|
|
32076
|
+
}
|
|
32077
|
+
// ========================
|
|
32078
|
+
// Session State Persistence (localStorage)
|
|
32079
|
+
// ========================
|
|
32080
|
+
persistSessionState(status) {
|
|
32081
|
+
try {
|
|
32082
|
+
const data = {
|
|
32083
|
+
proctoringId: this.proctoringId,
|
|
32084
|
+
status,
|
|
32085
|
+
timestamp: Date.now()
|
|
32086
|
+
};
|
|
32087
|
+
localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
|
|
32088
|
+
} catch (e3) {
|
|
32089
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
|
|
32090
|
+
}
|
|
32091
|
+
}
|
|
32092
|
+
clearSessionState() {
|
|
32093
|
+
try {
|
|
32094
|
+
localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32095
|
+
} catch (e3) {
|
|
32096
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
|
|
32097
|
+
}
|
|
32098
|
+
}
|
|
32099
|
+
/**
|
|
32100
|
+
* Verifica se existe uma sessão ativa anterior no localStorage.
|
|
32101
|
+
* Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
|
|
32102
|
+
*/
|
|
32103
|
+
static checkForActiveSession() {
|
|
32104
|
+
try {
|
|
32105
|
+
const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32106
|
+
if (!raw) return null;
|
|
32107
|
+
const data = JSON.parse(raw);
|
|
32108
|
+
if (data.status === "IN_PROGRESS") return data;
|
|
32109
|
+
return null;
|
|
32110
|
+
} catch {
|
|
32111
|
+
return null;
|
|
32112
|
+
}
|
|
32113
|
+
}
|
|
32114
|
+
// ========================
|
|
32115
|
+
// Page Lifecycle Management
|
|
32116
|
+
// ========================
|
|
32117
|
+
setupLifecycleListeners() {
|
|
32118
|
+
this.boundVisibilityHandler = () => this.handleVisibilityChange();
|
|
32119
|
+
this.boundPageHideHandler = () => this.handlePageHide();
|
|
32120
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32121
|
+
window.addEventListener("pagehide", this.boundPageHideHandler);
|
|
32122
|
+
}
|
|
32123
|
+
removeLifecycleListeners() {
|
|
32124
|
+
if (this.boundVisibilityHandler) {
|
|
32125
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32126
|
+
this.boundVisibilityHandler = null;
|
|
32127
|
+
}
|
|
32128
|
+
if (this.boundPageHideHandler) {
|
|
32129
|
+
window.removeEventListener("pagehide", this.boundPageHideHandler);
|
|
32130
|
+
this.boundPageHideHandler = null;
|
|
32131
|
+
}
|
|
32132
|
+
}
|
|
32133
|
+
handleVisibilityChange() {
|
|
32134
|
+
var _a2;
|
|
32135
|
+
if (document.visibilityState === "hidden") {
|
|
32136
|
+
console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
|
|
32137
|
+
this.persistSessionState("INTERRUPTED");
|
|
32138
|
+
this.proctoringId && trackers.registerError(
|
|
32139
|
+
this.proctoringId,
|
|
32140
|
+
"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
|
|
32141
|
+
);
|
|
32142
|
+
} else if (document.visibilityState === "visible") {
|
|
32143
|
+
console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
|
|
32144
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32145
|
+
this.proctoringId && trackers.registerError(
|
|
32146
|
+
this.proctoringId,
|
|
32147
|
+
"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
|
|
32148
|
+
);
|
|
32149
|
+
(_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
|
|
32150
|
+
}
|
|
32151
|
+
}
|
|
32152
|
+
handlePageHide() {
|
|
32153
|
+
console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
|
|
32154
|
+
this.persistSessionState("INTERRUPTED");
|
|
32155
|
+
this.proctoringId && trackers.registerError(
|
|
32156
|
+
this.proctoringId,
|
|
32157
|
+
"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
|
|
32158
|
+
);
|
|
31472
32159
|
}
|
|
31473
32160
|
async initializeDetectors() {
|
|
31474
32161
|
var _a2, _b, _c2;
|
|
@@ -31730,6 +32417,51 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31730
32417
|
await this.sendPackage();
|
|
31731
32418
|
await this.filesToUpload.splice(0, this.filesToUpload.length);
|
|
31732
32419
|
}
|
|
32420
|
+
if (this.isChunkEnabled) {
|
|
32421
|
+
if (this.backgroundUpload) {
|
|
32422
|
+
try {
|
|
32423
|
+
if (this.pendingChunkSaves.length > 0) {
|
|
32424
|
+
console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
|
|
32425
|
+
await Promise.all(this.pendingChunkSaves);
|
|
32426
|
+
}
|
|
32427
|
+
await this.backgroundUpload.flush();
|
|
32428
|
+
} catch (e3) {
|
|
32429
|
+
console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
|
|
32430
|
+
}
|
|
32431
|
+
this.backgroundUpload.stop();
|
|
32432
|
+
}
|
|
32433
|
+
this.removeLifecycleListeners();
|
|
32434
|
+
this.persistSessionState("FINISHED");
|
|
32435
|
+
}
|
|
32436
|
+
}
|
|
32437
|
+
/**
|
|
32438
|
+
* Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
|
|
32439
|
+
* Salva o chunk no IndexedDB para persistência e recuperação.
|
|
32440
|
+
*/
|
|
32441
|
+
async handleNewChunk(blob, idx) {
|
|
32442
|
+
if (!this.proctoringId || !this.chunkStorage) return;
|
|
32443
|
+
const savePromise = (async () => {
|
|
32444
|
+
var _a2;
|
|
32445
|
+
try {
|
|
32446
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
32447
|
+
await this.chunkStorage.saveChunk({
|
|
32448
|
+
proctoringId: this.proctoringId,
|
|
32449
|
+
chunkIndex: this.chunkIndex,
|
|
32450
|
+
arrayBuffer,
|
|
32451
|
+
timestamp: Date.now(),
|
|
32452
|
+
uploaded: 0,
|
|
32453
|
+
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
32454
|
+
});
|
|
32455
|
+
this.chunkIndex++;
|
|
32456
|
+
console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
|
|
32457
|
+
} catch (error) {
|
|
32458
|
+
console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
|
|
32459
|
+
}
|
|
32460
|
+
})();
|
|
32461
|
+
this.pendingChunkSaves.push(savePromise);
|
|
32462
|
+
savePromise.finally(() => {
|
|
32463
|
+
this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
|
|
32464
|
+
});
|
|
31733
32465
|
}
|
|
31734
32466
|
async pauseRecording() {
|
|
31735
32467
|
await this.recordingPause();
|
|
@@ -31870,21 +32602,36 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31870
32602
|
const settings = this.cameraStream.getVideoTracks()[0].getSettings();
|
|
31871
32603
|
const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
|
|
31872
32604
|
if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
|
|
31873
|
-
|
|
31874
|
-
|
|
31875
|
-
|
|
31876
|
-
|
|
31877
|
-
|
|
31878
|
-
|
|
31879
|
-
|
|
32605
|
+
let isUploaded = false;
|
|
32606
|
+
if (this.isChunkEnabled) {
|
|
32607
|
+
if (this.backend && this.backendToken && this.proctoringId) {
|
|
32608
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
32609
|
+
const objectName = `${this.proctoringId}/${fileName}`;
|
|
32610
|
+
isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
|
|
32611
|
+
this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
|
|
32612
|
+
}
|
|
32613
|
+
}
|
|
32614
|
+
if (!isUploaded) {
|
|
32615
|
+
const rawBlob = new Blob(this.blobs, {
|
|
32616
|
+
type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
|
|
32617
|
+
});
|
|
32618
|
+
let finalBlob = rawBlob;
|
|
32619
|
+
if (typeof fixWebmDuration === "function") {
|
|
32620
|
+
finalBlob = await fixWebmDuration(rawBlob, this.duration);
|
|
32621
|
+
} else {
|
|
32622
|
+
console.warn("fixWebmDuration n\xE3o dispon\xEDvel");
|
|
32623
|
+
}
|
|
32624
|
+
session.addRecording({
|
|
32625
|
+
device: `Audio
|
|
31880
32626
|
Sample Rate: ${settingsAudio.sampleRate}
|
|
31881
32627
|
Sample Size: ${settingsAudio.sampleSize}
|
|
31882
32628
|
|
|
31883
32629
|
Video:
|
|
31884
32630
|
${JSON.stringify(this.recorderOptions)}`,
|
|
31885
|
-
|
|
31886
|
-
|
|
31887
|
-
|
|
32631
|
+
arrayBuffer: await finalBlob.arrayBuffer(),
|
|
32632
|
+
origin: "Camera" /* Camera */
|
|
32633
|
+
});
|
|
32634
|
+
}
|
|
31888
32635
|
}
|
|
31889
32636
|
}
|
|
31890
32637
|
async getFile(file, name, type) {
|
|
@@ -31897,28 +32644,63 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31897
32644
|
});
|
|
31898
32645
|
});
|
|
31899
32646
|
}
|
|
31900
|
-
|
|
31901
|
-
|
|
31902
|
-
|
|
31903
|
-
|
|
31904
|
-
|
|
31905
|
-
|
|
31906
|
-
|
|
31907
|
-
|
|
31908
|
-
|
|
31909
|
-
|
|
31910
|
-
|
|
31911
|
-
|
|
31912
|
-
|
|
31913
|
-
|
|
31914
|
-
|
|
31915
|
-
|
|
31916
|
-
|
|
31917
|
-
|
|
31918
|
-
|
|
32647
|
+
};
|
|
32648
|
+
// ========================
|
|
32649
|
+
// Chunk & Lifecycle
|
|
32650
|
+
// ========================
|
|
32651
|
+
/** Intervalo de cada chunk em ms (padrão: 60 segundos) */
|
|
32652
|
+
_CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
|
|
32653
|
+
/** Chave do localStorage para persistir estado da sessão */
|
|
32654
|
+
_CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
|
|
32655
|
+
var CameraRecorder = _CameraRecorder;
|
|
32656
|
+
|
|
32657
|
+
// src/new-flow/recorders/VolumeMeter.ts
|
|
32658
|
+
var VolumeMeter = class {
|
|
32659
|
+
constructor(stream4) {
|
|
32660
|
+
this.volume = null;
|
|
32661
|
+
this.animationFrameId = null;
|
|
32662
|
+
this.stream = stream4;
|
|
32663
|
+
}
|
|
32664
|
+
async start(options = {}) {
|
|
32665
|
+
return new Promise((resolve, reject) => {
|
|
32666
|
+
try {
|
|
32667
|
+
this.audioContext = new AudioContext();
|
|
32668
|
+
this.analyser = this.audioContext.createAnalyser();
|
|
32669
|
+
this.microphone = this.audioContext.createMediaStreamSource(this.stream);
|
|
32670
|
+
this.analyser.smoothingTimeConstant = 0.8;
|
|
32671
|
+
this.analyser.fftSize = 1024;
|
|
32672
|
+
this.microphone.connect(this.analyser);
|
|
32673
|
+
const processAudio = () => {
|
|
32674
|
+
const array = new Uint8Array(this.analyser.frequencyBinCount);
|
|
32675
|
+
this.analyser.getByteFrequencyData(array);
|
|
32676
|
+
const arraySum = array.reduce((a3, value) => a3 + value, 0);
|
|
32677
|
+
const average = arraySum / array.length;
|
|
32678
|
+
this.setVolume(average);
|
|
32679
|
+
options.setVolume && options.setVolume(average);
|
|
32680
|
+
this.animationFrameId = requestAnimationFrame(processAudio);
|
|
32681
|
+
};
|
|
32682
|
+
this.animationFrameId = requestAnimationFrame(processAudio);
|
|
32683
|
+
resolve(true);
|
|
32684
|
+
} catch (error) {
|
|
32685
|
+
this.stop();
|
|
32686
|
+
reject(`Error: ${error}`);
|
|
31919
32687
|
}
|
|
32688
|
+
});
|
|
32689
|
+
}
|
|
32690
|
+
stop() {
|
|
32691
|
+
var _a2, _b, _c2;
|
|
32692
|
+
if (this.animationFrameId !== null) {
|
|
32693
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
31920
32694
|
}
|
|
31921
|
-
this.
|
|
32695
|
+
(_a2 = this.audioContext) == null ? void 0 : _a2.close();
|
|
32696
|
+
(_b = this.microphone) == null ? void 0 : _b.disconnect();
|
|
32697
|
+
(_c2 = this.analyser) == null ? void 0 : _c2.disconnect();
|
|
32698
|
+
}
|
|
32699
|
+
getVolume() {
|
|
32700
|
+
return this.volume;
|
|
32701
|
+
}
|
|
32702
|
+
setVolume(value) {
|
|
32703
|
+
this.volume = value;
|
|
31922
32704
|
}
|
|
31923
32705
|
};
|
|
31924
32706
|
|
|
@@ -31926,6 +32708,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31926
32708
|
var DeviceCheckerUI = class {
|
|
31927
32709
|
constructor(options = getDefaultProctoringOptions, _videoOptions) {
|
|
31928
32710
|
this.videoOptions = { width: 1080, height: 720, minWidth: 0, minHeight: 0 };
|
|
32711
|
+
this.autoConfirmTimer = null;
|
|
32712
|
+
this.autoConfirmSeconds = 5;
|
|
31929
32713
|
this.options = {
|
|
31930
32714
|
...getDefaultProctoringOptions,
|
|
31931
32715
|
...options,
|
|
@@ -32691,9 +33475,53 @@ var DeviceCheckerUI = class {
|
|
|
32691
33475
|
}));
|
|
32692
33476
|
}
|
|
32693
33477
|
closeModal() {
|
|
33478
|
+
this.stopAutoConfirm();
|
|
32694
33479
|
const checkDevices = document.querySelector("#checkDevices");
|
|
32695
33480
|
checkDevices == null ? void 0 : checkDevices.remove();
|
|
32696
33481
|
}
|
|
33482
|
+
verifyAutoConfirm(status) {
|
|
33483
|
+
var _a2;
|
|
33484
|
+
if (!((_a2 = this.options) == null ? void 0 : _a2.auto)) return;
|
|
33485
|
+
let isAllGreen = status.allowedResolution && status.allowedPositionFace && status.allowedAmbient && status.allowedMicrophone;
|
|
33486
|
+
if (this.options.useSpyScan) {
|
|
33487
|
+
isAllGreen = isAllGreen && status.allowedSpyScan === true;
|
|
33488
|
+
}
|
|
33489
|
+
if (isAllGreen) {
|
|
33490
|
+
if (!this.autoConfirmTimer) {
|
|
33491
|
+
this.startAutoConfirm();
|
|
33492
|
+
}
|
|
33493
|
+
} else {
|
|
33494
|
+
if (this.autoConfirmTimer) {
|
|
33495
|
+
this.stopAutoConfirm();
|
|
33496
|
+
}
|
|
33497
|
+
}
|
|
33498
|
+
}
|
|
33499
|
+
startAutoConfirm() {
|
|
33500
|
+
this.autoConfirmSeconds = 5;
|
|
33501
|
+
const btn = document.getElementById("confirmBtn");
|
|
33502
|
+
if (btn && !btn.disabled) {
|
|
33503
|
+
btn.innerText = `Continuando em ${this.autoConfirmSeconds}s...`;
|
|
33504
|
+
this.autoConfirmTimer = setInterval(() => {
|
|
33505
|
+
this.autoConfirmSeconds--;
|
|
33506
|
+
if (this.autoConfirmSeconds <= 0) {
|
|
33507
|
+
this.stopAutoConfirm();
|
|
33508
|
+
btn.click();
|
|
33509
|
+
} else {
|
|
33510
|
+
btn.innerText = `Continuando em ${this.autoConfirmSeconds}s...`;
|
|
33511
|
+
}
|
|
33512
|
+
}, 1e3);
|
|
33513
|
+
}
|
|
33514
|
+
}
|
|
33515
|
+
stopAutoConfirm() {
|
|
33516
|
+
if (this.autoConfirmTimer) {
|
|
33517
|
+
clearInterval(this.autoConfirmTimer);
|
|
33518
|
+
this.autoConfirmTimer = null;
|
|
33519
|
+
}
|
|
33520
|
+
const btn = document.getElementById("confirmBtn");
|
|
33521
|
+
if (btn) {
|
|
33522
|
+
btn.innerText = "Continuar";
|
|
33523
|
+
}
|
|
33524
|
+
}
|
|
32697
33525
|
modalActions(closeCheckDevices) {
|
|
32698
33526
|
const cancelBtn = document.getElementById("cancelBtn");
|
|
32699
33527
|
const confirmBtn = document.getElementById("confirmBtn");
|
|
@@ -33060,6 +33888,7 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
33060
33888
|
}
|
|
33061
33889
|
}
|
|
33062
33890
|
onUpdateCallback() {
|
|
33891
|
+
var _a2;
|
|
33063
33892
|
if (typeof this.onUpdateCb === "function") {
|
|
33064
33893
|
this.onUpdateCb({
|
|
33065
33894
|
allowedResolution: this.allowedResolution,
|
|
@@ -33070,6 +33899,15 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
33070
33899
|
faceDetectionAlerts: this.faceDetectionAlerts
|
|
33071
33900
|
});
|
|
33072
33901
|
}
|
|
33902
|
+
if (((_a2 = this.options) == null ? void 0 : _a2.auto) && this.DeviceCheckerUI) {
|
|
33903
|
+
this.DeviceCheckerUI.verifyAutoConfirm({
|
|
33904
|
+
allowedResolution: this.allowedResolution,
|
|
33905
|
+
allowedPositionFace: this.allowedPositionFace,
|
|
33906
|
+
allowedAmbient: this.allowedAmbient,
|
|
33907
|
+
allowedMicrophone: this.allowedMicrophone,
|
|
33908
|
+
allowedSpyScan: this.allowedSpyScan
|
|
33909
|
+
});
|
|
33910
|
+
}
|
|
33073
33911
|
}
|
|
33074
33912
|
async checkDevices(options = getDefaultProctoringOptions, _videoOptions = getDefaultProctoringVideoOptions) {
|
|
33075
33913
|
var _a2;
|
|
@@ -33276,16 +34114,19 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
33276
34114
|
}).finally(() => {
|
|
33277
34115
|
this.DeviceCheckerUI && this.DeviceCheckerUI.waitingSpyDevices(false);
|
|
33278
34116
|
this.DeviceCheckerUI && this.allowedSpyScan != null && this.DeviceCheckerUI.isSpyDevicesUI(this.allowedSpyScan);
|
|
34117
|
+
this.onUpdateCallback();
|
|
33279
34118
|
});
|
|
33280
34119
|
} else {
|
|
33281
34120
|
this.allowedSpyScan = false;
|
|
33282
34121
|
this.DeviceCheckerUI && this.DeviceCheckerUI.waitingSpyDevices(false);
|
|
33283
34122
|
this.DeviceCheckerUI && this.DeviceCheckerUI.isSpyDevicesUI(this.allowedSpyScan);
|
|
34123
|
+
this.onUpdateCallback();
|
|
33284
34124
|
}
|
|
33285
34125
|
} catch (error) {
|
|
33286
34126
|
this.allowedSpyScan = false;
|
|
33287
34127
|
this.DeviceCheckerUI && this.DeviceCheckerUI.waitingSpyDevices(false);
|
|
33288
34128
|
this.DeviceCheckerUI && this.DeviceCheckerUI.isSpyDevicesUI(this.allowedSpyScan);
|
|
34129
|
+
this.onUpdateCallback();
|
|
33289
34130
|
console.log(error);
|
|
33290
34131
|
}
|
|
33291
34132
|
}
|
|
@@ -33605,8 +34446,8 @@ var CapturePhoto = class {
|
|
|
33605
34446
|
}
|
|
33606
34447
|
};
|
|
33607
34448
|
|
|
33608
|
-
// src/extension/
|
|
33609
|
-
var
|
|
34449
|
+
// src/extension/extensionEasyProctor.ts
|
|
34450
|
+
var ExtensionEasyProctor = class {
|
|
33610
34451
|
constructor() {
|
|
33611
34452
|
this.hasExtension = false;
|
|
33612
34453
|
this.tryes = 0;
|
|
@@ -33651,6 +34492,89 @@ var Extension = class {
|
|
|
33651
34492
|
}
|
|
33652
34493
|
};
|
|
33653
34494
|
|
|
34495
|
+
// src/extension/extensionEasyCatcher.ts
|
|
34496
|
+
var ExtensionEasyCatcher = class {
|
|
34497
|
+
constructor(options) {
|
|
34498
|
+
this.hasExtension = false;
|
|
34499
|
+
this.tryes = 0;
|
|
34500
|
+
this.responseStart = false;
|
|
34501
|
+
this.options = options || {};
|
|
34502
|
+
}
|
|
34503
|
+
/**
|
|
34504
|
+
* Verifica se a extensão está instalada e ativa.
|
|
34505
|
+
* Retorna o número da versão se encontrada, ou lança erro após timeout.
|
|
34506
|
+
*/
|
|
34507
|
+
checkExtensionInstalled(timeoutMs = 2e3) {
|
|
34508
|
+
return new Promise((resolve, reject) => {
|
|
34509
|
+
let handled = false;
|
|
34510
|
+
const handler = (event) => {
|
|
34511
|
+
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "version") {
|
|
34512
|
+
handled = true;
|
|
34513
|
+
window.removeEventListener("message", handler);
|
|
34514
|
+
resolve(event.data.message);
|
|
34515
|
+
}
|
|
34516
|
+
};
|
|
34517
|
+
window.addEventListener("message", handler);
|
|
34518
|
+
window.postMessage({
|
|
34519
|
+
type: "easycatcher",
|
|
34520
|
+
func: "verifyExtensionEasycatcher"
|
|
34521
|
+
}, "*");
|
|
34522
|
+
setTimeout(() => {
|
|
34523
|
+
if (!handled) {
|
|
34524
|
+
window.removeEventListener("message", handler);
|
|
34525
|
+
reject(new Error("Extens\xE3o n\xE3o detectada ou n\xE3o respondeu."));
|
|
34526
|
+
}
|
|
34527
|
+
}, timeoutMs);
|
|
34528
|
+
});
|
|
34529
|
+
}
|
|
34530
|
+
/**
|
|
34531
|
+
* Solicita o JSON da sessão atual capturado pela extensão.
|
|
34532
|
+
*/
|
|
34533
|
+
getSessionData(timeoutMs = 5e3) {
|
|
34534
|
+
return new Promise((resolve, reject) => {
|
|
34535
|
+
let handled = false;
|
|
34536
|
+
const handler = (event) => {
|
|
34537
|
+
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "data_response") {
|
|
34538
|
+
handled = true;
|
|
34539
|
+
window.removeEventListener("message", handler);
|
|
34540
|
+
resolve(event.data.payload);
|
|
34541
|
+
}
|
|
34542
|
+
};
|
|
34543
|
+
window.addEventListener("message", handler);
|
|
34544
|
+
window.postMessage({
|
|
34545
|
+
type: "easycatcher",
|
|
34546
|
+
func: "getDataExtensionEasycatcher"
|
|
34547
|
+
}, "*");
|
|
34548
|
+
setTimeout(() => {
|
|
34549
|
+
if (!handled) {
|
|
34550
|
+
window.removeEventListener("message", handler);
|
|
34551
|
+
reject(new Error("Timeout ao aguardar dados da extens\xE3o."));
|
|
34552
|
+
}
|
|
34553
|
+
}, timeoutMs);
|
|
34554
|
+
});
|
|
34555
|
+
}
|
|
34556
|
+
start() {
|
|
34557
|
+
return new Promise((resolve, reject) => {
|
|
34558
|
+
let handled = false;
|
|
34559
|
+
const handler = (event) => {
|
|
34560
|
+
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "started_confirmed") {
|
|
34561
|
+
handled = true;
|
|
34562
|
+
window.removeEventListener("message", handler);
|
|
34563
|
+
resolve(true);
|
|
34564
|
+
}
|
|
34565
|
+
};
|
|
34566
|
+
window.addEventListener("message", handler);
|
|
34567
|
+
window.postMessage({ type: "easycatcher", func: "startExtensionEasycatcher" }, "*");
|
|
34568
|
+
setTimeout(() => {
|
|
34569
|
+
if (!handled) {
|
|
34570
|
+
window.removeEventListener("message", handler);
|
|
34571
|
+
reject(new Error("Timeout: Extens\xE3o n\xE3o confirmou o in\xEDcio."));
|
|
34572
|
+
}
|
|
34573
|
+
}, 3e3);
|
|
34574
|
+
});
|
|
34575
|
+
}
|
|
34576
|
+
};
|
|
34577
|
+
|
|
33654
34578
|
// src/modules/onChangeDevices.ts
|
|
33655
34579
|
var onChangeDevices = class {
|
|
33656
34580
|
constructor(repositoryDevices, proctoringId2, sessionOptions, allRecorders) {
|
|
@@ -33882,15 +34806,18 @@ Error: ${e3.message}
|
|
|
33882
34806
|
);
|
|
33883
34807
|
});
|
|
33884
34808
|
if (result) {
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
34809
|
+
let fileType = "";
|
|
34810
|
+
if (rec.origin === "Camera" /* Camera */) {
|
|
34811
|
+
fileType = "Camera";
|
|
34812
|
+
} else if (rec.origin === "Screen" /* Screen */) {
|
|
34813
|
+
fileType = "Screen";
|
|
34814
|
+
} else if (rec.origin === "Mic" /* Mic */) {
|
|
34815
|
+
fileType = "Audio";
|
|
34816
|
+
}
|
|
34817
|
+
trackers.registerUploadFile(this.proctoringId, `Upload File
|
|
33889
34818
|
Name: ${file.name}
|
|
33890
34819
|
Type: ${file.type}
|
|
33891
|
-
Size: ${file.size}`,
|
|
33892
|
-
fileType
|
|
33893
|
-
);
|
|
34820
|
+
Size: ${file.size}`, fileType);
|
|
33894
34821
|
return result;
|
|
33895
34822
|
}
|
|
33896
34823
|
}
|
|
@@ -36699,11 +37626,15 @@ var ScreenRecorder = class {
|
|
|
36699
37626
|
const rawBlob = new Blob(this.blobs, {
|
|
36700
37627
|
type: "video/webm"
|
|
36701
37628
|
});
|
|
36702
|
-
|
|
36703
|
-
|
|
37629
|
+
let finalBlob = rawBlob;
|
|
37630
|
+
if (typeof fixWebmDuration2 === "function") {
|
|
37631
|
+
finalBlob = await fixWebmDuration2(rawBlob, this.duration);
|
|
37632
|
+
} else {
|
|
37633
|
+
console.warn("fixWebmDuration n\xE3o dispon\xEDvel");
|
|
37634
|
+
}
|
|
36704
37635
|
session.addRecording({
|
|
36705
37636
|
device: "",
|
|
36706
|
-
arrayBuffer,
|
|
37637
|
+
arrayBuffer: await finalBlob.arrayBuffer(),
|
|
36707
37638
|
origin: "Screen" /* Screen */
|
|
36708
37639
|
});
|
|
36709
37640
|
}
|
|
@@ -37987,7 +38918,10 @@ var Proctoring = class {
|
|
|
37987
38918
|
if (this.context.token === void 0) {
|
|
37988
38919
|
throw TOKEN_MISSING;
|
|
37989
38920
|
}
|
|
37990
|
-
|
|
38921
|
+
if (options.useChallenge) {
|
|
38922
|
+
this.extensionEasycatcher = new ExtensionEasyCatcher();
|
|
38923
|
+
}
|
|
38924
|
+
this.extension = new ExtensionEasyProctor();
|
|
37991
38925
|
this.extension.addEventListener();
|
|
37992
38926
|
const baseURL = this.backend.selectBaseUrl(this.context.type);
|
|
37993
38927
|
const devices = await enumarateDevices();
|
|
@@ -38359,6 +39293,61 @@ Error: ` + error
|
|
|
38359
39293
|
_screenStream: (_a2 = this.allRecorders.screenRecorder) == null ? void 0 : _a2.screenStream
|
|
38360
39294
|
};
|
|
38361
39295
|
}
|
|
39296
|
+
async startChallenge(templateId) {
|
|
39297
|
+
var _a2;
|
|
39298
|
+
if (!this.sessionOptions.useChallenge) {
|
|
39299
|
+
throw new Error("useChallenge is set as false on start method");
|
|
39300
|
+
}
|
|
39301
|
+
await this.extensionEasycatcher.checkExtensionInstalled().catch((err) => {
|
|
39302
|
+
throw new Error("EasyCatcher Extension is not installed");
|
|
39303
|
+
});
|
|
39304
|
+
this.extensionEasycatcher.start();
|
|
39305
|
+
const start = Date.now() - ((_a2 = this.allRecorders.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0;
|
|
39306
|
+
await this.backend.startChallenge({
|
|
39307
|
+
proctoringId: this.proctoringId,
|
|
39308
|
+
templateId,
|
|
39309
|
+
start
|
|
39310
|
+
}).then((resp) => {
|
|
39311
|
+
console.log(resp);
|
|
39312
|
+
this.challengeId = resp.id;
|
|
39313
|
+
}).catch((reason) => {
|
|
39314
|
+
trackers.registerError(
|
|
39315
|
+
this.proctoringId,
|
|
39316
|
+
"N\xE3o foi poss\xEDvel iniciar desafio!"
|
|
39317
|
+
);
|
|
39318
|
+
throw reason;
|
|
39319
|
+
});
|
|
39320
|
+
this.isChallengeRunning = true;
|
|
39321
|
+
}
|
|
39322
|
+
async stopChallenge() {
|
|
39323
|
+
var _a2;
|
|
39324
|
+
if (!this.isChallengeRunning) {
|
|
39325
|
+
throw new Error("Challenge not started");
|
|
39326
|
+
}
|
|
39327
|
+
try {
|
|
39328
|
+
const sessionData = await this.extensionEasycatcher.getSessionData();
|
|
39329
|
+
const end = Date.now() - ((_a2 = this.allRecorders.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0;
|
|
39330
|
+
await this.backend.stopChallenge(
|
|
39331
|
+
this.challengeId,
|
|
39332
|
+
{
|
|
39333
|
+
end,
|
|
39334
|
+
data: sessionData
|
|
39335
|
+
}
|
|
39336
|
+
).catch((reason) => {
|
|
39337
|
+
trackers.registerError(
|
|
39338
|
+
this.proctoringId,
|
|
39339
|
+
"N\xE3o foi poss\xEDvel finalizar o desafio no backend!"
|
|
39340
|
+
);
|
|
39341
|
+
return void 0;
|
|
39342
|
+
});
|
|
39343
|
+
this.isChallengeRunning = false;
|
|
39344
|
+
} catch (error) {
|
|
39345
|
+
trackers.registerError(
|
|
39346
|
+
this.proctoringId,
|
|
39347
|
+
"Erro ao recuperar dados da extens\xE3o: " + error.message
|
|
39348
|
+
);
|
|
39349
|
+
}
|
|
39350
|
+
}
|
|
38362
39351
|
};
|
|
38363
39352
|
|
|
38364
39353
|
// src/proctoring/SignTerm.ts
|
|
@@ -38587,6 +39576,8 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38587
39576
|
return originalStart(parameters2, videoOptions);
|
|
38588
39577
|
};
|
|
38589
39578
|
const finish = proctoring.finish.bind(proctoring);
|
|
39579
|
+
const startChallenge = proctoring.startChallenge.bind(proctoring);
|
|
39580
|
+
const stopChallenge = proctoring.stopChallenge.bind(proctoring);
|
|
38590
39581
|
const pause = proctoring.pause.bind(proctoring);
|
|
38591
39582
|
const resume = proctoring.resume.bind(proctoring);
|
|
38592
39583
|
const onFocus = proctoring.setOnFocusCallback.bind(proctoring);
|
|
@@ -38611,6 +39602,8 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38611
39602
|
login,
|
|
38612
39603
|
start,
|
|
38613
39604
|
finish,
|
|
39605
|
+
startChallenge,
|
|
39606
|
+
stopChallenge,
|
|
38614
39607
|
onFocus,
|
|
38615
39608
|
onLostFocus,
|
|
38616
39609
|
onChangeDevices: onChangeDevices2,
|