easyproctor-hml 2.7.6 → 2.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.js +75 -37
- package/index.js +75 -37
- package/new-flow/chunk/BackgroundUploadService.d.ts +1 -0
- package/new-flow/chunk/ChunkStorageService.d.ts +2 -1
- package/package.json +1 -1
- package/unpkg/easyproctor.min.js +32 -32
package/esm/index.js
CHANGED
|
@@ -13317,6 +13317,16 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
13317
13317
|
constructor() {
|
|
13318
13318
|
this.db = null;
|
|
13319
13319
|
}
|
|
13320
|
+
/**
|
|
13321
|
+
* WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
|
|
13322
|
+
* ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
|
|
13323
|
+
* round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
|
|
13324
|
+
*/
|
|
13325
|
+
static detachedArrayBufferCopy(src) {
|
|
13326
|
+
const next = new Uint8Array(src.byteLength);
|
|
13327
|
+
next.set(new Uint8Array(src));
|
|
13328
|
+
return next.buffer;
|
|
13329
|
+
}
|
|
13320
13330
|
/**
|
|
13321
13331
|
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
13322
13332
|
*/
|
|
@@ -13356,10 +13366,18 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
13356
13366
|
*/
|
|
13357
13367
|
async saveChunk(chunk) {
|
|
13358
13368
|
const db = await this.connect();
|
|
13369
|
+
const record = {
|
|
13370
|
+
proctoringId: chunk.proctoringId,
|
|
13371
|
+
chunkIndex: chunk.chunkIndex,
|
|
13372
|
+
arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
|
|
13373
|
+
timestamp: chunk.timestamp,
|
|
13374
|
+
uploaded: chunk.uploaded,
|
|
13375
|
+
mimeType: chunk.mimeType
|
|
13376
|
+
};
|
|
13359
13377
|
return new Promise((resolve, reject) => {
|
|
13360
13378
|
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
13361
13379
|
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
13362
|
-
const request = store.add(
|
|
13380
|
+
const request = store.add(record);
|
|
13363
13381
|
request.onsuccess = () => {
|
|
13364
13382
|
resolve(request.result);
|
|
13365
13383
|
};
|
|
@@ -13430,8 +13448,16 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
13430
13448
|
resolve();
|
|
13431
13449
|
return;
|
|
13432
13450
|
}
|
|
13433
|
-
|
|
13434
|
-
|
|
13451
|
+
const updated = {
|
|
13452
|
+
id: chunk.id,
|
|
13453
|
+
proctoringId: chunk.proctoringId,
|
|
13454
|
+
chunkIndex: chunk.chunkIndex,
|
|
13455
|
+
arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
|
|
13456
|
+
timestamp: chunk.timestamp,
|
|
13457
|
+
uploaded: 1,
|
|
13458
|
+
mimeType: chunk.mimeType
|
|
13459
|
+
};
|
|
13460
|
+
const putRequest = store.put(updated);
|
|
13435
13461
|
putRequest.onsuccess = () => resolve();
|
|
13436
13462
|
putRequest.onerror = () => {
|
|
13437
13463
|
var _a2;
|
|
@@ -13551,8 +13577,8 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
13551
13577
|
}
|
|
13552
13578
|
};
|
|
13553
13579
|
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
13554
|
-
/**
|
|
13555
|
-
_ChunkStorageService.DB_VERSION =
|
|
13580
|
+
/** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
|
|
13581
|
+
_ChunkStorageService.DB_VERSION = 4;
|
|
13556
13582
|
_ChunkStorageService.STORE_NAME = "chunks";
|
|
13557
13583
|
var ChunkStorageService = _ChunkStorageService;
|
|
13558
13584
|
|
|
@@ -13712,7 +13738,8 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13712
13738
|
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
13713
13739
|
*/
|
|
13714
13740
|
async processQueue(isFinal = false) {
|
|
13715
|
-
var _a2, _b;
|
|
13741
|
+
var _a2, _b, _c2, _d, _e3, _f;
|
|
13742
|
+
console.log(`[BackgroundUpload] processQueue init`);
|
|
13716
13743
|
if (this.isProcessing) return;
|
|
13717
13744
|
this.isProcessing = true;
|
|
13718
13745
|
try {
|
|
@@ -13725,8 +13752,10 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13725
13752
|
return;
|
|
13726
13753
|
}
|
|
13727
13754
|
}
|
|
13755
|
+
console.log(`[BackgroundUpload] processQueue syncOffset ok`);
|
|
13728
13756
|
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
13729
13757
|
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
13758
|
+
console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
|
|
13730
13759
|
if (pendingChunks.length === 0 && !isFinal) {
|
|
13731
13760
|
this.isProcessing = false;
|
|
13732
13761
|
return;
|
|
@@ -13735,31 +13764,31 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13735
13764
|
let virtualStart = this.totalBytesPurged;
|
|
13736
13765
|
const chunksWithMeta = allChunks.map((c3) => {
|
|
13737
13766
|
const start = virtualStart;
|
|
13738
|
-
const end = start + c3.
|
|
13739
|
-
virtualStart += c3.
|
|
13767
|
+
const end = start + c3.arrayBuffer.byteLength - 1;
|
|
13768
|
+
virtualStart += c3.arrayBuffer.byteLength;
|
|
13740
13769
|
return { chunk: c3, start, end };
|
|
13741
13770
|
});
|
|
13742
|
-
|
|
13771
|
+
const combinedBufferParts = [];
|
|
13743
13772
|
let lastProcessedChunkId = null;
|
|
13744
13773
|
let finalChunkIndex = 0;
|
|
13745
|
-
|
|
13774
|
+
const mimeType = (_d = (_c2 = (_a2 = pendingChunks[0]) == null ? void 0 : _a2.mimeType) != null ? _c2 : (_b = allChunks[allChunks.length - 1]) == null ? void 0 : _b.mimeType) != null ? _d : "video/webm";
|
|
13746
13775
|
for (const meta of chunksWithMeta) {
|
|
13747
13776
|
if (this.currentOffset > meta.end) continue;
|
|
13748
13777
|
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
13749
|
-
const chunkSlice = meta.chunk.
|
|
13750
|
-
|
|
13778
|
+
const chunkSlice = meta.chunk.arrayBuffer.slice(sliceStart);
|
|
13779
|
+
combinedBufferParts.push(chunkSlice);
|
|
13751
13780
|
lastProcessedChunkId = meta.chunk.id;
|
|
13752
13781
|
finalChunkIndex = meta.chunk.chunkIndex;
|
|
13753
13782
|
}
|
|
13754
|
-
if (
|
|
13783
|
+
if (combinedBufferParts.length === 0 && !isFinal) {
|
|
13755
13784
|
this.isProcessing = false;
|
|
13756
13785
|
return;
|
|
13757
13786
|
}
|
|
13758
|
-
|
|
13759
|
-
let sendableSize =
|
|
13787
|
+
const fullBuffer = _BackgroundUploadService.concatArrayBuffers(combinedBufferParts);
|
|
13788
|
+
let sendableSize = fullBuffer.byteLength;
|
|
13760
13789
|
let totalSizeForHeader = void 0;
|
|
13761
13790
|
if (!isFinal) {
|
|
13762
|
-
sendableSize = Math.floor(
|
|
13791
|
+
sendableSize = Math.floor(fullBuffer.byteLength / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
13763
13792
|
if (sendableSize === 0) {
|
|
13764
13793
|
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
13765
13794
|
this.isProcessing = false;
|
|
@@ -13768,20 +13797,23 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13768
13797
|
} else {
|
|
13769
13798
|
totalSizeForHeader = virtualStart;
|
|
13770
13799
|
}
|
|
13771
|
-
const
|
|
13800
|
+
const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
|
|
13772
13801
|
try {
|
|
13773
|
-
await this.uploadData(
|
|
13802
|
+
await this.uploadData(bufferToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
13774
13803
|
for (const meta of chunksWithMeta) {
|
|
13775
13804
|
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
13776
13805
|
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
13777
13806
|
this.retryCount.delete(meta.chunk.id);
|
|
13778
|
-
(
|
|
13807
|
+
(_e3 = this.onChunkUploaded) == null ? void 0 : _e3.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
13779
13808
|
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
13780
13809
|
}
|
|
13781
13810
|
}
|
|
13782
13811
|
if (this.config.cleanAfterUpload) {
|
|
13783
13812
|
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
13784
|
-
const sizePurged = chunksToClear.reduce(
|
|
13813
|
+
const sizePurged = chunksToClear.reduce(
|
|
13814
|
+
(acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
|
|
13815
|
+
0
|
|
13816
|
+
);
|
|
13785
13817
|
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
13786
13818
|
if (sizePurged > 0) {
|
|
13787
13819
|
this.totalBytesPurged += sizePurged;
|
|
@@ -13794,7 +13826,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13794
13826
|
}
|
|
13795
13827
|
} catch (error) {
|
|
13796
13828
|
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
13797
|
-
(
|
|
13829
|
+
(_f = this.onUploadError) == null ? void 0 : _f.call(this, lastProcessedChunkId || 0, error);
|
|
13798
13830
|
}
|
|
13799
13831
|
} catch (error) {
|
|
13800
13832
|
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
@@ -13805,7 +13837,18 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13805
13837
|
/**
|
|
13806
13838
|
* Faz o upload bruto de dados para a sessão GCS.
|
|
13807
13839
|
*/
|
|
13808
|
-
|
|
13840
|
+
static concatArrayBuffers(parts) {
|
|
13841
|
+
if (parts.length === 0) return new ArrayBuffer(0);
|
|
13842
|
+
const total = parts.reduce((sum, p3) => sum + p3.byteLength, 0);
|
|
13843
|
+
const merged = new Uint8Array(total);
|
|
13844
|
+
let offset = 0;
|
|
13845
|
+
for (const p3 of parts) {
|
|
13846
|
+
merged.set(new Uint8Array(p3), offset);
|
|
13847
|
+
offset += p3.byteLength;
|
|
13848
|
+
}
|
|
13849
|
+
return merged.buffer.byteLength === total ? merged.buffer : merged.buffer.slice(merged.byteOffset, merged.byteOffset + merged.byteLength);
|
|
13850
|
+
}
|
|
13851
|
+
async uploadData(data, mimeType, chunkIndex, totalSize) {
|
|
13809
13852
|
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
13810
13853
|
if (!this.sessionUrl) {
|
|
13811
13854
|
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
@@ -13837,15 +13880,16 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13837
13880
|
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
13838
13881
|
}
|
|
13839
13882
|
const start = this.currentOffset;
|
|
13840
|
-
const end = start +
|
|
13883
|
+
const end = start + data.byteLength - 1;
|
|
13841
13884
|
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
13842
|
-
const contentRangeHeader =
|
|
13843
|
-
console.log(
|
|
13885
|
+
const contentRangeHeader = data.byteLength === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
13886
|
+
console.log(
|
|
13887
|
+
`[BackgroundUpload] Enviando ${data.byteLength > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${data.byteLength})`
|
|
13888
|
+
);
|
|
13844
13889
|
const response = await fetch(this.sessionUrl, {
|
|
13845
13890
|
method: "PUT",
|
|
13846
13891
|
headers: { "Content-Range": contentRangeHeader },
|
|
13847
|
-
body:
|
|
13848
|
-
// Usa null para garantir corpo vazio se necessário
|
|
13892
|
+
body: data.byteLength > 0 ? data : null
|
|
13849
13893
|
});
|
|
13850
13894
|
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
13851
13895
|
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
@@ -13858,13 +13902,13 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
13858
13902
|
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
13859
13903
|
this.currentOffset = lastByte + 1;
|
|
13860
13904
|
} else {
|
|
13861
|
-
this.currentOffset +=
|
|
13905
|
+
this.currentOffset += data.byteLength;
|
|
13862
13906
|
}
|
|
13863
13907
|
this.saveSessionState();
|
|
13864
13908
|
trackers.registerUploadFile(
|
|
13865
13909
|
this.proctoringId,
|
|
13866
13910
|
`GCS Stream Upload
|
|
13867
|
-
Size: ${
|
|
13911
|
+
Size: ${data.byteLength}
|
|
13868
13912
|
Range: ${start}-${end}
|
|
13869
13913
|
Last Index: ${chunkIndex}`,
|
|
13870
13914
|
"CameraChunk"
|
|
@@ -14407,10 +14451,11 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
14407
14451
|
const savePromise = (async () => {
|
|
14408
14452
|
var _a2;
|
|
14409
14453
|
try {
|
|
14454
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
14410
14455
|
await this.chunkStorage.saveChunk({
|
|
14411
14456
|
proctoringId: this.proctoringId,
|
|
14412
14457
|
chunkIndex: this.chunkIndex,
|
|
14413
|
-
|
|
14458
|
+
arrayBuffer,
|
|
14414
14459
|
timestamp: Date.now(),
|
|
14415
14460
|
uploaded: 0,
|
|
14416
14461
|
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
@@ -23730,15 +23775,11 @@ Error: ${error}`
|
|
|
23730
23775
|
await this.recorder.stopAll();
|
|
23731
23776
|
this.spyCam && this.spyCam.stopCheckSpyCam();
|
|
23732
23777
|
this.appChecker && await this.appChecker.disconnectWebSocket();
|
|
23733
|
-
trackers.registerError(this.proctoringId, `finish saveAllOnSession`);
|
|
23734
23778
|
await this.recorder.saveAllOnSession();
|
|
23735
|
-
trackers.registerError(this.proctoringId, `finish sendPendingRealtimeAlerts`);
|
|
23736
23779
|
await this.sendPendingRealtimeAlerts();
|
|
23737
|
-
trackers.registerError(this.proctoringId, `finish this.repository.save`);
|
|
23738
23780
|
await this.repository.save(this.proctoringSession);
|
|
23739
23781
|
let uploader;
|
|
23740
23782
|
let uploaderServices;
|
|
23741
|
-
trackers.registerError(this.proctoringId, `finish uploader`);
|
|
23742
23783
|
if (versionVerify() !== "1.0.0.0") {
|
|
23743
23784
|
uploader = new ProctoringUploader(
|
|
23744
23785
|
this.proctoringSession,
|
|
@@ -23812,7 +23853,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
23812
23853
|
this.serviceType
|
|
23813
23854
|
);
|
|
23814
23855
|
}
|
|
23815
|
-
trackers.registerError(this.proctoringId, `finish uploader success`);
|
|
23816
23856
|
if (this.proctoringSession.alerts.length > 0) {
|
|
23817
23857
|
await this.backend.saveAlerts(this.context, this.proctoringSession).catch((err) => {
|
|
23818
23858
|
trackers.registerFinish(
|
|
@@ -23822,7 +23862,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
23822
23862
|
);
|
|
23823
23863
|
});
|
|
23824
23864
|
}
|
|
23825
|
-
trackers.registerError(this.proctoringId, `finish saveAlerts ok`);
|
|
23826
23865
|
await this.backend.finishAndSendUrls(this.context).then((finishResponse) => {
|
|
23827
23866
|
var _a2, _b, _c2, _d;
|
|
23828
23867
|
trackers.registerFinish(this.proctoringSession.id, true, "");
|
|
@@ -23834,7 +23873,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
23834
23873
|
"finish error: " + error
|
|
23835
23874
|
);
|
|
23836
23875
|
});
|
|
23837
|
-
trackers.registerError(this.proctoringId, `finish call`);
|
|
23838
23876
|
if (this.appChecker) {
|
|
23839
23877
|
const externalSessionId = this.appChecker.getExternalCameraSessionId();
|
|
23840
23878
|
if (externalSessionId != "null") {
|
package/index.js
CHANGED
|
@@ -31414,6 +31414,16 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
31414
31414
|
constructor() {
|
|
31415
31415
|
this.db = null;
|
|
31416
31416
|
}
|
|
31417
|
+
/**
|
|
31418
|
+
* WebKit pode falhar em IDB.add/put com "Error preparing Blob/File data..." se o
|
|
31419
|
+
* ArrayBuffer ainda estiver associado ao Blob (ex.: blob.arrayBuffer()) ou no
|
|
31420
|
+
* round-trip get→put do mesmo registro. Esta cópia remove qualquer vínculo.
|
|
31421
|
+
*/
|
|
31422
|
+
static detachedArrayBufferCopy(src) {
|
|
31423
|
+
const next = new Uint8Array(src.byteLength);
|
|
31424
|
+
next.set(new Uint8Array(src));
|
|
31425
|
+
return next.buffer;
|
|
31426
|
+
}
|
|
31417
31427
|
/**
|
|
31418
31428
|
* Abre a conexão com o IndexedDB, criando o banco e o object store se necessário.
|
|
31419
31429
|
*/
|
|
@@ -31453,10 +31463,18 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
31453
31463
|
*/
|
|
31454
31464
|
async saveChunk(chunk) {
|
|
31455
31465
|
const db = await this.connect();
|
|
31466
|
+
const record = {
|
|
31467
|
+
proctoringId: chunk.proctoringId,
|
|
31468
|
+
chunkIndex: chunk.chunkIndex,
|
|
31469
|
+
arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
|
|
31470
|
+
timestamp: chunk.timestamp,
|
|
31471
|
+
uploaded: chunk.uploaded,
|
|
31472
|
+
mimeType: chunk.mimeType
|
|
31473
|
+
};
|
|
31456
31474
|
return new Promise((resolve, reject) => {
|
|
31457
31475
|
const transaction = db.transaction(_ChunkStorageService.STORE_NAME, "readwrite");
|
|
31458
31476
|
const store = transaction.objectStore(_ChunkStorageService.STORE_NAME);
|
|
31459
|
-
const request = store.add(
|
|
31477
|
+
const request = store.add(record);
|
|
31460
31478
|
request.onsuccess = () => {
|
|
31461
31479
|
resolve(request.result);
|
|
31462
31480
|
};
|
|
@@ -31527,8 +31545,16 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
31527
31545
|
resolve();
|
|
31528
31546
|
return;
|
|
31529
31547
|
}
|
|
31530
|
-
|
|
31531
|
-
|
|
31548
|
+
const updated = {
|
|
31549
|
+
id: chunk.id,
|
|
31550
|
+
proctoringId: chunk.proctoringId,
|
|
31551
|
+
chunkIndex: chunk.chunkIndex,
|
|
31552
|
+
arrayBuffer: _ChunkStorageService.detachedArrayBufferCopy(chunk.arrayBuffer),
|
|
31553
|
+
timestamp: chunk.timestamp,
|
|
31554
|
+
uploaded: 1,
|
|
31555
|
+
mimeType: chunk.mimeType
|
|
31556
|
+
};
|
|
31557
|
+
const putRequest = store.put(updated);
|
|
31532
31558
|
putRequest.onsuccess = () => resolve();
|
|
31533
31559
|
putRequest.onerror = () => {
|
|
31534
31560
|
var _a2;
|
|
@@ -31648,8 +31674,8 @@ var _ChunkStorageService = class _ChunkStorageService {
|
|
|
31648
31674
|
}
|
|
31649
31675
|
};
|
|
31650
31676
|
_ChunkStorageService.DB_NAME = "EasyProctorChunksDb";
|
|
31651
|
-
/**
|
|
31652
|
-
_ChunkStorageService.DB_VERSION =
|
|
31677
|
+
/** v2: índices numéricos; v3: ArrayBuffer no payload; v4: cópia explícita de bytes (WebKit/iOS) */
|
|
31678
|
+
_ChunkStorageService.DB_VERSION = 4;
|
|
31653
31679
|
_ChunkStorageService.STORE_NAME = "chunks";
|
|
31654
31680
|
var ChunkStorageService = _ChunkStorageService;
|
|
31655
31681
|
|
|
@@ -31809,7 +31835,8 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31809
31835
|
* @param isFinal Se true, não alinha a 256KB e fecha a sessão com /TOTAL no header.
|
|
31810
31836
|
*/
|
|
31811
31837
|
async processQueue(isFinal = false) {
|
|
31812
|
-
var _a2, _b;
|
|
31838
|
+
var _a2, _b, _c2, _d, _e3, _f;
|
|
31839
|
+
console.log(`[BackgroundUpload] processQueue init`);
|
|
31813
31840
|
if (this.isProcessing) return;
|
|
31814
31841
|
this.isProcessing = true;
|
|
31815
31842
|
try {
|
|
@@ -31822,8 +31849,10 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31822
31849
|
return;
|
|
31823
31850
|
}
|
|
31824
31851
|
}
|
|
31852
|
+
console.log(`[BackgroundUpload] processQueue syncOffset ok`);
|
|
31825
31853
|
const allChunks = await this.chunkStorage.getAllChunks(this.proctoringId);
|
|
31826
31854
|
const pendingChunks = allChunks.filter((c3) => c3.uploaded === 0);
|
|
31855
|
+
console.log(`[BackgroundUpload] processQueue getAllChunks ok`);
|
|
31827
31856
|
if (pendingChunks.length === 0 && !isFinal) {
|
|
31828
31857
|
this.isProcessing = false;
|
|
31829
31858
|
return;
|
|
@@ -31832,31 +31861,31 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31832
31861
|
let virtualStart = this.totalBytesPurged;
|
|
31833
31862
|
const chunksWithMeta = allChunks.map((c3) => {
|
|
31834
31863
|
const start = virtualStart;
|
|
31835
|
-
const end = start + c3.
|
|
31836
|
-
virtualStart += c3.
|
|
31864
|
+
const end = start + c3.arrayBuffer.byteLength - 1;
|
|
31865
|
+
virtualStart += c3.arrayBuffer.byteLength;
|
|
31837
31866
|
return { chunk: c3, start, end };
|
|
31838
31867
|
});
|
|
31839
|
-
|
|
31868
|
+
const combinedBufferParts = [];
|
|
31840
31869
|
let lastProcessedChunkId = null;
|
|
31841
31870
|
let finalChunkIndex = 0;
|
|
31842
|
-
|
|
31871
|
+
const mimeType = (_d = (_c2 = (_a2 = pendingChunks[0]) == null ? void 0 : _a2.mimeType) != null ? _c2 : (_b = allChunks[allChunks.length - 1]) == null ? void 0 : _b.mimeType) != null ? _d : "video/webm";
|
|
31843
31872
|
for (const meta of chunksWithMeta) {
|
|
31844
31873
|
if (this.currentOffset > meta.end) continue;
|
|
31845
31874
|
const sliceStart = Math.max(0, this.currentOffset - meta.start);
|
|
31846
|
-
const chunkSlice = meta.chunk.
|
|
31847
|
-
|
|
31875
|
+
const chunkSlice = meta.chunk.arrayBuffer.slice(sliceStart);
|
|
31876
|
+
combinedBufferParts.push(chunkSlice);
|
|
31848
31877
|
lastProcessedChunkId = meta.chunk.id;
|
|
31849
31878
|
finalChunkIndex = meta.chunk.chunkIndex;
|
|
31850
31879
|
}
|
|
31851
|
-
if (
|
|
31880
|
+
if (combinedBufferParts.length === 0 && !isFinal) {
|
|
31852
31881
|
this.isProcessing = false;
|
|
31853
31882
|
return;
|
|
31854
31883
|
}
|
|
31855
|
-
|
|
31856
|
-
let sendableSize =
|
|
31884
|
+
const fullBuffer = _BackgroundUploadService.concatArrayBuffers(combinedBufferParts);
|
|
31885
|
+
let sendableSize = fullBuffer.byteLength;
|
|
31857
31886
|
let totalSizeForHeader = void 0;
|
|
31858
31887
|
if (!isFinal) {
|
|
31859
|
-
sendableSize = Math.floor(
|
|
31888
|
+
sendableSize = Math.floor(fullBuffer.byteLength / this.GCS_CHUNK_SIZE) * this.GCS_CHUNK_SIZE;
|
|
31860
31889
|
if (sendableSize === 0) {
|
|
31861
31890
|
console.log("[BackgroundUpload] Dados insuficientes para atingir 256KB. Aguardando novo chunk...");
|
|
31862
31891
|
this.isProcessing = false;
|
|
@@ -31865,20 +31894,23 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31865
31894
|
} else {
|
|
31866
31895
|
totalSizeForHeader = virtualStart;
|
|
31867
31896
|
}
|
|
31868
|
-
const
|
|
31897
|
+
const bufferToSend = sendableSize < fullBuffer.byteLength ? fullBuffer.slice(0, sendableSize) : fullBuffer;
|
|
31869
31898
|
try {
|
|
31870
|
-
await this.uploadData(
|
|
31899
|
+
await this.uploadData(bufferToSend, mimeType, finalChunkIndex, totalSizeForHeader);
|
|
31871
31900
|
for (const meta of chunksWithMeta) {
|
|
31872
31901
|
if (meta.chunk.uploaded === 0 && meta.end < this.currentOffset) {
|
|
31873
31902
|
await this.chunkStorage.markAsUploaded(meta.chunk.id);
|
|
31874
31903
|
this.retryCount.delete(meta.chunk.id);
|
|
31875
|
-
(
|
|
31904
|
+
(_e3 = this.onChunkUploaded) == null ? void 0 : _e3.call(this, meta.chunk.id, meta.chunk.chunkIndex);
|
|
31876
31905
|
console.log(`[BackgroundUpload] Chunk ${meta.chunk.chunkIndex} marcado como enviado.`);
|
|
31877
31906
|
}
|
|
31878
31907
|
}
|
|
31879
31908
|
if (this.config.cleanAfterUpload) {
|
|
31880
31909
|
const chunksToClear = chunksWithMeta.filter((meta) => meta.chunk.uploaded === 1 || meta.chunk.uploaded === 0 && meta.end < this.currentOffset);
|
|
31881
|
-
const sizePurged = chunksToClear.reduce(
|
|
31910
|
+
const sizePurged = chunksToClear.reduce(
|
|
31911
|
+
(acc, meta) => acc + meta.chunk.arrayBuffer.byteLength,
|
|
31912
|
+
0
|
|
31913
|
+
);
|
|
31882
31914
|
await this.chunkStorage.clearUploadedChunks(this.proctoringId);
|
|
31883
31915
|
if (sizePurged > 0) {
|
|
31884
31916
|
this.totalBytesPurged += sizePurged;
|
|
@@ -31891,7 +31923,7 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31891
31923
|
}
|
|
31892
31924
|
} catch (error) {
|
|
31893
31925
|
console.error("[BackgroundUpload] Falha no upload:", error);
|
|
31894
|
-
(
|
|
31926
|
+
(_f = this.onUploadError) == null ? void 0 : _f.call(this, lastProcessedChunkId || 0, error);
|
|
31895
31927
|
}
|
|
31896
31928
|
} catch (error) {
|
|
31897
31929
|
console.error("[BackgroundUpload] Erro ao processar fila:", error);
|
|
@@ -31902,7 +31934,18 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31902
31934
|
/**
|
|
31903
31935
|
* Faz o upload bruto de dados para a sessão GCS.
|
|
31904
31936
|
*/
|
|
31905
|
-
|
|
31937
|
+
static concatArrayBuffers(parts) {
|
|
31938
|
+
if (parts.length === 0) return new ArrayBuffer(0);
|
|
31939
|
+
const total = parts.reduce((sum, p3) => sum + p3.byteLength, 0);
|
|
31940
|
+
const merged = new Uint8Array(total);
|
|
31941
|
+
let offset = 0;
|
|
31942
|
+
for (const p3 of parts) {
|
|
31943
|
+
merged.set(new Uint8Array(p3), offset);
|
|
31944
|
+
offset += p3.byteLength;
|
|
31945
|
+
}
|
|
31946
|
+
return merged.buffer.byteLength === total ? merged.buffer : merged.buffer.slice(merged.byteOffset, merged.byteOffset + merged.byteLength);
|
|
31947
|
+
}
|
|
31948
|
+
async uploadData(data, mimeType, chunkIndex, totalSize) {
|
|
31906
31949
|
const fileName = `EP_${this.proctoringId}_camera_0.webm`;
|
|
31907
31950
|
if (!this.sessionUrl) {
|
|
31908
31951
|
const initiateUrl = await this.backend.initiateUpload(this.token, `${this.proctoringId}/${fileName}`, mimeType);
|
|
@@ -31934,15 +31977,16 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31934
31977
|
console.log(`[BackgroundUpload] Usando sess\xE3o GCS existente: ${this.sessionUrl}`);
|
|
31935
31978
|
}
|
|
31936
31979
|
const start = this.currentOffset;
|
|
31937
|
-
const end = start +
|
|
31980
|
+
const end = start + data.byteLength - 1;
|
|
31938
31981
|
const totalHeader = totalSize !== void 0 ? totalSize.toString() : "*";
|
|
31939
|
-
const contentRangeHeader =
|
|
31940
|
-
console.log(
|
|
31982
|
+
const contentRangeHeader = data.byteLength === 0 && totalSize !== void 0 ? `bytes */${totalHeader}` : `bytes ${start}-${end}/${totalHeader}`;
|
|
31983
|
+
console.log(
|
|
31984
|
+
`[BackgroundUpload] Enviando ${data.byteLength > 0 ? "dados" : "finaliza\xE7\xE3o"}: ${contentRangeHeader} (Size: ${data.byteLength})`
|
|
31985
|
+
);
|
|
31941
31986
|
const response = await fetch(this.sessionUrl, {
|
|
31942
31987
|
method: "PUT",
|
|
31943
31988
|
headers: { "Content-Range": contentRangeHeader },
|
|
31944
|
-
body:
|
|
31945
|
-
// Usa null para garantir corpo vazio se necessário
|
|
31989
|
+
body: data.byteLength > 0 ? data : null
|
|
31946
31990
|
});
|
|
31947
31991
|
console.log(`[BackgroundUpload] Resposta GCS (uploadData): ${response.status}`);
|
|
31948
31992
|
if (response.status !== 200 && response.status !== 201 && response.status !== 308) {
|
|
@@ -31955,13 +31999,13 @@ var BackgroundUploadService = class _BackgroundUploadService {
|
|
|
31955
31999
|
const lastByte = parseInt(rangeHeader.split("-")[1], 10);
|
|
31956
32000
|
this.currentOffset = lastByte + 1;
|
|
31957
32001
|
} else {
|
|
31958
|
-
this.currentOffset +=
|
|
32002
|
+
this.currentOffset += data.byteLength;
|
|
31959
32003
|
}
|
|
31960
32004
|
this.saveSessionState();
|
|
31961
32005
|
trackers.registerUploadFile(
|
|
31962
32006
|
this.proctoringId,
|
|
31963
32007
|
`GCS Stream Upload
|
|
31964
|
-
Size: ${
|
|
32008
|
+
Size: ${data.byteLength}
|
|
31965
32009
|
Range: ${start}-${end}
|
|
31966
32010
|
Last Index: ${chunkIndex}`,
|
|
31967
32011
|
"CameraChunk"
|
|
@@ -32504,10 +32548,11 @@ Setting: ${JSON.stringify(settings, null, 2)}`
|
|
|
32504
32548
|
const savePromise = (async () => {
|
|
32505
32549
|
var _a2;
|
|
32506
32550
|
try {
|
|
32551
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
32507
32552
|
await this.chunkStorage.saveChunk({
|
|
32508
32553
|
proctoringId: this.proctoringId,
|
|
32509
32554
|
chunkIndex: this.chunkIndex,
|
|
32510
|
-
|
|
32555
|
+
arrayBuffer,
|
|
32511
32556
|
timestamp: Date.now(),
|
|
32512
32557
|
uploaded: 0,
|
|
32513
32558
|
mimeType: ((_a2 = this.recorderOptions) == null ? void 0 : _a2.mimeType) || "video/webm"
|
|
@@ -38979,15 +39024,11 @@ Error: ${error}`
|
|
|
38979
39024
|
await this.recorder.stopAll();
|
|
38980
39025
|
this.spyCam && this.spyCam.stopCheckSpyCam();
|
|
38981
39026
|
this.appChecker && await this.appChecker.disconnectWebSocket();
|
|
38982
|
-
trackers.registerError(this.proctoringId, `finish saveAllOnSession`);
|
|
38983
39027
|
await this.recorder.saveAllOnSession();
|
|
38984
|
-
trackers.registerError(this.proctoringId, `finish sendPendingRealtimeAlerts`);
|
|
38985
39028
|
await this.sendPendingRealtimeAlerts();
|
|
38986
|
-
trackers.registerError(this.proctoringId, `finish this.repository.save`);
|
|
38987
39029
|
await this.repository.save(this.proctoringSession);
|
|
38988
39030
|
let uploader;
|
|
38989
39031
|
let uploaderServices;
|
|
38990
|
-
trackers.registerError(this.proctoringId, `finish uploader`);
|
|
38991
39032
|
if (versionVerify() !== "1.0.0.0") {
|
|
38992
39033
|
uploader = new ProctoringUploader(
|
|
38993
39034
|
this.proctoringSession,
|
|
@@ -39061,7 +39102,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
39061
39102
|
this.serviceType
|
|
39062
39103
|
);
|
|
39063
39104
|
}
|
|
39064
|
-
trackers.registerError(this.proctoringId, `finish uploader success`);
|
|
39065
39105
|
if (this.proctoringSession.alerts.length > 0) {
|
|
39066
39106
|
await this.backend.saveAlerts(this.context, this.proctoringSession).catch((err) => {
|
|
39067
39107
|
trackers.registerFinish(
|
|
@@ -39071,7 +39111,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
39071
39111
|
);
|
|
39072
39112
|
});
|
|
39073
39113
|
}
|
|
39074
|
-
trackers.registerError(this.proctoringId, `finish saveAlerts ok`);
|
|
39075
39114
|
await this.backend.finishAndSendUrls(this.context).then((finishResponse) => {
|
|
39076
39115
|
var _a2, _b, _c2, _d;
|
|
39077
39116
|
trackers.registerFinish(this.proctoringSession.id, true, "");
|
|
@@ -39083,7 +39122,6 @@ Upload Services: ${uploaderServices}`,
|
|
|
39083
39122
|
"finish error: " + error
|
|
39084
39123
|
);
|
|
39085
39124
|
});
|
|
39086
|
-
trackers.registerError(this.proctoringId, `finish call`);
|
|
39087
39125
|
if (this.appChecker) {
|
|
39088
39126
|
const externalSessionId = this.appChecker.getExternalCameraSessionId();
|
|
39089
39127
|
if (externalSessionId != "null") {
|
|
@@ -32,6 +32,7 @@ export declare class BackgroundUploadService {
|
|
|
32
32
|
flush(): Promise<void>;
|
|
33
33
|
private syncOffset;
|
|
34
34
|
private processQueue;
|
|
35
|
+
private static concatArrayBuffers;
|
|
35
36
|
private uploadData;
|
|
36
37
|
static recoverPendingUploads(backend: BackendService, token: string): Promise<string[]>;
|
|
37
38
|
private sleep;
|
|
@@ -2,7 +2,7 @@ export interface VideoChunk {
|
|
|
2
2
|
id?: number;
|
|
3
3
|
proctoringId: string;
|
|
4
4
|
chunkIndex: number;
|
|
5
|
-
|
|
5
|
+
arrayBuffer: ArrayBuffer;
|
|
6
6
|
timestamp: number;
|
|
7
7
|
uploaded: number;
|
|
8
8
|
mimeType: string;
|
|
@@ -12,6 +12,7 @@ export declare class ChunkStorageService {
|
|
|
12
12
|
private static readonly DB_VERSION;
|
|
13
13
|
private static readonly STORE_NAME;
|
|
14
14
|
private db;
|
|
15
|
+
private static detachedArrayBufferCopy;
|
|
15
16
|
connect(): Promise<IDBDatabase>;
|
|
16
17
|
saveChunk(chunk: Omit<VideoChunk, "id">): Promise<number>;
|
|
17
18
|
getPendingChunks(proctoringId: string): Promise<VideoChunk[]>;
|