easyproctor-hml 2.6.0 → 2.7.1
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/README.md +31 -0
- package/esm/index.js +1152 -276
- package/extension/{extensionEasyProctor.d.ts → extension.d.ts} +1 -1
- package/index.js +1152 -276
- package/modules/BaseDetection.d.ts +1 -1
- package/new-flow/backend/BackendService.d.ts +7 -2
- package/new-flow/chunk/BackgroundUploadService.d.ts +38 -0
- package/new-flow/chunk/ChunkStorageService.d.ts +25 -0
- package/new-flow/proctoring/ProctoringSession.d.ts +1 -0
- package/new-flow/recorders/CameraRecorder.d.ts +27 -1
- package/new-flow/recorders/NoiseRecorder.d.ts +1 -1
- package/package.json +3 -2
- package/plugins/recorder.d.ts +5 -1
- package/proctoring/proctoring.d.ts +6 -5
- package/proctoring/useProctoring.d.ts +2 -2
- package/unpkg/easyproctor.min.js +45 -42
- package/utils/browserInformations.d.ts +1 -0
- package/extension/extensionEasyCatcher.d.ts +0 -11
package/index.js
CHANGED
|
@@ -22329,14 +22329,17 @@ module.exports = __toCommonJS(index_exports);
|
|
|
22329
22329
|
|
|
22330
22330
|
// src/modules/checkPermissions.ts
|
|
22331
22331
|
async function checkPermissions() {
|
|
22332
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
22333
|
+
return false;
|
|
22334
|
+
}
|
|
22332
22335
|
try {
|
|
22333
22336
|
const constraints = {
|
|
22334
22337
|
audio: true,
|
|
22335
22338
|
video: true
|
|
22336
22339
|
};
|
|
22337
22340
|
const stream4 = await navigator.mediaDevices.getUserMedia(constraints);
|
|
22338
|
-
stream4.getTracks().forEach((
|
|
22339
|
-
|
|
22341
|
+
stream4.getTracks().forEach((track) => {
|
|
22342
|
+
track.stop();
|
|
22340
22343
|
});
|
|
22341
22344
|
return true;
|
|
22342
22345
|
} catch (error) {
|
|
@@ -26430,7 +26433,6 @@ var BaseDetection = class {
|
|
|
26430
26433
|
// objectDetection
|
|
26431
26434
|
runningMode: this.runningMode
|
|
26432
26435
|
});
|
|
26433
|
-
console.log("BaseDetection initializeDetector", this.detectorType);
|
|
26434
26436
|
}
|
|
26435
26437
|
stopDetection() {
|
|
26436
26438
|
this.animationFrameId && clearTimeout(this.animationFrameId);
|
|
@@ -26440,7 +26442,7 @@ var BaseDetection = class {
|
|
|
26440
26442
|
this.createdVideo && this.video && document.body.removeChild(this.video);
|
|
26441
26443
|
this.createdVideo = false;
|
|
26442
26444
|
}
|
|
26443
|
-
enableCam(cameraStream) {
|
|
26445
|
+
enableCam(cameraStream, delay = 1e4) {
|
|
26444
26446
|
var _a2;
|
|
26445
26447
|
if (!this.detector) {
|
|
26446
26448
|
console.log("Wait! Detector not loaded yet.");
|
|
@@ -26470,7 +26472,7 @@ var BaseDetection = class {
|
|
|
26470
26472
|
(_a2 = this.video) == null ? void 0 : _a2.addEventListener("loadeddata", () => {
|
|
26471
26473
|
this.animationFrameId = setTimeout(() => {
|
|
26472
26474
|
that.predictWebcam();
|
|
26473
|
-
},
|
|
26475
|
+
}, delay);
|
|
26474
26476
|
});
|
|
26475
26477
|
const style = document.createElement("style");
|
|
26476
26478
|
style.type = "text/css";
|
|
@@ -26487,7 +26489,6 @@ var BaseDetection = class {
|
|
|
26487
26489
|
}
|
|
26488
26490
|
`;
|
|
26489
26491
|
document.getElementsByTagName("head")[0].appendChild(style);
|
|
26490
|
-
console.log("BaseDetection enableCam OK");
|
|
26491
26492
|
}
|
|
26492
26493
|
async predictWebcam() {
|
|
26493
26494
|
if (this.detecting == false) return;
|
|
@@ -26617,7 +26618,6 @@ var FaceDetection = class extends BaseDetection {
|
|
|
26617
26618
|
);
|
|
26618
26619
|
this.emmitedPositionAlert = false;
|
|
26619
26620
|
this.emmitedFaceAlert = false;
|
|
26620
|
-
console.log("FaceDetection constructor");
|
|
26621
26621
|
this.numFacesSent = -1;
|
|
26622
26622
|
}
|
|
26623
26623
|
stopDetection() {
|
|
@@ -30190,13 +30190,28 @@ var {
|
|
|
30190
30190
|
var DEV_BASE_URL = "https://proctoring-api-dev.easyproctor.tech/api";
|
|
30191
30191
|
var HOMOL_BASE_URL = "https://proctoring-api-hml.easyproctor.tech/api";
|
|
30192
30192
|
var PROD_BASE_URL = "https://proctoring-api.easyproctor.tech/api";
|
|
30193
|
+
var REALTIME_DEV_BASE_URL = "https://easyproctor-realtime-api-dev.easyproctor.tech/api";
|
|
30194
|
+
var REALTIME_HOMOL_BASE_URL = "https://easyproctor-realtime-api-hml.easyproctor.tech/api";
|
|
30195
|
+
var REALTIME_PROD_BASE_URL = "https://easyproctor-realtime-api.easyproctor.tech/api";
|
|
30193
30196
|
var BackendService = class {
|
|
30194
30197
|
constructor(options) {
|
|
30195
30198
|
this.options = options;
|
|
30196
|
-
this.baseUrl = this.selectBaseUrl(options.type);
|
|
30199
|
+
this.baseUrl = this.selectBaseUrl(options.type, options.isRealtime);
|
|
30197
30200
|
this.token = options.token;
|
|
30198
30201
|
}
|
|
30199
|
-
|
|
30202
|
+
getBaseUrl() {
|
|
30203
|
+
return this.baseUrl;
|
|
30204
|
+
}
|
|
30205
|
+
selectBaseUrl(type, isRealtime) {
|
|
30206
|
+
if (isRealtime) {
|
|
30207
|
+
if (type === "dev") {
|
|
30208
|
+
return REALTIME_DEV_BASE_URL;
|
|
30209
|
+
} else if (type === "homol") {
|
|
30210
|
+
return REALTIME_HOMOL_BASE_URL;
|
|
30211
|
+
} else {
|
|
30212
|
+
return REALTIME_PROD_BASE_URL;
|
|
30213
|
+
}
|
|
30214
|
+
}
|
|
30200
30215
|
if (type === "dev") {
|
|
30201
30216
|
return DEV_BASE_URL;
|
|
30202
30217
|
} else if (type === "homol") {
|
|
@@ -30207,6 +30222,9 @@ var BackendService = class {
|
|
|
30207
30222
|
return PROD_BASE_URL;
|
|
30208
30223
|
}
|
|
30209
30224
|
}
|
|
30225
|
+
setRealtime(isRealtime) {
|
|
30226
|
+
this.baseUrl = this.selectBaseUrl(this.options.type, isRealtime);
|
|
30227
|
+
}
|
|
30210
30228
|
getSocketUrl() {
|
|
30211
30229
|
return this.baseUrl.replace("/api", "/hub/sockethub");
|
|
30212
30230
|
}
|
|
@@ -30317,6 +30335,18 @@ var BackendService = class {
|
|
|
30317
30335
|
});
|
|
30318
30336
|
return url2.data;
|
|
30319
30337
|
}
|
|
30338
|
+
async initiateUpload(token, objectName, contentType) {
|
|
30339
|
+
const url2 = await this.makeRequestAxios({
|
|
30340
|
+
path: `/upload/initiate-upload`,
|
|
30341
|
+
method: "POST",
|
|
30342
|
+
jwt: token,
|
|
30343
|
+
body: {
|
|
30344
|
+
objectName,
|
|
30345
|
+
contentType
|
|
30346
|
+
}
|
|
30347
|
+
});
|
|
30348
|
+
return url2.data;
|
|
30349
|
+
}
|
|
30320
30350
|
async saveAlerts(proctoringOptions, proctoringSession) {
|
|
30321
30351
|
await this.makeRequest({
|
|
30322
30352
|
path: "/proctoring/save-alerts",
|
|
@@ -30420,6 +30450,22 @@ var BackendService = class {
|
|
|
30420
30450
|
});
|
|
30421
30451
|
return result.data;
|
|
30422
30452
|
}
|
|
30453
|
+
async checkUpload(token, objectName, contentType) {
|
|
30454
|
+
try {
|
|
30455
|
+
const result = await this.makeRequestAxios({
|
|
30456
|
+
path: `/Upload/check`,
|
|
30457
|
+
method: "POST",
|
|
30458
|
+
jwt: token,
|
|
30459
|
+
body: {
|
|
30460
|
+
objectName,
|
|
30461
|
+
contentType
|
|
30462
|
+
}
|
|
30463
|
+
});
|
|
30464
|
+
return result.data === true;
|
|
30465
|
+
} catch (e3) {
|
|
30466
|
+
return false;
|
|
30467
|
+
}
|
|
30468
|
+
}
|
|
30423
30469
|
async getServerHour(token) {
|
|
30424
30470
|
return await this.makeRequest({
|
|
30425
30471
|
path: `/Proctoring/server-hour`,
|
|
@@ -30620,7 +30666,8 @@ var SpyCam = class {
|
|
|
30620
30666
|
this.context = context;
|
|
30621
30667
|
this.backend = new BackendService({
|
|
30622
30668
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
30623
|
-
token: context.token
|
|
30669
|
+
token: context.token,
|
|
30670
|
+
isRealtime: false
|
|
30624
30671
|
});
|
|
30625
30672
|
this.currentIsPlugged = true;
|
|
30626
30673
|
}
|
|
@@ -30772,6 +30819,14 @@ var getDefaultProctoringVideoOptions = {
|
|
|
30772
30819
|
minHeight: 480
|
|
30773
30820
|
};
|
|
30774
30821
|
|
|
30822
|
+
// src/utils/verifyVersion.ts
|
|
30823
|
+
function versionVerify() {
|
|
30824
|
+
const agentStr = window.navigator.userAgent.split("SEB/");
|
|
30825
|
+
if (agentStr.length > 1)
|
|
30826
|
+
return agentStr[1];
|
|
30827
|
+
else return "1.0.0.0";
|
|
30828
|
+
}
|
|
30829
|
+
|
|
30775
30830
|
// src/utils/browserInformations.ts
|
|
30776
30831
|
function fnBrowserDetect() {
|
|
30777
30832
|
const userAgent = navigator.userAgent;
|
|
@@ -30800,13 +30855,16 @@ function isMobileDevice() {
|
|
|
30800
30855
|
}
|
|
30801
30856
|
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
30802
30857
|
}
|
|
30858
|
+
function isSafeBrowser() {
|
|
30859
|
+
return versionVerify() !== "1.0.0.0";
|
|
30860
|
+
}
|
|
30803
30861
|
|
|
30804
30862
|
// src/plugins/recorder.ts
|
|
30805
30863
|
var proctoringId;
|
|
30806
30864
|
function setRecorderProctoringId(id) {
|
|
30807
30865
|
proctoringId = id;
|
|
30808
30866
|
}
|
|
30809
|
-
function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
|
|
30867
|
+
function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false, recorderOpts) {
|
|
30810
30868
|
let resolvePromise;
|
|
30811
30869
|
let onBufferSizeInterval;
|
|
30812
30870
|
let lastEvent;
|
|
@@ -30814,6 +30872,7 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30814
30872
|
bufferSize = 0;
|
|
30815
30873
|
let startTime;
|
|
30816
30874
|
let duration = 0;
|
|
30875
|
+
let chunkIndex = 0;
|
|
30817
30876
|
let recorderOptions = {
|
|
30818
30877
|
// eslint-disable-next-line no-useless-escape
|
|
30819
30878
|
mimeType: "video/webm",
|
|
@@ -30848,6 +30907,10 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30848
30907
|
mediaRecorder2.ondataavailable = (e3) => {
|
|
30849
30908
|
bufferSize = bufferSize + e3.data.size;
|
|
30850
30909
|
if (e3.data.size > 0) {
|
|
30910
|
+
if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
|
|
30911
|
+
recorderOpts.onChunkAvailable(e3.data, chunkIndex);
|
|
30912
|
+
chunkIndex++;
|
|
30913
|
+
}
|
|
30851
30914
|
buffer.push(e3.data);
|
|
30852
30915
|
}
|
|
30853
30916
|
};
|
|
@@ -30879,7 +30942,13 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30879
30942
|
};
|
|
30880
30943
|
try {
|
|
30881
30944
|
console.log("State antes do start:", recorder2.state);
|
|
30882
|
-
|
|
30945
|
+
chunkIndex = 0;
|
|
30946
|
+
if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
|
|
30947
|
+
recorder2.start(recorderOpts.timeslice);
|
|
30948
|
+
} else {
|
|
30949
|
+
recorder2.start(1e4);
|
|
30950
|
+
}
|
|
30951
|
+
bufferSize = 0;
|
|
30883
30952
|
startTime = new Date(Date.now());
|
|
30884
30953
|
} catch (e3) {
|
|
30885
30954
|
console.error("Recorder erro ao chamar start event:", e3);
|
|
@@ -30921,6 +30990,9 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30921
30990
|
console.log("recorder onstop");
|
|
30922
30991
|
duration = Date.now() - startTime.getTime() || 0;
|
|
30923
30992
|
console.log("duration no onstop", duration);
|
|
30993
|
+
stream4.getTracks().forEach((el) => {
|
|
30994
|
+
el.stop();
|
|
30995
|
+
});
|
|
30924
30996
|
resolvePromise && resolvePromise();
|
|
30925
30997
|
};
|
|
30926
30998
|
mediaRecorder.stop();
|
|
@@ -30930,9 +31002,6 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30930
31002
|
console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
|
|
30931
31003
|
resolve();
|
|
30932
31004
|
}
|
|
30933
|
-
stream4.getTracks().forEach((el) => {
|
|
30934
|
-
el.stop();
|
|
30935
|
-
});
|
|
30936
31005
|
});
|
|
30937
31006
|
}
|
|
30938
31007
|
function pauseRecording() {
|
|
@@ -31026,36 +31095,34 @@ var UploadService = class {
|
|
|
31026
31095
|
this.proctoringId = proctoringId2;
|
|
31027
31096
|
}
|
|
31028
31097
|
async uploadPackage(data, token) {
|
|
31029
|
-
const { file
|
|
31098
|
+
const { file } = data;
|
|
31030
31099
|
try {
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
|
|
31100
|
+
console.log("Upload service: uploadPackage");
|
|
31101
|
+
var uploadUrl = "";
|
|
31102
|
+
await this.backend.getSignedUrl(token, file, this.proctoringId).then((result) => uploadUrl = result).catch((error) => {
|
|
31103
|
+
throw error;
|
|
31104
|
+
});
|
|
31105
|
+
console.log("Upload service: uploadUrl", uploadUrl);
|
|
31106
|
+
await axios_default.request({
|
|
31037
31107
|
url: uploadUrl,
|
|
31038
31108
|
method: "PUT",
|
|
31039
31109
|
headers: {
|
|
31040
31110
|
"Content-Type": file.type,
|
|
31041
31111
|
"x-ms-blob-type": "BlockBlob"
|
|
31042
31112
|
},
|
|
31043
|
-
data: file
|
|
31044
|
-
|
|
31045
|
-
|
|
31046
|
-
|
|
31047
|
-
|
|
31048
|
-
|
|
31049
|
-
|
|
31050
|
-
url: uploadUrl,
|
|
31051
|
-
uploaded
|
|
31052
|
-
};
|
|
31113
|
+
data: file
|
|
31114
|
+
}).then(() => {
|
|
31115
|
+
return true;
|
|
31116
|
+
}).catch((error) => {
|
|
31117
|
+
throw error;
|
|
31118
|
+
});
|
|
31119
|
+
return true;
|
|
31053
31120
|
} catch (err) {
|
|
31054
|
-
trackers.registerError(this.proctoringId, `Failed to upload
|
|
31121
|
+
trackers.registerError(this.proctoringId, `Failed to upload package ${err}
|
|
31055
31122
|
File name: ${file.name}
|
|
31056
31123
|
File type: ${file.type}
|
|
31057
31124
|
File size: ${file.size}`);
|
|
31058
|
-
throw
|
|
31125
|
+
throw err;
|
|
31059
31126
|
}
|
|
31060
31127
|
}
|
|
31061
31128
|
async uploadImages(data, token, packSize) {
|
|
@@ -31342,10 +31409,647 @@ var VolumeMeter = class {
|
|
|
31342
31409
|
}
|
|
31343
31410
|
};
|
|
31344
31411
|
|
|
31412
|
+
// src/new-flow/chunk/ChunkStorageService.ts
|
|
31413
|
+
var _ChunkStorageService = class _ChunkStorageService {
|
|
31414
|
+
constructor() {
|
|
31415
|
+
this.db = null;
|
|
31416
|
+
}
|
|
31417
|
+
/**
|
|
31418
|
+
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
31419
|
+
*/
|
|
31420
|
+
async connect() {
|
|
31421
|
+
if (this.db) return this.db;
|
|
31422
|
+
return new Promise((resolve, reject) => {
|
|
31423
|
+
const request = window.indexedDB.open(
|
|
31424
|
+
_ChunkStorageService.DB_NAME,
|
|
31425
|
+
_ChunkStorageService.DB_VERSION
|
|
31426
|
+
);
|
|
31427
|
+
request.onerror = () => {
|
|
31428
|
+
reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
|
|
31429
|
+
};
|
|
31430
|
+
request.onupgradeneeded = () => {
|
|
31431
|
+
const db = request.result;
|
|
31432
|
+
if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
|
|
31433
|
+
db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
|
|
31434
|
+
}
|
|
31435
|
+
const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
|
|
31436
|
+
keyPath: "id",
|
|
31437
|
+
autoIncrement: true
|
|
31438
|
+
});
|
|
31439
|
+
store.createIndex("proctoringId", "proctoringId", { unique: false });
|
|
31440
|
+
store.createIndex("uploaded", "uploaded", { unique: false });
|
|
31441
|
+
store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
|
|
31442
|
+
unique: false
|
|
31443
|
+
});
|
|
31444
|
+
};
|
|
31445
|
+
request.onsuccess = () => {
|
|
31446
|
+
this.db = request.result;
|
|
31447
|
+
resolve(this.db);
|
|
31448
|
+
};
|
|
31449
|
+
});
|
|
31450
|
+
}
|
|
31451
|
+
/**
|
|
31452
|
+
* Salva um chunk de vídeo no IndexedDB.
|
|
31453
|
+
*/
|
|
31454
|
+
async saveChunk(chunk) {
|
|
31455
|
+
const db = await this.connect();
|
|
31456
|
+
return new Promise((resolve, reject) => {
|
|
31457
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31458
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31459
|
+
const request = store.add(chunk);
|
|
31460
|
+
request.onsuccess = () => {
|
|
31461
|
+
resolve(request.result);
|
|
31462
|
+
};
|
|
31463
|
+
request.onerror = () => {
|
|
31464
|
+
var _a2;
|
|
31465
|
+
reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31466
|
+
};
|
|
31467
|
+
});
|
|
31468
|
+
}
|
|
31469
|
+
/**
|
|
31470
|
+
* Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
|
|
31471
|
+
*/
|
|
31472
|
+
async getPendingChunks(proctoringId2) {
|
|
31473
|
+
const db = await this.connect();
|
|
31474
|
+
return new Promise((resolve, reject) => {
|
|
31475
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31476
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31477
|
+
const index = store.index("proctoringId_uploaded");
|
|
31478
|
+
const range = IDBKeyRange.only([proctoringId2, 0]);
|
|
31479
|
+
const request = index.getAll(range);
|
|
31480
|
+
request.onsuccess = () => {
|
|
31481
|
+
const chunks = request.result.sort(
|
|
31482
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31483
|
+
);
|
|
31484
|
+
resolve(chunks);
|
|
31485
|
+
};
|
|
31486
|
+
request.onerror = () => {
|
|
31487
|
+
var _a2;
|
|
31488
|
+
reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31489
|
+
};
|
|
31490
|
+
});
|
|
31491
|
+
}
|
|
31492
|
+
/**
|
|
31493
|
+
* Retorna todos os chunks (enviados ou não) de um proctoringId específico.
|
|
31494
|
+
*/
|
|
31495
|
+
async getAllChunks(proctoringId2) {
|
|
31496
|
+
const db = await this.connect();
|
|
31497
|
+
return new Promise((resolve, reject) => {
|
|
31498
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31499
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31500
|
+
const index = store.index("proctoringId");
|
|
31501
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
31502
|
+
const request = index.getAll(range);
|
|
31503
|
+
request.onsuccess = () => {
|
|
31504
|
+
const chunks = request.result.sort(
|
|
31505
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
31506
|
+
);
|
|
31507
|
+
resolve(chunks);
|
|
31508
|
+
};
|
|
31509
|
+
request.onerror = () => {
|
|
31510
|
+
var _a2;
|
|
31511
|
+
reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31512
|
+
};
|
|
31513
|
+
});
|
|
31514
|
+
}
|
|
31515
|
+
/**
|
|
31516
|
+
* Marca um chunk como enviado (uploaded = 1).
|
|
31517
|
+
*/
|
|
31518
|
+
async markAsUploaded(chunkId) {
|
|
31519
|
+
const db = await this.connect();
|
|
31520
|
+
return new Promise((resolve, reject) => {
|
|
31521
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31522
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31523
|
+
const getRequest = store.get(chunkId);
|
|
31524
|
+
getRequest.onsuccess = () => {
|
|
31525
|
+
const chunk = getRequest.result;
|
|
31526
|
+
if (!chunk) {
|
|
31527
|
+
resolve();
|
|
31528
|
+
return;
|
|
31529
|
+
}
|
|
31530
|
+
chunk.uploaded = 1;
|
|
31531
|
+
const putRequest = store.put(chunk);
|
|
31532
|
+
putRequest.onsuccess = () => resolve();
|
|
31533
|
+
putRequest.onerror = () => {
|
|
31534
|
+
var _a2;
|
|
31535
|
+
return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
|
|
31536
|
+
};
|
|
31537
|
+
};
|
|
31538
|
+
getRequest.onerror = () => {
|
|
31539
|
+
var _a2;
|
|
31540
|
+
return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
|
|
31541
|
+
};
|
|
31542
|
+
});
|
|
31543
|
+
}
|
|
31544
|
+
/**
|
|
31545
|
+
* Remove todos os chunks já enviados de um proctoringId para liberar espaço.
|
|
31546
|
+
*/
|
|
31547
|
+
async clearUploadedChunks(proctoringId2) {
|
|
31548
|
+
const db = await this.connect();
|
|
31549
|
+
return new Promise((resolve, reject) => {
|
|
31550
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31551
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31552
|
+
const index = store.index("proctoringId_uploaded");
|
|
31553
|
+
const range = IDBKeyRange.only([proctoringId2, 1]);
|
|
31554
|
+
const request = index.openCursor(range);
|
|
31555
|
+
request.onsuccess = () => {
|
|
31556
|
+
const cursor = request.result;
|
|
31557
|
+
if (cursor) {
|
|
31558
|
+
cursor.delete();
|
|
31559
|
+
cursor.continue();
|
|
31560
|
+
} else {
|
|
31561
|
+
resolve();
|
|
31562
|
+
}
|
|
31563
|
+
};
|
|
31564
|
+
request.onerror = () => {
|
|
31565
|
+
var _a2;
|
|
31566
|
+
return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31567
|
+
};
|
|
31568
|
+
});
|
|
31569
|
+
}
|
|
31570
|
+
/**
|
|
31571
|
+
* Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
|
|
31572
|
+
*/
|
|
31573
|
+
async clearAllChunks(proctoringId2) {
|
|
31574
|
+
const db = await this.connect();
|
|
31575
|
+
return new Promise((resolve, reject) => {
|
|
31576
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31577
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31578
|
+
const index = store.index("proctoringId");
|
|
31579
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
31580
|
+
const request = index.openCursor(range);
|
|
31581
|
+
request.onsuccess = () => {
|
|
31582
|
+
const cursor = request.result;
|
|
31583
|
+
if (cursor) {
|
|
31584
|
+
cursor.delete();
|
|
31585
|
+
cursor.continue();
|
|
31586
|
+
} else {
|
|
31587
|
+
resolve();
|
|
31588
|
+
}
|
|
31589
|
+
};
|
|
31590
|
+
request.onerror = () => {
|
|
31591
|
+
var _a2;
|
|
31592
|
+
return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31593
|
+
};
|
|
31594
|
+
});
|
|
31595
|
+
}
|
|
31596
|
+
/**
|
|
31597
|
+
* Verifica se existem chunks pendentes para qualquer proctoringId.
|
|
31598
|
+
* Útil na recuperação pós-crash.
|
|
31599
|
+
*/
|
|
31600
|
+
async hasAnyPendingChunks() {
|
|
31601
|
+
const db = await this.connect();
|
|
31602
|
+
return new Promise((resolve, reject) => {
|
|
31603
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31604
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31605
|
+
const index = store.index("uploaded");
|
|
31606
|
+
const range = IDBKeyRange.only(0);
|
|
31607
|
+
const request = index.count(range);
|
|
31608
|
+
request.onsuccess = () => {
|
|
31609
|
+
resolve(request.result > 0);
|
|
31610
|
+
};
|
|
31611
|
+
request.onerror = () => {
|
|
31612
|
+
var _a2;
|
|
31613
|
+
return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31614
|
+
};
|
|
31615
|
+
});
|
|
31616
|
+
}
|
|
31617
|
+
/**
|
|
31618
|
+
* Retorna todos os proctoringIds que possuem chunks pendentes.
|
|
31619
|
+
* Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
|
|
31620
|
+
*/
|
|
31621
|
+
async getPendingProctoringIds() {
|
|
31622
|
+
const db = await this.connect();
|
|
31623
|
+
return new Promise((resolve, reject) => {
|
|
31624
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
31625
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31626
|
+
const index = store.index("uploaded");
|
|
31627
|
+
const range = IDBKeyRange.only(0);
|
|
31628
|
+
const request = index.getAll(range);
|
|
31629
|
+
request.onsuccess = () => {
|
|
31630
|
+
const chunks = request.result;
|
|
31631
|
+
const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
|
|
31632
|
+
resolve(ids);
|
|
31633
|
+
};
|
|
31634
|
+
request.onerror = () => {
|
|
31635
|
+
var _a2;
|
|
31636
|
+
return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
31637
|
+
};
|
|
31638
|
+
});
|
|
31639
|
+
}
|
|
31640
|
+
/**
|
|
31641
|
+
* Fecha a conexão com o banco.
|
|
31642
|
+
*/
|
|
31643
|
+
close() {
|
|
31644
|
+
if (this.db) {
|
|
31645
|
+
this.db.close();
|
|
31646
|
+
this.db = null;
|
|
31647
|
+
}
|
|
31648
|
+
}
|
|
31649
|
+
};
|
|
31650
|
+
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
31651
|
+
/** v2: índices numéricos; v3: payload como ArrayBuffer em vez de Blob (Safari iOS). */
|
|
31652
|
+
_ChunkStorageService.DB_VERSION = 3;
|
|
31653
|
+
_ChunkStorageService.STORE_NAME = "chunks";
|
|
31654
|
+
var ChunkStorageService = _ChunkStorageService;
|
|
31655
|
+
|
|
31656
|
+
// src/new-flow/chunk/BackgroundUploadService.ts
|
|
31657
|
+
var DEFAULT_CONFIG = {
|
|
31658
|
+
pollInterval: 5e3,
|
|
31659
|
+
maxRetries: 5,
|
|
31660
|
+
baseRetryDelay: 2e3,
|
|
31661
|
+
cleanAfterUpload: true
|
|
31662
|
+
};
|
|
31663
|
+
var BackgroundUploadService = class _BackgroundUploadService {
|
|
31664
|
+
constructor(proctoringId2, token, backend, chunkStorage, config) {
|
|
31665
|
+
this.pollTimer = null;
|
|
31666
|
+
this.isProcessing = false;
|
|
31667
|
+
this.isRunning = false;
|
|
31668
|
+
/** Mapa de chunkId -> número de tentativas já feitas */
|
|
31669
|
+
this.retryCount = /* @__PURE__ */ new Map();
|
|
31670
|
+
/** GCS Resumable Upload State */
|
|
31671
|
+
this.sessionUrl = null;
|
|
31672
|
+
this.currentOffset = 0;
|
|
31673
|
+
this.totalBytesPurged = 0;
|
|
31674
|
+
this.STORAGE_KEY_PREFIX = "ep_upload_session_";
|
|
31675
|
+
this.GCS_CHUNK_SIZE = 256 * 1024;
|
|
31676
|
+
this.proctoringId = proctoringId2.trim();
|
|
31677
|
+
this.token = token;
|
|
31678
|
+
this.backend = backend;
|
|
31679
|
+
this.chunkStorage = chunkStorage;
|
|
31680
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31681
|
+
this.loadSessionState();
|
|
31682
|
+
}
|
|
31683
|
+
loadSessionState() {
|
|
31684
|
+
try {
|
|
31685
|
+
const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31686
|
+
if (stored) {
|
|
31687
|
+
const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
|
|
31688
|
+
this.sessionUrl = sessionUrl;
|
|
31689
|
+
this.currentOffset = currentOffset;
|
|
31690
|
+
this.totalBytesPurged = totalBytesPurged || 0;
|
|
31691
|
+
}
|
|
31692
|
+
} catch (e3) {
|
|
31693
|
+
console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
|
|
31694
|
+
}
|
|
31695
|
+
}
|
|
31696
|
+
saveSessionState() {
|
|
31697
|
+
try {
|
|
31698
|
+
localStorage.setItem(
|
|
31699
|
+
`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
|
|
31700
|
+
JSON.stringify({
|
|
31701
|
+
sessionUrl: this.sessionUrl,
|
|
31702
|
+
currentOffset: this.currentOffset,
|
|
31703
|
+
totalBytesPurged: this.totalBytesPurged
|
|
31704
|
+
})
|
|
31705
|
+
);
|
|
31706
|
+
} catch (e3) {
|
|
31707
|
+
console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
|
|
31708
|
+
}
|
|
31709
|
+
}
|
|
31710
|
+
clearSessionState() {
|
|
31711
|
+
try {
|
|
31712
|
+
localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31713
|
+
this.sessionUrl = null;
|
|
31714
|
+
this.currentOffset = 0;
|
|
31715
|
+
this.totalBytesPurged = 0;
|
|
31716
|
+
} catch (e3) {
|
|
31717
|
+
console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
|
|
31718
|
+
}
|
|
31719
|
+
}
|
|
31720
|
+
/**
|
|
31721
|
+
* Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
|
|
31722
|
+
* para enviar chunks pendentes.
|
|
31723
|
+
*/
|
|
31724
|
+
start() {
|
|
31725
|
+
if (this.isRunning) return;
|
|
31726
|
+
this.isRunning = true;
|
|
31727
|
+
console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
|
|
31728
|
+
this.processQueue();
|
|
31729
|
+
this.pollTimer = setInterval(() => {
|
|
31730
|
+
this.processQueue(false);
|
|
31731
|
+
}, this.config.pollInterval);
|
|
31732
|
+
}
|
|
31733
|
+
/**
|
|
31734
|
+
* Para o serviço de upload em background.
|
|
31735
|
+
*/
|
|
31736
|
+
stop() {
|
|
31737
|
+
this.isRunning = false;
|
|
31738
|
+
if (this.pollTimer) {
|
|
31739
|
+
clearInterval(this.pollTimer);
|
|
31740
|
+
this.pollTimer = null;
|
|
31741
|
+
}
|
|
31742
|
+
console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
|
|
31743
|
+
}
|
|
31744
|
+
/**
|
|
31745
|
+
* Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
|
|
31746
|
+
* Útil quando a gravação é finalizada.
|
|
31747
|
+
*/
|
|
31748
|
+
async flush() {
|
|
31749
|
+
console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
|
|
31750
|
+
let waitAttempts = 0;
|
|
31751
|
+
while (this.isProcessing && waitAttempts < 10) {
|
|
31752
|
+
await this.sleep(1e3);
|
|
31753
|
+
waitAttempts++;
|
|
31754
|
+
}
|
|
31755
|
+
let flushRetries = 0;
|
|
31756
|
+
const maxFlushRetries = 3;
|
|
31757
|
+
while (flushRetries < maxFlushRetries) {
|
|
31758
|
+
try {
|
|
31759
|
+
await this.processQueue(true);
|
|
31760
|
+
console.log(`[BackgroundUpload] Flush completado com sucesso.`);
|
|
31761
|
+
return;
|
|
31762
|
+
} catch (error) {
|
|
31763
|
+
flushRetries++;
|
|
31764
|
+
console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
|
|
31765
|
+
if (flushRetries < maxFlushRetries) {
|
|
31766
|
+
await this.sleep(2e3);
|
|
31767
|
+
}
|
|
31768
|
+
}
|
|
31769
|
+
}
|
|
31770
|
+
throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
|
|
31771
|
+
}
|
|
31772
|
+
/**
|
|
31773
|
+
* Sincroniza o offset local com o estado real no Google Cloud Storage.
|
|
31774
|
+
*/
|
|
31775
|
+
async syncOffset() {
|
|
31776
|
+
if (!this.sessionUrl) return 0;
|
|
31777
|
+
try {
|
|
31778
|
+
console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
|
|
31779
|
+
const response = await fetch(this.sessionUrl, {
|
|
31780
|
+
method: "PUT",
|
|
31781
|
+
headers: {
|
|
31782
|
+
"Content-Range": "bytes */*"
|
|
31783
|
+
}
|
|
31784
|
+
});
|
|
31785
|
+
console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
|
|
31786
|
+
if (response.status === 308) {
|
|
31787
|
+
const range = response.headers.get("Range");
|
|
31788
|
+
if (range) {
|
|
31789
|
+
const lastByte = parseInt(range.split("-")[1], 10);
|
|
31790
|
+
this.currentOffset = lastByte + 1;
|
|
31791
|
+
this.saveSessionState();
|
|
31792
|
+
console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
|
|
31793
|
+
} else {
|
|
31794
|
+
this.currentOffset = 0;
|
|
31795
|
+
}
|
|
31796
|
+
} else if (response.ok || response.status === 201) {
|
|
31797
|
+
console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
|
|
31798
|
+
this.currentOffset = -1;
|
|
31799
|
+
} else {
|
|
31800
|
+
console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
|
|
31801
|
+
}
|
|
31802
|
+
} catch (error) {
|
|
31803
|
+
console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
|
|
31804
|
+
}
|
|
31805
|
+
return this.currentOffset;
|
|
31806
|
+
}
|
|
31807
|
+
/**
|
|
31808
|
+
* Verifica e envia chunks pendentes para o backend.
|
|
31809
|
+
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
31810
|
+
*/
|
|
31811
|
+
async processQueue(isFinal = false) {
|
|
31812
|
+
var _a2, _b;
|
|
31813
|
+
if (this.isProcessing) return;
|
|
31814
|
+
this.isProcessing = true;
|
|
31815
|
+
try {
|
|
31816
|
+
if (this.sessionUrl) {
|
|
31817
|
+
await this.syncOffset();
|
|
31818
|
+
if (this.currentOffset === -1) {
|
|
31819
|
+
console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
|
|
31820
|
+
this.clearSessionState();
|
|
31821
|
+
this.isProcessing = false;
|
|
31822
|
+
return;
|
|
31823
|
+
}
|
|
31824
|
+
}
|
|
31825
|
+
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
31826
|
+
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
31827
|
+
if (pendingChunks.length === 0 && !isFinal) {
|
|
31828
|
+
this.isProcessing = false;
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
31831
|
+
console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
|
|
31832
|
+
let virtualStart = this.totalBytesPurged;
|
|
31833
|
+
const chunksWithMeta = allChunks.map((c3) => {
|
|
31834
|
+
const byteLen = c3.arrayBuffer.byteLength;
|
|
31835
|
+
const start = virtualStart;
|
|
31836
|
+
const end = start + byteLen - 1;
|
|
31837
|
+
virtualStart += byteLen;
|
|
31838
|
+
return { chunk: c3, start, end };
|
|
31839
|
+
});
|
|
31840
|
+
const sliceParts = [];
|
|
31841
|
+
let lastProcessedChunkId = null;
|
|
31842
|
+
let finalChunkIndex = 0;
|
|
31843
|
+
let mimeType = pendingChunks[0].mimeType;
|
|
31844
|
+
for (const meta of chunksWithMeta) {
|
|
31845
|
+
if (this.currentOffset > meta.end) continue;
|
|
31846
|
+
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
31847
|
+
const view = new Uint8Array(meta.chunk.arrayBuffer);
|
|
31848
|
+
sliceParts.push(view.subarray(sliceStart));
|
|
31849
|
+
lastProcessedChunkId = meta.chunk.id;
|
|
31850
|
+
finalChunkIndex = meta.chunk.chunkIndex;
|
|
31851
|
+
}
|
|
31852
|
+
if (sliceParts.length === 0 && !isFinal) {
|
|
31853
|
+
this.isProcessing = false;
|
|
31854
|
+
return;
|
|
31855
|
+
}
|
|
31856
|
+
const combinedLength = sliceParts.reduce((acc, p3) => acc + p3.length, 0);
|
|
31857
|
+
const combined = new Uint8Array(combinedLength);
|
|
31858
|
+
{
|
|
31859
|
+
let off = 0;
|
|
31860
|
+
for (const p3 of sliceParts) {
|
|
31861
|
+
combined.set(p3, off);
|
|
31862
|
+
off += p3.length;
|
|
31863
|
+
}
|
|
31864
|
+
}
|
|
31865
|
+
let sendableSize = combined.byteLength;
|
|
31866
|
+
let totalSizeForHeader = void 0;
|
|
31867
|
+
if (!isFinal) {
|
|
31868
|
+
sendableSize = Math.floor(combined.byteLength / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
31869
|
+
if (sendableSize === 0) {
|
|
31870
|
+
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
31871
|
+
this.isProcessing = false;
|
|
31872
|
+
return;
|
|
31873
|
+
}
|
|
31874
|
+
} else {
|
|
31875
|
+
totalSizeForHeader = virtualStart;
|
|
31876
|
+
}
|
|
31877
|
+
const payload = sendableSize === combined.byteLength ? combined : combined.subarray(0, sendableSize);
|
|
31878
|
+
try {
|
|
31879
|
+
await this.uploadData(
|
|
31880
|
+
payload.byteLength > 0 ? payload : null,
|
|
31881
|
+
mimeType,
|
|
31882
|
+
finalChunkIndex,
|
|
31883
|
+
totalSizeForHeader
|
|
31884
|
+
);
|
|
31885
|
+
for (const meta of chunksWithMeta) {
|
|
31886
|
+
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
31887
|
+
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
31888
|
+
this.retryCount.delete(meta.chunk.id);
|
|
31889
|
+
(_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
31890
|
+
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
31891
|
+
}
|
|
31892
|
+
}
|
|
31893
|
+
if (this.config.cleanAfterUpload) {
|
|
31894
|
+
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
31895
|
+
const sizePurged = chunksToClear.reduce(
|
|
31896
|
+
(acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
|
|
31897
|
+
0
|
|
31898
|
+
);
|
|
31899
|
+
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
31900
|
+
if (sizePurged > 0) {
|
|
31901
|
+
this.totalBytesPurged += sizePurged;
|
|
31902
|
+
this.saveSessionState();
|
|
31903
|
+
console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
|
|
31904
|
+
}
|
|
31905
|
+
}
|
|
31906
|
+
if (isFinal) {
|
|
31907
|
+
this.clearSessionState();
|
|
31908
|
+
}
|
|
31909
|
+
} catch (error) {
|
|
31910
|
+
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
31911
|
+
(_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
|
|
31912
|
+
}
|
|
31913
|
+
} catch (error) {
|
|
31914
|
+
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
31915
|
+
} finally {
|
|
31916
|
+
this.isProcessing = false;
|
|
31917
|
+
}
|
|
31918
|
+
}
|
|
31919
|
+
/**
|
|
31920
|
+
* Faz o upload bruto de dados para a sessão GCS.
|
|
31921
|
+
*/
|
|
31922
|
+
async uploadData(body, mimeType, chunkIndex, totalSize) {
|
|
31923
|
+
var _a2;
|
|
31924
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
31925
|
+
if (!this.sessionUrl) {
|
|
31926
|
+
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
31927
|
+
const startResponse = await fetch(initiateUrl, {
|
|
31928
|
+
method: "POST",
|
|
31929
|
+
headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
|
|
31930
|
+
});
|
|
31931
|
+
if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
|
|
31932
|
+
this.sessionUrl = startResponse.headers.get("Location");
|
|
31933
|
+
if (!this.sessionUrl) throw new Error("Location header ausente");
|
|
31934
|
+
try {
|
|
31935
|
+
const urlObj = new URL(this.sessionUrl);
|
|
31936
|
+
const pathParts = urlObj.pathname.split("/");
|
|
31937
|
+
let bucket = pathParts[1];
|
|
31938
|
+
let object = decodeURIComponent(pathParts.slice(2).join("/"));
|
|
31939
|
+
if (pathParts.includes("b") && pathParts.includes("o")) {
|
|
31940
|
+
const bIdx = pathParts.indexOf("b") + 1;
|
|
31941
|
+
const oIdx = pathParts.indexOf("o") + 1;
|
|
31942
|
+
bucket = pathParts[bIdx];
|
|
31943
|
+
object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
|
|
31944
|
+
}
|
|
31945
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
|
|
31946
|
+
} catch (e3) {
|
|
31947
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
|
|
31948
|
+
}
|
|
31949
|
+
this.currentOffset = 0;
|
|
31950
|
+
this.saveSessionState();
|
|
31951
|
+
} else {
|
|
31952
|
+
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
31953
|
+
}
|
|
31954
|
+
const size = (_a2 = body == null ? void 0 : body.byteLength) != null ? _a2 : 0;
|
|
31955
|
+
const start = this.currentOffset;
|
|
31956
|
+
const end = start + size - 1;
|
|
31957
|
+
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
31958
|
+
const contentRangeHeader = size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
31959
|
+
console.log(
|
|
31960
|
+
`[BackgroundUpload] Enviando ${size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${size})`
|
|
31961
|
+
);
|
|
31962
|
+
let uploadBody = null;
|
|
31963
|
+
if (size > 0 && body) {
|
|
31964
|
+
const raw = body.buffer;
|
|
31965
|
+
if (raw instanceof ArrayBuffer) {
|
|
31966
|
+
uploadBody = body.byteOffset === 0 && body.byteLength === raw.byteLength ? raw : raw.slice(body.byteOffset, body.byteOffset + body.byteLength);
|
|
31967
|
+
} else {
|
|
31968
|
+
uploadBody = new ArrayBuffer(body.byteLength);
|
|
31969
|
+
new Uint8Array(uploadBody).set(body);
|
|
31970
|
+
}
|
|
31971
|
+
}
|
|
31972
|
+
const response = await fetch(this.sessionUrl, {
|
|
31973
|
+
method: "PUT",
|
|
31974
|
+
headers: { "Content-Range": contentRangeHeader },
|
|
31975
|
+
body: uploadBody
|
|
31976
|
+
});
|
|
31977
|
+
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
31978
|
+
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
31979
|
+
const errorText = await response.text();
|
|
31980
|
+
console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
|
|
31981
|
+
throw new Error(`Status HTTP inesperado: ${response.status}`);
|
|
31982
|
+
}
|
|
31983
|
+
const rangeHeader = response.headers.get("Range");
|
|
31984
|
+
if (rangeHeader) {
|
|
31985
|
+
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
31986
|
+
this.currentOffset = lastByte + 1;
|
|
31987
|
+
} else {
|
|
31988
|
+
this.currentOffset += size;
|
|
31989
|
+
}
|
|
31990
|
+
this.saveSessionState();
|
|
31991
|
+
trackers.registerUploadFile(
|
|
31992
|
+
this.proctoringId,
|
|
31993
|
+
`GCS Stream Upload
|
|
31994
|
+
Size: ${size}
|
|
31995
|
+
Range: ${start}-${end}
|
|
31996
|
+
Last Index: ${chunkIndex}`,
|
|
31997
|
+
"CameraChunk"
|
|
31998
|
+
);
|
|
31999
|
+
}
|
|
32000
|
+
/**
|
|
32001
|
+
* Método estático para recuperação pós-crash.
|
|
32002
|
+
* Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
|
|
32003
|
+
* e tenta enviar.
|
|
32004
|
+
*/
|
|
32005
|
+
static async recoverPendingUploads(backend, token) {
|
|
32006
|
+
const chunkStorage = new ChunkStorageService();
|
|
32007
|
+
const recoveredIds = [];
|
|
32008
|
+
try {
|
|
32009
|
+
const pendingIds = await chunkStorage.getPendingProctoringIds();
|
|
32010
|
+
if (pendingIds.length === 0) {
|
|
32011
|
+
console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
|
|
32012
|
+
return recoveredIds;
|
|
32013
|
+
}
|
|
32014
|
+
console.log(
|
|
32015
|
+
`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
|
|
32016
|
+
);
|
|
32017
|
+
for (const proctoringId2 of pendingIds) {
|
|
32018
|
+
try {
|
|
32019
|
+
const service = new _BackgroundUploadService(
|
|
32020
|
+
proctoringId2,
|
|
32021
|
+
token,
|
|
32022
|
+
backend,
|
|
32023
|
+
chunkStorage,
|
|
32024
|
+
{ cleanAfterUpload: true }
|
|
32025
|
+
);
|
|
32026
|
+
await service.flush();
|
|
32027
|
+
recoveredIds.push(proctoringId2);
|
|
32028
|
+
console.log(
|
|
32029
|
+
`[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
|
|
32030
|
+
);
|
|
32031
|
+
} catch (error) {
|
|
32032
|
+
console.error(
|
|
32033
|
+
`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
|
|
32034
|
+
error
|
|
32035
|
+
);
|
|
32036
|
+
}
|
|
32037
|
+
}
|
|
32038
|
+
} catch (error) {
|
|
32039
|
+
console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
|
|
32040
|
+
}
|
|
32041
|
+
return recoveredIds;
|
|
32042
|
+
}
|
|
32043
|
+
sleep(ms2) {
|
|
32044
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
32045
|
+
}
|
|
32046
|
+
};
|
|
32047
|
+
|
|
31345
32048
|
// src/new-flow/recorders/CameraRecorder.ts
|
|
31346
32049
|
var import_jszip_min = __toESM(require_jszip_min());
|
|
31347
|
-
var
|
|
31348
|
-
var
|
|
32050
|
+
var pkg = require_fix_webm_duration();
|
|
32051
|
+
var fixWebmDuration = pkg.default || pkg;
|
|
32052
|
+
var _CameraRecorder = class _CameraRecorder {
|
|
31349
32053
|
constructor(options, videoOptions, paramsConfig, backend, backendToken) {
|
|
31350
32054
|
this.blobs = [];
|
|
31351
32055
|
this.paramsConfig = {
|
|
@@ -31390,6 +32094,7 @@ var CameraRecorder = class {
|
|
|
31390
32094
|
this.blobsRTC = [];
|
|
31391
32095
|
this.imageCount = 0;
|
|
31392
32096
|
this.filesToUpload = [];
|
|
32097
|
+
this.pendingPackages = [];
|
|
31393
32098
|
this.animationFrameId = null;
|
|
31394
32099
|
this.isCanvasLoopActive = false;
|
|
31395
32100
|
this.hardwareStream = null;
|
|
@@ -31397,8 +32102,16 @@ var CameraRecorder = class {
|
|
|
31397
32102
|
this.videoElement = null;
|
|
31398
32103
|
this.duration = 0;
|
|
31399
32104
|
this.stopped = false;
|
|
32105
|
+
this.backgroundUpload = null;
|
|
32106
|
+
this.chunkIndex = 0;
|
|
32107
|
+
/** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
|
|
32108
|
+
this.pendingChunkSaves = [];
|
|
32109
|
+
// Handlers bound para poder remover os listeners depois
|
|
32110
|
+
this.boundVisibilityHandler = null;
|
|
32111
|
+
this.boundPageHideHandler = null;
|
|
31400
32112
|
this.currentRetries = 0;
|
|
31401
32113
|
this.packageCount = 0;
|
|
32114
|
+
this.failedUploads = 0;
|
|
31402
32115
|
this.noiseWait = 20;
|
|
31403
32116
|
this.options = options;
|
|
31404
32117
|
this.videoOptions = videoOptions;
|
|
@@ -31406,10 +32119,122 @@ var CameraRecorder = class {
|
|
|
31406
32119
|
this.backendToken = backendToken;
|
|
31407
32120
|
paramsConfig && (this.paramsConfig = paramsConfig);
|
|
31408
32121
|
}
|
|
32122
|
+
/**
|
|
32123
|
+
* Determina se o fluxo de chunks e lifecycle deve estar ativo.
|
|
32124
|
+
* Retorna true se:
|
|
32125
|
+
* 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
|
|
32126
|
+
* 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
|
|
32127
|
+
*/
|
|
32128
|
+
get isChunkEnabled() {
|
|
32129
|
+
return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
|
|
32130
|
+
}
|
|
31409
32131
|
setProctoringId(proctoringId2) {
|
|
31410
32132
|
this.proctoringId = proctoringId2;
|
|
31411
32133
|
this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
|
|
31412
32134
|
setRecorderProctoringId(proctoringId2);
|
|
32135
|
+
if (this.isChunkEnabled) {
|
|
32136
|
+
this.chunkStorage = new ChunkStorageService();
|
|
32137
|
+
if (this.backend && this.backendToken) {
|
|
32138
|
+
this.backgroundUpload = new BackgroundUploadService(
|
|
32139
|
+
this.proctoringId,
|
|
32140
|
+
this.backendToken,
|
|
32141
|
+
this.backend,
|
|
32142
|
+
this.chunkStorage,
|
|
32143
|
+
{ pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
|
|
32144
|
+
);
|
|
32145
|
+
}
|
|
32146
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32147
|
+
console.log(
|
|
32148
|
+
`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
|
|
32149
|
+
);
|
|
32150
|
+
} else {
|
|
32151
|
+
console.log(
|
|
32152
|
+
`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
|
|
32153
|
+
);
|
|
32154
|
+
}
|
|
32155
|
+
}
|
|
32156
|
+
// ========================
|
|
32157
|
+
// Session State Persistence (localStorage)
|
|
32158
|
+
// ========================
|
|
32159
|
+
persistSessionState(status) {
|
|
32160
|
+
try {
|
|
32161
|
+
const data = {
|
|
32162
|
+
proctoringId: this.proctoringId,
|
|
32163
|
+
status,
|
|
32164
|
+
timestamp: Date.now()
|
|
32165
|
+
};
|
|
32166
|
+
localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
|
|
32167
|
+
} catch (e3) {
|
|
32168
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
|
|
32169
|
+
}
|
|
32170
|
+
}
|
|
32171
|
+
clearSessionState() {
|
|
32172
|
+
try {
|
|
32173
|
+
localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32174
|
+
} catch (e3) {
|
|
32175
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
|
|
32176
|
+
}
|
|
32177
|
+
}
|
|
32178
|
+
/**
|
|
32179
|
+
* Verifica se existe uma sessão ativa anterior no localStorage.
|
|
32180
|
+
* Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
|
|
32181
|
+
*/
|
|
32182
|
+
static checkForActiveSession() {
|
|
32183
|
+
try {
|
|
32184
|
+
const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32185
|
+
if (!raw) return null;
|
|
32186
|
+
const data = JSON.parse(raw);
|
|
32187
|
+
if (data.status === "IN_PROGRESS") return data;
|
|
32188
|
+
return null;
|
|
32189
|
+
} catch {
|
|
32190
|
+
return null;
|
|
32191
|
+
}
|
|
32192
|
+
}
|
|
32193
|
+
// ========================
|
|
32194
|
+
// Page Lifecycle Management
|
|
32195
|
+
// ========================
|
|
32196
|
+
setupLifecycleListeners() {
|
|
32197
|
+
this.boundVisibilityHandler = () => this.handleVisibilityChange();
|
|
32198
|
+
this.boundPageHideHandler = () => this.handlePageHide();
|
|
32199
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32200
|
+
window.addEventListener("pagehide", this.boundPageHideHandler);
|
|
32201
|
+
}
|
|
32202
|
+
removeLifecycleListeners() {
|
|
32203
|
+
if (this.boundVisibilityHandler) {
|
|
32204
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32205
|
+
this.boundVisibilityHandler = null;
|
|
32206
|
+
}
|
|
32207
|
+
if (this.boundPageHideHandler) {
|
|
32208
|
+
window.removeEventListener("pagehide", this.boundPageHideHandler);
|
|
32209
|
+
this.boundPageHideHandler = null;
|
|
32210
|
+
}
|
|
32211
|
+
}
|
|
32212
|
+
handleVisibilityChange() {
|
|
32213
|
+
var _a2;
|
|
32214
|
+
if (document.visibilityState === "hidden") {
|
|
32215
|
+
console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
|
|
32216
|
+
this.persistSessionState("INTERRUPTED");
|
|
32217
|
+
this.proctoringId && trackers.registerError(
|
|
32218
|
+
this.proctoringId,
|
|
32219
|
+
"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
|
|
32220
|
+
);
|
|
32221
|
+
} else if (document.visibilityState === "visible") {
|
|
32222
|
+
console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
|
|
32223
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32224
|
+
this.proctoringId && trackers.registerError(
|
|
32225
|
+
this.proctoringId,
|
|
32226
|
+
"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
|
|
32227
|
+
);
|
|
32228
|
+
(_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
|
|
32229
|
+
}
|
|
32230
|
+
}
|
|
32231
|
+
handlePageHide() {
|
|
32232
|
+
console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
|
|
32233
|
+
this.persistSessionState("INTERRUPTED");
|
|
32234
|
+
this.proctoringId && trackers.registerError(
|
|
32235
|
+
this.proctoringId,
|
|
32236
|
+
"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
|
|
32237
|
+
);
|
|
31413
32238
|
}
|
|
31414
32239
|
async initializeDetectors() {
|
|
31415
32240
|
var _a2, _b, _c2;
|
|
@@ -31562,10 +32387,15 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31562
32387
|
await new Promise((r3) => setTimeout(r3, 300));
|
|
31563
32388
|
}
|
|
31564
32389
|
async startRecording() {
|
|
31565
|
-
var _a2, _b, _c2, _d, _e3, _f, _g;
|
|
31566
|
-
console.log("CameraRecorder startRecording");
|
|
32390
|
+
var _a2, _b, _c2, _d, _e3, _f, _g, _h;
|
|
31567
32391
|
await this.startStream();
|
|
31568
32392
|
await this.attachAndWarmup(this.cameraStream);
|
|
32393
|
+
const recorderOpts = this.isChunkEnabled ? {
|
|
32394
|
+
timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
|
|
32395
|
+
onChunkAvailable: (blob, idx) => {
|
|
32396
|
+
this.handleNewChunk(blob, idx);
|
|
32397
|
+
}
|
|
32398
|
+
} : {};
|
|
31569
32399
|
const {
|
|
31570
32400
|
startRecording,
|
|
31571
32401
|
stopRecording,
|
|
@@ -31581,7 +32411,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31581
32411
|
this.blobs,
|
|
31582
32412
|
this.options.onBufferSizeError,
|
|
31583
32413
|
(e3) => this.bufferError(e3),
|
|
31584
|
-
false
|
|
32414
|
+
false,
|
|
32415
|
+
recorderOpts
|
|
31585
32416
|
);
|
|
31586
32417
|
this.recordingStart = startRecording;
|
|
31587
32418
|
this.recordingStop = stopRecording;
|
|
@@ -31591,13 +32422,18 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31591
32422
|
this.getBufferSize = getBufferSize;
|
|
31592
32423
|
this.getStartTime = getStartTime;
|
|
31593
32424
|
this.getDuration = getDuration;
|
|
32425
|
+
this.chunkIndex = 0;
|
|
32426
|
+
if (this.isChunkEnabled) {
|
|
32427
|
+
(_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
|
|
32428
|
+
this.setupLifecycleListeners();
|
|
32429
|
+
}
|
|
31594
32430
|
try {
|
|
31595
32431
|
await new Promise((r3) => setTimeout(r3, 500));
|
|
31596
32432
|
await this.recordingStart();
|
|
31597
32433
|
} catch (error) {
|
|
31598
32434
|
console.log("Camera Recorder error", error);
|
|
31599
32435
|
this.stopRecording();
|
|
31600
|
-
const maxRetries = ((
|
|
32436
|
+
const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
|
|
31601
32437
|
if (this.currentRetries < maxRetries) {
|
|
31602
32438
|
console.log("Camera Recorder retry", this.currentRetries);
|
|
31603
32439
|
this.currentRetries++;
|
|
@@ -31607,16 +32443,17 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31607
32443
|
}
|
|
31608
32444
|
}
|
|
31609
32445
|
this.stopped = false;
|
|
31610
|
-
if (((
|
|
32446
|
+
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)) {
|
|
31611
32447
|
await this.initializeDetectors();
|
|
31612
32448
|
}
|
|
31613
|
-
if ((
|
|
32449
|
+
if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
|
|
31614
32450
|
await this.faceDetection.enableCam(this.cameraStream);
|
|
31615
32451
|
}
|
|
31616
|
-
if (((
|
|
32452
|
+
if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
|
|
31617
32453
|
await this.objectDetection.enableCam(this.cameraStream);
|
|
31618
32454
|
}
|
|
31619
32455
|
this.filesToUpload = [];
|
|
32456
|
+
this.pendingPackages = [];
|
|
31620
32457
|
if (this.options.proctoringType == "REALTIME") {
|
|
31621
32458
|
await this.startRealtimeCapture();
|
|
31622
32459
|
}
|
|
@@ -31636,6 +32473,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31636
32473
|
this.intervalNoiseDetection && clearInterval(this.intervalNoiseDetection);
|
|
31637
32474
|
this.recordingStop && await this.recordingStop();
|
|
31638
32475
|
this.duration = this.getDuration ? this.getDuration() : 0;
|
|
32476
|
+
await new Promise((r3) => setTimeout(r3, 200));
|
|
31639
32477
|
try {
|
|
31640
32478
|
if (this.animationFrameId) {
|
|
31641
32479
|
cancelAnimationFrame(this.animationFrameId);
|
|
@@ -31666,9 +32504,55 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31666
32504
|
console.error("Erro ao parar os streams de m\xEDdia.");
|
|
31667
32505
|
}
|
|
31668
32506
|
if (this.options.proctoringType == "REALTIME" && this.upload && this.backendToken) {
|
|
31669
|
-
|
|
32507
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, this.filesToUpload.length));
|
|
32508
|
+
await this.sendPackage();
|
|
31670
32509
|
await this.filesToUpload.splice(0, this.filesToUpload.length);
|
|
31671
32510
|
}
|
|
32511
|
+
if (this.isChunkEnabled) {
|
|
32512
|
+
if (this.backgroundUpload) {
|
|
32513
|
+
try {
|
|
32514
|
+
if (this.pendingChunkSaves.length > 0) {
|
|
32515
|
+
console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
|
|
32516
|
+
await Promise.all(this.pendingChunkSaves);
|
|
32517
|
+
}
|
|
32518
|
+
await this.backgroundUpload.flush();
|
|
32519
|
+
} catch (e3) {
|
|
32520
|
+
console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
|
|
32521
|
+
}
|
|
32522
|
+
this.backgroundUpload.stop();
|
|
32523
|
+
}
|
|
32524
|
+
this.removeLifecycleListeners();
|
|
32525
|
+
this.persistSessionState("FINISHED");
|
|
32526
|
+
}
|
|
32527
|
+
}
|
|
32528
|
+
/**
|
|
32529
|
+
* Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
|
|
32530
|
+
* Salva o chunk no IndexedDB para persistência e recuperação.
|
|
32531
|
+
*/
|
|
32532
|
+
async handleNewChunk(blob, idx) {
|
|
32533
|
+
if (!this.proctoringId || !this.chunkStorage) return;
|
|
32534
|
+
const savePromise = (async () => {
|
|
32535
|
+
var _a2;
|
|
32536
|
+
try {
|
|
32537
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
32538
|
+
await this.chunkStorage.saveChunk({
|
|
32539
|
+
proctoringId: this.proctoringId,
|
|
32540
|
+
chunkIndex: this.chunkIndex,
|
|
32541
|
+
arrayBuffer,
|
|
32542
|
+
timestamp: Date.now(),
|
|
32543
|
+
uploaded: 0,
|
|
32544
|
+
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
32545
|
+
});
|
|
32546
|
+
this.chunkIndex++;
|
|
32547
|
+
console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
|
|
32548
|
+
} catch (error) {
|
|
32549
|
+
console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
|
|
32550
|
+
}
|
|
32551
|
+
})();
|
|
32552
|
+
this.pendingChunkSaves.push(savePromise);
|
|
32553
|
+
savePromise.finally(() => {
|
|
32554
|
+
this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
|
|
32555
|
+
});
|
|
31672
32556
|
}
|
|
31673
32557
|
async pauseRecording() {
|
|
31674
32558
|
await this.recordingPause();
|
|
@@ -31715,9 +32599,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31715
32599
|
if (this.proctoringId == void 0) return;
|
|
31716
32600
|
if (packSize == this.imageCount) {
|
|
31717
32601
|
this.imageCount = 0;
|
|
31718
|
-
|
|
31719
|
-
this.sendPackage(
|
|
31720
|
-
await this.filesToUpload.splice(0,
|
|
32602
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, packSize));
|
|
32603
|
+
this.sendPackage();
|
|
32604
|
+
await this.filesToUpload.splice(0, packSize);
|
|
31721
32605
|
}
|
|
31722
32606
|
let imageName = `${this.proctoringId}_${this.imageCount + 1}.jpg`;
|
|
31723
32607
|
imageFile = await this.getFile(image_data_url, imageName, "image/jpeg");
|
|
@@ -31731,54 +32615,61 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31731
32615
|
var _a2;
|
|
31732
32616
|
this.configImageCapture();
|
|
31733
32617
|
this.imageCount = 0;
|
|
32618
|
+
this.pendingPackages = [];
|
|
31734
32619
|
await this.captureFrame();
|
|
31735
32620
|
this.imageInterval = setInterval(async () => {
|
|
31736
32621
|
await this.captureFrame();
|
|
31737
32622
|
}, ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimeCaptureInterval) * 1e3);
|
|
31738
32623
|
}
|
|
31739
32624
|
// envia pacote de imagens
|
|
31740
|
-
async sendPackage(
|
|
32625
|
+
async sendPackage() {
|
|
31741
32626
|
var _a2, _b;
|
|
31742
|
-
let pending = false;
|
|
31743
|
-
let undeliveredPackagesCount = 0;
|
|
31744
32627
|
const packSize = (_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimePackageSize;
|
|
31745
32628
|
const packCaptureInterval = (_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.realtimeCaptureInterval;
|
|
31746
|
-
if (this.upload && this.backendToken &&
|
|
31747
|
-
|
|
31748
|
-
|
|
31749
|
-
const
|
|
31750
|
-
|
|
31751
|
-
|
|
31752
|
-
|
|
31753
|
-
|
|
31754
|
-
|
|
31755
|
-
|
|
31756
|
-
|
|
31757
|
-
|
|
31758
|
-
|
|
31759
|
-
|
|
31760
|
-
|
|
31761
|
-
{
|
|
31762
|
-
|
|
31763
|
-
|
|
31764
|
-
|
|
31765
|
-
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
31769
|
-
|
|
32629
|
+
if (this.upload && this.backendToken && this.pendingPackages.length > 0) {
|
|
32630
|
+
let packagesToDelete = [];
|
|
32631
|
+
let packageIndex = 0;
|
|
32632
|
+
for (const packageToSend of this.pendingPackages) {
|
|
32633
|
+
const zip = new import_jszip_min.default();
|
|
32634
|
+
for (const file of packageToSend) {
|
|
32635
|
+
zip.file(file.name, file);
|
|
32636
|
+
}
|
|
32637
|
+
const blob = await zip.generateAsync({ type: "blob" });
|
|
32638
|
+
let packageName = "realtime_package_" + packSize * packCaptureInterval * this.packageCount + ".zip";
|
|
32639
|
+
const myPackage = new File(
|
|
32640
|
+
[blob],
|
|
32641
|
+
packageName,
|
|
32642
|
+
{ type: "application/zip" }
|
|
32643
|
+
);
|
|
32644
|
+
try {
|
|
32645
|
+
const uploadResult = await this.upload.uploadPackage(
|
|
32646
|
+
{
|
|
32647
|
+
file: myPackage
|
|
32648
|
+
},
|
|
32649
|
+
this.backendToken
|
|
32650
|
+
);
|
|
32651
|
+
if (uploadResult == true) {
|
|
32652
|
+
this.packageCount++;
|
|
32653
|
+
this.failedUploads = 0;
|
|
32654
|
+
packagesToDelete.push(packageIndex++);
|
|
32655
|
+
}
|
|
32656
|
+
} catch (error) {
|
|
32657
|
+
this.failedUploads++;
|
|
32658
|
+
if (this.failedUploads >= 2) {
|
|
32659
|
+
this.options.onRealtimeAlertsCallback({
|
|
32660
|
+
status: "ALERT",
|
|
32661
|
+
description: "Realtime n\xE3o est\xE1 enviando pacotes",
|
|
32662
|
+
type: "error_upload_package",
|
|
32663
|
+
category: "error_upload_package",
|
|
32664
|
+
begin: 0,
|
|
32665
|
+
end: 0
|
|
32666
|
+
});
|
|
32667
|
+
}
|
|
32668
|
+
break;
|
|
32669
|
+
}
|
|
31770
32670
|
}
|
|
31771
|
-
|
|
31772
|
-
|
|
31773
|
-
undeliveredPackagesCount++;
|
|
31774
|
-
if (undeliveredPackagesCount == 3) {
|
|
31775
|
-
undeliveredPackagesCount = 0;
|
|
31776
|
-
let newCanvasWidth = this.videoOptions.width / 2;
|
|
31777
|
-
let newCanvasHeight = this.videoOptions.height / 2;
|
|
31778
|
-
if (newCanvasWidth < 320) newCanvasWidth = 320;
|
|
31779
|
-
if (newCanvasHeight < 180) newCanvasHeight = 180;
|
|
31780
|
-
this.canvas.width = newCanvasWidth;
|
|
31781
|
-
this.canvas.height = newCanvasHeight;
|
|
32671
|
+
for (const packageToDelete of packagesToDelete) {
|
|
32672
|
+
await this.pendingPackages.splice(packageToDelete, 1);
|
|
31782
32673
|
}
|
|
31783
32674
|
}
|
|
31784
32675
|
}
|
|
@@ -31797,16 +32688,32 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31797
32688
|
if (this.blobs != null)
|
|
31798
32689
|
trackers.registerSaveOnSession(
|
|
31799
32690
|
this.proctoringId,
|
|
31800
|
-
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
|
|
32691
|
+
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
|
|
31801
32692
|
);
|
|
31802
32693
|
const settings = this.cameraStream.getVideoTracks()[0].getSettings();
|
|
31803
32694
|
const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
|
|
31804
32695
|
if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
|
|
32696
|
+
let videoFile;
|
|
32697
|
+
if (this.isChunkEnabled) {
|
|
32698
|
+
const isStable = await this.checkInternetStability();
|
|
32699
|
+
if (isStable) {
|
|
32700
|
+
} else {
|
|
32701
|
+
if (this.backend && this.backendToken && this.proctoringId) {
|
|
32702
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
32703
|
+
const objectName = `${this.proctoringId}/${fileName}`;
|
|
32704
|
+
const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
|
|
32705
|
+
if (isUploaded) {
|
|
32706
|
+
this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
|
|
32707
|
+
return;
|
|
32708
|
+
}
|
|
32709
|
+
}
|
|
32710
|
+
}
|
|
32711
|
+
}
|
|
31805
32712
|
const rawBlob = new Blob(this.blobs, {
|
|
31806
32713
|
type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
|
|
31807
32714
|
});
|
|
31808
|
-
const fixedBlob = await (
|
|
31809
|
-
|
|
32715
|
+
const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
|
|
32716
|
+
videoFile = new File(
|
|
31810
32717
|
[fixedBlob],
|
|
31811
32718
|
`EP_${session.id}_camera_0.webm`,
|
|
31812
32719
|
{ type: rawBlob.type }
|
|
@@ -31818,7 +32725,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31818
32725
|
|
|
31819
32726
|
Video:
|
|
31820
32727
|
${JSON.stringify(this.recorderOptions)}`,
|
|
31821
|
-
file:
|
|
32728
|
+
file: videoFile,
|
|
31822
32729
|
origin: "Camera" /* Camera */
|
|
31823
32730
|
});
|
|
31824
32731
|
}
|
|
@@ -31833,6 +32740,28 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31833
32740
|
});
|
|
31834
32741
|
});
|
|
31835
32742
|
}
|
|
32743
|
+
/**
|
|
32744
|
+
* Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
|
|
32745
|
+
*/
|
|
32746
|
+
async checkInternetStability() {
|
|
32747
|
+
var _a2;
|
|
32748
|
+
if (!navigator.onLine) return false;
|
|
32749
|
+
try {
|
|
32750
|
+
const controller = new AbortController();
|
|
32751
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
32752
|
+
const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
|
|
32753
|
+
if (!baseUrl) return true;
|
|
32754
|
+
const response = await fetch(`${baseUrl}/Client/health`, {
|
|
32755
|
+
method: "GET",
|
|
32756
|
+
signal: controller.signal
|
|
32757
|
+
});
|
|
32758
|
+
clearTimeout(timeoutId);
|
|
32759
|
+
return response.status < 500;
|
|
32760
|
+
} catch (e3) {
|
|
32761
|
+
console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
|
|
32762
|
+
return false;
|
|
32763
|
+
}
|
|
32764
|
+
}
|
|
31836
32765
|
onNoiseDetected() {
|
|
31837
32766
|
var _a2, _b, _c2;
|
|
31838
32767
|
if (this.options.proctoringType === "REALTIME") return;
|
|
@@ -31845,7 +32774,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31845
32774
|
}
|
|
31846
32775
|
const volume = (_b = (_a2 = this.volumeMeter) == null ? void 0 : _a2.getVolume()) != null ? _b : 0;
|
|
31847
32776
|
if (volume >= (((_c2 = this.paramsConfig.audioBehaviourParameters) == null ? void 0 : _c2.noiseLimit) || 40)) {
|
|
31848
|
-
console.log("entrou" + this.noiseWait);
|
|
31849
32777
|
if (this.noiseWait >= 20) {
|
|
31850
32778
|
this.options.onRealtimeAlertsCallback({
|
|
31851
32779
|
status: "ALERT",
|
|
@@ -31858,6 +32786,14 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31858
32786
|
this.noiseWait++;
|
|
31859
32787
|
}
|
|
31860
32788
|
};
|
|
32789
|
+
// ========================
|
|
32790
|
+
// Chunk & Lifecycle
|
|
32791
|
+
// ========================
|
|
32792
|
+
/** Intervalo de cada chunk em ms (padrão: 60 segundos) */
|
|
32793
|
+
_CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
|
|
32794
|
+
/** Chave do localStorage para persistir estado da sessão */
|
|
32795
|
+
_CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
|
|
32796
|
+
var CameraRecorder = _CameraRecorder;
|
|
31861
32797
|
|
|
31862
32798
|
// src/new-flow/checkers/DeviceCheckerUI.ts
|
|
31863
32799
|
var DeviceCheckerUI = class {
|
|
@@ -32930,7 +33866,8 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
32930
33866
|
this.context = context;
|
|
32931
33867
|
this.backend = new BackendService({
|
|
32932
33868
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
32933
|
-
token: context.token
|
|
33869
|
+
token: context.token,
|
|
33870
|
+
isRealtime: false
|
|
32934
33871
|
});
|
|
32935
33872
|
}
|
|
32936
33873
|
getDeviceCheckResult() {
|
|
@@ -33091,7 +34028,7 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
33091
34028
|
videoDeviceInterface(stream4) {
|
|
33092
34029
|
this.DeviceCheckerUI && this.DeviceCheckerUI.videoDeviceInterfaceUI(stream4);
|
|
33093
34030
|
this.isUnderResolution();
|
|
33094
|
-
this.faceDetection.enableCam(stream4);
|
|
34031
|
+
this.faceDetection.enableCam(stream4, 1e3);
|
|
33095
34032
|
}
|
|
33096
34033
|
audioDeviceInterface(stream4) {
|
|
33097
34034
|
this.volumeMeter = new VolumeMeter(this.cameraRecorder.cameraStream);
|
|
@@ -33543,8 +34480,8 @@ var CapturePhoto = class {
|
|
|
33543
34480
|
}
|
|
33544
34481
|
};
|
|
33545
34482
|
|
|
33546
|
-
// src/extension/
|
|
33547
|
-
var
|
|
34483
|
+
// src/extension/extension.ts
|
|
34484
|
+
var Extension = class {
|
|
33548
34485
|
constructor() {
|
|
33549
34486
|
this.hasExtension = false;
|
|
33550
34487
|
this.tryes = 0;
|
|
@@ -33589,89 +34526,6 @@ var ExtensionEasyProctor = class {
|
|
|
33589
34526
|
}
|
|
33590
34527
|
};
|
|
33591
34528
|
|
|
33592
|
-
// src/extension/extensionEasyCatcher.ts
|
|
33593
|
-
var ExtensionEasyCatcher = class {
|
|
33594
|
-
constructor(options) {
|
|
33595
|
-
this.hasExtension = false;
|
|
33596
|
-
this.tryes = 0;
|
|
33597
|
-
this.responseStart = false;
|
|
33598
|
-
this.options = options || {};
|
|
33599
|
-
}
|
|
33600
|
-
/**
|
|
33601
|
-
* Verifica se a extensão está instalada e ativa.
|
|
33602
|
-
* Retorna o número da versão se encontrada, ou lança erro após timeout.
|
|
33603
|
-
*/
|
|
33604
|
-
checkExtensionInstalled(timeoutMs = 2e3) {
|
|
33605
|
-
return new Promise((resolve, reject) => {
|
|
33606
|
-
let handled = false;
|
|
33607
|
-
const handler = (event) => {
|
|
33608
|
-
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "version") {
|
|
33609
|
-
handled = true;
|
|
33610
|
-
window.removeEventListener("message", handler);
|
|
33611
|
-
resolve(event.data.message);
|
|
33612
|
-
}
|
|
33613
|
-
};
|
|
33614
|
-
window.addEventListener("message", handler);
|
|
33615
|
-
window.postMessage({
|
|
33616
|
-
type: "easycatcher",
|
|
33617
|
-
func: "verifyExtensionEasycatcher"
|
|
33618
|
-
}, "*");
|
|
33619
|
-
setTimeout(() => {
|
|
33620
|
-
if (!handled) {
|
|
33621
|
-
window.removeEventListener("message", handler);
|
|
33622
|
-
reject(new Error("Extens\xE3o n\xE3o detectada ou n\xE3o respondeu."));
|
|
33623
|
-
}
|
|
33624
|
-
}, timeoutMs);
|
|
33625
|
-
});
|
|
33626
|
-
}
|
|
33627
|
-
/**
|
|
33628
|
-
* Solicita o JSON da sessão atual capturado pela extensão.
|
|
33629
|
-
*/
|
|
33630
|
-
getSessionData(timeoutMs = 5e3) {
|
|
33631
|
-
return new Promise((resolve, reject) => {
|
|
33632
|
-
let handled = false;
|
|
33633
|
-
const handler = (event) => {
|
|
33634
|
-
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "data_response") {
|
|
33635
|
-
handled = true;
|
|
33636
|
-
window.removeEventListener("message", handler);
|
|
33637
|
-
resolve(event.data.payload);
|
|
33638
|
-
}
|
|
33639
|
-
};
|
|
33640
|
-
window.addEventListener("message", handler);
|
|
33641
|
-
window.postMessage({
|
|
33642
|
-
type: "easycatcher",
|
|
33643
|
-
func: "getDataExtensionEasycatcher"
|
|
33644
|
-
}, "*");
|
|
33645
|
-
setTimeout(() => {
|
|
33646
|
-
if (!handled) {
|
|
33647
|
-
window.removeEventListener("message", handler);
|
|
33648
|
-
reject(new Error("Timeout ao aguardar dados da extens\xE3o."));
|
|
33649
|
-
}
|
|
33650
|
-
}, timeoutMs);
|
|
33651
|
-
});
|
|
33652
|
-
}
|
|
33653
|
-
start() {
|
|
33654
|
-
return new Promise((resolve, reject) => {
|
|
33655
|
-
let handled = false;
|
|
33656
|
-
const handler = (event) => {
|
|
33657
|
-
if (event.source === window && event.data.sender === "easyproctor-extension" && event.data.message_name === "started_confirmed") {
|
|
33658
|
-
handled = true;
|
|
33659
|
-
window.removeEventListener("message", handler);
|
|
33660
|
-
resolve(true);
|
|
33661
|
-
}
|
|
33662
|
-
};
|
|
33663
|
-
window.addEventListener("message", handler);
|
|
33664
|
-
window.postMessage({ type: "easycatcher", func: "startExtensionEasycatcher" }, "*");
|
|
33665
|
-
setTimeout(() => {
|
|
33666
|
-
if (!handled) {
|
|
33667
|
-
window.removeEventListener("message", handler);
|
|
33668
|
-
reject(new Error("Timeout: Extens\xE3o n\xE3o confirmou o in\xEDcio."));
|
|
33669
|
-
}
|
|
33670
|
-
}, 3e3);
|
|
33671
|
-
});
|
|
33672
|
-
}
|
|
33673
|
-
};
|
|
33674
|
-
|
|
33675
34529
|
// src/modules/onChangeDevices.ts
|
|
33676
34530
|
var onChangeDevices = class {
|
|
33677
34531
|
constructor(repositoryDevices, proctoringId2, sessionOptions, allRecorders) {
|
|
@@ -36357,6 +37211,7 @@ var NoiseRecorder = class {
|
|
|
36357
37211
|
this.MAX_PRE_ROLL_CHUNKS = 4;
|
|
36358
37212
|
this.lastNoiseTime = 0;
|
|
36359
37213
|
this.SILENCE_THRESHOLD = 3e3;
|
|
37214
|
+
this.filesToUpload = [];
|
|
36360
37215
|
this.optionsProctoring = optionsProctoring;
|
|
36361
37216
|
this.proctoringSession = proctoringSession;
|
|
36362
37217
|
this.paramsConfig = paramsConfig;
|
|
@@ -36448,7 +37303,7 @@ var NoiseRecorder = class {
|
|
|
36448
37303
|
}
|
|
36449
37304
|
}
|
|
36450
37305
|
async stopSoundRecord() {
|
|
36451
|
-
var _a2;
|
|
37306
|
+
var _a2, _b;
|
|
36452
37307
|
if (!this.recordingInProgress && this.recordingChunks.length === 0) return;
|
|
36453
37308
|
this.recordingEndTime = Date.now() - (((_a2 = this.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0);
|
|
36454
37309
|
if (this.optionsProctoring.proctoringType !== "REALTIME") return;
|
|
@@ -36462,7 +37317,33 @@ var NoiseRecorder = class {
|
|
|
36462
37317
|
type: "audio/wav"
|
|
36463
37318
|
}
|
|
36464
37319
|
);
|
|
36465
|
-
this.
|
|
37320
|
+
let filesToSend = [...this.filesToUpload];
|
|
37321
|
+
for (const myFile of filesToSend) {
|
|
37322
|
+
try {
|
|
37323
|
+
await ((_b = this.upload) == null ? void 0 : _b.upload(
|
|
37324
|
+
{
|
|
37325
|
+
file: myFile
|
|
37326
|
+
},
|
|
37327
|
+
this.backendToken
|
|
37328
|
+
));
|
|
37329
|
+
this.filesToUpload.splice(this.filesToUpload.indexOf(myFile), 1);
|
|
37330
|
+
} catch (error) {
|
|
37331
|
+
break;
|
|
37332
|
+
}
|
|
37333
|
+
}
|
|
37334
|
+
try {
|
|
37335
|
+
if (file && this.upload && this.backendToken) {
|
|
37336
|
+
this.upload.upload(
|
|
37337
|
+
{
|
|
37338
|
+
file
|
|
37339
|
+
},
|
|
37340
|
+
this.backendToken
|
|
37341
|
+
);
|
|
37342
|
+
}
|
|
37343
|
+
} catch (error) {
|
|
37344
|
+
console.log("error Noise recorder adicionando na fila", error);
|
|
37345
|
+
this.filesToUpload.push(file);
|
|
37346
|
+
}
|
|
36466
37347
|
this.recordingChunks = [];
|
|
36467
37348
|
this.recordingInProgress = false;
|
|
36468
37349
|
}
|
|
@@ -36476,16 +37357,6 @@ var NoiseRecorder = class {
|
|
|
36476
37357
|
a3.click();
|
|
36477
37358
|
window.URL.revokeObjectURL(url2);
|
|
36478
37359
|
}
|
|
36479
|
-
uploadRecord(file) {
|
|
36480
|
-
if (file && this.upload && this.backendToken) {
|
|
36481
|
-
this.upload.upload(
|
|
36482
|
-
{
|
|
36483
|
-
file
|
|
36484
|
-
},
|
|
36485
|
-
this.backendToken
|
|
36486
|
-
);
|
|
36487
|
-
}
|
|
36488
|
-
}
|
|
36489
37360
|
// CLASSIFIER -< Media Pipe
|
|
36490
37361
|
// Verify if has speech in the classifier array
|
|
36491
37362
|
hasDesiredResult(array) {
|
|
@@ -36592,7 +37463,8 @@ registerProcessor("audio-processor", AudioProcessor);
|
|
|
36592
37463
|
`;
|
|
36593
37464
|
|
|
36594
37465
|
// src/new-flow/recorders/ScreenRecorder.ts
|
|
36595
|
-
var
|
|
37466
|
+
var pkg2 = require_fix_webm_duration();
|
|
37467
|
+
var fixWebmDuration2 = pkg2.default || pkg2;
|
|
36596
37468
|
var ScreenRecorder = class {
|
|
36597
37469
|
constructor(options) {
|
|
36598
37470
|
this.blobs = [];
|
|
@@ -36688,7 +37560,7 @@ var ScreenRecorder = class {
|
|
|
36688
37560
|
const rawBlob = new Blob(this.blobs, {
|
|
36689
37561
|
type: "video/webm"
|
|
36690
37562
|
});
|
|
36691
|
-
const fixedBlob = await (
|
|
37563
|
+
const fixedBlob = await fixWebmDuration2(rawBlob, this.duration);
|
|
36692
37564
|
const file = new File(
|
|
36693
37565
|
[fixedBlob],
|
|
36694
37566
|
`EP_${session.id}_screen_0.webm`,
|
|
@@ -36793,14 +37665,6 @@ function getGeolocation() {
|
|
|
36793
37665
|
});
|
|
36794
37666
|
}
|
|
36795
37667
|
|
|
36796
|
-
// src/utils/verifyVersion.ts
|
|
36797
|
-
function versionVerify() {
|
|
36798
|
-
const agentStr = window.navigator.userAgent.split("SEB/");
|
|
36799
|
-
if (agentStr.length > 1)
|
|
36800
|
-
return agentStr[1];
|
|
36801
|
-
else return "1.0.0.0";
|
|
36802
|
-
}
|
|
36803
|
-
|
|
36804
37668
|
// src/proctoring/Auth.ts
|
|
36805
37669
|
var Auth = class {
|
|
36806
37670
|
constructor(cpf, backend) {
|
|
@@ -36946,7 +37810,8 @@ var _ExternalCameraChecker = class _ExternalCameraChecker {
|
|
|
36946
37810
|
this.onRealtimeAlertsCallback = onRealtimeAlertsCallback;
|
|
36947
37811
|
this.backend = new BackendService({
|
|
36948
37812
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
36949
|
-
token: context.token
|
|
37813
|
+
token: context.token,
|
|
37814
|
+
isRealtime: false
|
|
36950
37815
|
});
|
|
36951
37816
|
this.currentStep = -1 /* WAITING */;
|
|
36952
37817
|
}
|
|
@@ -37679,6 +38544,12 @@ var Proctoring = class {
|
|
|
37679
38544
|
this.serviceType = "Upload" /* Upload */;
|
|
37680
38545
|
this.onStopSharingScreenCallback = () => {
|
|
37681
38546
|
};
|
|
38547
|
+
/** Callback notificando que o usuário saiu do browser (minimizou/trocou de aba) */
|
|
38548
|
+
this.onVisibilityLostCallback = () => {
|
|
38549
|
+
};
|
|
38550
|
+
/** Callback notificando que o usuário retornou ao browser */
|
|
38551
|
+
this.onVisibilityRestoredCallback = () => {
|
|
38552
|
+
};
|
|
37682
38553
|
this.onLostFocusCallback = () => {
|
|
37683
38554
|
};
|
|
37684
38555
|
this.onLostFocusAlertRecorderCallback = (response) => {
|
|
@@ -37694,13 +38565,16 @@ var Proctoring = class {
|
|
|
37694
38565
|
await this.internalOnRealtimeAlerts(response);
|
|
37695
38566
|
return;
|
|
37696
38567
|
};
|
|
38568
|
+
this.realtimeAlertsToSend = [];
|
|
37697
38569
|
this.onBufferSizeErrorCallback = (cameraStream) => {
|
|
37698
38570
|
return;
|
|
37699
38571
|
};
|
|
37700
38572
|
var _a2;
|
|
37701
38573
|
this.backend = new BackendService({
|
|
37702
38574
|
type: context.type || "prod",
|
|
37703
|
-
token: context.token
|
|
38575
|
+
token: context.token,
|
|
38576
|
+
isRealtime: false
|
|
38577
|
+
// Default false, atualizado no start() via backend.setRealtime()
|
|
37704
38578
|
});
|
|
37705
38579
|
this.repository = new IndexDbSessionRepository("EasyProctorDb", "exams2");
|
|
37706
38580
|
this.repositoryDevices = new IndexDbSessionRepository(
|
|
@@ -37710,6 +38584,12 @@ var Proctoring = class {
|
|
|
37710
38584
|
((_a2 = this.context.credentials) == null ? void 0 : _a2.cpf) && (this.auth = new Auth(this.context.credentials.cpf, this.backend));
|
|
37711
38585
|
this.appChecker = new ExternalCameraChecker(this.context, (response) => this.onRealtimeAlertsCallback(response));
|
|
37712
38586
|
}
|
|
38587
|
+
setOnVisibilityLostCallback(cb) {
|
|
38588
|
+
this.onVisibilityLostCallback = cb;
|
|
38589
|
+
}
|
|
38590
|
+
setOnVisibilityRestoredCallback(cb) {
|
|
38591
|
+
this.onVisibilityRestoredCallback = cb;
|
|
38592
|
+
}
|
|
37713
38593
|
setOnStopSharingScreenCallback(cb) {
|
|
37714
38594
|
this.onStopSharingScreenCallback = async () => {
|
|
37715
38595
|
var _a2, _b, _c2, _d;
|
|
@@ -37789,6 +38669,8 @@ var Proctoring = class {
|
|
|
37789
38669
|
return 25 /* FocusOff */;
|
|
37790
38670
|
case "focus":
|
|
37791
38671
|
return 25 /* FocusOff */;
|
|
38672
|
+
case "error_upload_package":
|
|
38673
|
+
return 44 /* RealtimeOffline */;
|
|
37792
38674
|
default:
|
|
37793
38675
|
return null;
|
|
37794
38676
|
}
|
|
@@ -37829,19 +38711,53 @@ var Proctoring = class {
|
|
|
37829
38711
|
};
|
|
37830
38712
|
await verifyFace(1);
|
|
37831
38713
|
}
|
|
38714
|
+
async sendPendingRealtimeAlerts() {
|
|
38715
|
+
let alertsToSend = [...this.realtimeAlertsToSend];
|
|
38716
|
+
for (const alert of alertsToSend) {
|
|
38717
|
+
try {
|
|
38718
|
+
if (alert.status === "ALERT") {
|
|
38719
|
+
await this.backend.startRealtimeAlert({
|
|
38720
|
+
proctoringId: this.proctoringId,
|
|
38721
|
+
begin: alert.begin,
|
|
38722
|
+
end: alert.end,
|
|
38723
|
+
alert: this.convertRealtimeCategoryToAlertCategory(alert.category)
|
|
38724
|
+
});
|
|
38725
|
+
} else if (alert.status === "OK") {
|
|
38726
|
+
await this.stopRealtimeAlert(alert);
|
|
38727
|
+
}
|
|
38728
|
+
this.realtimeAlertsToSend.splice(this.realtimeAlertsToSend.indexOf(alert), 1);
|
|
38729
|
+
} catch (error) {
|
|
38730
|
+
console.log("error sendPendingRealtimeAlerts", error);
|
|
38731
|
+
this.realtimeAlertsToSend.push(alert);
|
|
38732
|
+
break;
|
|
38733
|
+
}
|
|
38734
|
+
}
|
|
38735
|
+
}
|
|
37832
38736
|
async internalOnRealtimeAlerts(response) {
|
|
37833
|
-
if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream" || response.type === "lost_focus" || response.type === "focus")) {
|
|
38737
|
+
if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream" || response.type === "lost_focus" || response.type === "focus" || response.type === "error_upload_package")) {
|
|
38738
|
+
await this.sendPendingRealtimeAlerts();
|
|
37834
38739
|
if (response.status === "ALERT") {
|
|
37835
38740
|
if (this.allRecorders.cameraRecorder.stopped) return;
|
|
37836
|
-
|
|
37837
|
-
|
|
37838
|
-
|
|
37839
|
-
|
|
37840
|
-
|
|
37841
|
-
|
|
38741
|
+
try {
|
|
38742
|
+
await this.backend.startRealtimeAlert({
|
|
38743
|
+
proctoringId: this.proctoringId,
|
|
38744
|
+
begin: response.begin,
|
|
38745
|
+
end: response.end,
|
|
38746
|
+
alert: this.convertRealtimeCategoryToAlertCategory(response.category)
|
|
38747
|
+
});
|
|
38748
|
+
} catch (error) {
|
|
38749
|
+
console.log("error startRealtimeAlert " + response.category);
|
|
38750
|
+
console.log("error startRealtimeAlert adicionando na fila", error);
|
|
38751
|
+
this.realtimeAlertsToSend.push(response);
|
|
38752
|
+
}
|
|
37842
38753
|
} else if (response.status === "OK") {
|
|
37843
38754
|
if (this.allRecorders.cameraRecorder.stopped && response.description !== "face_stop") return;
|
|
37844
|
-
|
|
38755
|
+
try {
|
|
38756
|
+
await this.stopRealtimeAlert(response);
|
|
38757
|
+
} catch (error) {
|
|
38758
|
+
console.log("error stopRealtimeAlert adicionando na fila", error);
|
|
38759
|
+
this.realtimeAlertsToSend.push(response);
|
|
38760
|
+
}
|
|
37845
38761
|
}
|
|
37846
38762
|
}
|
|
37847
38763
|
}
|
|
@@ -37849,8 +38765,8 @@ var Proctoring = class {
|
|
|
37849
38765
|
this.setOnLostFocusAlertRecorderCallback();
|
|
37850
38766
|
this.setOnFocusAlertRecorderCallback();
|
|
37851
38767
|
this.onRealtimeAlertsCallback = async (response) => {
|
|
37852
|
-
await this.internalOnRealtimeAlerts(response);
|
|
37853
38768
|
options.data && options.data(response);
|
|
38769
|
+
await this.internalOnRealtimeAlerts(response);
|
|
37854
38770
|
};
|
|
37855
38771
|
}
|
|
37856
38772
|
setOnBufferSizeErrorCallback(cb) {
|
|
@@ -37936,18 +38852,17 @@ var Proctoring = class {
|
|
|
37936
38852
|
if (this.context.token === void 0) {
|
|
37937
38853
|
throw TOKEN_MISSING;
|
|
37938
38854
|
}
|
|
37939
|
-
|
|
37940
|
-
this.extensionEasycatcher = new ExtensionEasyCatcher();
|
|
37941
|
-
}
|
|
37942
|
-
this.extension = new ExtensionEasyProctor();
|
|
38855
|
+
this.extension = new Extension();
|
|
37943
38856
|
this.extension.addEventListener();
|
|
37944
38857
|
const baseURL = this.backend.selectBaseUrl(this.context.type);
|
|
37945
38858
|
const devices = await enumarateDevices();
|
|
37946
38859
|
await this.repositoryDevices.save({ ...devices, id: "devices" });
|
|
37947
38860
|
this.sessionOptions = { ...getDefaultProctoringOptions, ...options };
|
|
37948
38861
|
this.videoOptions = validatePartialVideoOptions(_videoOptions);
|
|
38862
|
+
if (this.sessionOptions.proctoringType === "REALTIME") {
|
|
38863
|
+
this.backend.setRealtime(true);
|
|
38864
|
+
}
|
|
37949
38865
|
await this.initConfig(options.useGeolocation);
|
|
37950
|
-
await this.verifyBrowser();
|
|
37951
38866
|
this.sessionOptions.captureScreen && await this.verifyMultipleMonitors(this.sessionOptions);
|
|
37952
38867
|
try {
|
|
37953
38868
|
if (options == null ? void 0 : options.useSpyScan) {
|
|
@@ -38005,6 +38920,20 @@ var Proctoring = class {
|
|
|
38005
38920
|
} catch (error) {
|
|
38006
38921
|
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
38007
38922
|
}
|
|
38923
|
+
this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
|
|
38924
|
+
console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
|
|
38925
|
+
this.onVisibilityRestoredCallback();
|
|
38926
|
+
};
|
|
38927
|
+
if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
|
|
38928
|
+
try {
|
|
38929
|
+
await BackgroundUploadService.recoverPendingUploads(
|
|
38930
|
+
this.backend,
|
|
38931
|
+
this.context.token
|
|
38932
|
+
);
|
|
38933
|
+
} catch (e3) {
|
|
38934
|
+
console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
|
|
38935
|
+
}
|
|
38936
|
+
}
|
|
38008
38937
|
try {
|
|
38009
38938
|
console.log("Starting recorders");
|
|
38010
38939
|
await this.recorder.startAll();
|
|
@@ -38084,6 +39013,7 @@ Error: ${error}`
|
|
|
38084
39013
|
this.spyCam && this.spyCam.stopCheckSpyCam();
|
|
38085
39014
|
this.appChecker && await this.appChecker.disconnectWebSocket();
|
|
38086
39015
|
await this.recorder.saveAllOnSession();
|
|
39016
|
+
await this.sendPendingRealtimeAlerts();
|
|
38087
39017
|
await this.repository.save(this.proctoringSession);
|
|
38088
39018
|
let uploader;
|
|
38089
39019
|
let uploaderServices;
|
|
@@ -38306,61 +39236,6 @@ Error: ` + error
|
|
|
38306
39236
|
_screenStream: (_a2 = this.allRecorders.screenRecorder) == null ? void 0 : _a2.screenStream
|
|
38307
39237
|
};
|
|
38308
39238
|
}
|
|
38309
|
-
async startChallenge(templateId) {
|
|
38310
|
-
var _a2;
|
|
38311
|
-
if (!this.sessionOptions.useChallenge) {
|
|
38312
|
-
throw new Error("useChallenge is set as false on start method");
|
|
38313
|
-
}
|
|
38314
|
-
await this.extensionEasycatcher.checkExtensionInstalled().catch((err) => {
|
|
38315
|
-
throw new Error("EasyCatcher Extension is not installed");
|
|
38316
|
-
});
|
|
38317
|
-
this.extensionEasycatcher.start();
|
|
38318
|
-
const start = Date.now() - ((_a2 = this.allRecorders.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0;
|
|
38319
|
-
await this.backend.startChallenge({
|
|
38320
|
-
proctoringId: this.proctoringId,
|
|
38321
|
-
templateId,
|
|
38322
|
-
start
|
|
38323
|
-
}).then((resp) => {
|
|
38324
|
-
console.log(resp);
|
|
38325
|
-
this.challengeId = resp.id;
|
|
38326
|
-
}).catch((reason) => {
|
|
38327
|
-
trackers.registerError(
|
|
38328
|
-
this.proctoringId,
|
|
38329
|
-
"N\xE3o foi poss\xEDvel iniciar desafio!"
|
|
38330
|
-
);
|
|
38331
|
-
throw reason;
|
|
38332
|
-
});
|
|
38333
|
-
this.isChallengeRunning = true;
|
|
38334
|
-
}
|
|
38335
|
-
async stopChallenge() {
|
|
38336
|
-
var _a2;
|
|
38337
|
-
if (!this.isChallengeRunning) {
|
|
38338
|
-
throw new Error("Challenge not started");
|
|
38339
|
-
}
|
|
38340
|
-
try {
|
|
38341
|
-
const sessionData = await this.extensionEasycatcher.getSessionData();
|
|
38342
|
-
const end = Date.now() - ((_a2 = this.allRecorders.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0;
|
|
38343
|
-
await this.backend.stopChallenge(
|
|
38344
|
-
this.challengeId,
|
|
38345
|
-
{
|
|
38346
|
-
end,
|
|
38347
|
-
data: sessionData
|
|
38348
|
-
}
|
|
38349
|
-
).catch((reason) => {
|
|
38350
|
-
trackers.registerError(
|
|
38351
|
-
this.proctoringId,
|
|
38352
|
-
"N\xE3o foi poss\xEDvel finalizar o desafio no backend!"
|
|
38353
|
-
);
|
|
38354
|
-
return void 0;
|
|
38355
|
-
});
|
|
38356
|
-
this.isChallengeRunning = false;
|
|
38357
|
-
} catch (error) {
|
|
38358
|
-
trackers.registerError(
|
|
38359
|
-
this.proctoringId,
|
|
38360
|
-
"Erro ao recuperar dados da extens\xE3o: " + error.message
|
|
38361
|
-
);
|
|
38362
|
-
}
|
|
38363
|
-
}
|
|
38364
39239
|
};
|
|
38365
39240
|
|
|
38366
39241
|
// src/proctoring/SignTerm.ts
|
|
@@ -38368,7 +39243,8 @@ var _SignTerm = class _SignTerm {
|
|
|
38368
39243
|
constructor(context) {
|
|
38369
39244
|
this.backend = new BackendService({
|
|
38370
39245
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
38371
|
-
token: context.token
|
|
39246
|
+
token: context.token,
|
|
39247
|
+
isRealtime: false
|
|
38372
39248
|
});
|
|
38373
39249
|
}
|
|
38374
39250
|
async signInTerms() {
|
|
@@ -38588,8 +39464,6 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38588
39464
|
return originalStart(parameters2, videoOptions);
|
|
38589
39465
|
};
|
|
38590
39466
|
const finish = proctoring.finish.bind(proctoring);
|
|
38591
|
-
const startChallenge = proctoring.startChallenge.bind(proctoring);
|
|
38592
|
-
const stopChallenge = proctoring.stopChallenge.bind(proctoring);
|
|
38593
39467
|
const pause = proctoring.pause.bind(proctoring);
|
|
38594
39468
|
const resume = proctoring.resume.bind(proctoring);
|
|
38595
39469
|
const onFocus = proctoring.setOnFocusCallback.bind(proctoring);
|
|
@@ -38598,6 +39472,8 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38598
39472
|
const onBufferSizeError = proctoring.setOnBufferSizeErrorCallback.bind(proctoring);
|
|
38599
39473
|
const onStopSharingScreen = proctoring.setOnStopSharingScreenCallback.bind(proctoring);
|
|
38600
39474
|
const onRealtimeAlerts = proctoring.onRealtimeAlerts.bind(proctoring);
|
|
39475
|
+
const onVisibilityLost = proctoring.setOnVisibilityLostCallback.bind(proctoring);
|
|
39476
|
+
const onVisibilityRestored = proctoring.setOnVisibilityRestoredCallback.bind(proctoring);
|
|
38601
39477
|
const signInTerms = signTerm.signInTerms.bind(signTerm);
|
|
38602
39478
|
const checkDevices = checker.checkDevices.bind(checker);
|
|
38603
39479
|
const checkExternalCamera = proctoring.appChecker.checkExternalCamera.bind(proctoring.appChecker);
|
|
@@ -38612,8 +39488,6 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38612
39488
|
login,
|
|
38613
39489
|
start,
|
|
38614
39490
|
finish,
|
|
38615
|
-
startChallenge,
|
|
38616
|
-
stopChallenge,
|
|
38617
39491
|
onFocus,
|
|
38618
39492
|
onLostFocus,
|
|
38619
39493
|
onChangeDevices: onChangeDevices2,
|
|
@@ -38635,7 +39509,9 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38635
39509
|
startExternalCameraSession,
|
|
38636
39510
|
takeExternalCameraPicture,
|
|
38637
39511
|
goToExternalCameraPositionStep,
|
|
38638
|
-
startExternalCameraTransmission
|
|
39512
|
+
startExternalCameraTransmission,
|
|
39513
|
+
onVisibilityLost,
|
|
39514
|
+
onVisibilityRestored
|
|
38639
39515
|
};
|
|
38640
39516
|
}
|
|
38641
39517
|
|