easyproctor 0.0.19 → 0.0.20

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 CHANGED
@@ -35,14 +35,7 @@ Em um bundler
35
35
  ```javascript
36
36
  import { useProctoring } from "easyproctor";
37
37
 
38
- const {
39
- start,
40
- finish,
41
- onFocus,
42
- onLostFocus,
43
- enumarateDevices,
44
- checkPermissions,
45
- } = useProctoring({
38
+ const { start, finish, pause, resume, onFocus, onLostFocus, enumarateDevices, checkPermissions, checkIfhasMultipleMonitors, onStopSharingScreen } = useProctoring({
46
39
  examId: "00001",
47
40
  clientId: "000001",
48
41
  token: "...",
@@ -54,14 +47,7 @@ Via CDN: A função "useProctoring" é injetada para ser utilizada globalmente
54
47
  ```html
55
48
  <script src="https://cdn.jsdelivr.net/npm/easyproctor/unpkg/easyproctor.min.js"></script>
56
49
  <script>
57
- const {
58
- start,
59
- finish,
60
- onFocus,
61
- onLostFocus,
62
- enumarateDevices,
63
- checkPermissions,
64
- } = useProctoring({
50
+ const { start, finish, pause, resume, onFocus, onLostFocus, enumarateDevices, checkPermissions, checkIfhasMultipleMonitors, onStopSharingScreen } = useProctoring({
65
51
  examId: "00001",
66
52
  clientId: "000001",
67
53
  token: "...",
@@ -198,6 +184,12 @@ const {
198
184
  // Inicia a gravação da prova
199
185
  start,
200
186
 
187
+ // Pausa a gravação
188
+ pause,
189
+
190
+ // Reinicia a gravação
191
+ resume,
192
+
201
193
  // Finaliza a gravação da prova retornando os arquivos gerados
202
194
  finish,
203
195
 
@@ -207,11 +199,19 @@ const {
207
199
  // Adiciona uma função callback para ser executada quando o usuário perde o foco da tela
208
200
  onLostFocus,
209
201
 
202
+ // Adiciona uma função callback para ser executada quando o usuário cancela o compartilhamento de tela
203
+ onStopSharingScreen
204
+
210
205
  // Enumera os dispositivos de camera e microfone
211
206
  enumarateDevices,
212
207
 
213
208
  // Checa as permissões de camera e microfone
214
209
  checkPermissions,
210
+
211
+ // Checa se existe mais de um monitor
212
+ checkIfhasMultipleMonitors
213
+
214
+
215
215
  } = useProctoring({
216
216
  examId: "00001",
217
217
  clientId: "000001",
@@ -0,0 +1,10 @@
1
+ export declare const SCRIPT_NOT_CALLED_INSIDE_BODY = "script_not_called_inside_body";
2
+ export declare const INCOMPATIBLE_NAVIGATOR = "incompatible_navigator";
3
+ export declare const REQUIRED_FIELD_NOT_PROVIDED = "required_field_not_provided";
4
+ export declare const NOT_SHARED_FIRST_SCREEN = "not_shared_first_screen";
5
+ export declare const MULTIPLE_MONITORS_DETECTED = "multiple_monitors_detected";
6
+ export declare const PROCTORING_ALREADY_STARTED = "proctoring_already_started";
7
+ export declare const PROCTORING_NOT_STARTED = "proctoring_not_started";
8
+ export declare const PROCTORING_RUNNING = "proctoring_running";
9
+ export declare const NO_VIDEOS_RECORDED = "no_videos_recorded";
10
+ export declare const PERMISSIONS_NOT_GRANTED = "permissions_not_granted";
package/esm/index.js CHANGED
@@ -16,11 +16,15 @@ function recorder(stream, buffer) {
16
16
  mediaRecorder.start();
17
17
  function stopRecording() {
18
18
  return new Promise((resolve) => {
19
- resolvePromise = resolve;
20
- mediaRecorder.stop();
21
- stream.getTracks().forEach((el) => {
22
- el.stop();
23
- });
19
+ if (mediaRecorder.state == "recording") {
20
+ resolvePromise = resolve;
21
+ mediaRecorder.stop();
22
+ stream.getTracks().forEach((el) => {
23
+ el.stop();
24
+ });
25
+ } else {
26
+ resolve();
27
+ }
24
28
  });
25
29
  }
26
30
  return stopRecording;
@@ -43,8 +47,20 @@ async function startCameraCapture(buffer, options = { cameraId: void 0, micropho
43
47
  return { cameraStream, stopCameraRecording };
44
48
  }
45
49
 
50
+ // src/errors/errors.ts
51
+ var SCRIPT_NOT_CALLED_INSIDE_BODY = "script_not_called_inside_body";
52
+ var INCOMPATIBLE_NAVIGATOR = "incompatible_navigator";
53
+ var REQUIRED_FIELD_NOT_PROVIDED = "required_field_not_provided";
54
+ var NOT_SHARED_FIRST_SCREEN = "not_shared_first_screen";
55
+ var MULTIPLE_MONITORS_DETECTED = "multiple_monitors_detected";
56
+ var PROCTORING_ALREADY_STARTED = "proctoring_already_started";
57
+ var PROCTORING_NOT_STARTED = "proctoring_not_started";
58
+ var PROCTORING_RUNNING = "proctoring_running";
59
+ var NO_VIDEOS_RECORDED = "no_videos_recorded";
60
+
46
61
  // src/modules/startScreenCapture.ts
47
- async function startScreenCapture(buffer) {
62
+ async function startScreenCapture(buffer, options) {
63
+ const { allowOnlyFirstMonitor, onStopSharingScreenCallback } = options;
48
64
  const displayMediaStreamConstraints = {
49
65
  video: {
50
66
  cursor: "always"
@@ -52,6 +68,15 @@ async function startScreenCapture(buffer) {
52
68
  audio: false
53
69
  };
54
70
  const screenStream = await navigator.mediaDevices.getDisplayMedia(displayMediaStreamConstraints);
71
+ const tracks = screenStream.getVideoTracks();
72
+ tracks[0].onended = onStopSharingScreenCallback;
73
+ const sharedFirstScreen = tracks.find((el) => el.getSettings().deviceId == "screen:0:0") != null;
74
+ if (!sharedFirstScreen && allowOnlyFirstMonitor) {
75
+ tracks.forEach((el) => {
76
+ el.stop();
77
+ });
78
+ throw NOT_SHARED_FIRST_SCREEN;
79
+ }
55
80
  const stopScreenRecorder = recorder(screenStream, buffer);
56
81
  return { screenStream, stopScreenRecorder };
57
82
  }
@@ -125,21 +150,87 @@ async function checkPermissions() {
125
150
  return false;
126
151
  }
127
152
  }
153
+ async function checkIfhasMultipleMonitors() {
154
+ return new Promise((resolve, reject) => {
155
+ const presentationRequest = new PresentationRequest("receiver.html");
156
+ presentationRequest.getAvailability().then((availability) => {
157
+ let hasMultipleMonitors = availability.value;
158
+ availability.addEventListener("change", function() {
159
+ hasMultipleMonitors = availability.value;
160
+ });
161
+ setTimeout(() => {
162
+ resolve(hasMultipleMonitors);
163
+ }, 1e3);
164
+ }).catch((error) => reject(error));
165
+ });
166
+ }
167
+
168
+ // src/modules/database.ts
169
+ var databaseName = "EasyProctorPlugin";
170
+ var databaseVersion = 2;
171
+ function initializeDatabase(table) {
172
+ return new Promise((resolve, reject) => {
173
+ const request = indexedDB.open(databaseName, databaseVersion);
174
+ request.onupgradeneeded = () => {
175
+ request.result.createObjectStore("exams", { keyPath: "id" });
176
+ };
177
+ request.onerror = (e) => {
178
+ console.log(e);
179
+ reject("N\xE3o foi poss\xEDvel inicializar a biblioteca, por favor, entre em contato com o suporte e informe o erro acima");
180
+ };
181
+ request.onsuccess = () => {
182
+ const db = request.result;
183
+ const tableRef = db.transaction(table, "readwrite");
184
+ const store = tableRef.objectStore(table);
185
+ resolve(store);
186
+ };
187
+ });
188
+ }
189
+ async function insertRecord(table, data) {
190
+ const store = await initializeDatabase(table);
191
+ return new Promise((resolve, reject) => {
192
+ const request = store.put(data);
193
+ request.onsuccess = () => resolve();
194
+ request.onerror = (e) => reject(e);
195
+ });
196
+ }
197
+ async function getRecord(table) {
198
+ const store = await initializeDatabase(table);
199
+ return new Promise((resolve, reject) => {
200
+ const request = store.getAll();
201
+ request.onsuccess = () => {
202
+ const data = request.result[0];
203
+ resolve(data);
204
+ };
205
+ request.onerror = (e) => reject(e);
206
+ });
207
+ }
208
+ async function clearBuffers(table) {
209
+ const store = await initializeDatabase(table);
210
+ return new Promise((resolve, reject) => {
211
+ const request = store.clear();
212
+ request.onsuccess = () => resolve();
213
+ request.onerror = (e) => reject(e);
214
+ });
215
+ }
128
216
 
129
217
  // src/index.ts
218
+ var defaultProctoringOptions = {
219
+ cameraId: void 0,
220
+ microphoneId: void 0,
221
+ allowMulipleMonitors: false,
222
+ allowOnlyFirstMonitor: true
223
+ };
130
224
  var azureBlobUrl = "https://iarisprod.blob.core.windows.net/iaris";
131
225
  function useProctoring(proctoringOptions) {
132
226
  ["examId", "clientId", "token"].forEach((el) => {
133
227
  const key = el;
134
228
  if (!proctoringOptions[key]) {
135
- throw `O campo ${key} \xE9 obrigat\xF3rio`;
229
+ throw REQUIRED_FIELD_NOT_PROVIDED + ": " + key;
136
230
  }
137
231
  });
138
- if (!navigator.mediaDevices.getDisplayMedia) {
139
- throw "Voc\xEA est\xE1 utilizando uma vers\xE3o muito antiga do navegador, por favor, atualize a vers\xE3o";
140
- }
141
- if (!window.indexedDB) {
142
- throw "Voc\xEA est\xE1 usando uma vers\xE3o muito antiga do navegador, n\xE3o \xE9 poss\xEDvel relizar a requisi\xE7\xE3o";
232
+ if (!navigator.mediaDevices.getDisplayMedia || !window.indexedDB || typeof PresentationRequest === "undefined") {
233
+ throw INCOMPATIBLE_NAVIGATOR;
143
234
  }
144
235
  function download(file) {
145
236
  const url = URL.createObjectURL(file);
@@ -158,25 +249,36 @@ function useProctoring(proctoringOptions) {
158
249
  let alerts = [];
159
250
  let onLostFocusCallback;
160
251
  let onFocusCallback;
252
+ let onStopSharingScreenCallback = void 0;
161
253
  let cancelCallback = null;
254
+ function _clear() {
255
+ cameraBuffer = [];
256
+ screenBuffer = [];
257
+ proctoringId = "";
258
+ startTime = 0;
259
+ alerts = [];
260
+ }
162
261
  async function _startCapture(options) {
163
- console.log(cancelCallback);
164
262
  if (!document.body) {
165
- throw "A execu\xE7\xE3o do script deve ser feita por algum elemento dentro do <body> da p\xE1gina html";
263
+ throw SCRIPT_NOT_CALLED_INSIDE_BODY;
166
264
  }
167
265
  if (cancelCallback != null) {
168
- throw "Uma grava\xE7\xE3o ja est\xE1 em andamento";
266
+ throw PROCTORING_ALREADY_STARTED;
169
267
  }
170
268
  let cancelCameraCapture = null;
171
269
  let cancelScreenCapture = null;
270
+ const { cameraId, microphoneId, allowOnlyFirstMonitor } = options;
172
271
  try {
173
- const { screenStream, stopScreenRecorder } = await startScreenCapture(screenBuffer);
174
- const { cameraId, microphoneId } = options;
272
+ const { screenStream, stopScreenRecorder } = await startScreenCapture(screenBuffer, {
273
+ allowOnlyFirstMonitor,
274
+ onStopSharingScreenCallback: () => onStopSharingScreenCallback && onStopSharingScreenCallback()
275
+ });
175
276
  const { cameraStream, stopCameraRecording } = await startCameraCapture(cameraBuffer, { cameraId, microphoneId });
176
277
  cancelScreenCapture = stopScreenRecorder;
177
278
  cancelCameraCapture = stopCameraRecording;
178
279
  cancelCallback = async function() {
179
280
  await Promise.all([cancelCameraCapture(), cancelScreenCapture()]);
281
+ cancelCallback = null;
180
282
  };
181
283
  return { screenStream, cameraStream };
182
284
  } catch (error) {
@@ -185,7 +287,7 @@ function useProctoring(proctoringOptions) {
185
287
  cancelCameraCapture && await cancelCameraCapture();
186
288
  cameraBuffer = [];
187
289
  screenBuffer = [];
188
- throw "N\xE3o foi poss\xEDvel iniciar a captura, por favor, verifique as permiss\xF5es de camera e microfone";
290
+ throw error;
189
291
  }
190
292
  }
191
293
  const _onLostFocus = () => {
@@ -211,11 +313,33 @@ function useProctoring(proctoringOptions) {
211
313
  window.removeEventListener("blur", _onLostFocus);
212
314
  window.removeEventListener("focus", _onReturnFocus);
213
315
  };
214
- async function start(options = { microphoneId: void 0, cameraId: void 0 }) {
215
- const { cameraId, microphoneId } = options;
216
- const { cameraStream, screenStream } = await _startCapture({ cameraId, microphoneId });
217
- startTime = Date.now();
316
+ const _generateFiles = (index) => {
317
+ const finalCameraBuffer = cameraBuffer;
318
+ const finalScreenBuffer = screenBuffer;
319
+ if (finalCameraBuffer.length == 0 || finalScreenBuffer.length == 0) {
320
+ throw PROCTORING_NOT_STARTED;
321
+ }
322
+ const cameraFileName = `EP_${proctoringId}_camera_${index}.webm`;
323
+ const screenFIleName = `EP_${proctoringId}_screen_${index}.webm`;
324
+ const cameraFile = new File(finalCameraBuffer, cameraFileName, { type: "video/webm" });
325
+ const screenFile = new File(finalScreenBuffer, screenFIleName, { type: "video/webm" });
326
+ return { cameraFile, screenFile };
327
+ };
328
+ async function start(options = defaultProctoringOptions) {
329
+ const { cameraId, microphoneId, allowOnlyFirstMonitor = true, allowMulipleMonitors = false } = options;
330
+ if (!allowMulipleMonitors) {
331
+ const hasMultipleMonitors = await checkIfhasMultipleMonitors();
332
+ if (hasMultipleMonitors) {
333
+ throw MULTIPLE_MONITORS_DETECTED;
334
+ }
335
+ }
336
+ const hasExam = await getRecord("exams");
337
+ if (hasExam != null) {
338
+ throw PROCTORING_ALREADY_STARTED;
339
+ }
218
340
  try {
341
+ const { cameraStream, screenStream } = await _startCapture({ cameraId, microphoneId, allowOnlyFirstMonitor });
342
+ startTime = Date.now();
219
343
  const resp = await makeRequest({
220
344
  url: `/proctoring/start/${proctoringOptions.examId}`,
221
345
  method: "POST",
@@ -224,6 +348,12 @@ function useProctoring(proctoringOptions) {
224
348
  });
225
349
  resp.cameraStream = cameraStream;
226
350
  resp.screenStream = screenStream;
351
+ await insertRecord("exams", {
352
+ id: resp.id,
353
+ cameraBuffers: [],
354
+ screenBuffers: [],
355
+ alerts: []
356
+ });
227
357
  proctoringId = resp.id;
228
358
  _addListeners();
229
359
  return resp;
@@ -232,36 +362,74 @@ function useProctoring(proctoringOptions) {
232
362
  throw error;
233
363
  }
234
364
  }
365
+ async function pause() {
366
+ const record = await getRecord("exams");
367
+ if (!cancelCallback || !record) {
368
+ throw PROCTORING_NOT_STARTED;
369
+ }
370
+ onStopSharingScreenCallback = void 0;
371
+ await cancelCallback();
372
+ const { cameraFile, screenFile } = _generateFiles(record.cameraBuffers.length);
373
+ record.cameraBuffers.push(cameraFile);
374
+ record.screenBuffers.push(screenFile);
375
+ record.alerts.push(...alerts);
376
+ await insertRecord("exams", record);
377
+ _clear();
378
+ }
379
+ async function resume(options = defaultProctoringOptions) {
380
+ _clear();
381
+ const { cameraId, microphoneId, allowOnlyFirstMonitor = true, allowMulipleMonitors = false } = options;
382
+ if (!allowMulipleMonitors) {
383
+ const hasMultipleMonitors = await checkIfhasMultipleMonitors();
384
+ if (hasMultipleMonitors) {
385
+ throw MULTIPLE_MONITORS_DETECTED;
386
+ }
387
+ }
388
+ if (cancelCallback) {
389
+ throw PROCTORING_RUNNING;
390
+ }
391
+ const record = await getRecord("exams");
392
+ if (!record) {
393
+ throw PROCTORING_NOT_STARTED;
394
+ }
395
+ const { cameraStream, screenStream } = await _startCapture({ cameraId, microphoneId, allowOnlyFirstMonitor });
396
+ proctoringId = record.id;
397
+ startTime = Date.now();
398
+ return { cameraStream, screenStream };
399
+ }
235
400
  async function finish(options = {}) {
236
401
  const { onProgress } = options;
402
+ const record = await getRecord("exams");
403
+ if (!record) {
404
+ throw PROCTORING_NOT_STARTED;
405
+ }
406
+ onStopSharingScreenCallback = void 0;
237
407
  if (cancelCallback) {
238
408
  await cancelCallback();
409
+ const { cameraFile, screenFile } = _generateFiles(record.cameraBuffers.length);
410
+ record.cameraBuffers.push(cameraFile);
411
+ record.screenBuffers.push(screenFile);
239
412
  }
240
- const time = new Date().toISOString();
241
- const finalCameraBuffer = cameraBuffer;
242
- const finalScreenBuffer = screenBuffer;
243
- if (finalCameraBuffer.length == 0 || finalScreenBuffer.length == 0) {
244
- throw "N\xE3o existe nenhuma grava\xE7\xE3o iniciada";
413
+ if (record.cameraBuffers.length == 0 || record.screenBuffers.length == 0) {
414
+ throw NO_VIDEOS_RECORDED;
245
415
  }
246
- const cameraFileName = `EP_${proctoringId}_camera_0.webm`;
247
- const screenFIleName = `EP_${proctoringId}_screen_0.webm`;
248
- const cameraFile = new File(finalCameraBuffer, cameraFileName, { type: "video/webm" });
249
- const screenFile = new File(finalScreenBuffer, screenFIleName, { type: "video/webm" });
250
- let cameraProgress = 0;
251
- let screenProgress = 0;
416
+ const time = new Date().toISOString();
417
+ const totalFiles = record.cameraBuffers.length + record.screenBuffers.length;
418
+ const uploadProgress = Array(totalFiles).fill(0);
252
419
  const handleOnProgress = () => {
253
- onProgress && onProgress((cameraProgress + screenProgress) / 2);
420
+ onProgress && onProgress(uploadProgress.reduce((acc, el) => acc += el, 0) / totalFiles);
254
421
  };
255
- await Promise.all([
256
- upload_default({ file: cameraFile, onProgress: (progress) => {
257
- cameraProgress = progress;
258
- handleOnProgress();
259
- } }),
260
- upload_default({ file: screenFile, onProgress: (progress) => {
261
- screenProgress = progress;
262
- handleOnProgress();
263
- } })
264
- ]);
422
+ const allFiles = [...record.cameraBuffers, ...record.screenBuffers];
423
+ const uploadPromises = allFiles.map((c, i) => {
424
+ return upload_default({
425
+ file: c,
426
+ onProgress: (progress) => {
427
+ uploadProgress[i] = progress;
428
+ handleOnProgress();
429
+ }
430
+ });
431
+ });
432
+ await Promise.all(uploadPromises);
265
433
  _removeListeners();
266
434
  await makeRequest({
267
435
  url: "/proctoring/save-screen",
@@ -269,19 +437,20 @@ function useProctoring(proctoringOptions) {
269
437
  jwt: proctoringOptions.token,
270
438
  body: {
271
439
  proctoringId,
272
- alerts: [...alerts]
440
+ alerts: [...record.alerts, ...alerts]
273
441
  }
274
442
  });
275
443
  await makeRequest({
276
- url: `/proctoring/finish/${proctoringOptions.examId}`,
444
+ url: `/proctoring/finish/${record.id}`,
277
445
  method: "POST",
278
446
  body: {
279
447
  endDate: time,
280
- videoCameraUrl: `${azureBlobUrl}/${cameraFileName}`,
281
- videoScreenUrl: `${azureBlobUrl}/${screenFIleName}`
448
+ videoCameraUrl: `${azureBlobUrl}/${proctoringId}/cameraFiles`,
449
+ videoScreenUrl: `${azureBlobUrl}/${proctoringId}/screenFiles`
282
450
  },
283
451
  jwt: proctoringOptions.token
284
452
  });
453
+ await clearBuffers("exams");
285
454
  cameraBuffer = [];
286
455
  screenBuffer = [];
287
456
  alerts = [];
@@ -290,10 +459,13 @@ function useProctoring(proctoringOptions) {
290
459
  function onFocus(cb) {
291
460
  onFocusCallback = cb;
292
461
  }
462
+ function onStopSharingScreen(cb) {
463
+ onStopSharingScreenCallback = cb;
464
+ }
293
465
  function onLostFocus(cb) {
294
466
  onLostFocusCallback = cb;
295
467
  }
296
- return { start, finish, download, onFocus, onLostFocus, enumarateDevices, checkPermissions };
468
+ return { start, finish, pause, resume, onFocus, onLostFocus, enumarateDevices, checkPermissions, checkIfhasMultipleMonitors, onStopSharingScreen };
297
469
  }
298
470
  if (typeof window !== "undefined") {
299
471
  window.useProctoring = useProctoring;
package/index.d.ts CHANGED
@@ -1,21 +1,31 @@
1
1
  import enumarateDevices from "./modules/enumarateDevices";
2
- import checkPermissions from "./modules/checkPermissions";
2
+ import { checkPermissions, checkIfhasMultipleMonitors } from "./modules/checkPermissions";
3
3
  import StartProctoringResponse from "./dtos/StartProctoringResponse";
4
+ interface ProctoringOptions {
5
+ cameraId?: string;
6
+ microphoneId?: string;
7
+ allowMulipleMonitors?: boolean;
8
+ allowOnlyFirstMonitor?: boolean;
9
+ }
4
10
  export declare function useProctoring(proctoringOptions: {
5
11
  examId: string;
6
12
  clientId: string;
7
13
  token: string;
8
14
  }): {
9
- start: (options?: {
10
- cameraId?: string;
11
- microphoneId?: string;
12
- }) => Promise<StartProctoringResponse>;
15
+ start: (options?: ProctoringOptions) => Promise<StartProctoringResponse>;
13
16
  finish: (options?: {
14
17
  onProgress?: ((percentage: number) => void) | undefined;
15
18
  }) => Promise<void>;
16
- download: (file: File) => void;
19
+ pause: () => Promise<void>;
20
+ resume: (options?: ProctoringOptions) => Promise<{
21
+ cameraStream: MediaStream;
22
+ screenStream: MediaStream;
23
+ }>;
17
24
  onFocus: (cb: () => void) => void;
18
25
  onLostFocus: (cb: () => void) => void;
19
26
  enumarateDevices: typeof enumarateDevices;
20
27
  checkPermissions: typeof checkPermissions;
28
+ checkIfhasMultipleMonitors: typeof checkIfhasMultipleMonitors;
29
+ onStopSharingScreen: (cb: () => void) => void;
21
30
  };
31
+ export {};