easyproctor-hml 2.5.39 → 2.7.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/README.md +31 -0
- package/errors/errors.d.ts +1 -0
- package/esm/index.js +1184 -148
- package/index.js +1184 -148
- package/modules/BaseDetection.d.ts +1 -1
- package/new-flow/backend/BackendService.d.ts +7 -2
- package/new-flow/backend/SafeBrowserBackendService.d.ts +13 -0
- 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 +2 -1
- package/plugins/recorder.d.ts +5 -1
- package/proctoring/options/ProctoringOptions.d.ts +1 -0
- package/proctoring/proctoring.d.ts +7 -0
- package/proctoring/useProctoring.d.ts +2 -0
- package/unpkg/easyproctor.min.js +45 -47
- package/utils/browserInformations.d.ts +1 -0
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);
|
|
@@ -30919,8 +30988,11 @@ function recorder(stream4, buffer, onBufferSizeError = false, onBufferSizeErrorC
|
|
|
30919
30988
|
resolvePromise = resolve;
|
|
30920
30989
|
mediaRecorder.onstop = () => {
|
|
30921
30990
|
console.log("recorder onstop");
|
|
30922
|
-
|
|
30923
|
-
console.log("duration no onstop",
|
|
30991
|
+
duration = Date.now() - startTime.getTime() || 0;
|
|
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) {
|
|
@@ -31198,6 +31265,7 @@ var BROWSER_NOT_SUPPORTED = "browser_not_supported";
|
|
|
31198
31265
|
var TOKEN_MISSING = "token_missing";
|
|
31199
31266
|
var CREDENTIALS_MISSING = "credentials_missing";
|
|
31200
31267
|
var SPY_SCAN_API_NOT_FOUND = "spy_scan_api_not_found";
|
|
31268
|
+
var SAFE_BROWSER_API_NOT_FOUND = "safe_browser_api_not_found";
|
|
31201
31269
|
var EXTERNAL_CAMERA_NOT_STARTED = "external_camera_not_started";
|
|
31202
31270
|
|
|
31203
31271
|
// src/modules/objectDetection.ts
|
|
@@ -31341,10 +31409,617 @@ var VolumeMeter = class {
|
|
|
31341
31409
|
}
|
|
31342
31410
|
};
|
|
31343
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
|
+
/** Incrementado para v2 para recriar índices com tipos numéricos em vez de boolean */
|
|
31652
|
+
_ChunkStorageService.DB_VERSION = 2;
|
|
31653
|
+
_ChunkStorageService.STORE_NAME = "chunks";
|
|
31654
|
+
var ChunkStorageService = _ChunkStorageService;
|
|
31655
|
+
|
|
31656
|
+
// src/new-flow/chunk/BackgroundUploadService.ts
|
|
31657
|
+
var DEFAULT_CONFIG = {
|
|
31658
|
+
pollInterval: 5e3,
|
|
31659
|
+
maxRetries: 5,
|
|
31660
|
+
baseRetryDelay: 2e3,
|
|
31661
|
+
cleanAfterUpload: true
|
|
31662
|
+
};
|
|
31663
|
+
var BackgroundUploadService = class _BackgroundUploadService {
|
|
31664
|
+
constructor(proctoringId2, token, backend, chunkStorage, config) {
|
|
31665
|
+
this.pollTimer = null;
|
|
31666
|
+
this.isProcessing = false;
|
|
31667
|
+
this.isRunning = false;
|
|
31668
|
+
/** Mapa de chunkId -> número de tentativas já feitas */
|
|
31669
|
+
this.retryCount = /* @__PURE__ */ new Map();
|
|
31670
|
+
/** GCS Resumable Upload State */
|
|
31671
|
+
this.sessionUrl = null;
|
|
31672
|
+
this.currentOffset = 0;
|
|
31673
|
+
this.totalBytesPurged = 0;
|
|
31674
|
+
this.STORAGE_KEY_PREFIX = "ep_upload_session_";
|
|
31675
|
+
this.GCS_CHUNK_SIZE = 256 * 1024;
|
|
31676
|
+
this.proctoringId = proctoringId2.trim();
|
|
31677
|
+
this.token = token;
|
|
31678
|
+
this.backend = backend;
|
|
31679
|
+
this.chunkStorage = chunkStorage;
|
|
31680
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31681
|
+
this.loadSessionState();
|
|
31682
|
+
}
|
|
31683
|
+
loadSessionState() {
|
|
31684
|
+
try {
|
|
31685
|
+
const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31686
|
+
if (stored) {
|
|
31687
|
+
const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
|
|
31688
|
+
this.sessionUrl = sessionUrl;
|
|
31689
|
+
this.currentOffset = currentOffset;
|
|
31690
|
+
this.totalBytesPurged = totalBytesPurged || 0;
|
|
31691
|
+
}
|
|
31692
|
+
} catch (e3) {
|
|
31693
|
+
console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
|
|
31694
|
+
}
|
|
31695
|
+
}
|
|
31696
|
+
saveSessionState() {
|
|
31697
|
+
try {
|
|
31698
|
+
localStorage.setItem(
|
|
31699
|
+
`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
|
|
31700
|
+
JSON.stringify({
|
|
31701
|
+
sessionUrl: this.sessionUrl,
|
|
31702
|
+
currentOffset: this.currentOffset,
|
|
31703
|
+
totalBytesPurged: this.totalBytesPurged
|
|
31704
|
+
})
|
|
31705
|
+
);
|
|
31706
|
+
} catch (e3) {
|
|
31707
|
+
console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
|
|
31708
|
+
}
|
|
31709
|
+
}
|
|
31710
|
+
clearSessionState() {
|
|
31711
|
+
try {
|
|
31712
|
+
localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
31713
|
+
this.sessionUrl = null;
|
|
31714
|
+
this.currentOffset = 0;
|
|
31715
|
+
this.totalBytesPurged = 0;
|
|
31716
|
+
} catch (e3) {
|
|
31717
|
+
console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
|
|
31718
|
+
}
|
|
31719
|
+
}
|
|
31720
|
+
/**
|
|
31721
|
+
* Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
|
|
31722
|
+
* para enviar chunks pendentes.
|
|
31723
|
+
*/
|
|
31724
|
+
start() {
|
|
31725
|
+
if (this.isRunning) return;
|
|
31726
|
+
this.isRunning = true;
|
|
31727
|
+
console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
|
|
31728
|
+
this.processQueue();
|
|
31729
|
+
this.pollTimer = setInterval(() => {
|
|
31730
|
+
this.processQueue(false);
|
|
31731
|
+
}, this.config.pollInterval);
|
|
31732
|
+
}
|
|
31733
|
+
/**
|
|
31734
|
+
* Para o serviço de upload em background.
|
|
31735
|
+
*/
|
|
31736
|
+
stop() {
|
|
31737
|
+
this.isRunning = false;
|
|
31738
|
+
if (this.pollTimer) {
|
|
31739
|
+
clearInterval(this.pollTimer);
|
|
31740
|
+
this.pollTimer = null;
|
|
31741
|
+
}
|
|
31742
|
+
console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
|
|
31743
|
+
}
|
|
31744
|
+
/**
|
|
31745
|
+
* Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
|
|
31746
|
+
* Útil quando a gravação é finalizada.
|
|
31747
|
+
*/
|
|
31748
|
+
async flush() {
|
|
31749
|
+
console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
|
|
31750
|
+
let waitAttempts = 0;
|
|
31751
|
+
while (this.isProcessing && waitAttempts < 10) {
|
|
31752
|
+
await this.sleep(1e3);
|
|
31753
|
+
waitAttempts++;
|
|
31754
|
+
}
|
|
31755
|
+
let flushRetries = 0;
|
|
31756
|
+
const maxFlushRetries = 3;
|
|
31757
|
+
while (flushRetries < maxFlushRetries) {
|
|
31758
|
+
try {
|
|
31759
|
+
await this.processQueue(true);
|
|
31760
|
+
console.log(`[BackgroundUpload] Flush completado com sucesso.`);
|
|
31761
|
+
return;
|
|
31762
|
+
} catch (error) {
|
|
31763
|
+
flushRetries++;
|
|
31764
|
+
console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
|
|
31765
|
+
if (flushRetries < maxFlushRetries) {
|
|
31766
|
+
await this.sleep(2e3);
|
|
31767
|
+
}
|
|
31768
|
+
}
|
|
31769
|
+
}
|
|
31770
|
+
throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
|
|
31771
|
+
}
|
|
31772
|
+
/**
|
|
31773
|
+
* Sincroniza o offset local com o estado real no Google Cloud Storage.
|
|
31774
|
+
*/
|
|
31775
|
+
async syncOffset() {
|
|
31776
|
+
if (!this.sessionUrl) return 0;
|
|
31777
|
+
try {
|
|
31778
|
+
console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
|
|
31779
|
+
const response = await fetch(this.sessionUrl, {
|
|
31780
|
+
method: "PUT",
|
|
31781
|
+
headers: {
|
|
31782
|
+
"Content-Range": "bytes */*"
|
|
31783
|
+
}
|
|
31784
|
+
});
|
|
31785
|
+
console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
|
|
31786
|
+
if (response.status === 308) {
|
|
31787
|
+
const range = response.headers.get("Range");
|
|
31788
|
+
if (range) {
|
|
31789
|
+
const lastByte = parseInt(range.split("-")[1], 10);
|
|
31790
|
+
this.currentOffset = lastByte + 1;
|
|
31791
|
+
this.saveSessionState();
|
|
31792
|
+
console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
|
|
31793
|
+
} else {
|
|
31794
|
+
this.currentOffset = 0;
|
|
31795
|
+
}
|
|
31796
|
+
} else if (response.ok || response.status === 201) {
|
|
31797
|
+
console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
|
|
31798
|
+
this.currentOffset = -1;
|
|
31799
|
+
} else {
|
|
31800
|
+
console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
|
|
31801
|
+
}
|
|
31802
|
+
} catch (error) {
|
|
31803
|
+
console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
|
|
31804
|
+
}
|
|
31805
|
+
return this.currentOffset;
|
|
31806
|
+
}
|
|
31807
|
+
/**
|
|
31808
|
+
* Verifica e envia chunks pendentes para o backend.
|
|
31809
|
+
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
31810
|
+
*/
|
|
31811
|
+
async processQueue(isFinal = false) {
|
|
31812
|
+
var _a2, _b;
|
|
31813
|
+
if (this.isProcessing) return;
|
|
31814
|
+
this.isProcessing = true;
|
|
31815
|
+
try {
|
|
31816
|
+
if (this.sessionUrl) {
|
|
31817
|
+
await this.syncOffset();
|
|
31818
|
+
if (this.currentOffset === -1) {
|
|
31819
|
+
console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
|
|
31820
|
+
this.clearSessionState();
|
|
31821
|
+
this.isProcessing = false;
|
|
31822
|
+
return;
|
|
31823
|
+
}
|
|
31824
|
+
}
|
|
31825
|
+
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
31826
|
+
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
31827
|
+
if (pendingChunks.length === 0 && !isFinal) {
|
|
31828
|
+
this.isProcessing = false;
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
31831
|
+
console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
|
|
31832
|
+
let virtualStart = this.totalBytesPurged;
|
|
31833
|
+
const chunksWithMeta = allChunks.map((c3) => {
|
|
31834
|
+
const start = virtualStart;
|
|
31835
|
+
const end = start + c3.blob.size - 1;
|
|
31836
|
+
virtualStart += c3.blob.size;
|
|
31837
|
+
return { chunk: c3, start, end };
|
|
31838
|
+
});
|
|
31839
|
+
let combinedBlobParts = [];
|
|
31840
|
+
let lastProcessedChunkId = null;
|
|
31841
|
+
let finalChunkIndex = 0;
|
|
31842
|
+
let mimeType = pendingChunks[0].mimeType;
|
|
31843
|
+
for (const meta of chunksWithMeta) {
|
|
31844
|
+
if (this.currentOffset > meta.end) continue;
|
|
31845
|
+
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
31846
|
+
const chunkSlice = meta.chunk.blob.slice(sliceStart);
|
|
31847
|
+
combinedBlobParts.push(chunkSlice);
|
|
31848
|
+
lastProcessedChunkId = meta.chunk.id;
|
|
31849
|
+
finalChunkIndex = meta.chunk.chunkIndex;
|
|
31850
|
+
}
|
|
31851
|
+
if (combinedBlobParts.length === 0 && !isFinal) {
|
|
31852
|
+
this.isProcessing = false;
|
|
31853
|
+
return;
|
|
31854
|
+
}
|
|
31855
|
+
let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
|
|
31856
|
+
let sendableSize = fullBlob.size;
|
|
31857
|
+
let totalSizeForHeader = void 0;
|
|
31858
|
+
if (!isFinal) {
|
|
31859
|
+
sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
31860
|
+
if (sendableSize === 0) {
|
|
31861
|
+
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
31862
|
+
this.isProcessing = false;
|
|
31863
|
+
return;
|
|
31864
|
+
}
|
|
31865
|
+
} else {
|
|
31866
|
+
totalSizeForHeader = virtualStart;
|
|
31867
|
+
}
|
|
31868
|
+
const blobToSend = fullBlob.slice(0, sendableSize);
|
|
31869
|
+
try {
|
|
31870
|
+
await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
31871
|
+
for (const meta of chunksWithMeta) {
|
|
31872
|
+
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
31873
|
+
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
31874
|
+
this.retryCount.delete(meta.chunk.id);
|
|
31875
|
+
(_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
31876
|
+
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
31877
|
+
}
|
|
31878
|
+
}
|
|
31879
|
+
if (this.config.cleanAfterUpload) {
|
|
31880
|
+
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
31881
|
+
const sizePurged = chunksToClear.reduce((acc, meta) => acc + meta.chunk.blob.size, 0);
|
|
31882
|
+
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
31883
|
+
if (sizePurged > 0) {
|
|
31884
|
+
this.totalBytesPurged += sizePurged;
|
|
31885
|
+
this.saveSessionState();
|
|
31886
|
+
console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
|
|
31887
|
+
}
|
|
31888
|
+
}
|
|
31889
|
+
if (isFinal) {
|
|
31890
|
+
this.clearSessionState();
|
|
31891
|
+
}
|
|
31892
|
+
} catch (error) {
|
|
31893
|
+
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
31894
|
+
(_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
|
|
31895
|
+
}
|
|
31896
|
+
} catch (error) {
|
|
31897
|
+
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
31898
|
+
} finally {
|
|
31899
|
+
this.isProcessing = false;
|
|
31900
|
+
}
|
|
31901
|
+
}
|
|
31902
|
+
/**
|
|
31903
|
+
* Faz o upload bruto de dados para a sessão GCS.
|
|
31904
|
+
*/
|
|
31905
|
+
async uploadData(blob, mimeType, chunkIndex, totalSize) {
|
|
31906
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
31907
|
+
if (!this.sessionUrl) {
|
|
31908
|
+
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
31909
|
+
const startResponse = await fetch(initiateUrl, {
|
|
31910
|
+
method: "POST",
|
|
31911
|
+
headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
|
|
31912
|
+
});
|
|
31913
|
+
if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
|
|
31914
|
+
this.sessionUrl = startResponse.headers.get("Location");
|
|
31915
|
+
if (!this.sessionUrl) throw new Error("Location header ausente");
|
|
31916
|
+
try {
|
|
31917
|
+
const urlObj = new URL(this.sessionUrl);
|
|
31918
|
+
const pathParts = urlObj.pathname.split("/");
|
|
31919
|
+
let bucket = pathParts[1];
|
|
31920
|
+
let object = decodeURIComponent(pathParts.slice(2).join("/"));
|
|
31921
|
+
if (pathParts.includes("b") && pathParts.includes("o")) {
|
|
31922
|
+
const bIdx = pathParts.indexOf("b") + 1;
|
|
31923
|
+
const oIdx = pathParts.indexOf("o") + 1;
|
|
31924
|
+
bucket = pathParts[bIdx];
|
|
31925
|
+
object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
|
|
31926
|
+
}
|
|
31927
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
|
|
31928
|
+
} catch (e3) {
|
|
31929
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
|
|
31930
|
+
}
|
|
31931
|
+
this.currentOffset = 0;
|
|
31932
|
+
this.saveSessionState();
|
|
31933
|
+
} else {
|
|
31934
|
+
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
31935
|
+
}
|
|
31936
|
+
const start = this.currentOffset;
|
|
31937
|
+
const end = start + blob.size - 1;
|
|
31938
|
+
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
31939
|
+
const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
31940
|
+
console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
|
|
31941
|
+
const response = await fetch(this.sessionUrl, {
|
|
31942
|
+
method: "PUT",
|
|
31943
|
+
headers: { "Content-Range": contentRangeHeader },
|
|
31944
|
+
body: blob.size > 0 ? blob : null
|
|
31945
|
+
// Usa null para garantir corpo vazio se necessário
|
|
31946
|
+
});
|
|
31947
|
+
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
31948
|
+
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
31949
|
+
const errorText = await response.text();
|
|
31950
|
+
console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
|
|
31951
|
+
throw new Error(`Status HTTP inesperado: ${response.status}`);
|
|
31952
|
+
}
|
|
31953
|
+
const rangeHeader = response.headers.get("Range");
|
|
31954
|
+
if (rangeHeader) {
|
|
31955
|
+
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
31956
|
+
this.currentOffset = lastByte + 1;
|
|
31957
|
+
} else {
|
|
31958
|
+
this.currentOffset += blob.size;
|
|
31959
|
+
}
|
|
31960
|
+
this.saveSessionState();
|
|
31961
|
+
trackers.registerUploadFile(
|
|
31962
|
+
this.proctoringId,
|
|
31963
|
+
`GCS Stream Upload
|
|
31964
|
+
Size: ${blob.size}
|
|
31965
|
+
Range: ${start}-${end}
|
|
31966
|
+
Last Index: ${chunkIndex}`,
|
|
31967
|
+
"CameraChunk"
|
|
31968
|
+
);
|
|
31969
|
+
}
|
|
31970
|
+
/**
|
|
31971
|
+
* Método estático para recuperação pós-crash.
|
|
31972
|
+
* Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
|
|
31973
|
+
* e tenta enviar.
|
|
31974
|
+
*/
|
|
31975
|
+
static async recoverPendingUploads(backend, token) {
|
|
31976
|
+
const chunkStorage = new ChunkStorageService();
|
|
31977
|
+
const recoveredIds = [];
|
|
31978
|
+
try {
|
|
31979
|
+
const pendingIds = await chunkStorage.getPendingProctoringIds();
|
|
31980
|
+
if (pendingIds.length === 0) {
|
|
31981
|
+
console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
|
|
31982
|
+
return recoveredIds;
|
|
31983
|
+
}
|
|
31984
|
+
console.log(
|
|
31985
|
+
`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
|
|
31986
|
+
);
|
|
31987
|
+
for (const proctoringId2 of pendingIds) {
|
|
31988
|
+
try {
|
|
31989
|
+
const service = new _BackgroundUploadService(
|
|
31990
|
+
proctoringId2,
|
|
31991
|
+
token,
|
|
31992
|
+
backend,
|
|
31993
|
+
chunkStorage,
|
|
31994
|
+
{ cleanAfterUpload: true }
|
|
31995
|
+
);
|
|
31996
|
+
await service.flush();
|
|
31997
|
+
recoveredIds.push(proctoringId2);
|
|
31998
|
+
console.log(
|
|
31999
|
+
`[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
|
|
32000
|
+
);
|
|
32001
|
+
} catch (error) {
|
|
32002
|
+
console.error(
|
|
32003
|
+
`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
|
|
32004
|
+
error
|
|
32005
|
+
);
|
|
32006
|
+
}
|
|
32007
|
+
}
|
|
32008
|
+
} catch (error) {
|
|
32009
|
+
console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
|
|
32010
|
+
}
|
|
32011
|
+
return recoveredIds;
|
|
32012
|
+
}
|
|
32013
|
+
sleep(ms2) {
|
|
32014
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
32015
|
+
}
|
|
32016
|
+
};
|
|
32017
|
+
|
|
31344
32018
|
// src/new-flow/recorders/CameraRecorder.ts
|
|
31345
32019
|
var import_jszip_min = __toESM(require_jszip_min());
|
|
31346
|
-
var
|
|
31347
|
-
var
|
|
32020
|
+
var pkg = require_fix_webm_duration();
|
|
32021
|
+
var fixWebmDuration = pkg.default || pkg;
|
|
32022
|
+
var _CameraRecorder = class _CameraRecorder {
|
|
31348
32023
|
constructor(options, videoOptions, paramsConfig, backend, backendToken) {
|
|
31349
32024
|
this.blobs = [];
|
|
31350
32025
|
this.paramsConfig = {
|
|
@@ -31389,6 +32064,7 @@ var CameraRecorder = class {
|
|
|
31389
32064
|
this.blobsRTC = [];
|
|
31390
32065
|
this.imageCount = 0;
|
|
31391
32066
|
this.filesToUpload = [];
|
|
32067
|
+
this.pendingPackages = [];
|
|
31392
32068
|
this.animationFrameId = null;
|
|
31393
32069
|
this.isCanvasLoopActive = false;
|
|
31394
32070
|
this.hardwareStream = null;
|
|
@@ -31396,8 +32072,16 @@ var CameraRecorder = class {
|
|
|
31396
32072
|
this.videoElement = null;
|
|
31397
32073
|
this.duration = 0;
|
|
31398
32074
|
this.stopped = false;
|
|
32075
|
+
this.backgroundUpload = null;
|
|
32076
|
+
this.chunkIndex = 0;
|
|
32077
|
+
/** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
|
|
32078
|
+
this.pendingChunkSaves = [];
|
|
32079
|
+
// Handlers bound para poder remover os listeners depois
|
|
32080
|
+
this.boundVisibilityHandler = null;
|
|
32081
|
+
this.boundPageHideHandler = null;
|
|
31399
32082
|
this.currentRetries = 0;
|
|
31400
32083
|
this.packageCount = 0;
|
|
32084
|
+
this.failedUploads = 0;
|
|
31401
32085
|
this.noiseWait = 20;
|
|
31402
32086
|
this.options = options;
|
|
31403
32087
|
this.videoOptions = videoOptions;
|
|
@@ -31405,10 +32089,122 @@ var CameraRecorder = class {
|
|
|
31405
32089
|
this.backendToken = backendToken;
|
|
31406
32090
|
paramsConfig && (this.paramsConfig = paramsConfig);
|
|
31407
32091
|
}
|
|
32092
|
+
/**
|
|
32093
|
+
* Determina se o fluxo de chunks e lifecycle deve estar ativo.
|
|
32094
|
+
* Retorna true se:
|
|
32095
|
+
* 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
|
|
32096
|
+
* 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
|
|
32097
|
+
*/
|
|
32098
|
+
get isChunkEnabled() {
|
|
32099
|
+
return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
|
|
32100
|
+
}
|
|
31408
32101
|
setProctoringId(proctoringId2) {
|
|
31409
32102
|
this.proctoringId = proctoringId2;
|
|
31410
32103
|
this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
|
|
31411
32104
|
setRecorderProctoringId(proctoringId2);
|
|
32105
|
+
if (this.isChunkEnabled) {
|
|
32106
|
+
this.chunkStorage = new ChunkStorageService();
|
|
32107
|
+
if (this.backend && this.backendToken) {
|
|
32108
|
+
this.backgroundUpload = new BackgroundUploadService(
|
|
32109
|
+
this.proctoringId,
|
|
32110
|
+
this.backendToken,
|
|
32111
|
+
this.backend,
|
|
32112
|
+
this.chunkStorage,
|
|
32113
|
+
{ pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
|
|
32114
|
+
);
|
|
32115
|
+
}
|
|
32116
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32117
|
+
console.log(
|
|
32118
|
+
`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
|
|
32119
|
+
);
|
|
32120
|
+
} else {
|
|
32121
|
+
console.log(
|
|
32122
|
+
`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
|
|
32123
|
+
);
|
|
32124
|
+
}
|
|
32125
|
+
}
|
|
32126
|
+
// ========================
|
|
32127
|
+
// Session State Persistence (localStorage)
|
|
32128
|
+
// ========================
|
|
32129
|
+
persistSessionState(status) {
|
|
32130
|
+
try {
|
|
32131
|
+
const data = {
|
|
32132
|
+
proctoringId: this.proctoringId,
|
|
32133
|
+
status,
|
|
32134
|
+
timestamp: Date.now()
|
|
32135
|
+
};
|
|
32136
|
+
localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
|
|
32137
|
+
} catch (e3) {
|
|
32138
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
|
|
32139
|
+
}
|
|
32140
|
+
}
|
|
32141
|
+
clearSessionState() {
|
|
32142
|
+
try {
|
|
32143
|
+
localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32144
|
+
} catch (e3) {
|
|
32145
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
|
|
32146
|
+
}
|
|
32147
|
+
}
|
|
32148
|
+
/**
|
|
32149
|
+
* Verifica se existe uma sessão ativa anterior no localStorage.
|
|
32150
|
+
* Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
|
|
32151
|
+
*/
|
|
32152
|
+
static checkForActiveSession() {
|
|
32153
|
+
try {
|
|
32154
|
+
const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
|
|
32155
|
+
if (!raw) return null;
|
|
32156
|
+
const data = JSON.parse(raw);
|
|
32157
|
+
if (data.status === "IN_PROGRESS") return data;
|
|
32158
|
+
return null;
|
|
32159
|
+
} catch {
|
|
32160
|
+
return null;
|
|
32161
|
+
}
|
|
32162
|
+
}
|
|
32163
|
+
// ========================
|
|
32164
|
+
// Page Lifecycle Management
|
|
32165
|
+
// ========================
|
|
32166
|
+
setupLifecycleListeners() {
|
|
32167
|
+
this.boundVisibilityHandler = () => this.handleVisibilityChange();
|
|
32168
|
+
this.boundPageHideHandler = () => this.handlePageHide();
|
|
32169
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32170
|
+
window.addEventListener("pagehide", this.boundPageHideHandler);
|
|
32171
|
+
}
|
|
32172
|
+
removeLifecycleListeners() {
|
|
32173
|
+
if (this.boundVisibilityHandler) {
|
|
32174
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
32175
|
+
this.boundVisibilityHandler = null;
|
|
32176
|
+
}
|
|
32177
|
+
if (this.boundPageHideHandler) {
|
|
32178
|
+
window.removeEventListener("pagehide", this.boundPageHideHandler);
|
|
32179
|
+
this.boundPageHideHandler = null;
|
|
32180
|
+
}
|
|
32181
|
+
}
|
|
32182
|
+
handleVisibilityChange() {
|
|
32183
|
+
var _a2;
|
|
32184
|
+
if (document.visibilityState === "hidden") {
|
|
32185
|
+
console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
|
|
32186
|
+
this.persistSessionState("INTERRUPTED");
|
|
32187
|
+
this.proctoringId && trackers.registerError(
|
|
32188
|
+
this.proctoringId,
|
|
32189
|
+
"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
|
|
32190
|
+
);
|
|
32191
|
+
} else if (document.visibilityState === "visible") {
|
|
32192
|
+
console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
|
|
32193
|
+
this.persistSessionState("IN_PROGRESS");
|
|
32194
|
+
this.proctoringId && trackers.registerError(
|
|
32195
|
+
this.proctoringId,
|
|
32196
|
+
"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
|
|
32197
|
+
);
|
|
32198
|
+
(_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
|
|
32199
|
+
}
|
|
32200
|
+
}
|
|
32201
|
+
handlePageHide() {
|
|
32202
|
+
console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
|
|
32203
|
+
this.persistSessionState("INTERRUPTED");
|
|
32204
|
+
this.proctoringId && trackers.registerError(
|
|
32205
|
+
this.proctoringId,
|
|
32206
|
+
"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
|
|
32207
|
+
);
|
|
31412
32208
|
}
|
|
31413
32209
|
async initializeDetectors() {
|
|
31414
32210
|
var _a2, _b, _c2;
|
|
@@ -31561,10 +32357,15 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31561
32357
|
await new Promise((r3) => setTimeout(r3, 300));
|
|
31562
32358
|
}
|
|
31563
32359
|
async startRecording() {
|
|
31564
|
-
var _a2, _b, _c2, _d, _e3, _f, _g;
|
|
31565
|
-
console.log("CameraRecorder startRecording");
|
|
32360
|
+
var _a2, _b, _c2, _d, _e3, _f, _g, _h;
|
|
31566
32361
|
await this.startStream();
|
|
31567
32362
|
await this.attachAndWarmup(this.cameraStream);
|
|
32363
|
+
const recorderOpts = this.isChunkEnabled ? {
|
|
32364
|
+
timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
|
|
32365
|
+
onChunkAvailable: (blob, idx) => {
|
|
32366
|
+
this.handleNewChunk(blob, idx);
|
|
32367
|
+
}
|
|
32368
|
+
} : {};
|
|
31568
32369
|
const {
|
|
31569
32370
|
startRecording,
|
|
31570
32371
|
stopRecording,
|
|
@@ -31580,7 +32381,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31580
32381
|
this.blobs,
|
|
31581
32382
|
this.options.onBufferSizeError,
|
|
31582
32383
|
(e3) => this.bufferError(e3),
|
|
31583
|
-
false
|
|
32384
|
+
false,
|
|
32385
|
+
recorderOpts
|
|
31584
32386
|
);
|
|
31585
32387
|
this.recordingStart = startRecording;
|
|
31586
32388
|
this.recordingStop = stopRecording;
|
|
@@ -31590,13 +32392,18 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31590
32392
|
this.getBufferSize = getBufferSize;
|
|
31591
32393
|
this.getStartTime = getStartTime;
|
|
31592
32394
|
this.getDuration = getDuration;
|
|
32395
|
+
this.chunkIndex = 0;
|
|
32396
|
+
if (this.isChunkEnabled) {
|
|
32397
|
+
(_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
|
|
32398
|
+
this.setupLifecycleListeners();
|
|
32399
|
+
}
|
|
31593
32400
|
try {
|
|
31594
32401
|
await new Promise((r3) => setTimeout(r3, 500));
|
|
31595
32402
|
await this.recordingStart();
|
|
31596
32403
|
} catch (error) {
|
|
31597
32404
|
console.log("Camera Recorder error", error);
|
|
31598
32405
|
this.stopRecording();
|
|
31599
|
-
const maxRetries = ((
|
|
32406
|
+
const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
|
|
31600
32407
|
if (this.currentRetries < maxRetries) {
|
|
31601
32408
|
console.log("Camera Recorder retry", this.currentRetries);
|
|
31602
32409
|
this.currentRetries++;
|
|
@@ -31606,16 +32413,17 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31606
32413
|
}
|
|
31607
32414
|
}
|
|
31608
32415
|
this.stopped = false;
|
|
31609
|
-
if (((
|
|
32416
|
+
if (((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectPerson) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectCellPhone) || ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace)) {
|
|
31610
32417
|
await this.initializeDetectors();
|
|
31611
32418
|
}
|
|
31612
|
-
if ((
|
|
32419
|
+
if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
|
|
31613
32420
|
await this.faceDetection.enableCam(this.cameraStream);
|
|
31614
32421
|
}
|
|
31615
|
-
if (((
|
|
32422
|
+
if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
|
|
31616
32423
|
await this.objectDetection.enableCam(this.cameraStream);
|
|
31617
32424
|
}
|
|
31618
32425
|
this.filesToUpload = [];
|
|
32426
|
+
this.pendingPackages = [];
|
|
31619
32427
|
if (this.options.proctoringType == "REALTIME") {
|
|
31620
32428
|
await this.startRealtimeCapture();
|
|
31621
32429
|
}
|
|
@@ -31634,7 +32442,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31634
32442
|
this.volumeMeter && this.volumeMeter.stop();
|
|
31635
32443
|
this.intervalNoiseDetection && clearInterval(this.intervalNoiseDetection);
|
|
31636
32444
|
this.recordingStop && await this.recordingStop();
|
|
31637
|
-
this.duration = this.getDuration();
|
|
32445
|
+
this.duration = this.getDuration ? this.getDuration() : 0;
|
|
32446
|
+
await new Promise((r3) => setTimeout(r3, 200));
|
|
31638
32447
|
try {
|
|
31639
32448
|
if (this.animationFrameId) {
|
|
31640
32449
|
cancelAnimationFrame(this.animationFrameId);
|
|
@@ -31665,9 +32474,54 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31665
32474
|
console.error("Erro ao parar os streams de m\xEDdia.");
|
|
31666
32475
|
}
|
|
31667
32476
|
if (this.options.proctoringType == "REALTIME" && this.upload && this.backendToken) {
|
|
31668
|
-
|
|
32477
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, this.filesToUpload.length));
|
|
32478
|
+
await this.sendPackage();
|
|
31669
32479
|
await this.filesToUpload.splice(0, this.filesToUpload.length);
|
|
31670
32480
|
}
|
|
32481
|
+
if (this.isChunkEnabled) {
|
|
32482
|
+
if (this.backgroundUpload) {
|
|
32483
|
+
try {
|
|
32484
|
+
if (this.pendingChunkSaves.length > 0) {
|
|
32485
|
+
console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
|
|
32486
|
+
await Promise.all(this.pendingChunkSaves);
|
|
32487
|
+
}
|
|
32488
|
+
await this.backgroundUpload.flush();
|
|
32489
|
+
} catch (e3) {
|
|
32490
|
+
console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
|
|
32491
|
+
}
|
|
32492
|
+
this.backgroundUpload.stop();
|
|
32493
|
+
}
|
|
32494
|
+
this.removeLifecycleListeners();
|
|
32495
|
+
this.persistSessionState("FINISHED");
|
|
32496
|
+
}
|
|
32497
|
+
}
|
|
32498
|
+
/**
|
|
32499
|
+
* Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
|
|
32500
|
+
* Salva o chunk no IndexedDB para persistência e recuperação.
|
|
32501
|
+
*/
|
|
32502
|
+
async handleNewChunk(blob, idx) {
|
|
32503
|
+
if (!this.proctoringId || !this.chunkStorage) return;
|
|
32504
|
+
const savePromise = (async () => {
|
|
32505
|
+
var _a2;
|
|
32506
|
+
try {
|
|
32507
|
+
await this.chunkStorage.saveChunk({
|
|
32508
|
+
proctoringId: this.proctoringId,
|
|
32509
|
+
chunkIndex: this.chunkIndex,
|
|
32510
|
+
blob,
|
|
32511
|
+
timestamp: Date.now(),
|
|
32512
|
+
uploaded: 0,
|
|
32513
|
+
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
32514
|
+
});
|
|
32515
|
+
this.chunkIndex++;
|
|
32516
|
+
console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
|
|
32517
|
+
} catch (error) {
|
|
32518
|
+
console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
|
|
32519
|
+
}
|
|
32520
|
+
})();
|
|
32521
|
+
this.pendingChunkSaves.push(savePromise);
|
|
32522
|
+
savePromise.finally(() => {
|
|
32523
|
+
this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
|
|
32524
|
+
});
|
|
31671
32525
|
}
|
|
31672
32526
|
async pauseRecording() {
|
|
31673
32527
|
await this.recordingPause();
|
|
@@ -31714,9 +32568,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31714
32568
|
if (this.proctoringId == void 0) return;
|
|
31715
32569
|
if (packSize == this.imageCount) {
|
|
31716
32570
|
this.imageCount = 0;
|
|
31717
|
-
|
|
31718
|
-
this.sendPackage(
|
|
31719
|
-
await this.filesToUpload.splice(0,
|
|
32571
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, packSize));
|
|
32572
|
+
this.sendPackage();
|
|
32573
|
+
await this.filesToUpload.splice(0, packSize);
|
|
31720
32574
|
}
|
|
31721
32575
|
let imageName = `${this.proctoringId}_${this.imageCount + 1}.jpg`;
|
|
31722
32576
|
imageFile = await this.getFile(image_data_url, imageName, "image/jpeg");
|
|
@@ -31730,54 +32584,61 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31730
32584
|
var _a2;
|
|
31731
32585
|
this.configImageCapture();
|
|
31732
32586
|
this.imageCount = 0;
|
|
32587
|
+
this.pendingPackages = [];
|
|
31733
32588
|
await this.captureFrame();
|
|
31734
32589
|
this.imageInterval = setInterval(async () => {
|
|
31735
32590
|
await this.captureFrame();
|
|
31736
32591
|
}, ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimeCaptureInterval) * 1e3);
|
|
31737
32592
|
}
|
|
31738
32593
|
// envia pacote de imagens
|
|
31739
|
-
async sendPackage(
|
|
32594
|
+
async sendPackage() {
|
|
31740
32595
|
var _a2, _b;
|
|
31741
|
-
let pending = false;
|
|
31742
|
-
let undeliveredPackagesCount = 0;
|
|
31743
32596
|
const packSize = (_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimePackageSize;
|
|
31744
32597
|
const packCaptureInterval = (_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.realtimeCaptureInterval;
|
|
31745
|
-
if (this.upload && this.backendToken &&
|
|
31746
|
-
|
|
31747
|
-
|
|
31748
|
-
const
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
|
|
31752
|
-
|
|
31753
|
-
|
|
31754
|
-
|
|
31755
|
-
|
|
31756
|
-
|
|
31757
|
-
|
|
31758
|
-
|
|
31759
|
-
|
|
31760
|
-
{
|
|
31761
|
-
|
|
31762
|
-
|
|
31763
|
-
|
|
31764
|
-
|
|
31765
|
-
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
32598
|
+
if (this.upload && this.backendToken && this.pendingPackages.length > 0) {
|
|
32599
|
+
let packagesToDelete = [];
|
|
32600
|
+
let packageIndex = 0;
|
|
32601
|
+
for (const packageToSend of this.pendingPackages) {
|
|
32602
|
+
const zip = new import_jszip_min.default();
|
|
32603
|
+
for (const file of packageToSend) {
|
|
32604
|
+
zip.file(file.name, file);
|
|
32605
|
+
}
|
|
32606
|
+
const blob = await zip.generateAsync({ type: "blob" });
|
|
32607
|
+
let packageName = "realtime_package_" + packSize * packCaptureInterval * this.packageCount + ".zip";
|
|
32608
|
+
const myPackage = new File(
|
|
32609
|
+
[blob],
|
|
32610
|
+
packageName,
|
|
32611
|
+
{ type: "application/zip" }
|
|
32612
|
+
);
|
|
32613
|
+
try {
|
|
32614
|
+
const uploadResult = await this.upload.uploadPackage(
|
|
32615
|
+
{
|
|
32616
|
+
file: myPackage
|
|
32617
|
+
},
|
|
32618
|
+
this.backendToken
|
|
32619
|
+
);
|
|
32620
|
+
if (uploadResult == true) {
|
|
32621
|
+
this.packageCount++;
|
|
32622
|
+
this.failedUploads = 0;
|
|
32623
|
+
packagesToDelete.push(packageIndex++);
|
|
32624
|
+
}
|
|
32625
|
+
} catch (error) {
|
|
32626
|
+
this.failedUploads++;
|
|
32627
|
+
if (this.failedUploads >= 2) {
|
|
32628
|
+
this.options.onRealtimeAlertsCallback({
|
|
32629
|
+
status: "ALERT",
|
|
32630
|
+
description: "Realtime n\xE3o est\xE1 enviando pacotes",
|
|
32631
|
+
type: "error_upload_package",
|
|
32632
|
+
category: "error_upload_package",
|
|
32633
|
+
begin: 0,
|
|
32634
|
+
end: 0
|
|
32635
|
+
});
|
|
32636
|
+
}
|
|
32637
|
+
break;
|
|
32638
|
+
}
|
|
31769
32639
|
}
|
|
31770
|
-
|
|
31771
|
-
|
|
31772
|
-
undeliveredPackagesCount++;
|
|
31773
|
-
if (undeliveredPackagesCount == 3) {
|
|
31774
|
-
undeliveredPackagesCount = 0;
|
|
31775
|
-
let newCanvasWidth = this.videoOptions.width / 2;
|
|
31776
|
-
let newCanvasHeight = this.videoOptions.height / 2;
|
|
31777
|
-
if (newCanvasWidth < 320) newCanvasWidth = 320;
|
|
31778
|
-
if (newCanvasHeight < 180) newCanvasHeight = 180;
|
|
31779
|
-
this.canvas.width = newCanvasWidth;
|
|
31780
|
-
this.canvas.height = newCanvasHeight;
|
|
32640
|
+
for (const packageToDelete of packagesToDelete) {
|
|
32641
|
+
await this.pendingPackages.splice(packageToDelete, 1);
|
|
31781
32642
|
}
|
|
31782
32643
|
}
|
|
31783
32644
|
}
|
|
@@ -31796,33 +32657,34 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31796
32657
|
if (this.blobs != null)
|
|
31797
32658
|
trackers.registerSaveOnSession(
|
|
31798
32659
|
this.proctoringId,
|
|
31799
|
-
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
|
|
32660
|
+
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
|
|
31800
32661
|
);
|
|
31801
32662
|
const settings = this.cameraStream.getVideoTracks()[0].getSettings();
|
|
31802
32663
|
const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
|
|
31803
32664
|
if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
|
|
32665
|
+
let videoFile;
|
|
32666
|
+
if (this.isChunkEnabled) {
|
|
32667
|
+
const isStable = await this.checkInternetStability();
|
|
32668
|
+
if (isStable) {
|
|
32669
|
+
} else {
|
|
32670
|
+
if (this.backend && this.backendToken && this.proctoringId) {
|
|
32671
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
32672
|
+
const objectName = `${this.proctoringId}/${fileName}`;
|
|
32673
|
+
const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
|
|
32674
|
+
if (isUploaded) {
|
|
32675
|
+
this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
|
|
32676
|
+
return;
|
|
32677
|
+
}
|
|
32678
|
+
}
|
|
32679
|
+
}
|
|
32680
|
+
}
|
|
31804
32681
|
const rawBlob = new Blob(this.blobs, {
|
|
31805
32682
|
type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
|
|
31806
32683
|
});
|
|
31807
|
-
const fixedBlob = await (
|
|
31808
|
-
|
|
31809
|
-
[rawBlob],
|
|
31810
|
-
`EP_${session.id}_camera_0.webm`,
|
|
31811
|
-
{ type: rawBlob.type }
|
|
31812
|
-
);
|
|
31813
|
-
session.addRecording({
|
|
31814
|
-
device: `Audio
|
|
31815
|
-
Sample Rate: ${settingsAudio.sampleRate}
|
|
31816
|
-
Sample Size: ${settingsAudio.sampleSize}
|
|
31817
|
-
|
|
31818
|
-
Video:
|
|
31819
|
-
${JSON.stringify(this.recorderOptions)}`,
|
|
31820
|
-
file,
|
|
31821
|
-
origin: "Camera" /* Camera */
|
|
31822
|
-
});
|
|
31823
|
-
const fileWithDuration = new File(
|
|
32684
|
+
const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
|
|
32685
|
+
videoFile = new File(
|
|
31824
32686
|
[fixedBlob],
|
|
31825
|
-
`EP_${session.id}
|
|
32687
|
+
`EP_${session.id}_camera_0.webm`,
|
|
31826
32688
|
{ type: rawBlob.type }
|
|
31827
32689
|
);
|
|
31828
32690
|
session.addRecording({
|
|
@@ -31832,7 +32694,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31832
32694
|
|
|
31833
32695
|
Video:
|
|
31834
32696
|
${JSON.stringify(this.recorderOptions)}`,
|
|
31835
|
-
file:
|
|
32697
|
+
file: videoFile,
|
|
31836
32698
|
origin: "Camera" /* Camera */
|
|
31837
32699
|
});
|
|
31838
32700
|
}
|
|
@@ -31847,6 +32709,28 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31847
32709
|
});
|
|
31848
32710
|
});
|
|
31849
32711
|
}
|
|
32712
|
+
/**
|
|
32713
|
+
* Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
|
|
32714
|
+
*/
|
|
32715
|
+
async checkInternetStability() {
|
|
32716
|
+
var _a2;
|
|
32717
|
+
if (!navigator.onLine) return false;
|
|
32718
|
+
try {
|
|
32719
|
+
const controller = new AbortController();
|
|
32720
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
32721
|
+
const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
|
|
32722
|
+
if (!baseUrl) return true;
|
|
32723
|
+
const response = await fetch(`${baseUrl}/Client/health`, {
|
|
32724
|
+
method: "GET",
|
|
32725
|
+
signal: controller.signal
|
|
32726
|
+
});
|
|
32727
|
+
clearTimeout(timeoutId);
|
|
32728
|
+
return response.status < 500;
|
|
32729
|
+
} catch (e3) {
|
|
32730
|
+
console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
|
|
32731
|
+
return false;
|
|
32732
|
+
}
|
|
32733
|
+
}
|
|
31850
32734
|
onNoiseDetected() {
|
|
31851
32735
|
var _a2, _b, _c2;
|
|
31852
32736
|
if (this.options.proctoringType === "REALTIME") return;
|
|
@@ -31859,7 +32743,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31859
32743
|
}
|
|
31860
32744
|
const volume = (_b = (_a2 = this.volumeMeter) == null ? void 0 : _a2.getVolume()) != null ? _b : 0;
|
|
31861
32745
|
if (volume >= (((_c2 = this.paramsConfig.audioBehaviourParameters) == null ? void 0 : _c2.noiseLimit) || 40)) {
|
|
31862
|
-
console.log("entrou" + this.noiseWait);
|
|
31863
32746
|
if (this.noiseWait >= 20) {
|
|
31864
32747
|
this.options.onRealtimeAlertsCallback({
|
|
31865
32748
|
status: "ALERT",
|
|
@@ -31872,6 +32755,14 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
31872
32755
|
this.noiseWait++;
|
|
31873
32756
|
}
|
|
31874
32757
|
};
|
|
32758
|
+
// ========================
|
|
32759
|
+
// Chunk & Lifecycle
|
|
32760
|
+
// ========================
|
|
32761
|
+
/** Intervalo de cada chunk em ms (padrão: 60 segundos) */
|
|
32762
|
+
_CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
|
|
32763
|
+
/** Chave do localStorage para persistir estado da sessão */
|
|
32764
|
+
_CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
|
|
32765
|
+
var CameraRecorder = _CameraRecorder;
|
|
31875
32766
|
|
|
31876
32767
|
// src/new-flow/checkers/DeviceCheckerUI.ts
|
|
31877
32768
|
var DeviceCheckerUI = class {
|
|
@@ -32944,7 +33835,8 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
32944
33835
|
this.context = context;
|
|
32945
33836
|
this.backend = new BackendService({
|
|
32946
33837
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
32947
|
-
token: context.token
|
|
33838
|
+
token: context.token,
|
|
33839
|
+
isRealtime: false
|
|
32948
33840
|
});
|
|
32949
33841
|
}
|
|
32950
33842
|
getDeviceCheckResult() {
|
|
@@ -33105,7 +33997,7 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
33105
33997
|
videoDeviceInterface(stream4) {
|
|
33106
33998
|
this.DeviceCheckerUI && this.DeviceCheckerUI.videoDeviceInterfaceUI(stream4);
|
|
33107
33999
|
this.isUnderResolution();
|
|
33108
|
-
this.faceDetection.enableCam(stream4);
|
|
34000
|
+
this.faceDetection.enableCam(stream4, 1e3);
|
|
33109
34001
|
}
|
|
33110
34002
|
audioDeviceInterface(stream4) {
|
|
33111
34003
|
this.volumeMeter = new VolumeMeter(this.cameraRecorder.cameraStream);
|
|
@@ -36371,6 +37263,7 @@ var NoiseRecorder = class {
|
|
|
36371
37263
|
this.MAX_PRE_ROLL_CHUNKS = 4;
|
|
36372
37264
|
this.lastNoiseTime = 0;
|
|
36373
37265
|
this.SILENCE_THRESHOLD = 3e3;
|
|
37266
|
+
this.filesToUpload = [];
|
|
36374
37267
|
this.optionsProctoring = optionsProctoring;
|
|
36375
37268
|
this.proctoringSession = proctoringSession;
|
|
36376
37269
|
this.paramsConfig = paramsConfig;
|
|
@@ -36462,7 +37355,7 @@ var NoiseRecorder = class {
|
|
|
36462
37355
|
}
|
|
36463
37356
|
}
|
|
36464
37357
|
async stopSoundRecord() {
|
|
36465
|
-
var _a2;
|
|
37358
|
+
var _a2, _b;
|
|
36466
37359
|
if (!this.recordingInProgress && this.recordingChunks.length === 0) return;
|
|
36467
37360
|
this.recordingEndTime = Date.now() - (((_a2 = this.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0);
|
|
36468
37361
|
if (this.optionsProctoring.proctoringType !== "REALTIME") return;
|
|
@@ -36476,7 +37369,33 @@ var NoiseRecorder = class {
|
|
|
36476
37369
|
type: "audio/wav"
|
|
36477
37370
|
}
|
|
36478
37371
|
);
|
|
36479
|
-
this.
|
|
37372
|
+
let filesToSend = [...this.filesToUpload];
|
|
37373
|
+
for (const myFile of filesToSend) {
|
|
37374
|
+
try {
|
|
37375
|
+
await ((_b = this.upload) == null ? void 0 : _b.upload(
|
|
37376
|
+
{
|
|
37377
|
+
file: myFile
|
|
37378
|
+
},
|
|
37379
|
+
this.backendToken
|
|
37380
|
+
));
|
|
37381
|
+
this.filesToUpload.splice(this.filesToUpload.indexOf(myFile), 1);
|
|
37382
|
+
} catch (error) {
|
|
37383
|
+
break;
|
|
37384
|
+
}
|
|
37385
|
+
}
|
|
37386
|
+
try {
|
|
37387
|
+
if (file && this.upload && this.backendToken) {
|
|
37388
|
+
this.upload.upload(
|
|
37389
|
+
{
|
|
37390
|
+
file
|
|
37391
|
+
},
|
|
37392
|
+
this.backendToken
|
|
37393
|
+
);
|
|
37394
|
+
}
|
|
37395
|
+
} catch (error) {
|
|
37396
|
+
console.log("error Noise recorder adicionando na fila", error);
|
|
37397
|
+
this.filesToUpload.push(file);
|
|
37398
|
+
}
|
|
36480
37399
|
this.recordingChunks = [];
|
|
36481
37400
|
this.recordingInProgress = false;
|
|
36482
37401
|
}
|
|
@@ -36490,16 +37409,6 @@ var NoiseRecorder = class {
|
|
|
36490
37409
|
a3.click();
|
|
36491
37410
|
window.URL.revokeObjectURL(url2);
|
|
36492
37411
|
}
|
|
36493
|
-
uploadRecord(file) {
|
|
36494
|
-
if (file && this.upload && this.backendToken) {
|
|
36495
|
-
this.upload.upload(
|
|
36496
|
-
{
|
|
36497
|
-
file
|
|
36498
|
-
},
|
|
36499
|
-
this.backendToken
|
|
36500
|
-
);
|
|
36501
|
-
}
|
|
36502
|
-
}
|
|
36503
37412
|
// CLASSIFIER -< Media Pipe
|
|
36504
37413
|
// Verify if has speech in the classifier array
|
|
36505
37414
|
hasDesiredResult(array) {
|
|
@@ -36606,7 +37515,8 @@ registerProcessor("audio-processor", AudioProcessor);
|
|
|
36606
37515
|
`;
|
|
36607
37516
|
|
|
36608
37517
|
// src/new-flow/recorders/ScreenRecorder.ts
|
|
36609
|
-
var
|
|
37518
|
+
var pkg2 = require_fix_webm_duration();
|
|
37519
|
+
var fixWebmDuration2 = pkg2.default || pkg2;
|
|
36610
37520
|
var ScreenRecorder = class {
|
|
36611
37521
|
constructor(options) {
|
|
36612
37522
|
this.blobs = [];
|
|
@@ -36702,7 +37612,7 @@ var ScreenRecorder = class {
|
|
|
36702
37612
|
const rawBlob = new Blob(this.blobs, {
|
|
36703
37613
|
type: "video/webm"
|
|
36704
37614
|
});
|
|
36705
|
-
const fixedBlob = await (
|
|
37615
|
+
const fixedBlob = await fixWebmDuration2(rawBlob, this.duration);
|
|
36706
37616
|
const file = new File(
|
|
36707
37617
|
[fixedBlob],
|
|
36708
37618
|
`EP_${session.id}_screen_0.webm`,
|
|
@@ -36807,14 +37717,6 @@ function getGeolocation() {
|
|
|
36807
37717
|
});
|
|
36808
37718
|
}
|
|
36809
37719
|
|
|
36810
|
-
// src/utils/verifyVersion.ts
|
|
36811
|
-
function versionVerify() {
|
|
36812
|
-
const agentStr = window.navigator.userAgent.split("SEB/");
|
|
36813
|
-
if (agentStr.length > 1)
|
|
36814
|
-
return agentStr[1];
|
|
36815
|
-
else return "1.0.0.0";
|
|
36816
|
-
}
|
|
36817
|
-
|
|
36818
37720
|
// src/proctoring/Auth.ts
|
|
36819
37721
|
var Auth = class {
|
|
36820
37722
|
constructor(cpf, backend) {
|
|
@@ -36889,6 +37791,49 @@ var Auth = class {
|
|
|
36889
37791
|
}
|
|
36890
37792
|
};
|
|
36891
37793
|
|
|
37794
|
+
// src/new-flow/backend/SafeBrowserBackendService.ts
|
|
37795
|
+
var SafeBrowserBackendService = class {
|
|
37796
|
+
constructor() {
|
|
37797
|
+
this.baseUrl = "http://localhost:7485/Station/api";
|
|
37798
|
+
}
|
|
37799
|
+
async isAlive() {
|
|
37800
|
+
const paramsConfig = await this.makeRequestAxios({
|
|
37801
|
+
path: `/health`,
|
|
37802
|
+
method: "GET",
|
|
37803
|
+
jwt: this.token
|
|
37804
|
+
}).catch((error) => {
|
|
37805
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
37806
|
+
});
|
|
37807
|
+
return paramsConfig.data;
|
|
37808
|
+
}
|
|
37809
|
+
async setProctoringId(proctoringId2) {
|
|
37810
|
+
const paramsConfig = await this.makeRequestAxios({
|
|
37811
|
+
path: `/update-station/` + proctoringId2,
|
|
37812
|
+
method: "POST",
|
|
37813
|
+
jwt: this.token
|
|
37814
|
+
}).catch((error) => {
|
|
37815
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
37816
|
+
});
|
|
37817
|
+
return paramsConfig.data;
|
|
37818
|
+
}
|
|
37819
|
+
async makeRequestAxios(data) {
|
|
37820
|
+
const { path, method, body, jwt } = data;
|
|
37821
|
+
const resp = await axios_default.request({
|
|
37822
|
+
url: this.baseUrl + path,
|
|
37823
|
+
method,
|
|
37824
|
+
headers: {
|
|
37825
|
+
Authorization: `Bearer ${jwt}`,
|
|
37826
|
+
"Access-Control-Allow-Origin": "*"
|
|
37827
|
+
},
|
|
37828
|
+
data: body
|
|
37829
|
+
});
|
|
37830
|
+
if (resp.status >= 400) {
|
|
37831
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
37832
|
+
}
|
|
37833
|
+
return resp;
|
|
37834
|
+
}
|
|
37835
|
+
};
|
|
37836
|
+
|
|
36892
37837
|
// src/proctoring/ExternalCameraChecker.ts
|
|
36893
37838
|
var import_signalr = __toESM(require_cjs());
|
|
36894
37839
|
var import_qrcode = __toESM(require_lib());
|
|
@@ -36917,7 +37862,8 @@ var _ExternalCameraChecker = class _ExternalCameraChecker {
|
|
|
36917
37862
|
this.onRealtimeAlertsCallback = onRealtimeAlertsCallback;
|
|
36918
37863
|
this.backend = new BackendService({
|
|
36919
37864
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
36920
|
-
token: context.token
|
|
37865
|
+
token: context.token,
|
|
37866
|
+
isRealtime: false
|
|
36921
37867
|
});
|
|
36922
37868
|
this.currentStep = -1 /* WAITING */;
|
|
36923
37869
|
}
|
|
@@ -37650,6 +38596,12 @@ var Proctoring = class {
|
|
|
37650
38596
|
this.serviceType = "Upload" /* Upload */;
|
|
37651
38597
|
this.onStopSharingScreenCallback = () => {
|
|
37652
38598
|
};
|
|
38599
|
+
/** Callback notificando que o usuário saiu do browser (minimizou/trocou de aba) */
|
|
38600
|
+
this.onVisibilityLostCallback = () => {
|
|
38601
|
+
};
|
|
38602
|
+
/** Callback notificando que o usuário retornou ao browser */
|
|
38603
|
+
this.onVisibilityRestoredCallback = () => {
|
|
38604
|
+
};
|
|
37653
38605
|
this.onLostFocusCallback = () => {
|
|
37654
38606
|
};
|
|
37655
38607
|
this.onLostFocusAlertRecorderCallback = (response) => {
|
|
@@ -37665,13 +38617,16 @@ var Proctoring = class {
|
|
|
37665
38617
|
await this.internalOnRealtimeAlerts(response);
|
|
37666
38618
|
return;
|
|
37667
38619
|
};
|
|
38620
|
+
this.realtimeAlertsToSend = [];
|
|
37668
38621
|
this.onBufferSizeErrorCallback = (cameraStream) => {
|
|
37669
38622
|
return;
|
|
37670
38623
|
};
|
|
37671
38624
|
var _a2;
|
|
37672
38625
|
this.backend = new BackendService({
|
|
37673
38626
|
type: context.type || "prod",
|
|
37674
|
-
token: context.token
|
|
38627
|
+
token: context.token,
|
|
38628
|
+
isRealtime: false
|
|
38629
|
+
// Default false, atualizado no start() via backend.setRealtime()
|
|
37675
38630
|
});
|
|
37676
38631
|
this.repository = new IndexDbSessionRepository("EasyProctorDb", "exams2");
|
|
37677
38632
|
this.repositoryDevices = new IndexDbSessionRepository(
|
|
@@ -37681,6 +38636,12 @@ var Proctoring = class {
|
|
|
37681
38636
|
((_a2 = this.context.credentials) == null ? void 0 : _a2.cpf) && (this.auth = new Auth(this.context.credentials.cpf, this.backend));
|
|
37682
38637
|
this.appChecker = new ExternalCameraChecker(this.context, (response) => this.onRealtimeAlertsCallback(response));
|
|
37683
38638
|
}
|
|
38639
|
+
setOnVisibilityLostCallback(cb) {
|
|
38640
|
+
this.onVisibilityLostCallback = cb;
|
|
38641
|
+
}
|
|
38642
|
+
setOnVisibilityRestoredCallback(cb) {
|
|
38643
|
+
this.onVisibilityRestoredCallback = cb;
|
|
38644
|
+
}
|
|
37684
38645
|
setOnStopSharingScreenCallback(cb) {
|
|
37685
38646
|
this.onStopSharingScreenCallback = async () => {
|
|
37686
38647
|
var _a2, _b, _c2, _d;
|
|
@@ -37760,6 +38721,8 @@ var Proctoring = class {
|
|
|
37760
38721
|
return 25 /* FocusOff */;
|
|
37761
38722
|
case "focus":
|
|
37762
38723
|
return 25 /* FocusOff */;
|
|
38724
|
+
case "error_upload_package":
|
|
38725
|
+
return 44 /* RealtimeOffline */;
|
|
37763
38726
|
default:
|
|
37764
38727
|
return null;
|
|
37765
38728
|
}
|
|
@@ -37800,19 +38763,53 @@ var Proctoring = class {
|
|
|
37800
38763
|
};
|
|
37801
38764
|
await verifyFace(1);
|
|
37802
38765
|
}
|
|
38766
|
+
async sendPendingRealtimeAlerts() {
|
|
38767
|
+
let alertsToSend = [...this.realtimeAlertsToSend];
|
|
38768
|
+
for (const alert of alertsToSend) {
|
|
38769
|
+
try {
|
|
38770
|
+
if (alert.status === "ALERT") {
|
|
38771
|
+
await this.backend.startRealtimeAlert({
|
|
38772
|
+
proctoringId: this.proctoringId,
|
|
38773
|
+
begin: alert.begin,
|
|
38774
|
+
end: alert.end,
|
|
38775
|
+
alert: this.convertRealtimeCategoryToAlertCategory(alert.category)
|
|
38776
|
+
});
|
|
38777
|
+
} else if (alert.status === "OK") {
|
|
38778
|
+
await this.stopRealtimeAlert(alert);
|
|
38779
|
+
}
|
|
38780
|
+
this.realtimeAlertsToSend.splice(this.realtimeAlertsToSend.indexOf(alert), 1);
|
|
38781
|
+
} catch (error) {
|
|
38782
|
+
console.log("error sendPendingRealtimeAlerts", error);
|
|
38783
|
+
this.realtimeAlertsToSend.push(alert);
|
|
38784
|
+
break;
|
|
38785
|
+
}
|
|
38786
|
+
}
|
|
38787
|
+
}
|
|
37803
38788
|
async internalOnRealtimeAlerts(response) {
|
|
37804
|
-
if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream" || response.type === "lost_focus" || response.type === "focus")) {
|
|
38789
|
+
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")) {
|
|
38790
|
+
await this.sendPendingRealtimeAlerts();
|
|
37805
38791
|
if (response.status === "ALERT") {
|
|
37806
38792
|
if (this.allRecorders.cameraRecorder.stopped) return;
|
|
37807
|
-
|
|
37808
|
-
|
|
37809
|
-
|
|
37810
|
-
|
|
37811
|
-
|
|
37812
|
-
|
|
38793
|
+
try {
|
|
38794
|
+
await this.backend.startRealtimeAlert({
|
|
38795
|
+
proctoringId: this.proctoringId,
|
|
38796
|
+
begin: response.begin,
|
|
38797
|
+
end: response.end,
|
|
38798
|
+
alert: this.convertRealtimeCategoryToAlertCategory(response.category)
|
|
38799
|
+
});
|
|
38800
|
+
} catch (error) {
|
|
38801
|
+
console.log("error startRealtimeAlert " + response.category);
|
|
38802
|
+
console.log("error startRealtimeAlert adicionando na fila", error);
|
|
38803
|
+
this.realtimeAlertsToSend.push(response);
|
|
38804
|
+
}
|
|
37813
38805
|
} else if (response.status === "OK") {
|
|
37814
38806
|
if (this.allRecorders.cameraRecorder.stopped && response.description !== "face_stop") return;
|
|
37815
|
-
|
|
38807
|
+
try {
|
|
38808
|
+
await this.stopRealtimeAlert(response);
|
|
38809
|
+
} catch (error) {
|
|
38810
|
+
console.log("error stopRealtimeAlert adicionando na fila", error);
|
|
38811
|
+
this.realtimeAlertsToSend.push(response);
|
|
38812
|
+
}
|
|
37816
38813
|
}
|
|
37817
38814
|
}
|
|
37818
38815
|
}
|
|
@@ -37820,8 +38817,8 @@ var Proctoring = class {
|
|
|
37820
38817
|
this.setOnLostFocusAlertRecorderCallback();
|
|
37821
38818
|
this.setOnFocusAlertRecorderCallback();
|
|
37822
38819
|
this.onRealtimeAlertsCallback = async (response) => {
|
|
37823
|
-
await this.internalOnRealtimeAlerts(response);
|
|
37824
38820
|
options.data && options.data(response);
|
|
38821
|
+
await this.internalOnRealtimeAlerts(response);
|
|
37825
38822
|
};
|
|
37826
38823
|
}
|
|
37827
38824
|
setOnBufferSizeErrorCallback(cb) {
|
|
@@ -37917,8 +38914,10 @@ var Proctoring = class {
|
|
|
37917
38914
|
await this.repositoryDevices.save({ ...devices, id: "devices" });
|
|
37918
38915
|
this.sessionOptions = { ...getDefaultProctoringOptions, ...options };
|
|
37919
38916
|
this.videoOptions = validatePartialVideoOptions(_videoOptions);
|
|
38917
|
+
if (this.sessionOptions.proctoringType === "REALTIME") {
|
|
38918
|
+
this.backend.setRealtime(true);
|
|
38919
|
+
}
|
|
37920
38920
|
await this.initConfig(options.useGeolocation);
|
|
37921
|
-
await this.verifyBrowser();
|
|
37922
38921
|
this.sessionOptions.captureScreen && await this.verifyMultipleMonitors(this.sessionOptions);
|
|
37923
38922
|
try {
|
|
37924
38923
|
if (options == null ? void 0 : options.useSpyScan) {
|
|
@@ -37927,6 +38926,13 @@ var Proctoring = class {
|
|
|
37927
38926
|
} catch (error) {
|
|
37928
38927
|
throw SPY_SCAN_API_NOT_FOUND;
|
|
37929
38928
|
}
|
|
38929
|
+
try {
|
|
38930
|
+
if (options == null ? void 0 : options.useSafeBrowserAPI) {
|
|
38931
|
+
await this.safeBrowserBackendService.isAlive();
|
|
38932
|
+
}
|
|
38933
|
+
} catch (error) {
|
|
38934
|
+
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
38935
|
+
}
|
|
37930
38936
|
if (this.state != "Stop" /* Stop */) {
|
|
37931
38937
|
throw PROCTORING_ALREADY_STARTED;
|
|
37932
38938
|
}
|
|
@@ -37962,6 +38968,27 @@ var Proctoring = class {
|
|
|
37962
38968
|
this.allRecorders.cameraRecorder.setProctoringId(this.proctoringId);
|
|
37963
38969
|
this.allRecorders.noiseRecorder.setProctoringId(this.proctoringId);
|
|
37964
38970
|
this.proctoringSession.setProctoringId(this.proctoringId);
|
|
38971
|
+
try {
|
|
38972
|
+
if (options == null ? void 0 : options.useSafeBrowserAPI) {
|
|
38973
|
+
await this.safeBrowserBackendService.setProctoringId(this.proctoringId);
|
|
38974
|
+
}
|
|
38975
|
+
} catch (error) {
|
|
38976
|
+
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
38977
|
+
}
|
|
38978
|
+
this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
|
|
38979
|
+
console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
|
|
38980
|
+
this.onVisibilityRestoredCallback();
|
|
38981
|
+
};
|
|
38982
|
+
if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
|
|
38983
|
+
try {
|
|
38984
|
+
await BackgroundUploadService.recoverPendingUploads(
|
|
38985
|
+
this.backend,
|
|
38986
|
+
this.context.token
|
|
38987
|
+
);
|
|
38988
|
+
} catch (e3) {
|
|
38989
|
+
console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
|
|
38990
|
+
}
|
|
38991
|
+
}
|
|
37965
38992
|
try {
|
|
37966
38993
|
console.log("Starting recorders");
|
|
37967
38994
|
await this.recorder.startAll();
|
|
@@ -38041,6 +39068,7 @@ Error: ${error}`
|
|
|
38041
39068
|
this.spyCam && this.spyCam.stopCheckSpyCam();
|
|
38042
39069
|
this.appChecker && await this.appChecker.disconnectWebSocket();
|
|
38043
39070
|
await this.recorder.saveAllOnSession();
|
|
39071
|
+
await this.sendPendingRealtimeAlerts();
|
|
38044
39072
|
await this.repository.save(this.proctoringSession);
|
|
38045
39073
|
let uploader;
|
|
38046
39074
|
let uploaderServices;
|
|
@@ -38183,7 +39211,7 @@ Error: ` + error
|
|
|
38183
39211
|
}
|
|
38184
39212
|
}
|
|
38185
39213
|
async initConfig(useGeolocation) {
|
|
38186
|
-
var _a2;
|
|
39214
|
+
var _a2, _b;
|
|
38187
39215
|
try {
|
|
38188
39216
|
const paramsConfig = await this.backend.getParamsConfig(
|
|
38189
39217
|
this.context
|
|
@@ -38215,6 +39243,9 @@ Error: ` + error
|
|
|
38215
39243
|
onRealtimeAlertsCallback: (response) => this.onRealtimeAlertsCallback(response)
|
|
38216
39244
|
});
|
|
38217
39245
|
}
|
|
39246
|
+
if ((_b = this.sessionOptions) == null ? void 0 : _b.useSafeBrowserAPI) {
|
|
39247
|
+
this.safeBrowserBackendService = new SafeBrowserBackendService();
|
|
39248
|
+
}
|
|
38218
39249
|
} catch (err) {
|
|
38219
39250
|
this.proctoringId && trackers.registerError(this.proctoringId, "Erro no initConfig!");
|
|
38220
39251
|
await this.cancel();
|
|
@@ -38322,7 +39353,8 @@ var _SignTerm = class _SignTerm {
|
|
|
38322
39353
|
constructor(context) {
|
|
38323
39354
|
this.backend = new BackendService({
|
|
38324
39355
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
38325
|
-
token: context.token
|
|
39356
|
+
token: context.token,
|
|
39357
|
+
isRealtime: false
|
|
38326
39358
|
});
|
|
38327
39359
|
}
|
|
38328
39360
|
async signInTerms() {
|
|
@@ -38552,6 +39584,8 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38552
39584
|
const onBufferSizeError = proctoring.setOnBufferSizeErrorCallback.bind(proctoring);
|
|
38553
39585
|
const onStopSharingScreen = proctoring.setOnStopSharingScreenCallback.bind(proctoring);
|
|
38554
39586
|
const onRealtimeAlerts = proctoring.onRealtimeAlerts.bind(proctoring);
|
|
39587
|
+
const onVisibilityLost = proctoring.setOnVisibilityLostCallback.bind(proctoring);
|
|
39588
|
+
const onVisibilityRestored = proctoring.setOnVisibilityRestoredCallback.bind(proctoring);
|
|
38555
39589
|
const signInTerms = signTerm.signInTerms.bind(signTerm);
|
|
38556
39590
|
const checkDevices = checker.checkDevices.bind(checker);
|
|
38557
39591
|
const checkExternalCamera = proctoring.appChecker.checkExternalCamera.bind(proctoring.appChecker);
|
|
@@ -38589,7 +39623,9 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
38589
39623
|
startExternalCameraSession,
|
|
38590
39624
|
takeExternalCameraPicture,
|
|
38591
39625
|
goToExternalCameraPositionStep,
|
|
38592
|
-
startExternalCameraTransmission
|
|
39626
|
+
startExternalCameraTransmission,
|
|
39627
|
+
onVisibilityLost,
|
|
39628
|
+
onVisibilityRestored
|
|
38593
39629
|
};
|
|
38594
39630
|
}
|
|
38595
39631
|
|