easyproctor 0.0.15 → 0.0.19

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
@@ -11,6 +11,7 @@ npm install --save easyproctor
11
11
  ```
12
12
 
13
13
  Via CDN
14
+
14
15
  ```html
15
16
  <script src="https://cdn.jsdelivr.net/npm/easyproctor/unpkg/easyproctor.min.js"></script>
16
17
  ```
@@ -18,6 +19,7 @@ Via CDN
18
19
  ## Utilização
19
20
 
20
21
  Para utilizar o componente será necessário utilizar os seguintes parâmetros:
22
+
21
23
  - examId: identificador do exame
22
24
  - clientId: identificador do parceiro
23
25
  - token: token de autenticação biométrica (utilizar SDK biométrico do EasyProctor)
@@ -28,27 +30,42 @@ Após finalizar a sessão de proctoring através do método finish você poderá
28
30
 
29
31
  https://iaris.easyproctor.tech/index.html
30
32
 
31
-
32
33
  Em um bundler
34
+
33
35
  ```javascript
34
36
  import { useProctoring } from "easyproctor";
35
37
 
36
- const { start, finish } = useProctoring({
37
- examId: "00001",
38
- clientId: "000001",
39
- token: "..."
38
+ const {
39
+ start,
40
+ finish,
41
+ onFocus,
42
+ onLostFocus,
43
+ enumarateDevices,
44
+ checkPermissions,
45
+ } = useProctoring({
46
+ examId: "00001",
47
+ clientId: "000001",
48
+ token: "...",
40
49
  });
41
50
  ```
42
51
 
43
52
  Via CDN: A função "useProctoring" é injetada para ser utilizada globalmente
53
+
44
54
  ```html
45
55
  <script src="https://cdn.jsdelivr.net/npm/easyproctor/unpkg/easyproctor.min.js"></script>
46
56
  <script>
47
- const { start, finish } = useProctoring({
48
- examId: "00001",
49
- clientId: "000001",
50
- token: "..."
51
- });
57
+ const {
58
+ start,
59
+ finish,
60
+ onFocus,
61
+ onLostFocus,
62
+ enumarateDevices,
63
+ checkPermissions,
64
+ } = useProctoring({
65
+ examId: "00001",
66
+ clientId: "000001",
67
+ token: "...",
68
+ });
52
69
  </script>
53
70
  ```
54
71
 
@@ -57,64 +74,151 @@ Via CDN: A função "useProctoring" é injetada para ser utilizada globalmente
57
74
  ```html
58
75
  <!DOCTYPE html>
59
76
  <html lang="en">
60
-
61
- <head>
62
- <meta charset="UTF-8">
63
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
64
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
77
+ <head>
78
+ <meta charset="UTF-8" />
79
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
80
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
65
81
  <title>Document</title>
66
82
  <script src="dist/unpkg/easyproctor.min.js"></script>
67
83
  <script>
84
+ const {
85
+ start,
86
+ finish,
87
+ onFocus,
88
+ onLostFocus,
89
+ enumarateDevices,
90
+ checkPermissions,
91
+ } = useProctoring({
92
+ examId: "00001",
93
+ clientId: "000001",
94
+ token: "...",
95
+ });
96
+
97
+ async function startExam() {
98
+ try {
99
+ // Verificar se existem dispositívos disponíveis
100
+ if (devices.cameras.length == 0 || devices.microphones.length == 0) {
101
+ throw "Você precisa possuir ao menos uma câmera e um microfone";
102
+ }
103
+
104
+ // Verificar se foram setados
105
+ const selectedCamera = cameras.value;
106
+ const selectedMicrophone = microphones.value;
107
+
108
+ if (!selectedCamera || !selectedMicrophone) {
109
+ throw "Você precisa selecionar uma camera e um microfone";
110
+ }
111
+
112
+ startButton.innerHTML = "Carregando";
113
+
114
+ onFocus(() => generateAlert(false));
115
+ onLostFocus(() => generateAlert(true));
116
+ // Adicionar o dispositivo selecionado é opcional
117
+ const { cameraStream } = await start({
118
+ cameraId: selectedCamera,
119
+ microphoneId: selectedMicrophone,
120
+ });
121
+ } catch (error) {
122
+ alert(error);
123
+ }
124
+ }
125
+
126
+ async function finishExam() {
127
+ try {
128
+ await finish({ onProgress: (percentage) => console.log(percentage) });
129
+ console.log("EXAME FINALIZADO");
130
+ } catch (error) {
131
+ alert(error);
132
+ }
133
+ }
68
134
 
69
- const { start, finish } = useProctoring({
70
- examId: "00001",
71
- clientId: "000001",
72
- token: "..."
73
- });
135
+ // Verificar permissões
136
+ let devices = { cameras: [], microphones: [] };
137
+ async function verifyPermissions() {
138
+ // Checar permissões
139
+ var hasPermissions = await checkPermissions();
74
140
 
75
- async function startExam() {
76
- try {
77
- const resp = await start();
78
- } catch (error) {
79
- alert(error);
80
- }
141
+ if (!hasPermissions) {
142
+ window.alert("Para continuar você precisa conceder as permissões");
143
+ return;
81
144
  }
82
145
 
83
- async function finishExam() {
84
- try {
85
- await finish({ onProgress: (percentage) => console.log(percentage) });
86
- console.log("EXAME FINALIZADO");
87
- } catch (error) {
88
- alert(error);
89
- }
146
+ // Listar os dispositivos
147
+ devices = await enumarateDevices();
148
+
149
+ const generateOption = (text, value) => {
150
+ const el = document.createElement("option");
151
+ el.setAttribute("value", value);
152
+ el.innerHTML = text;
153
+ return el;
154
+ };
155
+
156
+ devices.cameras.forEach((camera) => {
157
+ const option = generateOption(camera.label, camera.id);
158
+ cameras.appendChild(option);
159
+ });
160
+
161
+ devices.microphones.forEach((microphone) => {
162
+ const option = generateOption(microphone.label, microphone.id);
163
+ microphones.appendChild(option);
164
+ });
165
+ }
166
+
167
+ // Gerar alerta
168
+ function generateAlert(lostFocus) {
169
+ const p = document.createElement("p");
170
+ if (lostFocus) {
171
+ p.classList.add("text-red-500");
172
+ p.innerHTML = `Perdeu o foco em: ${new Date().toLocaleTimeString()}`;
173
+ } else {
174
+ p.classList.add("text-green-500");
175
+ p.innerHTML = `Retomou o foco em: ${new Date().toLocaleTimeString()}`;
90
176
  }
177
+ alerts.appendChild(p);
178
+ }
91
179
  </script>
92
- </head>
93
-
94
- <body>
95
- <div style="height: 100vh; display: flex; align-items: center; justify-content: center;">
96
- <button onclick="startExam()">Iniciar</button>
97
- <button onclick="finishExam()">Finalizar</button>
180
+ </head>
181
+
182
+ <body>
183
+ <div
184
+ style="height: 100vh; display: flex; align-items: center; justify-content: center;"
185
+ >
186
+ <button onclick="startExam()">Iniciar</button>
187
+ <button onclick="finishExam()">Finalizar</button>
188
+ <button onclick="verifyPermissions()">Verificar permissões</button>
98
189
  </div>
99
- </body>
100
-
190
+ </body>
101
191
  </html>
102
192
  ```
103
193
 
104
194
  ## API
195
+
105
196
  ```javascript
106
- const {
107
- // Inicia a gravação da prova
108
- start,
197
+ const {
198
+ // Inicia a gravação da prova
199
+ start,
200
+
201
+ // Finaliza a gravação da prova retornando os arquivos gerados
202
+ finish,
203
+
204
+ // Adiciona uma função callback para ser executada quando o usuário recupera o foco da tela
205
+ onFocus,
206
+
207
+ // Adiciona uma função callback para ser executada quando o usuário perde o foco da tela
208
+ onLostFocus,
109
209
 
110
- // Finaliza a gravação da prova retornando os arquivos gerados
111
- finish,
210
+ // Enumera os dispositivos de camera e microfone
211
+ enumarateDevices,
112
212
 
213
+ // Checa as permissões de camera e microfone
214
+ checkPermissions,
113
215
  } = useProctoring({
114
- examId: "00001",
115
- clientId: "000001",
116
- token: "..."
117
- });
216
+ examId: "00001",
217
+ clientId: "000001",
218
+ token: "...",
219
+ });
118
220
  ```
221
+
119
222
  ## License
120
- [MIT](https://choosealicense.com/licenses/mit/)
223
+
224
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -6,4 +6,6 @@ export default interface StartProctoringDTO {
6
6
  examStartTime: string;
7
7
  examEndTime: string;
8
8
  status: number;
9
+ cameraStream: MediaStream;
10
+ screenStream: MediaStream;
9
11
  }
package/esm/index.js CHANGED
@@ -1,46 +1,3 @@
1
- // src/modules/database.ts
2
- var databaseName = "EasyProctorPlugin";
3
- var databaseVersion = 1;
4
- function initializeDatabase(table) {
5
- return new Promise((resolve, reject) => {
6
- const request = indexedDB.open(databaseName, databaseVersion);
7
- request.onupgradeneeded = () => {
8
- request.result.createObjectStore("cameraBuffers", { keyPath: "id", autoIncrement: true });
9
- request.result.createObjectStore("screenBuffers", { keyPath: "id", autoIncrement: true });
10
- };
11
- request.onerror = (e) => {
12
- console.log(e);
13
- reject("N\xE3o foi poss\xEDvel inicializar a biblioteca, por favor, entre em contato com o suporte e informe o erro acima");
14
- };
15
- request.onsuccess = () => {
16
- const db = request.result;
17
- const tableRef = db.transaction(table, "readwrite");
18
- const store = tableRef.objectStore(table);
19
- resolve(store);
20
- };
21
- });
22
- }
23
- async function getBuffers(table) {
24
- const store = await initializeDatabase(table);
25
- return new Promise((resolve, reject) => {
26
- const request = store.getAll();
27
- request.onsuccess = () => {
28
- const data = request.result;
29
- const blobs = data.reduce((acc, el) => [...acc, ...el.data], []);
30
- resolve(blobs);
31
- };
32
- request.onerror = (e) => reject(e);
33
- });
34
- }
35
- async function clearBuffers(table) {
36
- const store = await initializeDatabase(table);
37
- return new Promise((resolve, reject) => {
38
- const request = store.clear();
39
- request.onsuccess = () => resolve();
40
- request.onerror = (e) => reject(e);
41
- });
42
- }
43
-
44
1
  // src/modules/recorder.ts
45
2
  function recorder(stream, buffer) {
46
3
  let resolvePromise;
@@ -70,18 +27,20 @@ function recorder(stream, buffer) {
70
27
  }
71
28
 
72
29
  // src/modules/startCameraCapture.ts
73
- async function startCameraCapture(buffer) {
30
+ async function startCameraCapture(buffer, options = { cameraId: void 0, microphoneId: void 0 }) {
31
+ const { cameraId, microphoneId } = options;
74
32
  const constraints = {
75
- audio: true,
33
+ audio: { deviceId: microphoneId },
76
34
  video: {
35
+ deviceId: cameraId,
77
36
  width: { max: 1280, ideal: 640 },
78
37
  height: { max: 720, ideal: 480 },
79
38
  frameRate: 15
80
39
  }
81
40
  };
82
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
83
- const stopRecording = recorder(stream, buffer);
84
- return stopRecording;
41
+ const cameraStream = await navigator.mediaDevices.getUserMedia(constraints);
42
+ const stopCameraRecording = recorder(cameraStream, buffer);
43
+ return { cameraStream, stopCameraRecording };
85
44
  }
86
45
 
87
46
  // src/modules/startScreenCapture.ts
@@ -92,9 +51,9 @@ async function startScreenCapture(buffer) {
92
51
  },
93
52
  audio: false
94
53
  };
95
- const stream = await navigator.mediaDevices.getDisplayMedia(displayMediaStreamConstraints);
96
- const stopRecording = recorder(stream, buffer);
97
- return stopRecording;
54
+ const screenStream = await navigator.mediaDevices.getDisplayMedia(displayMediaStreamConstraints);
55
+ const stopScreenRecorder = recorder(screenStream, buffer);
56
+ return { screenStream, stopScreenRecorder };
98
57
  }
99
58
 
100
59
  // src/modules/upload.ts
@@ -130,13 +89,45 @@ async function makeRequest(data) {
130
89
  if (resp.status >= 400) {
131
90
  throw "N\xE3o foi poss\xEDvel realizar a requisi\xE7\xE3o, tente novamente mais tarde";
132
91
  }
133
- const isJson = resp.headers.get("content-type")?.includes("application/json");
92
+ const content = resp.headers.get("content-type");
93
+ const isJson = content ? content.includes("application/json") : false;
134
94
  const responseData = isJson ? await resp.json() : null;
135
95
  return responseData;
136
96
  }
137
97
 
98
+ // src/modules/enumarateDevices.ts
99
+ async function enumarateDevices() {
100
+ const mediaDevices = await navigator.mediaDevices.enumerateDevices();
101
+ const devices = {
102
+ cameras: mediaDevices.filter((el) => el.kind == "videoinput" && el.deviceId && el.label).map((e) => {
103
+ return { label: e.label, id: e.deviceId };
104
+ }),
105
+ microphones: mediaDevices.filter((el) => el.kind == "audioinput" && el.deviceId && el.label).map((e) => {
106
+ return { label: e.label, id: e.deviceId };
107
+ })
108
+ };
109
+ return devices;
110
+ }
111
+
112
+ // src/modules/checkPermissions.ts
113
+ async function checkPermissions() {
114
+ try {
115
+ const constraints = {
116
+ audio: true,
117
+ video: true
118
+ };
119
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
120
+ stream.getTracks().forEach((el) => {
121
+ el.stop();
122
+ });
123
+ return true;
124
+ } catch (error) {
125
+ return false;
126
+ }
127
+ }
128
+
138
129
  // src/index.ts
139
- var azureBlobUrl = "https://iarisprod.azureedge.net/iaris";
130
+ var azureBlobUrl = "https://iarisprod.blob.core.windows.net/iaris";
140
131
  function useProctoring(proctoringOptions) {
141
132
  ["examId", "clientId", "token"].forEach((el) => {
142
133
  const key = el;
@@ -165,8 +156,11 @@ function useProctoring(proctoringOptions) {
165
156
  let proctoringId = "";
166
157
  let startTime = 0;
167
158
  let alerts = [];
159
+ let onLostFocusCallback;
160
+ let onFocusCallback;
168
161
  let cancelCallback = null;
169
- async function _startCapture() {
162
+ async function _startCapture(options) {
163
+ console.log(cancelCallback);
170
164
  if (!document.body) {
171
165
  throw "A execu\xE7\xE3o do script deve ser feita por algum elemento dentro do <body> da p\xE1gina html";
172
166
  }
@@ -176,11 +170,15 @@ function useProctoring(proctoringOptions) {
176
170
  let cancelCameraCapture = null;
177
171
  let cancelScreenCapture = null;
178
172
  try {
179
- cancelScreenCapture = await startScreenCapture(screenBuffer);
180
- cancelCameraCapture = await startCameraCapture(cameraBuffer);
173
+ const { screenStream, stopScreenRecorder } = await startScreenCapture(screenBuffer);
174
+ const { cameraId, microphoneId } = options;
175
+ const { cameraStream, stopCameraRecording } = await startCameraCapture(cameraBuffer, { cameraId, microphoneId });
176
+ cancelScreenCapture = stopScreenRecorder;
177
+ cancelCameraCapture = stopCameraRecording;
181
178
  cancelCallback = async function() {
182
179
  await Promise.all([cancelCameraCapture(), cancelScreenCapture()]);
183
180
  };
181
+ return { screenStream, cameraStream };
184
182
  } catch (error) {
185
183
  cancelCallback = null;
186
184
  cancelScreenCapture && await cancelScreenCapture();
@@ -191,6 +189,7 @@ function useProctoring(proctoringOptions) {
191
189
  }
192
190
  }
193
191
  const _onLostFocus = () => {
192
+ onLostFocusCallback && onLostFocusCallback();
194
193
  alerts.push({
195
194
  begin: Date.now() - startTime,
196
195
  alert: 25,
@@ -200,6 +199,7 @@ function useProctoring(proctoringOptions) {
200
199
  const _onReturnFocus = () => {
201
200
  const lastAlert = alerts[alerts.length - 1];
202
201
  if (lastAlert) {
202
+ onFocusCallback && onFocusCallback();
203
203
  lastAlert.end = Date.now() - startTime;
204
204
  }
205
205
  };
@@ -211,17 +211,9 @@ function useProctoring(proctoringOptions) {
211
211
  window.removeEventListener("blur", _onLostFocus);
212
212
  window.removeEventListener("focus", _onReturnFocus);
213
213
  };
214
- async function start(options = { override: false }) {
215
- const { override } = options;
216
- if (override) {
217
- await Promise.all([clearBuffers("cameraBuffers"), clearBuffers("screenBuffers")]);
218
- } else {
219
- const [storedCameraBuffers, storedScreenBuffers] = await Promise.all([getBuffers("cameraBuffers"), getBuffers("screenBuffers")]);
220
- if (storedCameraBuffers.length > 0 || storedScreenBuffers.length > 0) {
221
- throw "Existe uma grava\xE7\xE3o iniciada, por favor, execute o m\xE9todo resume() para retomar, ou utilize o parametro start({ override: true }) para limpar os dados";
222
- }
223
- }
224
- await _startCapture();
214
+ async function start(options = { microphoneId: void 0, cameraId: void 0 }) {
215
+ const { cameraId, microphoneId } = options;
216
+ const { cameraStream, screenStream } = await _startCapture({ cameraId, microphoneId });
225
217
  startTime = Date.now();
226
218
  try {
227
219
  const resp = await makeRequest({
@@ -230,6 +222,8 @@ function useProctoring(proctoringOptions) {
230
222
  body: { clientId: proctoringOptions.clientId },
231
223
  jwt: proctoringOptions.token
232
224
  });
225
+ resp.cameraStream = cameraStream;
226
+ resp.screenStream = screenStream;
233
227
  proctoringId = resp.id;
234
228
  _addListeners();
235
229
  return resp;
@@ -249,12 +243,12 @@ function useProctoring(proctoringOptions) {
249
243
  if (finalCameraBuffer.length == 0 || finalScreenBuffer.length == 0) {
250
244
  throw "N\xE3o existe nenhuma grava\xE7\xE3o iniciada";
251
245
  }
252
- const cameraFileName = `EP_${proctoringId}_${time}_camera_0.webm`;
253
- const screenFIleName = `EP_${proctoringId}_${time}_screen_0.webm`;
246
+ const cameraFileName = `EP_${proctoringId}_camera_0.webm`;
247
+ const screenFIleName = `EP_${proctoringId}_screen_0.webm`;
254
248
  const cameraFile = new File(finalCameraBuffer, cameraFileName, { type: "video/webm" });
255
249
  const screenFile = new File(finalScreenBuffer, screenFIleName, { type: "video/webm" });
256
250
  let cameraProgress = 0;
257
- const screenProgress = 0;
251
+ let screenProgress = 0;
258
252
  const handleOnProgress = () => {
259
253
  onProgress && onProgress((cameraProgress + screenProgress) / 2);
260
254
  };
@@ -264,7 +258,7 @@ function useProctoring(proctoringOptions) {
264
258
  handleOnProgress();
265
259
  } }),
266
260
  upload_default({ file: screenFile, onProgress: (progress) => {
267
- cameraProgress = progress;
261
+ screenProgress = progress;
268
262
  handleOnProgress();
269
263
  } })
270
264
  ]);
@@ -288,13 +282,18 @@ function useProctoring(proctoringOptions) {
288
282
  },
289
283
  jwt: proctoringOptions.token
290
284
  });
291
- await Promise.all([clearBuffers("cameraBuffers"), clearBuffers("screenBuffers")]);
292
285
  cameraBuffer = [];
293
286
  screenBuffer = [];
294
287
  alerts = [];
295
288
  cancelCallback = null;
296
289
  }
297
- return { start, finish, download };
290
+ function onFocus(cb) {
291
+ onFocusCallback = cb;
292
+ }
293
+ function onLostFocus(cb) {
294
+ onLostFocusCallback = cb;
295
+ }
296
+ return { start, finish, download, onFocus, onLostFocus, enumarateDevices, checkPermissions };
298
297
  }
299
298
  if (typeof window !== "undefined") {
300
299
  window.useProctoring = useProctoring;
package/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import enumarateDevices from "./modules/enumarateDevices";
2
+ import checkPermissions from "./modules/checkPermissions";
1
3
  import StartProctoringResponse from "./dtos/StartProctoringResponse";
2
4
  export declare function useProctoring(proctoringOptions: {
3
5
  examId: string;
@@ -5,10 +7,15 @@ export declare function useProctoring(proctoringOptions: {
5
7
  token: string;
6
8
  }): {
7
9
  start: (options?: {
8
- override: boolean;
10
+ cameraId?: string;
11
+ microphoneId?: string;
9
12
  }) => Promise<StartProctoringResponse>;
10
13
  finish: (options?: {
11
14
  onProgress?: ((percentage: number) => void) | undefined;
12
15
  }) => Promise<void>;
13
16
  download: (file: File) => void;
17
+ onFocus: (cb: () => void) => void;
18
+ onLostFocus: (cb: () => void) => void;
19
+ enumarateDevices: typeof enumarateDevices;
20
+ checkPermissions: typeof checkPermissions;
14
21
  };