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/esm/index.js
CHANGED
|
@@ -5044,14 +5044,17 @@ var require_browser = __commonJS({
|
|
|
5044
5044
|
|
|
5045
5045
|
// src/modules/checkPermissions.ts
|
|
5046
5046
|
async function checkPermissions() {
|
|
5047
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
5048
|
+
return false;
|
|
5049
|
+
}
|
|
5047
5050
|
try {
|
|
5048
5051
|
const constraints = {
|
|
5049
5052
|
audio: true,
|
|
5050
5053
|
video: true
|
|
5051
5054
|
};
|
|
5052
5055
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
5053
|
-
stream.getTracks().forEach((
|
|
5054
|
-
|
|
5056
|
+
stream.getTracks().forEach((track) => {
|
|
5057
|
+
track.stop();
|
|
5055
5058
|
});
|
|
5056
5059
|
return true;
|
|
5057
5060
|
} catch (error) {
|
|
@@ -9145,7 +9148,6 @@ var BaseDetection = class {
|
|
|
9145
9148
|
// objectDetection
|
|
9146
9149
|
runningMode: this.runningMode
|
|
9147
9150
|
});
|
|
9148
|
-
console.log("BaseDetection initializeDetector", this.detectorType);
|
|
9149
9151
|
}
|
|
9150
9152
|
stopDetection() {
|
|
9151
9153
|
this.animationFrameId && clearTimeout(this.animationFrameId);
|
|
@@ -9155,7 +9157,7 @@ var BaseDetection = class {
|
|
|
9155
9157
|
this.createdVideo && this.video && document.body.removeChild(this.video);
|
|
9156
9158
|
this.createdVideo = false;
|
|
9157
9159
|
}
|
|
9158
|
-
enableCam(cameraStream) {
|
|
9160
|
+
enableCam(cameraStream, delay = 1e4) {
|
|
9159
9161
|
var _a2;
|
|
9160
9162
|
if (!this.detector) {
|
|
9161
9163
|
console.log("Wait! Detector not loaded yet.");
|
|
@@ -9185,7 +9187,7 @@ var BaseDetection = class {
|
|
|
9185
9187
|
(_a2 = this.video) == null ? void 0 : _a2.addEventListener("loadeddata", () => {
|
|
9186
9188
|
this.animationFrameId = setTimeout(() => {
|
|
9187
9189
|
that.predictWebcam();
|
|
9188
|
-
},
|
|
9190
|
+
}, delay);
|
|
9189
9191
|
});
|
|
9190
9192
|
const style = document.createElement("style");
|
|
9191
9193
|
style.type = "text/css";
|
|
@@ -9202,7 +9204,6 @@ var BaseDetection = class {
|
|
|
9202
9204
|
}
|
|
9203
9205
|
`;
|
|
9204
9206
|
document.getElementsByTagName("head")[0].appendChild(style);
|
|
9205
|
-
console.log("BaseDetection enableCam OK");
|
|
9206
9207
|
}
|
|
9207
9208
|
async predictWebcam() {
|
|
9208
9209
|
if (this.detecting == false) return;
|
|
@@ -9332,7 +9333,6 @@ var FaceDetection = class extends BaseDetection {
|
|
|
9332
9333
|
);
|
|
9333
9334
|
this.emmitedPositionAlert = false;
|
|
9334
9335
|
this.emmitedFaceAlert = false;
|
|
9335
|
-
console.log("FaceDetection constructor");
|
|
9336
9336
|
this.numFacesSent = -1;
|
|
9337
9337
|
}
|
|
9338
9338
|
stopDetection() {
|
|
@@ -12093,13 +12093,28 @@ var {
|
|
|
12093
12093
|
var DEV_BASE_URL = "https://proctoring-api-dev.easyproctor.tech/api";
|
|
12094
12094
|
var HOMOL_BASE_URL = "https://proctoring-api-hml.easyproctor.tech/api";
|
|
12095
12095
|
var PROD_BASE_URL = "https://proctoring-api.easyproctor.tech/api";
|
|
12096
|
+
var REALTIME_DEV_BASE_URL = "https://easyproctor-realtime-api-dev.easyproctor.tech/api";
|
|
12097
|
+
var REALTIME_HOMOL_BASE_URL = "https://easyproctor-realtime-api-hml.easyproctor.tech/api";
|
|
12098
|
+
var REALTIME_PROD_BASE_URL = "https://easyproctor-realtime-api.easyproctor.tech/api";
|
|
12096
12099
|
var BackendService = class {
|
|
12097
12100
|
constructor(options) {
|
|
12098
12101
|
this.options = options;
|
|
12099
|
-
this.baseUrl = this.selectBaseUrl(options.type);
|
|
12102
|
+
this.baseUrl = this.selectBaseUrl(options.type, options.isRealtime);
|
|
12100
12103
|
this.token = options.token;
|
|
12101
12104
|
}
|
|
12102
|
-
|
|
12105
|
+
getBaseUrl() {
|
|
12106
|
+
return this.baseUrl;
|
|
12107
|
+
}
|
|
12108
|
+
selectBaseUrl(type, isRealtime) {
|
|
12109
|
+
if (isRealtime) {
|
|
12110
|
+
if (type === "dev") {
|
|
12111
|
+
return REALTIME_DEV_BASE_URL;
|
|
12112
|
+
} else if (type === "homol") {
|
|
12113
|
+
return REALTIME_HOMOL_BASE_URL;
|
|
12114
|
+
} else {
|
|
12115
|
+
return REALTIME_PROD_BASE_URL;
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12103
12118
|
if (type === "dev") {
|
|
12104
12119
|
return DEV_BASE_URL;
|
|
12105
12120
|
} else if (type === "homol") {
|
|
@@ -12110,6 +12125,9 @@ var BackendService = class {
|
|
|
12110
12125
|
return PROD_BASE_URL;
|
|
12111
12126
|
}
|
|
12112
12127
|
}
|
|
12128
|
+
setRealtime(isRealtime) {
|
|
12129
|
+
this.baseUrl = this.selectBaseUrl(this.options.type, isRealtime);
|
|
12130
|
+
}
|
|
12113
12131
|
getSocketUrl() {
|
|
12114
12132
|
return this.baseUrl.replace("/api", "/hub/sockethub");
|
|
12115
12133
|
}
|
|
@@ -12220,6 +12238,18 @@ var BackendService = class {
|
|
|
12220
12238
|
});
|
|
12221
12239
|
return url.data;
|
|
12222
12240
|
}
|
|
12241
|
+
async initiateUpload(token, objectName, contentType) {
|
|
12242
|
+
const url = await this.makeRequestAxios({
|
|
12243
|
+
path: `/upload/initiate-upload`,
|
|
12244
|
+
method: "POST",
|
|
12245
|
+
jwt: token,
|
|
12246
|
+
body: {
|
|
12247
|
+
objectName,
|
|
12248
|
+
contentType
|
|
12249
|
+
}
|
|
12250
|
+
});
|
|
12251
|
+
return url.data;
|
|
12252
|
+
}
|
|
12223
12253
|
async saveAlerts(proctoringOptions, proctoringSession) {
|
|
12224
12254
|
await this.makeRequest({
|
|
12225
12255
|
path: "/proctoring/save-alerts",
|
|
@@ -12323,6 +12353,22 @@ var BackendService = class {
|
|
|
12323
12353
|
});
|
|
12324
12354
|
return result.data;
|
|
12325
12355
|
}
|
|
12356
|
+
async checkUpload(token, objectName, contentType) {
|
|
12357
|
+
try {
|
|
12358
|
+
const result = await this.makeRequestAxios({
|
|
12359
|
+
path: `/Upload/check`,
|
|
12360
|
+
method: "POST",
|
|
12361
|
+
jwt: token,
|
|
12362
|
+
body: {
|
|
12363
|
+
objectName,
|
|
12364
|
+
contentType
|
|
12365
|
+
}
|
|
12366
|
+
});
|
|
12367
|
+
return result.data === true;
|
|
12368
|
+
} catch (e3) {
|
|
12369
|
+
return false;
|
|
12370
|
+
}
|
|
12371
|
+
}
|
|
12326
12372
|
async getServerHour(token) {
|
|
12327
12373
|
return await this.makeRequest({
|
|
12328
12374
|
path: `/Proctoring/server-hour`,
|
|
@@ -12523,7 +12569,8 @@ var SpyCam = class {
|
|
|
12523
12569
|
this.context = context;
|
|
12524
12570
|
this.backend = new BackendService({
|
|
12525
12571
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
12526
|
-
token: context.token
|
|
12572
|
+
token: context.token,
|
|
12573
|
+
isRealtime: false
|
|
12527
12574
|
});
|
|
12528
12575
|
this.currentIsPlugged = true;
|
|
12529
12576
|
}
|
|
@@ -12675,6 +12722,14 @@ var getDefaultProctoringVideoOptions = {
|
|
|
12675
12722
|
minHeight: 480
|
|
12676
12723
|
};
|
|
12677
12724
|
|
|
12725
|
+
// src/utils/verifyVersion.ts
|
|
12726
|
+
function versionVerify() {
|
|
12727
|
+
const agentStr = window.navigator.userAgent.split("SEB/");
|
|
12728
|
+
if (agentStr.length > 1)
|
|
12729
|
+
return agentStr[1];
|
|
12730
|
+
else return "1.0.0.0";
|
|
12731
|
+
}
|
|
12732
|
+
|
|
12678
12733
|
// src/utils/browserInformations.ts
|
|
12679
12734
|
function fnBrowserDetect() {
|
|
12680
12735
|
const userAgent = navigator.userAgent;
|
|
@@ -12703,13 +12758,16 @@ function isMobileDevice() {
|
|
|
12703
12758
|
}
|
|
12704
12759
|
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
12705
12760
|
}
|
|
12761
|
+
function isSafeBrowser() {
|
|
12762
|
+
return versionVerify() !== "1.0.0.0";
|
|
12763
|
+
}
|
|
12706
12764
|
|
|
12707
12765
|
// src/plugins/recorder.ts
|
|
12708
12766
|
var proctoringId;
|
|
12709
12767
|
function setRecorderProctoringId(id) {
|
|
12710
12768
|
proctoringId = id;
|
|
12711
12769
|
}
|
|
12712
|
-
function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false) {
|
|
12770
|
+
function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCallback, audio = false, recorderOpts) {
|
|
12713
12771
|
let resolvePromise;
|
|
12714
12772
|
let onBufferSizeInterval;
|
|
12715
12773
|
let lastEvent;
|
|
@@ -12717,6 +12775,7 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
|
|
|
12717
12775
|
bufferSize = 0;
|
|
12718
12776
|
let startTime;
|
|
12719
12777
|
let duration = 0;
|
|
12778
|
+
let chunkIndex = 0;
|
|
12720
12779
|
let recorderOptions = {
|
|
12721
12780
|
// eslint-disable-next-line no-useless-escape
|
|
12722
12781
|
mimeType: "video/webm",
|
|
@@ -12751,6 +12810,10 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
|
|
|
12751
12810
|
mediaRecorder2.ondataavailable = (e3) => {
|
|
12752
12811
|
bufferSize = bufferSize + e3.data.size;
|
|
12753
12812
|
if (e3.data.size > 0) {
|
|
12813
|
+
if (recorderOpts == null ? void 0 : recorderOpts.onChunkAvailable) {
|
|
12814
|
+
recorderOpts.onChunkAvailable(e3.data, chunkIndex);
|
|
12815
|
+
chunkIndex++;
|
|
12816
|
+
}
|
|
12754
12817
|
buffer.push(e3.data);
|
|
12755
12818
|
}
|
|
12756
12819
|
};
|
|
@@ -12782,7 +12845,13 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
|
|
|
12782
12845
|
};
|
|
12783
12846
|
try {
|
|
12784
12847
|
console.log("State antes do start:", recorder2.state);
|
|
12785
|
-
|
|
12848
|
+
chunkIndex = 0;
|
|
12849
|
+
if ((recorderOpts == null ? void 0 : recorderOpts.timeslice) && (recorderOpts == null ? void 0 : recorderOpts.timeslice) > 0) {
|
|
12850
|
+
recorder2.start(recorderOpts.timeslice);
|
|
12851
|
+
} else {
|
|
12852
|
+
recorder2.start(1e4);
|
|
12853
|
+
}
|
|
12854
|
+
bufferSize = 0;
|
|
12786
12855
|
startTime = new Date(Date.now());
|
|
12787
12856
|
} catch (e3) {
|
|
12788
12857
|
console.error("Recorder erro ao chamar start event:", e3);
|
|
@@ -12822,8 +12891,11 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
|
|
|
12822
12891
|
resolvePromise = resolve;
|
|
12823
12892
|
mediaRecorder.onstop = () => {
|
|
12824
12893
|
console.log("recorder onstop");
|
|
12825
|
-
|
|
12826
|
-
console.log("duration no onstop",
|
|
12894
|
+
duration = Date.now() - startTime.getTime() || 0;
|
|
12895
|
+
console.log("duration no onstop", duration);
|
|
12896
|
+
stream.getTracks().forEach((el) => {
|
|
12897
|
+
el.stop();
|
|
12898
|
+
});
|
|
12827
12899
|
resolvePromise && resolvePromise();
|
|
12828
12900
|
};
|
|
12829
12901
|
mediaRecorder.stop();
|
|
@@ -12833,9 +12905,6 @@ function recorder(stream, buffer, onBufferSizeError = false, onBufferSizeErrorCa
|
|
|
12833
12905
|
console.log("stopRecording Recorder n\xE3o est\xE1 em estado recording");
|
|
12834
12906
|
resolve();
|
|
12835
12907
|
}
|
|
12836
|
-
stream.getTracks().forEach((el) => {
|
|
12837
|
-
el.stop();
|
|
12838
|
-
});
|
|
12839
12908
|
});
|
|
12840
12909
|
}
|
|
12841
12910
|
function pauseRecording() {
|
|
@@ -12929,36 +12998,34 @@ var UploadService = class {
|
|
|
12929
12998
|
this.proctoringId = proctoringId2;
|
|
12930
12999
|
}
|
|
12931
13000
|
async uploadPackage(data, token) {
|
|
12932
|
-
const { file
|
|
13001
|
+
const { file } = data;
|
|
12933
13002
|
try {
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
13003
|
+
console.log("Upload service: uploadPackage");
|
|
13004
|
+
var uploadUrl = "";
|
|
13005
|
+
await this.backend.getSignedUrl(token, file, this.proctoringId).then((result) => uploadUrl = result).catch((error) => {
|
|
13006
|
+
throw error;
|
|
13007
|
+
});
|
|
13008
|
+
console.log("Upload service: uploadUrl", uploadUrl);
|
|
13009
|
+
await axios_default.request({
|
|
12940
13010
|
url: uploadUrl,
|
|
12941
13011
|
method: "PUT",
|
|
12942
13012
|
headers: {
|
|
12943
13013
|
"Content-Type": file.type,
|
|
12944
13014
|
"x-ms-blob-type": "BlockBlob"
|
|
12945
13015
|
},
|
|
12946
|
-
data: file
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
url: uploadUrl,
|
|
12954
|
-
uploaded
|
|
12955
|
-
};
|
|
13016
|
+
data: file
|
|
13017
|
+
}).then(() => {
|
|
13018
|
+
return true;
|
|
13019
|
+
}).catch((error) => {
|
|
13020
|
+
throw error;
|
|
13021
|
+
});
|
|
13022
|
+
return true;
|
|
12956
13023
|
} catch (err) {
|
|
12957
|
-
trackers.registerError(this.proctoringId, `Failed to upload
|
|
13024
|
+
trackers.registerError(this.proctoringId, `Failed to upload package ${err}
|
|
12958
13025
|
File name: ${file.name}
|
|
12959
13026
|
File type: ${file.type}
|
|
12960
13027
|
File size: ${file.size}`);
|
|
12961
|
-
throw
|
|
13028
|
+
throw err;
|
|
12962
13029
|
}
|
|
12963
13030
|
}
|
|
12964
13031
|
async uploadImages(data, token, packSize) {
|
|
@@ -13101,6 +13168,7 @@ var BROWSER_NOT_SUPPORTED = "browser_not_supported";
|
|
|
13101
13168
|
var TOKEN_MISSING = "token_missing";
|
|
13102
13169
|
var CREDENTIALS_MISSING = "credentials_missing";
|
|
13103
13170
|
var SPY_SCAN_API_NOT_FOUND = "spy_scan_api_not_found";
|
|
13171
|
+
var SAFE_BROWSER_API_NOT_FOUND = "safe_browser_api_not_found";
|
|
13104
13172
|
var EXTERNAL_CAMERA_NOT_STARTED = "external_camera_not_started";
|
|
13105
13173
|
|
|
13106
13174
|
// src/modules/objectDetection.ts
|
|
@@ -13244,10 +13312,617 @@ var VolumeMeter = class {
|
|
|
13244
13312
|
}
|
|
13245
13313
|
};
|
|
13246
13314
|
|
|
13315
|
+
// src/new-flow/chunk/ChunkStorageService.ts
|
|
13316
|
+
var _ChunkStorageService = class _ChunkStorageService {
|
|
13317
|
+
constructor() {
|
|
13318
|
+
this.db = null;
|
|
13319
|
+
}
|
|
13320
|
+
/**
|
|
13321
|
+
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
13322
|
+
*/
|
|
13323
|
+
async connect() {
|
|
13324
|
+
if (this.db) return this.db;
|
|
13325
|
+
return new Promise((resolve, reject) => {
|
|
13326
|
+
const request = window.indexedDB.open(
|
|
13327
|
+
_ChunkStorageService.DB_NAME,
|
|
13328
|
+
_ChunkStorageService.DB_VERSION
|
|
13329
|
+
);
|
|
13330
|
+
request.onerror = () => {
|
|
13331
|
+
reject(new Error("N\xE3o foi poss\xEDvel conectar ao IndexedDB para chunks."));
|
|
13332
|
+
};
|
|
13333
|
+
request.onupgradeneeded = () => {
|
|
13334
|
+
const db = request.result;
|
|
13335
|
+
if (db.objectStoreNames.contains(_ChunkStorageService.STORE_NAME)) {
|
|
13336
|
+
db.deleteObjectStore(_ChunkStorageService.STORE_NAME);
|
|
13337
|
+
}
|
|
13338
|
+
const store = db.createObjectStore(_ChunkStorageService.STORE_NAME, {
|
|
13339
|
+
keyPath: "id",
|
|
13340
|
+
autoIncrement: true
|
|
13341
|
+
});
|
|
13342
|
+
store.createIndex("proctoringId", "proctoringId", { unique: false });
|
|
13343
|
+
store.createIndex("uploaded", "uploaded", { unique: false });
|
|
13344
|
+
store.createIndex("proctoringId_uploaded", ["proctoringId", "uploaded"], {
|
|
13345
|
+
unique: false
|
|
13346
|
+
});
|
|
13347
|
+
};
|
|
13348
|
+
request.onsuccess = () => {
|
|
13349
|
+
this.db = request.result;
|
|
13350
|
+
resolve(this.db);
|
|
13351
|
+
};
|
|
13352
|
+
});
|
|
13353
|
+
}
|
|
13354
|
+
/**
|
|
13355
|
+
* Salva um chunk de vídeo no IndexedDB.
|
|
13356
|
+
*/
|
|
13357
|
+
async saveChunk(chunk) {
|
|
13358
|
+
const db = await this.connect();
|
|
13359
|
+
return new Promise((resolve, reject) => {
|
|
13360
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
13361
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13362
|
+
const request = store.add(chunk);
|
|
13363
|
+
request.onsuccess = () => {
|
|
13364
|
+
resolve(request.result);
|
|
13365
|
+
};
|
|
13366
|
+
request.onerror = () => {
|
|
13367
|
+
var _a2;
|
|
13368
|
+
reject(new Error(`Erro ao salvar chunk no IndexedDB: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13369
|
+
};
|
|
13370
|
+
});
|
|
13371
|
+
}
|
|
13372
|
+
/**
|
|
13373
|
+
* Retorna todos os chunks pendentes (não enviados) de um proctoringId específico.
|
|
13374
|
+
*/
|
|
13375
|
+
async getPendingChunks(proctoringId2) {
|
|
13376
|
+
const db = await this.connect();
|
|
13377
|
+
return new Promise((resolve, reject) => {
|
|
13378
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
13379
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13380
|
+
const index = store.index("proctoringId_uploaded");
|
|
13381
|
+
const range = IDBKeyRange.only([proctoringId2, 0]);
|
|
13382
|
+
const request = index.getAll(range);
|
|
13383
|
+
request.onsuccess = () => {
|
|
13384
|
+
const chunks = request.result.sort(
|
|
13385
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
13386
|
+
);
|
|
13387
|
+
resolve(chunks);
|
|
13388
|
+
};
|
|
13389
|
+
request.onerror = () => {
|
|
13390
|
+
var _a2;
|
|
13391
|
+
reject(new Error(`Erro ao buscar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13392
|
+
};
|
|
13393
|
+
});
|
|
13394
|
+
}
|
|
13395
|
+
/**
|
|
13396
|
+
* Retorna todos os chunks (enviados ou não) de um proctoringId específico.
|
|
13397
|
+
*/
|
|
13398
|
+
async getAllChunks(proctoringId2) {
|
|
13399
|
+
const db = await this.connect();
|
|
13400
|
+
return new Promise((resolve, reject) => {
|
|
13401
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
13402
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13403
|
+
const index = store.index("proctoringId");
|
|
13404
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
13405
|
+
const request = index.getAll(range);
|
|
13406
|
+
request.onsuccess = () => {
|
|
13407
|
+
const chunks = request.result.sort(
|
|
13408
|
+
(a3, b3) => a3.chunkIndex - b3.chunkIndex
|
|
13409
|
+
);
|
|
13410
|
+
resolve(chunks);
|
|
13411
|
+
};
|
|
13412
|
+
request.onerror = () => {
|
|
13413
|
+
var _a2;
|
|
13414
|
+
reject(new Error(`Erro ao buscar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13415
|
+
};
|
|
13416
|
+
});
|
|
13417
|
+
}
|
|
13418
|
+
/**
|
|
13419
|
+
* Marca um chunk como enviado (uploaded = 1).
|
|
13420
|
+
*/
|
|
13421
|
+
async markAsUploaded(chunkId) {
|
|
13422
|
+
const db = await this.connect();
|
|
13423
|
+
return new Promise((resolve, reject) => {
|
|
13424
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
13425
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13426
|
+
const getRequest = store.get(chunkId);
|
|
13427
|
+
getRequest.onsuccess = () => {
|
|
13428
|
+
const chunk = getRequest.result;
|
|
13429
|
+
if (!chunk) {
|
|
13430
|
+
resolve();
|
|
13431
|
+
return;
|
|
13432
|
+
}
|
|
13433
|
+
chunk.uploaded = 1;
|
|
13434
|
+
const putRequest = store.put(chunk);
|
|
13435
|
+
putRequest.onsuccess = () => resolve();
|
|
13436
|
+
putRequest.onerror = () => {
|
|
13437
|
+
var _a2;
|
|
13438
|
+
return reject(new Error(`Erro ao marcar chunk como enviado: ${(_a2 = putRequest.error) == null ? void 0 : _a2.message}`));
|
|
13439
|
+
};
|
|
13440
|
+
};
|
|
13441
|
+
getRequest.onerror = () => {
|
|
13442
|
+
var _a2;
|
|
13443
|
+
return reject(new Error(`Erro ao buscar chunk para marcar: ${(_a2 = getRequest.error) == null ? void 0 : _a2.message}`));
|
|
13444
|
+
};
|
|
13445
|
+
});
|
|
13446
|
+
}
|
|
13447
|
+
/**
|
|
13448
|
+
* Remove todos os chunks já enviados de um proctoringId para liberar espaço.
|
|
13449
|
+
*/
|
|
13450
|
+
async clearUploadedChunks(proctoringId2) {
|
|
13451
|
+
const db = await this.connect();
|
|
13452
|
+
return new Promise((resolve, reject) => {
|
|
13453
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
13454
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13455
|
+
const index = store.index("proctoringId_uploaded");
|
|
13456
|
+
const range = IDBKeyRange.only([proctoringId2, 1]);
|
|
13457
|
+
const request = index.openCursor(range);
|
|
13458
|
+
request.onsuccess = () => {
|
|
13459
|
+
const cursor = request.result;
|
|
13460
|
+
if (cursor) {
|
|
13461
|
+
cursor.delete();
|
|
13462
|
+
cursor.continue();
|
|
13463
|
+
} else {
|
|
13464
|
+
resolve();
|
|
13465
|
+
}
|
|
13466
|
+
};
|
|
13467
|
+
request.onerror = () => {
|
|
13468
|
+
var _a2;
|
|
13469
|
+
return reject(new Error(`Erro ao limpar chunks enviados: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13470
|
+
};
|
|
13471
|
+
});
|
|
13472
|
+
}
|
|
13473
|
+
/**
|
|
13474
|
+
* Remove TODOS os chunks de um proctoringId (limpeza completa pós-finalização).
|
|
13475
|
+
*/
|
|
13476
|
+
async clearAllChunks(proctoringId2) {
|
|
13477
|
+
const db = await this.connect();
|
|
13478
|
+
return new Promise((resolve, reject) => {
|
|
13479
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
13480
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13481
|
+
const index = store.index("proctoringId");
|
|
13482
|
+
const range = IDBKeyRange.only(proctoringId2);
|
|
13483
|
+
const request = index.openCursor(range);
|
|
13484
|
+
request.onsuccess = () => {
|
|
13485
|
+
const cursor = request.result;
|
|
13486
|
+
if (cursor) {
|
|
13487
|
+
cursor.delete();
|
|
13488
|
+
cursor.continue();
|
|
13489
|
+
} else {
|
|
13490
|
+
resolve();
|
|
13491
|
+
}
|
|
13492
|
+
};
|
|
13493
|
+
request.onerror = () => {
|
|
13494
|
+
var _a2;
|
|
13495
|
+
return reject(new Error(`Erro ao limpar todos os chunks: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13496
|
+
};
|
|
13497
|
+
});
|
|
13498
|
+
}
|
|
13499
|
+
/**
|
|
13500
|
+
* Verifica se existem chunks pendentes para qualquer proctoringId.
|
|
13501
|
+
* Útil na recuperação pós-crash.
|
|
13502
|
+
*/
|
|
13503
|
+
async hasAnyPendingChunks() {
|
|
13504
|
+
const db = await this.connect();
|
|
13505
|
+
return new Promise((resolve, reject) => {
|
|
13506
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
13507
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13508
|
+
const index = store.index("uploaded");
|
|
13509
|
+
const range = IDBKeyRange.only(0);
|
|
13510
|
+
const request = index.count(range);
|
|
13511
|
+
request.onsuccess = () => {
|
|
13512
|
+
resolve(request.result > 0);
|
|
13513
|
+
};
|
|
13514
|
+
request.onerror = () => {
|
|
13515
|
+
var _a2;
|
|
13516
|
+
return reject(new Error(`Erro ao verificar chunks pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13517
|
+
};
|
|
13518
|
+
});
|
|
13519
|
+
}
|
|
13520
|
+
/**
|
|
13521
|
+
* Retorna todos os proctoringIds que possuem chunks pendentes.
|
|
13522
|
+
* Útil na recuperação pós-crash para saber quais sessões precisam ser finalizadas.
|
|
13523
|
+
*/
|
|
13524
|
+
async getPendingProctoringIds() {
|
|
13525
|
+
const db = await this.connect();
|
|
13526
|
+
return new Promise((resolve, reject) => {
|
|
13527
|
+
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readonly");
|
|
13528
|
+
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13529
|
+
const index = store.index("uploaded");
|
|
13530
|
+
const range = IDBKeyRange.only(0);
|
|
13531
|
+
const request = index.getAll(range);
|
|
13532
|
+
request.onsuccess = () => {
|
|
13533
|
+
const chunks = request.result;
|
|
13534
|
+
const ids = [...new Set(chunks.map((c3) => c3.proctoringId))];
|
|
13535
|
+
resolve(ids);
|
|
13536
|
+
};
|
|
13537
|
+
request.onerror = () => {
|
|
13538
|
+
var _a2;
|
|
13539
|
+
return reject(new Error(`Erro ao buscar proctoringIds pendentes: ${(_a2 = request.error) == null ? void 0 : _a2.message}`));
|
|
13540
|
+
};
|
|
13541
|
+
});
|
|
13542
|
+
}
|
|
13543
|
+
/**
|
|
13544
|
+
* Fecha a conexão com o banco.
|
|
13545
|
+
*/
|
|
13546
|
+
close() {
|
|
13547
|
+
if (this.db) {
|
|
13548
|
+
this.db.close();
|
|
13549
|
+
this.db = null;
|
|
13550
|
+
}
|
|
13551
|
+
}
|
|
13552
|
+
};
|
|
13553
|
+
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
13554
|
+
/** Incrementado para v2 para recriar índices com tipos numéricos em vez de boolean */
|
|
13555
|
+
_ChunkStorageService.DB_VERSION = 2;
|
|
13556
|
+
_ChunkStorageService.STORE_NAME = "chunks";
|
|
13557
|
+
var ChunkStorageService = _ChunkStorageService;
|
|
13558
|
+
|
|
13559
|
+
// src/new-flow/chunk/BackgroundUploadService.ts
|
|
13560
|
+
var DEFAULT_CONFIG = {
|
|
13561
|
+
pollInterval: 5e3,
|
|
13562
|
+
maxRetries: 5,
|
|
13563
|
+
baseRetryDelay: 2e3,
|
|
13564
|
+
cleanAfterUpload: true
|
|
13565
|
+
};
|
|
13566
|
+
var BackgroundUploadService = class _BackgroundUploadService {
|
|
13567
|
+
constructor(proctoringId2, token, backend, chunkStorage, config) {
|
|
13568
|
+
this.pollTimer = null;
|
|
13569
|
+
this.isProcessing = false;
|
|
13570
|
+
this.isRunning = false;
|
|
13571
|
+
/** Mapa de chunkId -> número de tentativas já feitas */
|
|
13572
|
+
this.retryCount = /* @__PURE__ */ new Map();
|
|
13573
|
+
/** GCS Resumable Upload State */
|
|
13574
|
+
this.sessionUrl = null;
|
|
13575
|
+
this.currentOffset = 0;
|
|
13576
|
+
this.totalBytesPurged = 0;
|
|
13577
|
+
this.STORAGE_KEY_PREFIX = "ep_upload_session_";
|
|
13578
|
+
this.GCS_CHUNK_SIZE = 256 * 1024;
|
|
13579
|
+
this.proctoringId = proctoringId2.trim();
|
|
13580
|
+
this.token = token;
|
|
13581
|
+
this.backend = backend;
|
|
13582
|
+
this.chunkStorage = chunkStorage;
|
|
13583
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
13584
|
+
this.loadSessionState();
|
|
13585
|
+
}
|
|
13586
|
+
loadSessionState() {
|
|
13587
|
+
try {
|
|
13588
|
+
const stored = localStorage.getItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
13589
|
+
if (stored) {
|
|
13590
|
+
const { sessionUrl, currentOffset, totalBytesPurged } = JSON.parse(stored);
|
|
13591
|
+
this.sessionUrl = sessionUrl;
|
|
13592
|
+
this.currentOffset = currentOffset;
|
|
13593
|
+
this.totalBytesPurged = totalBytesPurged || 0;
|
|
13594
|
+
}
|
|
13595
|
+
} catch (e3) {
|
|
13596
|
+
console.warn("[BackgroundUpload] Erro ao carregar estado da sess\xE3o:", e3);
|
|
13597
|
+
}
|
|
13598
|
+
}
|
|
13599
|
+
saveSessionState() {
|
|
13600
|
+
try {
|
|
13601
|
+
localStorage.setItem(
|
|
13602
|
+
`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`,
|
|
13603
|
+
JSON.stringify({
|
|
13604
|
+
sessionUrl: this.sessionUrl,
|
|
13605
|
+
currentOffset: this.currentOffset,
|
|
13606
|
+
totalBytesPurged: this.totalBytesPurged
|
|
13607
|
+
})
|
|
13608
|
+
);
|
|
13609
|
+
} catch (e3) {
|
|
13610
|
+
console.warn("[BackgroundUpload] Erro ao salvar estado da sess\xE3o:", e3);
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
13613
|
+
clearSessionState() {
|
|
13614
|
+
try {
|
|
13615
|
+
localStorage.removeItem(`${this.STORAGE_KEY_PREFIX}${this.proctoringId}`);
|
|
13616
|
+
this.sessionUrl = null;
|
|
13617
|
+
this.currentOffset = 0;
|
|
13618
|
+
this.totalBytesPurged = 0;
|
|
13619
|
+
} catch (e3) {
|
|
13620
|
+
console.warn("[BackgroundUpload] Erro ao limpar estado da sess\xE3o:", e3);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
/**
|
|
13624
|
+
* Inicia o serviço de upload em background. Faz polling periódico no IndexedDB
|
|
13625
|
+
* para enviar chunks pendentes.
|
|
13626
|
+
*/
|
|
13627
|
+
start() {
|
|
13628
|
+
if (this.isRunning) return;
|
|
13629
|
+
this.isRunning = true;
|
|
13630
|
+
console.log(`[BackgroundUpload] Iniciando servi\xE7o para proctoringId: ${this.proctoringId}`);
|
|
13631
|
+
this.processQueue();
|
|
13632
|
+
this.pollTimer = setInterval(() => {
|
|
13633
|
+
this.processQueue(false);
|
|
13634
|
+
}, this.config.pollInterval);
|
|
13635
|
+
}
|
|
13636
|
+
/**
|
|
13637
|
+
* Para o serviço de upload em background.
|
|
13638
|
+
*/
|
|
13639
|
+
stop() {
|
|
13640
|
+
this.isRunning = false;
|
|
13641
|
+
if (this.pollTimer) {
|
|
13642
|
+
clearInterval(this.pollTimer);
|
|
13643
|
+
this.pollTimer = null;
|
|
13644
|
+
}
|
|
13645
|
+
console.log(`[BackgroundUpload] Servi\xE7o parado para proctoringId: ${this.proctoringId}`);
|
|
13646
|
+
}
|
|
13647
|
+
/**
|
|
13648
|
+
* Força o processamento de todos os chunks pendentes e encerra a sessão GCS.
|
|
13649
|
+
* Útil quando a gravação é finalizada.
|
|
13650
|
+
*/
|
|
13651
|
+
async flush() {
|
|
13652
|
+
console.log(`[BackgroundUpload] Flush: enviando todos os chunks pendentes e finalizando...`);
|
|
13653
|
+
let waitAttempts = 0;
|
|
13654
|
+
while (this.isProcessing && waitAttempts < 10) {
|
|
13655
|
+
await this.sleep(1e3);
|
|
13656
|
+
waitAttempts++;
|
|
13657
|
+
}
|
|
13658
|
+
let flushRetries = 0;
|
|
13659
|
+
const maxFlushRetries = 3;
|
|
13660
|
+
while (flushRetries < maxFlushRetries) {
|
|
13661
|
+
try {
|
|
13662
|
+
await this.processQueue(true);
|
|
13663
|
+
console.log(`[BackgroundUpload] Flush completado com sucesso.`);
|
|
13664
|
+
return;
|
|
13665
|
+
} catch (error) {
|
|
13666
|
+
flushRetries++;
|
|
13667
|
+
console.error(`[BackgroundUpload] Erro no flush (tentativa ${flushRetries}/${maxFlushRetries}):`, error);
|
|
13668
|
+
if (flushRetries < maxFlushRetries) {
|
|
13669
|
+
await this.sleep(2e3);
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
}
|
|
13673
|
+
throw new Error(`[BackgroundUpload] Falha ao finalizar upload ap\xF3s ${maxFlushRetries} tentativas.`);
|
|
13674
|
+
}
|
|
13675
|
+
/**
|
|
13676
|
+
* Sincroniza o offset local com o estado real no Google Cloud Storage.
|
|
13677
|
+
*/
|
|
13678
|
+
async syncOffset() {
|
|
13679
|
+
if (!this.sessionUrl) return 0;
|
|
13680
|
+
try {
|
|
13681
|
+
console.log(`[BackgroundUpload] Sincronizando offset com GCS...`);
|
|
13682
|
+
const response = await fetch(this.sessionUrl, {
|
|
13683
|
+
method: "PUT",
|
|
13684
|
+
headers: {
|
|
13685
|
+
"Content-Range": "bytes */*"
|
|
13686
|
+
}
|
|
13687
|
+
});
|
|
13688
|
+
console.log(`[BackgroundUpload] Status da sincroniza\xE7\xE3o (syncOffset): ${response.status}`);
|
|
13689
|
+
if (response.status === 308) {
|
|
13690
|
+
const range = response.headers.get("Range");
|
|
13691
|
+
if (range) {
|
|
13692
|
+
const lastByte = parseInt(range.split("-")[1], 10);
|
|
13693
|
+
this.currentOffset = lastByte + 1;
|
|
13694
|
+
this.saveSessionState();
|
|
13695
|
+
console.log(`[BackgroundUpload] Offset sincronizado: ${this.currentOffset}`);
|
|
13696
|
+
} else {
|
|
13697
|
+
this.currentOffset = 0;
|
|
13698
|
+
}
|
|
13699
|
+
} else if (response.ok || response.status === 201) {
|
|
13700
|
+
console.log("[BackgroundUpload] Sincroniza\xE7\xE3o indicou upload JA FINALIZADO.");
|
|
13701
|
+
this.currentOffset = -1;
|
|
13702
|
+
} else {
|
|
13703
|
+
console.warn(`[BackgroundUpload] Status inesperado na sincroniza\xE7\xE3o: ${response.status}`);
|
|
13704
|
+
}
|
|
13705
|
+
} catch (error) {
|
|
13706
|
+
console.warn("[BackgroundUpload] Erro ao sincronizar offset:", error);
|
|
13707
|
+
}
|
|
13708
|
+
return this.currentOffset;
|
|
13709
|
+
}
|
|
13710
|
+
/**
|
|
13711
|
+
* Verifica e envia chunks pendentes para o backend.
|
|
13712
|
+
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
13713
|
+
*/
|
|
13714
|
+
async processQueue(isFinal = false) {
|
|
13715
|
+
var _a2, _b;
|
|
13716
|
+
if (this.isProcessing) return;
|
|
13717
|
+
this.isProcessing = true;
|
|
13718
|
+
try {
|
|
13719
|
+
if (this.sessionUrl) {
|
|
13720
|
+
await this.syncOffset();
|
|
13721
|
+
if (this.currentOffset === -1) {
|
|
13722
|
+
console.log("[BackgroundUpload] Sess\xE3o j\xE1 finalizada no servidor.");
|
|
13723
|
+
this.clearSessionState();
|
|
13724
|
+
this.isProcessing = false;
|
|
13725
|
+
return;
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
13729
|
+
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
13730
|
+
if (pendingChunks.length === 0 && !isFinal) {
|
|
13731
|
+
this.isProcessing = false;
|
|
13732
|
+
return;
|
|
13733
|
+
}
|
|
13734
|
+
console.log(`[BackgroundUpload] ${pendingChunks.length} chunks pendentes encontrados. Modo final: ${isFinal}`);
|
|
13735
|
+
let virtualStart = this.totalBytesPurged;
|
|
13736
|
+
const chunksWithMeta = allChunks.map((c3) => {
|
|
13737
|
+
const start = virtualStart;
|
|
13738
|
+
const end = start + c3.blob.size - 1;
|
|
13739
|
+
virtualStart += c3.blob.size;
|
|
13740
|
+
return { chunk: c3, start, end };
|
|
13741
|
+
});
|
|
13742
|
+
let combinedBlobParts = [];
|
|
13743
|
+
let lastProcessedChunkId = null;
|
|
13744
|
+
let finalChunkIndex = 0;
|
|
13745
|
+
let mimeType = pendingChunks[0].mimeType;
|
|
13746
|
+
for (const meta of chunksWithMeta) {
|
|
13747
|
+
if (this.currentOffset > meta.end) continue;
|
|
13748
|
+
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
13749
|
+
const chunkSlice = meta.chunk.blob.slice(sliceStart);
|
|
13750
|
+
combinedBlobParts.push(chunkSlice);
|
|
13751
|
+
lastProcessedChunkId = meta.chunk.id;
|
|
13752
|
+
finalChunkIndex = meta.chunk.chunkIndex;
|
|
13753
|
+
}
|
|
13754
|
+
if (combinedBlobParts.length === 0 && !isFinal) {
|
|
13755
|
+
this.isProcessing = false;
|
|
13756
|
+
return;
|
|
13757
|
+
}
|
|
13758
|
+
let fullBlob = new Blob(combinedBlobParts, { type: mimeType });
|
|
13759
|
+
let sendableSize = fullBlob.size;
|
|
13760
|
+
let totalSizeForHeader = void 0;
|
|
13761
|
+
if (!isFinal) {
|
|
13762
|
+
sendableSize = Math.floor(fullBlob.size / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
13763
|
+
if (sendableSize === 0) {
|
|
13764
|
+
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
13765
|
+
this.isProcessing = false;
|
|
13766
|
+
return;
|
|
13767
|
+
}
|
|
13768
|
+
} else {
|
|
13769
|
+
totalSizeForHeader = virtualStart;
|
|
13770
|
+
}
|
|
13771
|
+
const blobToSend = fullBlob.slice(0, sendableSize);
|
|
13772
|
+
try {
|
|
13773
|
+
await this.uploadData(blobToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
13774
|
+
for (const meta of chunksWithMeta) {
|
|
13775
|
+
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
13776
|
+
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
13777
|
+
this.retryCount.delete(meta.chunk.id);
|
|
13778
|
+
(_a2 = this.onChunkUploaded) == null ? void 0 : _a2.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
13779
|
+
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
13780
|
+
}
|
|
13781
|
+
}
|
|
13782
|
+
if (this.config.cleanAfterUpload) {
|
|
13783
|
+
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
13784
|
+
const sizePurged = chunksToClear.reduce((acc, meta) => acc + meta.chunk.blob.size, 0);
|
|
13785
|
+
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
13786
|
+
if (sizePurged > 0) {
|
|
13787
|
+
this.totalBytesPurged += sizePurged;
|
|
13788
|
+
this.saveSessionState();
|
|
13789
|
+
console.log(`[BackgroundUpload] ${sizePurged} bytes limpos do armazenamento local. Total purgado: ${this.totalBytesPurged}`);
|
|
13790
|
+
}
|
|
13791
|
+
}
|
|
13792
|
+
if (isFinal) {
|
|
13793
|
+
this.clearSessionState();
|
|
13794
|
+
}
|
|
13795
|
+
} catch (error) {
|
|
13796
|
+
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
13797
|
+
(_b = this.onUploadError) == null ? void 0 : _b.call(this, lastProcessedChunkId || 0, error);
|
|
13798
|
+
}
|
|
13799
|
+
} catch (error) {
|
|
13800
|
+
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
13801
|
+
} finally {
|
|
13802
|
+
this.isProcessing = false;
|
|
13803
|
+
}
|
|
13804
|
+
}
|
|
13805
|
+
/**
|
|
13806
|
+
* Faz o upload bruto de dados para a sessão GCS.
|
|
13807
|
+
*/
|
|
13808
|
+
async uploadData(blob, mimeType, chunkIndex, totalSize) {
|
|
13809
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
13810
|
+
if (!this.sessionUrl) {
|
|
13811
|
+
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
13812
|
+
const startResponse = await fetch(initiateUrl, {
|
|
13813
|
+
method: "POST",
|
|
13814
|
+
headers: { "x-goog-resumable": "start", "Content-Type": mimeType }
|
|
13815
|
+
});
|
|
13816
|
+
if (!startResponse.ok) throw new Error(`Falha ao iniciar: ${startResponse.status}`);
|
|
13817
|
+
this.sessionUrl = startResponse.headers.get("Location");
|
|
13818
|
+
if (!this.sessionUrl) throw new Error("Location header ausente");
|
|
13819
|
+
try {
|
|
13820
|
+
const urlObj = new URL(this.sessionUrl);
|
|
13821
|
+
const pathParts = urlObj.pathname.split("/");
|
|
13822
|
+
let bucket = pathParts[1];
|
|
13823
|
+
let object = decodeURIComponent(pathParts.slice(2).join("/"));
|
|
13824
|
+
if (pathParts.includes("b") && pathParts.includes("o")) {
|
|
13825
|
+
const bIdx = pathParts.indexOf("b") + 1;
|
|
13826
|
+
const oIdx = pathParts.indexOf("o") + 1;
|
|
13827
|
+
bucket = pathParts[bIdx];
|
|
13828
|
+
object = decodeURIComponent(pathParts.slice(oIdx).join("/"));
|
|
13829
|
+
}
|
|
13830
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada -> Bucket: ${bucket}, Objeto: ${object}`);
|
|
13831
|
+
} catch (e3) {
|
|
13832
|
+
console.log(`[BackgroundUpload] Sess\xE3o Iniciada. URL: ${this.sessionUrl}`);
|
|
13833
|
+
}
|
|
13834
|
+
this.currentOffset = 0;
|
|
13835
|
+
this.saveSessionState();
|
|
13836
|
+
} else {
|
|
13837
|
+
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
13838
|
+
}
|
|
13839
|
+
const start = this.currentOffset;
|
|
13840
|
+
const end = start + blob.size - 1;
|
|
13841
|
+
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
13842
|
+
const contentRangeHeader = blob.size === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
13843
|
+
console.log(`[BackgroundUpload] Enviando ${blob.size > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${blob.size})`);
|
|
13844
|
+
const response = await fetch(this.sessionUrl, {
|
|
13845
|
+
method: "PUT",
|
|
13846
|
+
headers: { "Content-Range": contentRangeHeader },
|
|
13847
|
+
body: blob.size > 0 ? blob : null
|
|
13848
|
+
// Usa null para garantir corpo vazio se necessário
|
|
13849
|
+
});
|
|
13850
|
+
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
13851
|
+
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
13852
|
+
const errorText = await response.text();
|
|
13853
|
+
console.error(`[BackgroundUpload] Erro GCS: ${errorText}`);
|
|
13854
|
+
throw new Error(`Status HTTP inesperado: ${response.status}`);
|
|
13855
|
+
}
|
|
13856
|
+
const rangeHeader = response.headers.get("Range");
|
|
13857
|
+
if (rangeHeader) {
|
|
13858
|
+
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
13859
|
+
this.currentOffset = lastByte + 1;
|
|
13860
|
+
} else {
|
|
13861
|
+
this.currentOffset += blob.size;
|
|
13862
|
+
}
|
|
13863
|
+
this.saveSessionState();
|
|
13864
|
+
trackers.registerUploadFile(
|
|
13865
|
+
this.proctoringId,
|
|
13866
|
+
`GCS Stream Upload
|
|
13867
|
+
Size: ${blob.size}
|
|
13868
|
+
Range: ${start}-${end}
|
|
13869
|
+
Last Index: ${chunkIndex}`,
|
|
13870
|
+
"CameraChunk"
|
|
13871
|
+
);
|
|
13872
|
+
}
|
|
13873
|
+
/**
|
|
13874
|
+
* Método estático para recuperação pós-crash.
|
|
13875
|
+
* Verifica o IndexedDB em busca de chunks pendentes de qualquer sessão
|
|
13876
|
+
* e tenta enviar.
|
|
13877
|
+
*/
|
|
13878
|
+
static async recoverPendingUploads(backend, token) {
|
|
13879
|
+
const chunkStorage = new ChunkStorageService();
|
|
13880
|
+
const recoveredIds = [];
|
|
13881
|
+
try {
|
|
13882
|
+
const pendingIds = await chunkStorage.getPendingProctoringIds();
|
|
13883
|
+
if (pendingIds.length === 0) {
|
|
13884
|
+
console.log("[BackgroundUpload] Nenhum chunk pendente encontrado para recupera\xE7\xE3o.");
|
|
13885
|
+
return recoveredIds;
|
|
13886
|
+
}
|
|
13887
|
+
console.log(
|
|
13888
|
+
`[BackgroundUpload] Recupera\xE7\xE3o p\xF3s-crash: ${pendingIds.length} sess\xE3o(\xF5es) com chunks pendentes.`
|
|
13889
|
+
);
|
|
13890
|
+
for (const proctoringId2 of pendingIds) {
|
|
13891
|
+
try {
|
|
13892
|
+
const service = new _BackgroundUploadService(
|
|
13893
|
+
proctoringId2,
|
|
13894
|
+
token,
|
|
13895
|
+
backend,
|
|
13896
|
+
chunkStorage,
|
|
13897
|
+
{ cleanAfterUpload: true }
|
|
13898
|
+
);
|
|
13899
|
+
await service.flush();
|
|
13900
|
+
recoveredIds.push(proctoringId2);
|
|
13901
|
+
console.log(
|
|
13902
|
+
`[BackgroundUpload] Chunks da sess\xE3o ${proctoringId2} recuperados com sucesso.`
|
|
13903
|
+
);
|
|
13904
|
+
} catch (error) {
|
|
13905
|
+
console.error(
|
|
13906
|
+
`[BackgroundUpload] Erro ao recuperar chunks da sess\xE3o ${proctoringId2}:`,
|
|
13907
|
+
error
|
|
13908
|
+
);
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
} catch (error) {
|
|
13912
|
+
console.error("[BackgroundUpload] Erro geral na recupera\xE7\xE3o:", error);
|
|
13913
|
+
}
|
|
13914
|
+
return recoveredIds;
|
|
13915
|
+
}
|
|
13916
|
+
sleep(ms2) {
|
|
13917
|
+
return new Promise((resolve) => setTimeout(resolve, ms2));
|
|
13918
|
+
}
|
|
13919
|
+
};
|
|
13920
|
+
|
|
13247
13921
|
// src/new-flow/recorders/CameraRecorder.ts
|
|
13248
13922
|
var import_jszip_min = __toESM(require_jszip_min());
|
|
13249
|
-
var
|
|
13250
|
-
var
|
|
13923
|
+
var pkg = require_fix_webm_duration();
|
|
13924
|
+
var fixWebmDuration = pkg.default || pkg;
|
|
13925
|
+
var _CameraRecorder = class _CameraRecorder {
|
|
13251
13926
|
constructor(options, videoOptions, paramsConfig, backend, backendToken) {
|
|
13252
13927
|
this.blobs = [];
|
|
13253
13928
|
this.paramsConfig = {
|
|
@@ -13292,6 +13967,7 @@ var CameraRecorder = class {
|
|
|
13292
13967
|
this.blobsRTC = [];
|
|
13293
13968
|
this.imageCount = 0;
|
|
13294
13969
|
this.filesToUpload = [];
|
|
13970
|
+
this.pendingPackages = [];
|
|
13295
13971
|
this.animationFrameId = null;
|
|
13296
13972
|
this.isCanvasLoopActive = false;
|
|
13297
13973
|
this.hardwareStream = null;
|
|
@@ -13299,8 +13975,16 @@ var CameraRecorder = class {
|
|
|
13299
13975
|
this.videoElement = null;
|
|
13300
13976
|
this.duration = 0;
|
|
13301
13977
|
this.stopped = false;
|
|
13978
|
+
this.backgroundUpload = null;
|
|
13979
|
+
this.chunkIndex = 0;
|
|
13980
|
+
/** Lista de promises de chunks sendo salvos no IndexedDB para evitar race conditions no stop */
|
|
13981
|
+
this.pendingChunkSaves = [];
|
|
13982
|
+
// Handlers bound para poder remover os listeners depois
|
|
13983
|
+
this.boundVisibilityHandler = null;
|
|
13984
|
+
this.boundPageHideHandler = null;
|
|
13302
13985
|
this.currentRetries = 0;
|
|
13303
13986
|
this.packageCount = 0;
|
|
13987
|
+
this.failedUploads = 0;
|
|
13304
13988
|
this.noiseWait = 20;
|
|
13305
13989
|
this.options = options;
|
|
13306
13990
|
this.videoOptions = videoOptions;
|
|
@@ -13308,10 +13992,122 @@ var CameraRecorder = class {
|
|
|
13308
13992
|
this.backendToken = backendToken;
|
|
13309
13993
|
paramsConfig && (this.paramsConfig = paramsConfig);
|
|
13310
13994
|
}
|
|
13995
|
+
/**
|
|
13996
|
+
* Determina se o fluxo de chunks e lifecycle deve estar ativo.
|
|
13997
|
+
* Retorna true se:
|
|
13998
|
+
* 1. O proctoringId já foi definido (ou seja, estamos em uma sessão real, NÃO no checkDevices)
|
|
13999
|
+
* 2. E (`useChunkRecording` foi explicitamente setado como true OU o dispositivo é mobile)
|
|
14000
|
+
*/
|
|
14001
|
+
get isChunkEnabled() {
|
|
14002
|
+
return !!this.proctoringId && this.options.proctoringType === "REALTIME" && !isSafeBrowser();
|
|
14003
|
+
}
|
|
13311
14004
|
setProctoringId(proctoringId2) {
|
|
13312
14005
|
this.proctoringId = proctoringId2;
|
|
13313
14006
|
this.proctoringId && this.backend && (this.upload = new UploadService(this.proctoringId, this.backend));
|
|
13314
14007
|
setRecorderProctoringId(proctoringId2);
|
|
14008
|
+
if (this.isChunkEnabled) {
|
|
14009
|
+
this.chunkStorage = new ChunkStorageService();
|
|
14010
|
+
if (this.backend && this.backendToken) {
|
|
14011
|
+
this.backgroundUpload = new BackgroundUploadService(
|
|
14012
|
+
this.proctoringId,
|
|
14013
|
+
this.backendToken,
|
|
14014
|
+
this.backend,
|
|
14015
|
+
this.chunkStorage,
|
|
14016
|
+
{ pollInterval: 5e3, maxRetries: 5, cleanAfterUpload: true }
|
|
14017
|
+
);
|
|
14018
|
+
}
|
|
14019
|
+
this.persistSessionState("IN_PROGRESS");
|
|
14020
|
+
console.log(
|
|
14021
|
+
`[CameraRecorder] Chunk recording ATIVO (type: ${this.options.proctoringType}, mobile: ${isMobileDevice()})`
|
|
14022
|
+
);
|
|
14023
|
+
} else {
|
|
14024
|
+
console.log(
|
|
14025
|
+
`[CameraRecorder] Chunk recording INATIVO (type: ${this.options.proctoringType}) \u2014 modo cl\xE1ssico.`
|
|
14026
|
+
);
|
|
14027
|
+
}
|
|
14028
|
+
}
|
|
14029
|
+
// ========================
|
|
14030
|
+
// Session State Persistence (localStorage)
|
|
14031
|
+
// ========================
|
|
14032
|
+
persistSessionState(status) {
|
|
14033
|
+
try {
|
|
14034
|
+
const data = {
|
|
14035
|
+
proctoringId: this.proctoringId,
|
|
14036
|
+
status,
|
|
14037
|
+
timestamp: Date.now()
|
|
14038
|
+
};
|
|
14039
|
+
localStorage.setItem(_CameraRecorder.LS_SESSION_KEY, JSON.stringify(data));
|
|
14040
|
+
} catch (e3) {
|
|
14041
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel salvar estado no localStorage:", e3);
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
clearSessionState() {
|
|
14045
|
+
try {
|
|
14046
|
+
localStorage.removeItem(_CameraRecorder.LS_SESSION_KEY);
|
|
14047
|
+
} catch (e3) {
|
|
14048
|
+
console.warn("[CameraRecorder] N\xE3o foi poss\xEDvel limpar estado do localStorage:", e3);
|
|
14049
|
+
}
|
|
14050
|
+
}
|
|
14051
|
+
/**
|
|
14052
|
+
* Verifica se existe uma sessão ativa anterior no localStorage.
|
|
14053
|
+
* Retorna os dados da sessão se ela estiver em IN_PROGRESS, null caso contrário.
|
|
14054
|
+
*/
|
|
14055
|
+
static checkForActiveSession() {
|
|
14056
|
+
try {
|
|
14057
|
+
const raw = localStorage.getItem(_CameraRecorder.LS_SESSION_KEY);
|
|
14058
|
+
if (!raw) return null;
|
|
14059
|
+
const data = JSON.parse(raw);
|
|
14060
|
+
if (data.status === "IN_PROGRESS") return data;
|
|
14061
|
+
return null;
|
|
14062
|
+
} catch (e3) {
|
|
14063
|
+
return null;
|
|
14064
|
+
}
|
|
14065
|
+
}
|
|
14066
|
+
// ========================
|
|
14067
|
+
// Page Lifecycle Management
|
|
14068
|
+
// ========================
|
|
14069
|
+
setupLifecycleListeners() {
|
|
14070
|
+
this.boundVisibilityHandler = () => this.handleVisibilityChange();
|
|
14071
|
+
this.boundPageHideHandler = () => this.handlePageHide();
|
|
14072
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
14073
|
+
window.addEventListener("pagehide", this.boundPageHideHandler);
|
|
14074
|
+
}
|
|
14075
|
+
removeLifecycleListeners() {
|
|
14076
|
+
if (this.boundVisibilityHandler) {
|
|
14077
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
14078
|
+
this.boundVisibilityHandler = null;
|
|
14079
|
+
}
|
|
14080
|
+
if (this.boundPageHideHandler) {
|
|
14081
|
+
window.removeEventListener("pagehide", this.boundPageHideHandler);
|
|
14082
|
+
this.boundPageHideHandler = null;
|
|
14083
|
+
}
|
|
14084
|
+
}
|
|
14085
|
+
handleVisibilityChange() {
|
|
14086
|
+
var _a2;
|
|
14087
|
+
if (document.visibilityState === "hidden") {
|
|
14088
|
+
console.log("[CameraRecorder] P\xE1gina ficou invis\xEDvel \u2014 sess\xE3o potencialmente interrompida.");
|
|
14089
|
+
this.persistSessionState("INTERRUPTED");
|
|
14090
|
+
this.proctoringId && trackers.registerError(
|
|
14091
|
+
this.proctoringId,
|
|
14092
|
+
"Visibility API: P\xE1gina ficou oculta (hidden). Poss\xEDvel troca de app ou minimiza\xE7\xE3o."
|
|
14093
|
+
);
|
|
14094
|
+
} else if (document.visibilityState === "visible") {
|
|
14095
|
+
console.log("[CameraRecorder] P\xE1gina vis\xEDvel novamente \u2014 verificando estado da grava\xE7\xE3o.");
|
|
14096
|
+
this.persistSessionState("IN_PROGRESS");
|
|
14097
|
+
this.proctoringId && trackers.registerError(
|
|
14098
|
+
this.proctoringId,
|
|
14099
|
+
"Visibility API: P\xE1gina voltou a ficar vis\xEDvel. Usu\xE1rio retornou."
|
|
14100
|
+
);
|
|
14101
|
+
(_a2 = this.onVisibilityRestored) == null ? void 0 : _a2.call(this);
|
|
14102
|
+
}
|
|
14103
|
+
}
|
|
14104
|
+
handlePageHide() {
|
|
14105
|
+
console.log("[CameraRecorder] pagehide detectado \u2014 persistindo estado.");
|
|
14106
|
+
this.persistSessionState("INTERRUPTED");
|
|
14107
|
+
this.proctoringId && trackers.registerError(
|
|
14108
|
+
this.proctoringId,
|
|
14109
|
+
"Page Lifecycle: pagehide event detectado. P\xE1gina est\xE1 sendo descarregada."
|
|
14110
|
+
);
|
|
13315
14111
|
}
|
|
13316
14112
|
async initializeDetectors() {
|
|
13317
14113
|
var _a2, _b, _c2;
|
|
@@ -13464,10 +14260,15 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13464
14260
|
await new Promise((r3) => setTimeout(r3, 300));
|
|
13465
14261
|
}
|
|
13466
14262
|
async startRecording() {
|
|
13467
|
-
var _a2, _b, _c2, _d, _e3, _f, _g;
|
|
13468
|
-
console.log("CameraRecorder startRecording");
|
|
14263
|
+
var _a2, _b, _c2, _d, _e3, _f, _g, _h;
|
|
13469
14264
|
await this.startStream();
|
|
13470
14265
|
await this.attachAndWarmup(this.cameraStream);
|
|
14266
|
+
const recorderOpts = this.isChunkEnabled ? {
|
|
14267
|
+
timeslice: _CameraRecorder.CHUNK_TIMESLICE_MS,
|
|
14268
|
+
onChunkAvailable: (blob, idx) => {
|
|
14269
|
+
this.handleNewChunk(blob, idx);
|
|
14270
|
+
}
|
|
14271
|
+
} : {};
|
|
13471
14272
|
const {
|
|
13472
14273
|
startRecording,
|
|
13473
14274
|
stopRecording,
|
|
@@ -13483,7 +14284,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13483
14284
|
this.blobs,
|
|
13484
14285
|
this.options.onBufferSizeError,
|
|
13485
14286
|
(e3) => this.bufferError(e3),
|
|
13486
|
-
false
|
|
14287
|
+
false,
|
|
14288
|
+
recorderOpts
|
|
13487
14289
|
);
|
|
13488
14290
|
this.recordingStart = startRecording;
|
|
13489
14291
|
this.recordingStop = stopRecording;
|
|
@@ -13493,13 +14295,18 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13493
14295
|
this.getBufferSize = getBufferSize;
|
|
13494
14296
|
this.getStartTime = getStartTime;
|
|
13495
14297
|
this.getDuration = getDuration;
|
|
14298
|
+
this.chunkIndex = 0;
|
|
14299
|
+
if (this.isChunkEnabled) {
|
|
14300
|
+
(_a2 = this.backgroundUpload) == null ? void 0 : _a2.start();
|
|
14301
|
+
this.setupLifecycleListeners();
|
|
14302
|
+
}
|
|
13496
14303
|
try {
|
|
13497
14304
|
await new Promise((r3) => setTimeout(r3, 500));
|
|
13498
14305
|
await this.recordingStart();
|
|
13499
14306
|
} catch (error) {
|
|
13500
14307
|
console.log("Camera Recorder error", error);
|
|
13501
14308
|
this.stopRecording();
|
|
13502
|
-
const maxRetries = ((
|
|
14309
|
+
const maxRetries = ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.maxRetries) || 3;
|
|
13503
14310
|
if (this.currentRetries < maxRetries) {
|
|
13504
14311
|
console.log("Camera Recorder retry", this.currentRetries);
|
|
13505
14312
|
this.currentRetries++;
|
|
@@ -13509,16 +14316,17 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13509
14316
|
}
|
|
13510
14317
|
}
|
|
13511
14318
|
this.stopped = false;
|
|
13512
|
-
if (((
|
|
14319
|
+
if (((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectPerson) || ((_d = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _d.detectCellPhone) || ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace)) {
|
|
13513
14320
|
await this.initializeDetectors();
|
|
13514
14321
|
}
|
|
13515
|
-
if ((
|
|
14322
|
+
if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
|
|
13516
14323
|
await this.faceDetection.enableCam(this.cameraStream);
|
|
13517
14324
|
}
|
|
13518
|
-
if (((
|
|
14325
|
+
if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
|
|
13519
14326
|
await this.objectDetection.enableCam(this.cameraStream);
|
|
13520
14327
|
}
|
|
13521
14328
|
this.filesToUpload = [];
|
|
14329
|
+
this.pendingPackages = [];
|
|
13522
14330
|
if (this.options.proctoringType == "REALTIME") {
|
|
13523
14331
|
await this.startRealtimeCapture();
|
|
13524
14332
|
}
|
|
@@ -13537,7 +14345,8 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13537
14345
|
this.volumeMeter && this.volumeMeter.stop();
|
|
13538
14346
|
this.intervalNoiseDetection && clearInterval(this.intervalNoiseDetection);
|
|
13539
14347
|
this.recordingStop && await this.recordingStop();
|
|
13540
|
-
this.duration = this.getDuration();
|
|
14348
|
+
this.duration = this.getDuration ? this.getDuration() : 0;
|
|
14349
|
+
await new Promise((r3) => setTimeout(r3, 200));
|
|
13541
14350
|
try {
|
|
13542
14351
|
if (this.animationFrameId) {
|
|
13543
14352
|
cancelAnimationFrame(this.animationFrameId);
|
|
@@ -13568,9 +14377,54 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13568
14377
|
console.error("Erro ao parar os streams de m\xEDdia.");
|
|
13569
14378
|
}
|
|
13570
14379
|
if (this.options.proctoringType == "REALTIME" && this.upload && this.backendToken) {
|
|
13571
|
-
|
|
14380
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, this.filesToUpload.length));
|
|
14381
|
+
await this.sendPackage();
|
|
13572
14382
|
await this.filesToUpload.splice(0, this.filesToUpload.length);
|
|
13573
14383
|
}
|
|
14384
|
+
if (this.isChunkEnabled) {
|
|
14385
|
+
if (this.backgroundUpload) {
|
|
14386
|
+
try {
|
|
14387
|
+
if (this.pendingChunkSaves.length > 0) {
|
|
14388
|
+
console.log(`[CameraRecorder] Aguardando ${this.pendingChunkSaves.length} salvamentos de chunks pendentes...`);
|
|
14389
|
+
await Promise.all(this.pendingChunkSaves);
|
|
14390
|
+
}
|
|
14391
|
+
await this.backgroundUpload.flush();
|
|
14392
|
+
} catch (e3) {
|
|
14393
|
+
console.warn("[CameraRecorder] Erro ao fazer flush dos chunks:", e3);
|
|
14394
|
+
}
|
|
14395
|
+
this.backgroundUpload.stop();
|
|
14396
|
+
}
|
|
14397
|
+
this.removeLifecycleListeners();
|
|
14398
|
+
this.persistSessionState("FINISHED");
|
|
14399
|
+
}
|
|
14400
|
+
}
|
|
14401
|
+
/**
|
|
14402
|
+
* Callback chamado pelo recorder a cada novo chunk de vídeo disponível.
|
|
14403
|
+
* Salva o chunk no IndexedDB para persistência e recuperação.
|
|
14404
|
+
*/
|
|
14405
|
+
async handleNewChunk(blob, idx) {
|
|
14406
|
+
if (!this.proctoringId || !this.chunkStorage) return;
|
|
14407
|
+
const savePromise = (async () => {
|
|
14408
|
+
var _a2;
|
|
14409
|
+
try {
|
|
14410
|
+
await this.chunkStorage.saveChunk({
|
|
14411
|
+
proctoringId: this.proctoringId,
|
|
14412
|
+
chunkIndex: this.chunkIndex,
|
|
14413
|
+
blob,
|
|
14414
|
+
timestamp: Date.now(),
|
|
14415
|
+
uploaded: 0,
|
|
14416
|
+
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
14417
|
+
});
|
|
14418
|
+
this.chunkIndex++;
|
|
14419
|
+
console.log(`[CameraRecorder] Chunk ${this.chunkIndex - 1} salvo no IndexedDB.`);
|
|
14420
|
+
} catch (error) {
|
|
14421
|
+
console.error("[CameraRecorder] Erro ao salvar chunk no IndexedDB:", error);
|
|
14422
|
+
}
|
|
14423
|
+
})();
|
|
14424
|
+
this.pendingChunkSaves.push(savePromise);
|
|
14425
|
+
savePromise.finally(() => {
|
|
14426
|
+
this.pendingChunkSaves = this.pendingChunkSaves.filter((p3) => p3 !== savePromise);
|
|
14427
|
+
});
|
|
13574
14428
|
}
|
|
13575
14429
|
async pauseRecording() {
|
|
13576
14430
|
await this.recordingPause();
|
|
@@ -13617,9 +14471,9 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13617
14471
|
if (this.proctoringId == void 0) return;
|
|
13618
14472
|
if (packSize == this.imageCount) {
|
|
13619
14473
|
this.imageCount = 0;
|
|
13620
|
-
|
|
13621
|
-
this.sendPackage(
|
|
13622
|
-
await this.filesToUpload.splice(0,
|
|
14474
|
+
this.pendingPackages.push(this.filesToUpload.slice(0, packSize));
|
|
14475
|
+
this.sendPackage();
|
|
14476
|
+
await this.filesToUpload.splice(0, packSize);
|
|
13623
14477
|
}
|
|
13624
14478
|
let imageName = `${this.proctoringId}_${this.imageCount + 1}.jpg`;
|
|
13625
14479
|
imageFile = await this.getFile(image_data_url, imageName, "image/jpeg");
|
|
@@ -13633,54 +14487,61 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13633
14487
|
var _a2;
|
|
13634
14488
|
this.configImageCapture();
|
|
13635
14489
|
this.imageCount = 0;
|
|
14490
|
+
this.pendingPackages = [];
|
|
13636
14491
|
await this.captureFrame();
|
|
13637
14492
|
this.imageInterval = setInterval(async () => {
|
|
13638
14493
|
await this.captureFrame();
|
|
13639
14494
|
}, ((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimeCaptureInterval) * 1e3);
|
|
13640
14495
|
}
|
|
13641
14496
|
// envia pacote de imagens
|
|
13642
|
-
async sendPackage(
|
|
14497
|
+
async sendPackage() {
|
|
13643
14498
|
var _a2, _b;
|
|
13644
|
-
let pending = false;
|
|
13645
|
-
let undeliveredPackagesCount = 0;
|
|
13646
14499
|
const packSize = (_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.realtimePackageSize;
|
|
13647
14500
|
const packCaptureInterval = (_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.realtimeCaptureInterval;
|
|
13648
|
-
if (this.upload && this.backendToken &&
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
const
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
{
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
14501
|
+
if (this.upload && this.backendToken && this.pendingPackages.length > 0) {
|
|
14502
|
+
let packagesToDelete = [];
|
|
14503
|
+
let packageIndex = 0;
|
|
14504
|
+
for (const packageToSend of this.pendingPackages) {
|
|
14505
|
+
const zip = new import_jszip_min.default();
|
|
14506
|
+
for (const file of packageToSend) {
|
|
14507
|
+
zip.file(file.name, file);
|
|
14508
|
+
}
|
|
14509
|
+
const blob = await zip.generateAsync({ type: "blob" });
|
|
14510
|
+
let packageName = "realtime_package_" + packSize * packCaptureInterval * this.packageCount + ".zip";
|
|
14511
|
+
const myPackage = new File(
|
|
14512
|
+
[blob],
|
|
14513
|
+
packageName,
|
|
14514
|
+
{ type: "application/zip" }
|
|
14515
|
+
);
|
|
14516
|
+
try {
|
|
14517
|
+
const uploadResult = await this.upload.uploadPackage(
|
|
14518
|
+
{
|
|
14519
|
+
file: myPackage
|
|
14520
|
+
},
|
|
14521
|
+
this.backendToken
|
|
14522
|
+
);
|
|
14523
|
+
if (uploadResult == true) {
|
|
14524
|
+
this.packageCount++;
|
|
14525
|
+
this.failedUploads = 0;
|
|
14526
|
+
packagesToDelete.push(packageIndex++);
|
|
14527
|
+
}
|
|
14528
|
+
} catch (error) {
|
|
14529
|
+
this.failedUploads++;
|
|
14530
|
+
if (this.failedUploads >= 2) {
|
|
14531
|
+
this.options.onRealtimeAlertsCallback({
|
|
14532
|
+
status: "ALERT",
|
|
14533
|
+
description: "Realtime n\xE3o est\xE1 enviando pacotes",
|
|
14534
|
+
type: "error_upload_package",
|
|
14535
|
+
category: "error_upload_package",
|
|
14536
|
+
begin: 0,
|
|
14537
|
+
end: 0
|
|
14538
|
+
});
|
|
14539
|
+
}
|
|
14540
|
+
break;
|
|
14541
|
+
}
|
|
13672
14542
|
}
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
undeliveredPackagesCount++;
|
|
13676
|
-
if (undeliveredPackagesCount == 3) {
|
|
13677
|
-
undeliveredPackagesCount = 0;
|
|
13678
|
-
let newCanvasWidth = this.videoOptions.width / 2;
|
|
13679
|
-
let newCanvasHeight = this.videoOptions.height / 2;
|
|
13680
|
-
if (newCanvasWidth < 320) newCanvasWidth = 320;
|
|
13681
|
-
if (newCanvasHeight < 180) newCanvasHeight = 180;
|
|
13682
|
-
this.canvas.width = newCanvasWidth;
|
|
13683
|
-
this.canvas.height = newCanvasHeight;
|
|
14543
|
+
for (const packageToDelete of packagesToDelete) {
|
|
14544
|
+
await this.pendingPackages.splice(packageToDelete, 1);
|
|
13684
14545
|
}
|
|
13685
14546
|
}
|
|
13686
14547
|
}
|
|
@@ -13699,33 +14560,34 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13699
14560
|
if (this.blobs != null)
|
|
13700
14561
|
trackers.registerSaveOnSession(
|
|
13701
14562
|
this.proctoringId,
|
|
13702
|
-
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} `
|
|
14563
|
+
`Blobs Length: ${this.blobs.length} Buffer Size: ${this.getBufferSize()} ChunkEnabled: ${this.isChunkEnabled}`
|
|
13703
14564
|
);
|
|
13704
14565
|
const settings = this.cameraStream.getVideoTracks()[0].getSettings();
|
|
13705
14566
|
const settingsAudio = this.cameraStream.getAudioTracks()[0].getSettings();
|
|
13706
14567
|
if (this.options.proctoringType == "VIDEO" || this.options.proctoringType == "REALTIME" || this.options.proctoringType == "IMAGE" && ((_a2 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _a2.saveVideo)) {
|
|
14568
|
+
let videoFile;
|
|
14569
|
+
if (this.isChunkEnabled) {
|
|
14570
|
+
const isStable = await this.checkInternetStability();
|
|
14571
|
+
if (isStable) {
|
|
14572
|
+
} else {
|
|
14573
|
+
if (this.backend && this.backendToken && this.proctoringId) {
|
|
14574
|
+
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
14575
|
+
const objectName = `${this.proctoringId}/${fileName}`;
|
|
14576
|
+
const isUploaded = await this.backend.checkUpload(this.backendToken, objectName, "video/webm");
|
|
14577
|
+
if (isUploaded) {
|
|
14578
|
+
this.chunkStorage && await this.chunkStorage.clearAllChunks(session.id);
|
|
14579
|
+
return;
|
|
14580
|
+
}
|
|
14581
|
+
}
|
|
14582
|
+
}
|
|
14583
|
+
}
|
|
13707
14584
|
const rawBlob = new Blob(this.blobs, {
|
|
13708
14585
|
type: ((_b = this.recorderOptions) == null ? void 0 : _b.mimeType) || "video/webm"
|
|
13709
14586
|
});
|
|
13710
|
-
const fixedBlob = await (
|
|
13711
|
-
|
|
13712
|
-
[rawBlob],
|
|
13713
|
-
`EP_${session.id}_camera_0.webm`,
|
|
13714
|
-
{ type: rawBlob.type }
|
|
13715
|
-
);
|
|
13716
|
-
session.addRecording({
|
|
13717
|
-
device: `Audio
|
|
13718
|
-
Sample Rate: ${settingsAudio.sampleRate}
|
|
13719
|
-
Sample Size: ${settingsAudio.sampleSize}
|
|
13720
|
-
|
|
13721
|
-
Video:
|
|
13722
|
-
${JSON.stringify(this.recorderOptions)}`,
|
|
13723
|
-
file,
|
|
13724
|
-
origin: "Camera" /* Camera */
|
|
13725
|
-
});
|
|
13726
|
-
const fileWithDuration = new File(
|
|
14587
|
+
const fixedBlob = await fixWebmDuration(rawBlob, this.duration);
|
|
14588
|
+
videoFile = new File(
|
|
13727
14589
|
[fixedBlob],
|
|
13728
|
-
`EP_${session.id}
|
|
14590
|
+
`EP_${session.id}_camera_0.webm`,
|
|
13729
14591
|
{ type: rawBlob.type }
|
|
13730
14592
|
);
|
|
13731
14593
|
session.addRecording({
|
|
@@ -13735,7 +14597,7 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13735
14597
|
|
|
13736
14598
|
Video:
|
|
13737
14599
|
${JSON.stringify(this.recorderOptions)}`,
|
|
13738
|
-
file:
|
|
14600
|
+
file: videoFile,
|
|
13739
14601
|
origin: "Camera" /* Camera */
|
|
13740
14602
|
});
|
|
13741
14603
|
}
|
|
@@ -13750,6 +14612,28 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13750
14612
|
});
|
|
13751
14613
|
});
|
|
13752
14614
|
}
|
|
14615
|
+
/**
|
|
14616
|
+
* Verifica se a internet está estável para realizar o upload do vídeo na íntegra.
|
|
14617
|
+
*/
|
|
14618
|
+
async checkInternetStability() {
|
|
14619
|
+
var _a2;
|
|
14620
|
+
if (!navigator.onLine) return false;
|
|
14621
|
+
try {
|
|
14622
|
+
const controller = new AbortController();
|
|
14623
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
14624
|
+
const baseUrl = (_a2 = this.backend) == null ? void 0 : _a2.getBaseUrl();
|
|
14625
|
+
if (!baseUrl) return true;
|
|
14626
|
+
const response = await fetch(`${baseUrl}/Client/health`, {
|
|
14627
|
+
method: "GET",
|
|
14628
|
+
signal: controller.signal
|
|
14629
|
+
});
|
|
14630
|
+
clearTimeout(timeoutId);
|
|
14631
|
+
return response.status < 500;
|
|
14632
|
+
} catch (e3) {
|
|
14633
|
+
console.warn("[CameraRecorder] Internet inst\xE1vel ou lenta detectada para upload integral.");
|
|
14634
|
+
return false;
|
|
14635
|
+
}
|
|
14636
|
+
}
|
|
13753
14637
|
onNoiseDetected() {
|
|
13754
14638
|
var _a2, _b, _c2;
|
|
13755
14639
|
if (this.options.proctoringType === "REALTIME") return;
|
|
@@ -13762,7 +14646,6 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13762
14646
|
}
|
|
13763
14647
|
const volume = (_b = (_a2 = this.volumeMeter) == null ? void 0 : _a2.getVolume()) != null ? _b : 0;
|
|
13764
14648
|
if (volume >= (((_c2 = this.paramsConfig.audioBehaviourParameters) == null ? void 0 : _c2.noiseLimit) || 40)) {
|
|
13765
|
-
console.log("entrou" + this.noiseWait);
|
|
13766
14649
|
if (this.noiseWait >= 20) {
|
|
13767
14650
|
this.options.onRealtimeAlertsCallback({
|
|
13768
14651
|
status: "ALERT",
|
|
@@ -13775,6 +14658,14 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
13775
14658
|
this.noiseWait++;
|
|
13776
14659
|
}
|
|
13777
14660
|
};
|
|
14661
|
+
// ========================
|
|
14662
|
+
// Chunk & Lifecycle
|
|
14663
|
+
// ========================
|
|
14664
|
+
/** Intervalo de cada chunk em ms (padrão: 60 segundos) */
|
|
14665
|
+
_CameraRecorder.CHUNK_TIMESLICE_MS = 6e4;
|
|
14666
|
+
/** Chave do localStorage para persistir estado da sessão */
|
|
14667
|
+
_CameraRecorder.LS_SESSION_KEY = "ep_proctoring_session";
|
|
14668
|
+
var CameraRecorder = _CameraRecorder;
|
|
13778
14669
|
|
|
13779
14670
|
// src/new-flow/checkers/DeviceCheckerUI.ts
|
|
13780
14671
|
var DeviceCheckerUI = class {
|
|
@@ -14847,7 +15738,8 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
14847
15738
|
this.context = context;
|
|
14848
15739
|
this.backend = new BackendService({
|
|
14849
15740
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
14850
|
-
token: context.token
|
|
15741
|
+
token: context.token,
|
|
15742
|
+
isRealtime: false
|
|
14851
15743
|
});
|
|
14852
15744
|
}
|
|
14853
15745
|
getDeviceCheckResult() {
|
|
@@ -15008,7 +15900,7 @@ var _DeviceCheckerService = class _DeviceCheckerService {
|
|
|
15008
15900
|
videoDeviceInterface(stream) {
|
|
15009
15901
|
this.DeviceCheckerUI && this.DeviceCheckerUI.videoDeviceInterfaceUI(stream);
|
|
15010
15902
|
this.isUnderResolution();
|
|
15011
|
-
this.faceDetection.enableCam(stream);
|
|
15903
|
+
this.faceDetection.enableCam(stream, 1e3);
|
|
15012
15904
|
}
|
|
15013
15905
|
audioDeviceInterface(stream) {
|
|
15014
15906
|
this.volumeMeter = new VolumeMeter(this.cameraRecorder.cameraStream);
|
|
@@ -18274,6 +19166,7 @@ var NoiseRecorder = class {
|
|
|
18274
19166
|
this.MAX_PRE_ROLL_CHUNKS = 4;
|
|
18275
19167
|
this.lastNoiseTime = 0;
|
|
18276
19168
|
this.SILENCE_THRESHOLD = 3e3;
|
|
19169
|
+
this.filesToUpload = [];
|
|
18277
19170
|
this.optionsProctoring = optionsProctoring;
|
|
18278
19171
|
this.proctoringSession = proctoringSession;
|
|
18279
19172
|
this.paramsConfig = paramsConfig;
|
|
@@ -18365,7 +19258,7 @@ var NoiseRecorder = class {
|
|
|
18365
19258
|
}
|
|
18366
19259
|
}
|
|
18367
19260
|
async stopSoundRecord() {
|
|
18368
|
-
var _a2;
|
|
19261
|
+
var _a2, _b;
|
|
18369
19262
|
if (!this.recordingInProgress && this.recordingChunks.length === 0) return;
|
|
18370
19263
|
this.recordingEndTime = Date.now() - (((_a2 = this.cameraRecorder.getStartTime()) == null ? void 0 : _a2.getTime()) || 0);
|
|
18371
19264
|
if (this.optionsProctoring.proctoringType !== "REALTIME") return;
|
|
@@ -18379,7 +19272,33 @@ var NoiseRecorder = class {
|
|
|
18379
19272
|
type: "audio/wav"
|
|
18380
19273
|
}
|
|
18381
19274
|
);
|
|
18382
|
-
this.
|
|
19275
|
+
let filesToSend = [...this.filesToUpload];
|
|
19276
|
+
for (const myFile of filesToSend) {
|
|
19277
|
+
try {
|
|
19278
|
+
await ((_b = this.upload) == null ? void 0 : _b.upload(
|
|
19279
|
+
{
|
|
19280
|
+
file: myFile
|
|
19281
|
+
},
|
|
19282
|
+
this.backendToken
|
|
19283
|
+
));
|
|
19284
|
+
this.filesToUpload.splice(this.filesToUpload.indexOf(myFile), 1);
|
|
19285
|
+
} catch (error) {
|
|
19286
|
+
break;
|
|
19287
|
+
}
|
|
19288
|
+
}
|
|
19289
|
+
try {
|
|
19290
|
+
if (file && this.upload && this.backendToken) {
|
|
19291
|
+
this.upload.upload(
|
|
19292
|
+
{
|
|
19293
|
+
file
|
|
19294
|
+
},
|
|
19295
|
+
this.backendToken
|
|
19296
|
+
);
|
|
19297
|
+
}
|
|
19298
|
+
} catch (error) {
|
|
19299
|
+
console.log("error Noise recorder adicionando na fila", error);
|
|
19300
|
+
this.filesToUpload.push(file);
|
|
19301
|
+
}
|
|
18383
19302
|
this.recordingChunks = [];
|
|
18384
19303
|
this.recordingInProgress = false;
|
|
18385
19304
|
}
|
|
@@ -18393,16 +19312,6 @@ var NoiseRecorder = class {
|
|
|
18393
19312
|
a3.click();
|
|
18394
19313
|
window.URL.revokeObjectURL(url);
|
|
18395
19314
|
}
|
|
18396
|
-
uploadRecord(file) {
|
|
18397
|
-
if (file && this.upload && this.backendToken) {
|
|
18398
|
-
this.upload.upload(
|
|
18399
|
-
{
|
|
18400
|
-
file
|
|
18401
|
-
},
|
|
18402
|
-
this.backendToken
|
|
18403
|
-
);
|
|
18404
|
-
}
|
|
18405
|
-
}
|
|
18406
19315
|
// CLASSIFIER -< Media Pipe
|
|
18407
19316
|
// Verify if has speech in the classifier array
|
|
18408
19317
|
hasDesiredResult(array) {
|
|
@@ -18509,7 +19418,8 @@ registerProcessor("audio-processor", AudioProcessor);
|
|
|
18509
19418
|
`;
|
|
18510
19419
|
|
|
18511
19420
|
// src/new-flow/recorders/ScreenRecorder.ts
|
|
18512
|
-
var
|
|
19421
|
+
var pkg2 = require_fix_webm_duration();
|
|
19422
|
+
var fixWebmDuration2 = pkg2.default || pkg2;
|
|
18513
19423
|
var ScreenRecorder = class {
|
|
18514
19424
|
constructor(options) {
|
|
18515
19425
|
this.blobs = [];
|
|
@@ -18605,7 +19515,7 @@ var ScreenRecorder = class {
|
|
|
18605
19515
|
const rawBlob = new Blob(this.blobs, {
|
|
18606
19516
|
type: "video/webm"
|
|
18607
19517
|
});
|
|
18608
|
-
const fixedBlob = await (
|
|
19518
|
+
const fixedBlob = await fixWebmDuration2(rawBlob, this.duration);
|
|
18609
19519
|
const file = new File(
|
|
18610
19520
|
[fixedBlob],
|
|
18611
19521
|
`EP_${session.id}_screen_0.webm`,
|
|
@@ -18710,14 +19620,6 @@ function getGeolocation() {
|
|
|
18710
19620
|
});
|
|
18711
19621
|
}
|
|
18712
19622
|
|
|
18713
|
-
// src/utils/verifyVersion.ts
|
|
18714
|
-
function versionVerify() {
|
|
18715
|
-
const agentStr = window.navigator.userAgent.split("SEB/");
|
|
18716
|
-
if (agentStr.length > 1)
|
|
18717
|
-
return agentStr[1];
|
|
18718
|
-
else return "1.0.0.0";
|
|
18719
|
-
}
|
|
18720
|
-
|
|
18721
19623
|
// src/proctoring/Auth.ts
|
|
18722
19624
|
var Auth = class {
|
|
18723
19625
|
constructor(cpf, backend) {
|
|
@@ -18792,6 +19694,49 @@ var Auth = class {
|
|
|
18792
19694
|
}
|
|
18793
19695
|
};
|
|
18794
19696
|
|
|
19697
|
+
// src/new-flow/backend/SafeBrowserBackendService.ts
|
|
19698
|
+
var SafeBrowserBackendService = class {
|
|
19699
|
+
constructor() {
|
|
19700
|
+
this.baseUrl = "http://localhost:7485/Station/api";
|
|
19701
|
+
}
|
|
19702
|
+
async isAlive() {
|
|
19703
|
+
const paramsConfig = await this.makeRequestAxios({
|
|
19704
|
+
path: `/health`,
|
|
19705
|
+
method: "GET",
|
|
19706
|
+
jwt: this.token
|
|
19707
|
+
}).catch((error) => {
|
|
19708
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
19709
|
+
});
|
|
19710
|
+
return paramsConfig.data;
|
|
19711
|
+
}
|
|
19712
|
+
async setProctoringId(proctoringId2) {
|
|
19713
|
+
const paramsConfig = await this.makeRequestAxios({
|
|
19714
|
+
path: `/update-station/` + proctoringId2,
|
|
19715
|
+
method: "POST",
|
|
19716
|
+
jwt: this.token
|
|
19717
|
+
}).catch((error) => {
|
|
19718
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
19719
|
+
});
|
|
19720
|
+
return paramsConfig.data;
|
|
19721
|
+
}
|
|
19722
|
+
async makeRequestAxios(data) {
|
|
19723
|
+
const { path, method, body, jwt } = data;
|
|
19724
|
+
const resp = await axios_default.request({
|
|
19725
|
+
url: this.baseUrl + path,
|
|
19726
|
+
method,
|
|
19727
|
+
headers: {
|
|
19728
|
+
Authorization: `Bearer ${jwt}`,
|
|
19729
|
+
"Access-Control-Allow-Origin": "*"
|
|
19730
|
+
},
|
|
19731
|
+
data: body
|
|
19732
|
+
});
|
|
19733
|
+
if (resp.status >= 400) {
|
|
19734
|
+
throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
|
|
19735
|
+
}
|
|
19736
|
+
return resp;
|
|
19737
|
+
}
|
|
19738
|
+
};
|
|
19739
|
+
|
|
18795
19740
|
// node_modules/@microsoft/signalr/dist/esm/Errors.js
|
|
18796
19741
|
var HttpError = class extends Error {
|
|
18797
19742
|
/** Constructs a new instance of {@link @microsoft/signalr.HttpError}.
|
|
@@ -21668,7 +22613,8 @@ var _ExternalCameraChecker = class _ExternalCameraChecker {
|
|
|
21668
22613
|
this.onRealtimeAlertsCallback = onRealtimeAlertsCallback;
|
|
21669
22614
|
this.backend = new BackendService({
|
|
21670
22615
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
21671
|
-
token: context.token
|
|
22616
|
+
token: context.token,
|
|
22617
|
+
isRealtime: false
|
|
21672
22618
|
});
|
|
21673
22619
|
this.currentStep = -1 /* WAITING */;
|
|
21674
22620
|
}
|
|
@@ -22401,6 +23347,12 @@ var Proctoring = class {
|
|
|
22401
23347
|
this.serviceType = "Upload" /* Upload */;
|
|
22402
23348
|
this.onStopSharingScreenCallback = () => {
|
|
22403
23349
|
};
|
|
23350
|
+
/** Callback notificando que o usuário saiu do browser (minimizou/trocou de aba) */
|
|
23351
|
+
this.onVisibilityLostCallback = () => {
|
|
23352
|
+
};
|
|
23353
|
+
/** Callback notificando que o usuário retornou ao browser */
|
|
23354
|
+
this.onVisibilityRestoredCallback = () => {
|
|
23355
|
+
};
|
|
22404
23356
|
this.onLostFocusCallback = () => {
|
|
22405
23357
|
};
|
|
22406
23358
|
this.onLostFocusAlertRecorderCallback = (response) => {
|
|
@@ -22416,13 +23368,16 @@ var Proctoring = class {
|
|
|
22416
23368
|
await this.internalOnRealtimeAlerts(response);
|
|
22417
23369
|
return;
|
|
22418
23370
|
};
|
|
23371
|
+
this.realtimeAlertsToSend = [];
|
|
22419
23372
|
this.onBufferSizeErrorCallback = (cameraStream) => {
|
|
22420
23373
|
return;
|
|
22421
23374
|
};
|
|
22422
23375
|
var _a2;
|
|
22423
23376
|
this.backend = new BackendService({
|
|
22424
23377
|
type: context.type || "prod",
|
|
22425
|
-
token: context.token
|
|
23378
|
+
token: context.token,
|
|
23379
|
+
isRealtime: false
|
|
23380
|
+
// Default false, atualizado no start() via backend.setRealtime()
|
|
22426
23381
|
});
|
|
22427
23382
|
this.repository = new IndexDbSessionRepository("EasyProctorDb", "exams2");
|
|
22428
23383
|
this.repositoryDevices = new IndexDbSessionRepository(
|
|
@@ -22432,6 +23387,12 @@ var Proctoring = class {
|
|
|
22432
23387
|
((_a2 = this.context.credentials) == null ? void 0 : _a2.cpf) && (this.auth = new Auth(this.context.credentials.cpf, this.backend));
|
|
22433
23388
|
this.appChecker = new ExternalCameraChecker(this.context, (response) => this.onRealtimeAlertsCallback(response));
|
|
22434
23389
|
}
|
|
23390
|
+
setOnVisibilityLostCallback(cb) {
|
|
23391
|
+
this.onVisibilityLostCallback = cb;
|
|
23392
|
+
}
|
|
23393
|
+
setOnVisibilityRestoredCallback(cb) {
|
|
23394
|
+
this.onVisibilityRestoredCallback = cb;
|
|
23395
|
+
}
|
|
22435
23396
|
setOnStopSharingScreenCallback(cb) {
|
|
22436
23397
|
this.onStopSharingScreenCallback = async () => {
|
|
22437
23398
|
var _a2, _b, _c2, _d;
|
|
@@ -22511,6 +23472,8 @@ var Proctoring = class {
|
|
|
22511
23472
|
return 25 /* FocusOff */;
|
|
22512
23473
|
case "focus":
|
|
22513
23474
|
return 25 /* FocusOff */;
|
|
23475
|
+
case "error_upload_package":
|
|
23476
|
+
return 44 /* RealtimeOffline */;
|
|
22514
23477
|
default:
|
|
22515
23478
|
return null;
|
|
22516
23479
|
}
|
|
@@ -22551,19 +23514,53 @@ var Proctoring = class {
|
|
|
22551
23514
|
};
|
|
22552
23515
|
await verifyFace(1);
|
|
22553
23516
|
}
|
|
23517
|
+
async sendPendingRealtimeAlerts() {
|
|
23518
|
+
let alertsToSend = [...this.realtimeAlertsToSend];
|
|
23519
|
+
for (const alert of alertsToSend) {
|
|
23520
|
+
try {
|
|
23521
|
+
if (alert.status === "ALERT") {
|
|
23522
|
+
await this.backend.startRealtimeAlert({
|
|
23523
|
+
proctoringId: this.proctoringId,
|
|
23524
|
+
begin: alert.begin,
|
|
23525
|
+
end: alert.end,
|
|
23526
|
+
alert: this.convertRealtimeCategoryToAlertCategory(alert.category)
|
|
23527
|
+
});
|
|
23528
|
+
} else if (alert.status === "OK") {
|
|
23529
|
+
await this.stopRealtimeAlert(alert);
|
|
23530
|
+
}
|
|
23531
|
+
this.realtimeAlertsToSend.splice(this.realtimeAlertsToSend.indexOf(alert), 1);
|
|
23532
|
+
} catch (error) {
|
|
23533
|
+
console.log("error sendPendingRealtimeAlerts", error);
|
|
23534
|
+
this.realtimeAlertsToSend.push(alert);
|
|
23535
|
+
break;
|
|
23536
|
+
}
|
|
23537
|
+
}
|
|
23538
|
+
}
|
|
22554
23539
|
async internalOnRealtimeAlerts(response) {
|
|
22555
|
-
if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream" || response.type === "lost_focus" || response.type === "focus")) {
|
|
23540
|
+
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")) {
|
|
23541
|
+
await this.sendPendingRealtimeAlerts();
|
|
22556
23542
|
if (response.status === "ALERT") {
|
|
22557
23543
|
if (this.allRecorders.cameraRecorder.stopped) return;
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
23544
|
+
try {
|
|
23545
|
+
await this.backend.startRealtimeAlert({
|
|
23546
|
+
proctoringId: this.proctoringId,
|
|
23547
|
+
begin: response.begin,
|
|
23548
|
+
end: response.end,
|
|
23549
|
+
alert: this.convertRealtimeCategoryToAlertCategory(response.category)
|
|
23550
|
+
});
|
|
23551
|
+
} catch (error) {
|
|
23552
|
+
console.log("error startRealtimeAlert " + response.category);
|
|
23553
|
+
console.log("error startRealtimeAlert adicionando na fila", error);
|
|
23554
|
+
this.realtimeAlertsToSend.push(response);
|
|
23555
|
+
}
|
|
22564
23556
|
} else if (response.status === "OK") {
|
|
22565
23557
|
if (this.allRecorders.cameraRecorder.stopped && response.description !== "face_stop") return;
|
|
22566
|
-
|
|
23558
|
+
try {
|
|
23559
|
+
await this.stopRealtimeAlert(response);
|
|
23560
|
+
} catch (error) {
|
|
23561
|
+
console.log("error stopRealtimeAlert adicionando na fila", error);
|
|
23562
|
+
this.realtimeAlertsToSend.push(response);
|
|
23563
|
+
}
|
|
22567
23564
|
}
|
|
22568
23565
|
}
|
|
22569
23566
|
}
|
|
@@ -22571,8 +23568,8 @@ var Proctoring = class {
|
|
|
22571
23568
|
this.setOnLostFocusAlertRecorderCallback();
|
|
22572
23569
|
this.setOnFocusAlertRecorderCallback();
|
|
22573
23570
|
this.onRealtimeAlertsCallback = async (response) => {
|
|
22574
|
-
await this.internalOnRealtimeAlerts(response);
|
|
22575
23571
|
options.data && options.data(response);
|
|
23572
|
+
await this.internalOnRealtimeAlerts(response);
|
|
22576
23573
|
};
|
|
22577
23574
|
}
|
|
22578
23575
|
setOnBufferSizeErrorCallback(cb) {
|
|
@@ -22668,8 +23665,10 @@ var Proctoring = class {
|
|
|
22668
23665
|
await this.repositoryDevices.save({ ...devices, id: "devices" });
|
|
22669
23666
|
this.sessionOptions = { ...getDefaultProctoringOptions, ...options };
|
|
22670
23667
|
this.videoOptions = validatePartialVideoOptions(_videoOptions);
|
|
23668
|
+
if (this.sessionOptions.proctoringType === "REALTIME") {
|
|
23669
|
+
this.backend.setRealtime(true);
|
|
23670
|
+
}
|
|
22671
23671
|
await this.initConfig(options.useGeolocation);
|
|
22672
|
-
await this.verifyBrowser();
|
|
22673
23672
|
this.sessionOptions.captureScreen && await this.verifyMultipleMonitors(this.sessionOptions);
|
|
22674
23673
|
try {
|
|
22675
23674
|
if (options == null ? void 0 : options.useSpyScan) {
|
|
@@ -22678,6 +23677,13 @@ var Proctoring = class {
|
|
|
22678
23677
|
} catch (error) {
|
|
22679
23678
|
throw SPY_SCAN_API_NOT_FOUND;
|
|
22680
23679
|
}
|
|
23680
|
+
try {
|
|
23681
|
+
if (options == null ? void 0 : options.useSafeBrowserAPI) {
|
|
23682
|
+
await this.safeBrowserBackendService.isAlive();
|
|
23683
|
+
}
|
|
23684
|
+
} catch (error) {
|
|
23685
|
+
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
23686
|
+
}
|
|
22681
23687
|
if (this.state != "Stop" /* Stop */) {
|
|
22682
23688
|
throw PROCTORING_ALREADY_STARTED;
|
|
22683
23689
|
}
|
|
@@ -22713,6 +23719,27 @@ var Proctoring = class {
|
|
|
22713
23719
|
this.allRecorders.cameraRecorder.setProctoringId(this.proctoringId);
|
|
22714
23720
|
this.allRecorders.noiseRecorder.setProctoringId(this.proctoringId);
|
|
22715
23721
|
this.proctoringSession.setProctoringId(this.proctoringId);
|
|
23722
|
+
try {
|
|
23723
|
+
if (options == null ? void 0 : options.useSafeBrowserAPI) {
|
|
23724
|
+
await this.safeBrowserBackendService.setProctoringId(this.proctoringId);
|
|
23725
|
+
}
|
|
23726
|
+
} catch (error) {
|
|
23727
|
+
throw SAFE_BROWSER_API_NOT_FOUND;
|
|
23728
|
+
}
|
|
23729
|
+
this.allRecorders.cameraRecorder.onVisibilityRestored = () => {
|
|
23730
|
+
console.log("[Proctoring] Usu\xE1rio retornou ao browser.");
|
|
23731
|
+
this.onVisibilityRestoredCallback();
|
|
23732
|
+
};
|
|
23733
|
+
if (this.sessionOptions.proctoringType === "REALTIME" && !isSafeBrowser()) {
|
|
23734
|
+
try {
|
|
23735
|
+
await BackgroundUploadService.recoverPendingUploads(
|
|
23736
|
+
this.backend,
|
|
23737
|
+
this.context.token
|
|
23738
|
+
);
|
|
23739
|
+
} catch (e3) {
|
|
23740
|
+
console.warn("[Proctoring] Erro ao recuperar chunks de sess\xE3o anterior:", e3);
|
|
23741
|
+
}
|
|
23742
|
+
}
|
|
22716
23743
|
try {
|
|
22717
23744
|
console.log("Starting recorders");
|
|
22718
23745
|
await this.recorder.startAll();
|
|
@@ -22792,6 +23819,7 @@ Error: ${error}`
|
|
|
22792
23819
|
this.spyCam && this.spyCam.stopCheckSpyCam();
|
|
22793
23820
|
this.appChecker && await this.appChecker.disconnectWebSocket();
|
|
22794
23821
|
await this.recorder.saveAllOnSession();
|
|
23822
|
+
await this.sendPendingRealtimeAlerts();
|
|
22795
23823
|
await this.repository.save(this.proctoringSession);
|
|
22796
23824
|
let uploader;
|
|
22797
23825
|
let uploaderServices;
|
|
@@ -22934,7 +23962,7 @@ Error: ` + error
|
|
|
22934
23962
|
}
|
|
22935
23963
|
}
|
|
22936
23964
|
async initConfig(useGeolocation) {
|
|
22937
|
-
var _a2;
|
|
23965
|
+
var _a2, _b;
|
|
22938
23966
|
try {
|
|
22939
23967
|
const paramsConfig = await this.backend.getParamsConfig(
|
|
22940
23968
|
this.context
|
|
@@ -22966,6 +23994,9 @@ Error: ` + error
|
|
|
22966
23994
|
onRealtimeAlertsCallback: (response) => this.onRealtimeAlertsCallback(response)
|
|
22967
23995
|
});
|
|
22968
23996
|
}
|
|
23997
|
+
if ((_b = this.sessionOptions) == null ? void 0 : _b.useSafeBrowserAPI) {
|
|
23998
|
+
this.safeBrowserBackendService = new SafeBrowserBackendService();
|
|
23999
|
+
}
|
|
22969
24000
|
} catch (err) {
|
|
22970
24001
|
this.proctoringId && trackers.registerError(this.proctoringId, "Erro no initConfig!");
|
|
22971
24002
|
await this.cancel();
|
|
@@ -23073,7 +24104,8 @@ var _SignTerm = class _SignTerm {
|
|
|
23073
24104
|
constructor(context) {
|
|
23074
24105
|
this.backend = new BackendService({
|
|
23075
24106
|
type: (context == null ? void 0 : context.type) || "prod",
|
|
23076
|
-
token: context.token
|
|
24107
|
+
token: context.token,
|
|
24108
|
+
isRealtime: false
|
|
23077
24109
|
});
|
|
23078
24110
|
}
|
|
23079
24111
|
async signInTerms() {
|
|
@@ -23303,6 +24335,8 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
23303
24335
|
const onBufferSizeError = proctoring.setOnBufferSizeErrorCallback.bind(proctoring);
|
|
23304
24336
|
const onStopSharingScreen = proctoring.setOnStopSharingScreenCallback.bind(proctoring);
|
|
23305
24337
|
const onRealtimeAlerts = proctoring.onRealtimeAlerts.bind(proctoring);
|
|
24338
|
+
const onVisibilityLost = proctoring.setOnVisibilityLostCallback.bind(proctoring);
|
|
24339
|
+
const onVisibilityRestored = proctoring.setOnVisibilityRestoredCallback.bind(proctoring);
|
|
23306
24340
|
const signInTerms = signTerm.signInTerms.bind(signTerm);
|
|
23307
24341
|
const checkDevices = checker.checkDevices.bind(checker);
|
|
23308
24342
|
const checkExternalCamera = proctoring.appChecker.checkExternalCamera.bind(proctoring.appChecker);
|
|
@@ -23340,7 +24374,9 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
|
|
|
23340
24374
|
startExternalCameraSession,
|
|
23341
24375
|
takeExternalCameraPicture,
|
|
23342
24376
|
goToExternalCameraPositionStep,
|
|
23343
|
-
startExternalCameraTransmission
|
|
24377
|
+
startExternalCameraTransmission,
|
|
24378
|
+
onVisibilityLost,
|
|
24379
|
+
onVisibilityRestored
|
|
23344
24380
|
};
|
|
23345
24381
|
}
|
|
23346
24382
|
|