easyproctor 2.4.0 → 2.5.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/esm/index.js CHANGED
@@ -6278,6 +6278,7 @@ var BaseDetection = class {
6278
6278
  this.modelAssetPath = modelAssetPath;
6279
6279
  this.classVideo = classVideo;
6280
6280
  this.classDiv = classDiv;
6281
+ this.startTime = new Date(Date.now());
6281
6282
  paramsConfig && (this.paramsConfig = paramsConfig);
6282
6283
  options && (this.options = options);
6283
6284
  }
@@ -6367,15 +6368,22 @@ var BaseDetection = class {
6367
6368
  this.options.onRealtimeAlertsCallback && this.options.onRealtimeAlertsCallback({
6368
6369
  status: "ALERT",
6369
6370
  description: this.alertTranslate(description),
6370
- type
6371
+ type,
6372
+ category: description,
6373
+ begin: Date.now() - this.startTime.getTime(),
6374
+ end: Date.now() - this.startTime.getTime()
6371
6375
  });
6372
- this.error && (this.error.innerText = description);
6376
+ if (this.options.onRealtimeAlertsCallback == null)
6377
+ this.error && (this.error.innerText = description);
6373
6378
  }
6374
6379
  handleOk(description, type) {
6375
6380
  this.options.onRealtimeAlertsCallback && this.options.onRealtimeAlertsCallback({
6376
6381
  status: "OK",
6377
6382
  description: this.alertTranslate(description),
6378
- type
6383
+ type,
6384
+ category: description,
6385
+ begin: Date.now() - this.startTime.getTime(),
6386
+ end: Date.now() - this.startTime.getTime()
6379
6387
  });
6380
6388
  this.error && (this.error.innerText = "");
6381
6389
  }
@@ -6405,6 +6413,18 @@ var BaseDetection = class {
6405
6413
  return "Face detectada";
6406
6414
  case "ok_position_face_detected":
6407
6415
  return "Face na posi\xE7\xE3o correta";
6416
+ case "wrong_face_size_detected":
6417
+ return "Face muito perto da c\xE2mera, afaste-se um pouco mais";
6418
+ case "wrong_face_position_edge_detected":
6419
+ return "Face muito pr\xF3xima da borda, mova-se para o centro da tela";
6420
+ case "wrong_face_position_move_right_detected":
6421
+ return "Face n\xE3o centralizada, mova-se para a direita";
6422
+ case "wrong_face_position_move_left_detected":
6423
+ return "Face n\xE3o centralizada, mova-se para a esquerda";
6424
+ case "wrong_face_position_move_top_detected":
6425
+ return "Face n\xE3o centralizada, mova-se para cima";
6426
+ case "wrong_face_position_move_bottom_detected":
6427
+ return "Face n\xE3o centralizada, mova-se para baixo";
6408
6428
  default:
6409
6429
  return description;
6410
6430
  }
@@ -6437,6 +6457,13 @@ var FaceDetection = class extends BaseDetection {
6437
6457
  constructor(options, paramsConfig, classVideo = "videoPreviewFrameDetection", classDiv = "liveViewFrameDetection") {
6438
6458
  super("FaceDetector", `https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite`, options, paramsConfig, classVideo, classDiv);
6439
6459
  this.emmitedPositionAlert = false;
6460
+ this.emmitedFaceAlert = false;
6461
+ }
6462
+ stopDetection() {
6463
+ super.stopDetection();
6464
+ if (this.emmitedFaceAlert) {
6465
+ this.handleOk("face_ok", "face_detection_on_stream");
6466
+ }
6440
6467
  }
6441
6468
  displayVideoDetections(result) {
6442
6469
  for (const child of this.children) {
@@ -6499,31 +6526,71 @@ var FaceDetection = class extends BaseDetection {
6499
6526
  }
6500
6527
  }
6501
6528
  verify(result) {
6502
- var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
6529
+ var _a2;
6503
6530
  if (((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.detectFace) && result.detections.length !== this.numFacesSent) {
6504
6531
  this.numFacesSent = result.detections.length;
6505
6532
  if (result.detections.length === 0) {
6506
6533
  this.handleAlert("no_face_detected", "face_detection_on_stream");
6534
+ this.emmitedFaceAlert = true;
6535
+ return;
6507
6536
  } else if (result.detections.length > 1) {
6508
6537
  this.handleAlert("multiple_faces_detected", "face_detection_on_stream");
6538
+ this.emmitedFaceAlert = true;
6539
+ return;
6509
6540
  } else {
6510
6541
  this.handleOk("face_ok", "face_detection_on_stream");
6511
- }
6512
- }
6513
- const faceSize = ((_b = result.detections[0]) == null ? void 0 : _b.keypoints[3].y) - ((_c2 = result.detections[0]) == null ? void 0 : _c2.keypoints[0].y);
6514
- if (((_d = result.detections[0]) == null ? void 0 : _d.keypoints[4].x) < 0.2 || ((_e3 = result.detections[0]) == null ? void 0 : _e3.keypoints[5].x) > 0.8) {
6515
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
6542
+ this.emmitedFaceAlert = false;
6543
+ }
6544
+ }
6545
+ if (result.detections.length === 0) return;
6546
+ let face = result.detections[0].boundingBox;
6547
+ let video = document.getElementById(this.classVideo);
6548
+ const imageWidth = video.videoWidth;
6549
+ const imageHeight = video.videoHeight;
6550
+ let failedFacePosition = false;
6551
+ if (imageWidth > imageHeight) {
6552
+ if (face.height / imageHeight > 0.7) {
6553
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_size_detected", "position_detection_on_stream");
6554
+ failedFacePosition = true;
6555
+ this.emmitedPositionAlert = true;
6556
+ } else if (face.width / imageWidth > 0.7) {
6557
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_size_detected", "position_detection_on_stream");
6558
+ this.emmitedPositionAlert = true;
6559
+ failedFacePosition = true;
6560
+ }
6561
+ }
6562
+ let start = [face.originX, face.originY];
6563
+ let end = [face.originX + face.width, face.originY + face.height];
6564
+ if (start[0] < 0.1 * face.width || start[1] < 0.2 * face.height || end[0] > imageWidth - 0.1 * face.width || end[1] > imageHeight - 5) {
6565
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_edge_detected", "position_detection_on_stream");
6516
6566
  this.emmitedPositionAlert = true;
6517
- } else if (((_f = result.detections[0]) == null ? void 0 : _f.keypoints[0].y) < 0.2 || ((_g = result.detections[0]) == null ? void 0 : _g.keypoints[0].y) > 0.8) {
6518
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
6567
+ failedFacePosition = true;
6568
+ }
6569
+ let leftGap = start[0];
6570
+ let rightGap = imageWidth - end[0];
6571
+ let topGap = start[1];
6572
+ let bottomGap = imageHeight - end[1];
6573
+ if (leftGap > 2 * rightGap) {
6574
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_right_detected", "position_detection_on_stream");
6519
6575
  this.emmitedPositionAlert = true;
6520
- } else if (((_h = result.detections[0]) == null ? void 0 : _h.keypoints[3].y) < 0.4 || ((_i3 = result.detections[0]) == null ? void 0 : _i3.keypoints[3].y) > 0.9) {
6521
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
6576
+ failedFacePosition = true;
6577
+ }
6578
+ if (topGap > 4 * bottomGap) {
6579
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_top_detected", "position_detection_on_stream");
6522
6580
  this.emmitedPositionAlert = true;
6523
- } else if (faceSize > 0.35) {
6524
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
6581
+ failedFacePosition = true;
6582
+ }
6583
+ if (rightGap > 2 * leftGap) {
6584
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_left_detected", "position_detection_on_stream");
6525
6585
  this.emmitedPositionAlert = true;
6526
- } else {
6586
+ failedFacePosition = true;
6587
+ }
6588
+ if (bottomGap > 3 * topGap) {
6589
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_bottom_detected", "position_detection_on_stream");
6590
+ this.emmitedPositionAlert = true;
6591
+ failedFacePosition = true;
6592
+ }
6593
+ if (failedFacePosition == false) {
6527
6594
  this.emmitedPositionAlert && this.handleOk("ok_position_face_detected", "position_detection_on_stream");
6528
6595
  this.emmitedPositionAlert = false;
6529
6596
  }
@@ -9086,6 +9153,14 @@ var BackendService = class {
9086
9153
  jwt: this.token
9087
9154
  });
9088
9155
  }
9156
+ async goToExternalCameraPositionStep(externalSessionId) {
9157
+ return await this.makeRequest({
9158
+ path: `/ExternalCamera/go-to-position-step/${externalSessionId}`,
9159
+ method: "POST",
9160
+ body: {},
9161
+ jwt: this.token
9162
+ });
9163
+ }
9089
9164
  async externalCameraFinish(externalSessionId) {
9090
9165
  return await this.makeRequest({
9091
9166
  path: `/ExternalCamera/finish/${externalSessionId}`,
@@ -9094,15 +9169,16 @@ var BackendService = class {
9094
9169
  jwt: this.token
9095
9170
  });
9096
9171
  }
9097
- async confirmStart(proctoringOptions, proctoringType, latitude, longitude) {
9172
+ async confirmStart(proctoringOptions, sessionOptions, latitude, longitude) {
9098
9173
  return await this.makeRequest({
9099
9174
  path: `/proctoring/start/${proctoringOptions.examId}`,
9100
9175
  method: "POST",
9101
9176
  body: {
9102
9177
  clientId: proctoringOptions.clientId,
9103
- proctoringType,
9178
+ proctoringType: sessionOptions.proctoringType,
9104
9179
  latitude,
9105
- longitude
9180
+ longitude,
9181
+ captureScreen: sessionOptions.captureScreen
9106
9182
  },
9107
9183
  jwt: proctoringOptions.token
9108
9184
  });
@@ -9149,8 +9225,8 @@ var BackendService = class {
9149
9225
  }
9150
9226
  });
9151
9227
  }
9152
- async finishAndSendUrls(proctoringOptions, proctoringSession) {
9153
- await this.makeRequest({
9228
+ async finishAndSendUrls(proctoringOptions) {
9229
+ return await this.makeRequest({
9154
9230
  path: `/proctoring/finish/${proctoringOptions.examId}`,
9155
9231
  method: "POST",
9156
9232
  body: {
@@ -9196,6 +9272,51 @@ var BackendService = class {
9196
9272
  });
9197
9273
  return result.data;
9198
9274
  }
9275
+ async startChallenge(body) {
9276
+ const result = await this.makeRequestAxios({
9277
+ path: `/Challenge/start`,
9278
+ method: "POST",
9279
+ jwt: this.token,
9280
+ body
9281
+ });
9282
+ return result.data;
9283
+ }
9284
+ async stopChallenge(challengeId, body) {
9285
+ const result = await this.makeRequestAxios({
9286
+ path: `/Challenge/stop/${challengeId}`,
9287
+ method: "POST",
9288
+ jwt: this.token,
9289
+ body
9290
+ });
9291
+ return result.data;
9292
+ }
9293
+ async startRealtimeAlert(body) {
9294
+ const result = await this.makeRequestAxios({
9295
+ path: `/Realtime/start-warning`,
9296
+ method: "POST",
9297
+ jwt: this.token,
9298
+ body
9299
+ });
9300
+ return result.data;
9301
+ }
9302
+ async stopRealtimeAlert(body) {
9303
+ const result = await this.makeRequestAxios({
9304
+ path: `/Realtime/stop-warning`,
9305
+ method: "POST",
9306
+ jwt: this.token,
9307
+ body
9308
+ });
9309
+ return result.data;
9310
+ }
9311
+ async verifyFace(proctoringId2, faceImage) {
9312
+ const result = await this.makeRequestAxios({
9313
+ path: `/Realtime/verify-face`,
9314
+ method: "POST",
9315
+ jwt: this.token,
9316
+ body: { "proctoringId": proctoringId2, "base64": faceImage }
9317
+ });
9318
+ return result.data;
9319
+ }
9199
9320
  async getServerHour(token) {
9200
9321
  return await this.makeRequest({
9201
9322
  path: `/Proctoring/server-hour`,
@@ -9523,7 +9644,9 @@ var getDefaultProctoringOptions = {
9523
9644
  onBufferSizeError: false,
9524
9645
  useGeolocation: false,
9525
9646
  useSpyScan: false,
9526
- useExternalCamera: false
9647
+ useExternalCamera: false,
9648
+ useChallenge: false,
9649
+ screenRecorderOptions: { width: 1280, height: 720 }
9527
9650
  };
9528
9651
 
9529
9652
  // src/proctoring/options/ProctoringVideoOptions.ts
@@ -9582,6 +9705,7 @@ var init = (backend) => {
9582
9705
  return backendService;
9583
9706
  };
9584
9707
  var eventNames = {
9708
+ DEVICES_CHECKED: "devices_checked",
9585
9709
  START: "start",
9586
9710
  FINISH: "finish",
9587
9711
  ERROR: "error",
@@ -9598,6 +9722,7 @@ var eventNames = {
9598
9722
  };
9599
9723
  var log = (eventName, properties) => backendService && backendService.log(eventName, properties);
9600
9724
  var trackers = {
9725
+ registerDevicesChecked: (proctoringId2, success, description) => log(eventNames.DEVICES_CHECKED, { proctoringId: proctoringId2, success, description }),
9601
9726
  registerStart: (proctoringId2, success, description) => log(eventNames.START, { proctoringId: proctoringId2, success, description }),
9602
9727
  registerFinish: (proctoringId2, success, description) => log(eventNames.FINISH, { proctoringId: proctoringId2, success, description }),
9603
9728
  registerError: (proctoringId2, description) => log(eventNames.ERROR, { proctoringId: proctoringId2, description }),
@@ -9888,6 +10013,12 @@ var ObjectDetection = class extends BaseDetection {
9888
10013
  constructor(options, paramsConfig, classVideo = "videoPreviewFrameDetection", classDiv = "liveViewFrameDetection") {
9889
10014
  super("ObjectDetector", `https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float16/1/efficientdet_lite0.tflite`, options, paramsConfig, classVideo, classDiv);
9890
10015
  }
10016
+ stopDetection() {
10017
+ super.stopDetection();
10018
+ if (this.numPersonsSent > 0) {
10019
+ this.handleOk("person_ok", "person_detection_on_stream");
10020
+ }
10021
+ }
9891
10022
  displayVideoDetections(result) {
9892
10023
  for (const child of this.children) {
9893
10024
  this.liveView.removeChild(child);
@@ -10062,6 +10193,10 @@ var CameraRecorder = class {
10062
10193
  this.blobsRTC = [];
10063
10194
  this.imageCount = 0;
10064
10195
  this.filesToUpload = [];
10196
+ this.animationFrameId = null;
10197
+ this.isCanvasLoopActive = false;
10198
+ this.hardwareStream = null;
10199
+ this.internalClonedStream = null;
10065
10200
  this.currentRetries = 0;
10066
10201
  this.noiseWait = 20;
10067
10202
  this.options = options;
@@ -10123,7 +10258,7 @@ var CameraRecorder = class {
10123
10258
  }
10124
10259
  }
10125
10260
  async startRecording(options) {
10126
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
10261
+ var _a2, _b, _c2, _d, _e3, _f, _g;
10127
10262
  if ((((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.detectPerson) || ((_b = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _b.detectCellPhone) || ((_c2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _c2.detectFace)) && !(options == null ? void 0 : options.retry)) {
10128
10263
  await this.initializeDetectors();
10129
10264
  }
@@ -10138,7 +10273,7 @@ var CameraRecorder = class {
10138
10273
  }
10139
10274
  };
10140
10275
  try {
10141
- this.cameraStream = await navigator.mediaDevices.getUserMedia(
10276
+ this.hardwareStream = await navigator.mediaDevices.getUserMedia(
10142
10277
  constraints
10143
10278
  );
10144
10279
  } catch (error) {
@@ -10146,6 +10281,16 @@ var CameraRecorder = class {
10146
10281
  throw "N\xE3o foi poss\xEDvel conectar a camera, ela pode estar sendo utilizada por outro programa";
10147
10282
  throw error;
10148
10283
  }
10284
+ this.cameraStream = this.hardwareStream;
10285
+ const track = this.cameraStream.getVideoTracks()[0];
10286
+ const settings = track.getSettings();
10287
+ const { width = 0, height = 0 } = settings;
10288
+ const needsRotationFix = isMobileDevice() && width > height;
10289
+ this.isCanvasLoopActive = true;
10290
+ if (needsRotationFix) {
10291
+ console.log("Aplicando corre\xE7\xE3o e substituindo stream p\xFAblico...");
10292
+ this.cameraStream = this.createRotatedStream(this.hardwareStream);
10293
+ }
10149
10294
  const {
10150
10295
  startRecording,
10151
10296
  stopRecording,
@@ -10155,6 +10300,7 @@ var CameraRecorder = class {
10155
10300
  getBufferSize
10156
10301
  } = recorder(
10157
10302
  this.cameraStream,
10303
+ // streamToRecord,
10158
10304
  this.blobs,
10159
10305
  this.options.onBufferSizeError,
10160
10306
  (e3) => this.bufferError(e3),
@@ -10167,25 +10313,6 @@ var CameraRecorder = class {
10167
10313
  this.recorderOptions = recorderOptions;
10168
10314
  this.getBufferSize = getBufferSize;
10169
10315
  this.recordingStart();
10170
- const tracks = this.cameraStream.getVideoTracks();
10171
- const settings = tracks[0].getSettings();
10172
- let { width = 0, height = 0 } = settings;
10173
- const isPortrait = (_d = screen.orientation) == null ? void 0 : _d.type.includes("portrait");
10174
- if (isPortrait && isMobileDevice()) {
10175
- if (this.videoOptions.width == height && this.videoOptions.height == width) {
10176
- [width, height] = [height, width];
10177
- }
10178
- }
10179
- console.log("isPortrait -> ", isPortrait);
10180
- console.log("this.isMobileDevice() -> ", isMobileDevice());
10181
- console.log("width -> ", width);
10182
- console.log("height -> ", height);
10183
- console.log("minWidth -> ", this.videoOptions.minWidth);
10184
- console.log("minHeight -> ", this.videoOptions.minHeight);
10185
- console.log("this.videoOptions.width -> ", this.videoOptions.width);
10186
- console.log("this.videoOptions.height -> ", this.videoOptions.height);
10187
- console.log("settings.width -> ", settings.width);
10188
- console.log("settings.height -> ", settings.height);
10189
10316
  if (this.videoOptions.minWidth > width || this.videoOptions.minHeight > height) {
10190
10317
  throw STREAM_UNDER_MINIMUM_PERMITTED;
10191
10318
  } else if (this.videoOptions.width !== width || this.videoOptions.height !== height) {
@@ -10201,18 +10328,37 @@ Setting: ${JSON.stringify(settings, null, 2)}`
10201
10328
  );
10202
10329
  throw ANOTHER_STREAM_ACTIVE;
10203
10330
  }
10204
- ((_e3 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _e3.useUploadImage) && this.options.proctoringType == "IMAGE" && this.photoShotsCycle();
10205
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
10331
+ ((_d = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _d.useUploadImage) && this.options.proctoringType == "IMAGE" && this.photoShotsCycle();
10332
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
10206
10333
  await this.faceDetection.enableCam(this.cameraStream);
10207
10334
  }
10208
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
10335
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
10209
10336
  await this.objectDetection.enableCam(this.cameraStream);
10210
10337
  }
10211
10338
  this.filesToUpload = [];
10212
- this.options.proctoringType == "REALTIME" && this.captureFrame();
10213
10339
  }
10214
10340
  async stopRecording() {
10341
+ this.isCanvasLoopActive = false;
10215
10342
  this.recordingStop && await this.recordingStop();
10343
+ try {
10344
+ if (this.animationFrameId) {
10345
+ cancelAnimationFrame(this.animationFrameId);
10346
+ this.animationFrameId = null;
10347
+ }
10348
+ if (this.cameraStream) {
10349
+ this.cameraStream.getTracks().forEach((track) => track.stop());
10350
+ }
10351
+ if (this.internalClonedStream) {
10352
+ this.internalClonedStream.getTracks().forEach((track) => track.stop());
10353
+ this.internalClonedStream = null;
10354
+ }
10355
+ if (this.hardwareStream) {
10356
+ this.hardwareStream.getTracks().forEach((track) => track.stop());
10357
+ this.hardwareStream = null;
10358
+ }
10359
+ } catch (e3) {
10360
+ console.error("Erro ao parar os streams de m\xEDdia.");
10361
+ }
10216
10362
  this.faceDetection && this.faceDetection.detecting && this.faceDetection.stopDetection();
10217
10363
  this.objectDetection && this.objectDetection.detecting && this.objectDetection.stopDetection();
10218
10364
  clearInterval(this.imageInterval);
@@ -10279,6 +10425,36 @@ Setting: ${JSON.stringify(settings, null, 2)}`
10279
10425
  }
10280
10426
  }, this.paramsConfig.imageBehaviourParameters.uploadInterval * 1e3);
10281
10427
  }
10428
+ async getCurrentImageBase64() {
10429
+ if (!this.video || !this.canvas) {
10430
+ this.configImageCapture();
10431
+ } else {
10432
+ if (this.video.srcObject !== this.cameraStream) {
10433
+ this.video.srcObject = this.cameraStream;
10434
+ await this.video.play();
10435
+ }
10436
+ }
10437
+ if (this.video.paused) {
10438
+ await this.video.play();
10439
+ }
10440
+ await new Promise((resolve) => {
10441
+ if (this.video.readyState >= 2) {
10442
+ resolve();
10443
+ return;
10444
+ }
10445
+ const onLoadedMetadata = () => {
10446
+ this.video.removeEventListener("loadedmetadata", onLoadedMetadata);
10447
+ setTimeout(() => resolve(), 50);
10448
+ };
10449
+ this.video.addEventListener("loadedmetadata", onLoadedMetadata);
10450
+ setTimeout(() => {
10451
+ this.video.removeEventListener("loadedmetadata", onLoadedMetadata);
10452
+ resolve();
10453
+ }, 500);
10454
+ });
10455
+ this.canvas.getContext("2d").drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
10456
+ return this.canvas.toDataURL("image/jpeg");
10457
+ }
10282
10458
  // De um em um segundo captura um frame
10283
10459
  captureFrame() {
10284
10460
  let imageFile;
@@ -10413,6 +10589,46 @@ Setting: ${JSON.stringify(settings, null, 2)}`
10413
10589
  }
10414
10590
  this.noiseWait++;
10415
10591
  }
10592
+ /**
10593
+ * Cria um stream processado onde os frames são rotacionados via Canvas.
10594
+ * Isso corrige o problema de gravação deitada em iOS/Mobile.
10595
+ */
10596
+ createRotatedStream(originalStream) {
10597
+ this.internalClonedStream = originalStream.clone();
10598
+ const video = document.createElement("video");
10599
+ video.srcObject = this.internalClonedStream;
10600
+ video.muted = true;
10601
+ video.playsInline = true;
10602
+ video.play();
10603
+ const canvas = document.createElement("canvas");
10604
+ const ctx = canvas.getContext("2d");
10605
+ const track = originalStream.getVideoTracks()[0];
10606
+ const settings = track.getSettings();
10607
+ const width = settings.width || 640;
10608
+ const height = settings.height || 480;
10609
+ canvas.width = height;
10610
+ canvas.height = width;
10611
+ const draw = () => {
10612
+ if (video.paused || video.ended) return;
10613
+ if (!this.isCanvasLoopActive) return;
10614
+ if (ctx) {
10615
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
10616
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
10617
+ }
10618
+ if (this.isCanvasLoopActive) {
10619
+ this.animationFrameId = requestAnimationFrame(draw);
10620
+ }
10621
+ };
10622
+ video.onplaying = () => {
10623
+ this.isCanvasLoopActive = true;
10624
+ draw();
10625
+ };
10626
+ const canvasStream = canvas.captureStream(30);
10627
+ originalStream.getAudioTracks().forEach((track2) => {
10628
+ canvasStream.addTrack(track2);
10629
+ });
10630
+ return canvasStream;
10631
+ }
10416
10632
  };
10417
10633
 
10418
10634
  // src/new-flow/checkers/DeviceCheckerUI.ts
@@ -10797,6 +11013,9 @@ var DeviceCheckerUI = class {
10797
11013
  const center = document.createElement("div");
10798
11014
  video.setAttribute("id", "cameraStream");
10799
11015
  video.muted = true;
11016
+ video.setAttribute("playsinline", "true");
11017
+ video.setAttribute("webkit-playsinline", "true");
11018
+ video.autoplay = true;
10800
11019
  const divCameraStyles = {
10801
11020
  width: "100%",
10802
11021
  display: "flex",
@@ -11225,7 +11444,7 @@ var DeviceCheckerUI = class {
11225
11444
  checkmark_kick_AmbientVerify.setAttribute("class", "checkmark_kick");
11226
11445
  }
11227
11446
  } else {
11228
- if (checkmark_FacePosition && (response.description === "Nenhuma face encontrada" || response.description === "Face na posi\xE7\xE3o errada")) {
11447
+ if (checkmark_FacePosition && (response.type === "position_detection_on_stream" || response.type === "face_detection_on_stream")) {
11229
11448
  facePositionAlert && (facePositionAlert.style.color = "#FF0000");
11230
11449
  checkmark_FacePosition.setAttribute("class", "checkmark_error");
11231
11450
  checkmark_stem_FacePosition.setAttribute("class", "checkmark_stem_error");
@@ -11330,7 +11549,9 @@ Para iniciar um exame utilize uma outra c\xE2mera.`);
11330
11549
  const cameraStream = document.querySelector("#cameraStream");
11331
11550
  if (cameraStream) {
11332
11551
  cameraStream.srcObject = stream;
11333
- cameraStream.play();
11552
+ cameraStream.play().catch((e3) => {
11553
+ console.warn("Erro ao iniciar preview de v\xEDdeo:", e3);
11554
+ });
11334
11555
  }
11335
11556
  }
11336
11557
  audioDeviceInterfaceUIAllowedAmbient(allowedAmbient) {
@@ -12116,9 +12337,11 @@ var Extension = class {
12116
12337
 
12117
12338
  // src/modules/onChangeDevices.ts
12118
12339
  var onChangeDevices = class {
12119
- constructor(repositoryDevices, proctoringId2) {
12340
+ constructor(repositoryDevices, proctoringId2, sessionOptions, allRecorders) {
12120
12341
  this.repositoryDevices = repositoryDevices;
12121
12342
  this.proctoringId = proctoringId2;
12343
+ this.sessionOptions = sessionOptions;
12344
+ this.allRecorders = allRecorders;
12122
12345
  }
12123
12346
  startRecording(options) {
12124
12347
  navigator.mediaDevices.ondevicechange = () => {
@@ -12132,24 +12355,49 @@ var onChangeDevices = class {
12132
12355
  const response = await this.repositoryDevices.getDevices("devices");
12133
12356
  const defaultDevice = { label: "", id: "" };
12134
12357
  const copy = { cameras: (response == null ? void 0 : response.cameras) || [defaultDevice], microphones: (response == null ? void 0 : response.microphones) || [defaultDevice] };
12135
- let resultCameras;
12136
- let resultMicrophones;
12358
+ const status = devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) || devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? "in" : "out";
12359
+ let resultCameras = [];
12360
+ let resultMicrophones = [];
12361
+ let isActiveDeviceRemoved = false;
12137
12362
  if (devices.cameras.length != (copy == null ? void 0 : copy.cameras.length)) {
12138
- resultCameras = devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) ? onlyInLeft(devices.cameras, copy == null ? void 0 : copy.cameras, isSameDevice) : onlyInLeft(copy == null ? void 0 : copy.cameras, devices.cameras, isSameDevice);
12363
+ const removedCameras = status === "out" ? onlyInLeft(copy == null ? void 0 : copy.cameras, devices.cameras, isSameDevice) : [];
12364
+ const addedCameras = status === "in" ? onlyInLeft(devices.cameras, copy == null ? void 0 : copy.cameras, isSameDevice) : [];
12365
+ resultCameras = status === "out" ? removedCameras : addedCameras;
12139
12366
  resultCameras = resultCameras.filter((item) => item.id != "default");
12367
+ console.log(removedCameras);
12368
+ console.log(this.sessionOptions.cameraId);
12369
+ if (status === "out") {
12370
+ isActiveDeviceRemoved = removedCameras.some(
12371
+ (device) => device.id === this.sessionOptions.cameraId
12372
+ );
12373
+ }
12140
12374
  }
12141
12375
  if (devices.microphones.length != (copy == null ? void 0 : copy.microphones.length)) {
12142
- resultMicrophones = devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? onlyInLeft(devices.microphones, copy == null ? void 0 : copy.microphones, isSameDevice) : onlyInLeft(copy == null ? void 0 : copy.microphones, devices.microphones, isSameDevice);
12376
+ const removedMicrophones = status === "out" ? onlyInLeft(copy == null ? void 0 : copy.microphones, devices.microphones, isSameDevice) : [];
12377
+ const addedMicrophones = status === "in" ? onlyInLeft(devices.microphones, copy == null ? void 0 : copy.microphones, isSameDevice) : [];
12378
+ resultMicrophones = status === "out" ? removedMicrophones : addedMicrophones;
12143
12379
  resultMicrophones = resultMicrophones.filter((item) => item.id != "default" && item.id != "communications");
12380
+ if (status === "out" && !isActiveDeviceRemoved) {
12381
+ isActiveDeviceRemoved = removedMicrophones.some(
12382
+ (device) => device.id === this.sessionOptions.microphoneId
12383
+ );
12384
+ }
12144
12385
  }
12145
12386
  const devicesChanged = {
12146
12387
  cameras: resultCameras || [],
12147
12388
  microphones: resultMicrophones || [],
12148
- status: devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) || devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? "in" : "out"
12389
+ status,
12390
+ isActiveDevice: isActiveDeviceRemoved
12149
12391
  };
12150
- await this.repositoryDevices.save({ ...devices, id: "devices", status: devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) || devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? "in" : "out" });
12392
+ await this.repositoryDevices.save({ ...devices, id: "devices", status });
12151
12393
  if (options.status && (devicesChanged.cameras.length != 0 || devicesChanged.microphones.length != 0)) {
12152
12394
  trackers.registerChangeDevice(this.proctoringId, devicesChanged.status, JSON.stringify(devicesChanged, null, 2));
12395
+ if (devicesChanged.isActiveDevice) {
12396
+ this.allRecorders.alertRecorder.addAlert({
12397
+ alert: 39 /* ChangeDevices */,
12398
+ type: 2 /* Video */
12399
+ });
12400
+ }
12153
12401
  options.status(devicesChanged);
12154
12402
  }
12155
12403
  }
@@ -12357,6 +12605,14 @@ var AlertRecorder = class {
12357
12605
  lastAlert.end = Date.now() - this.startTime.getTime();
12358
12606
  }
12359
12607
  }
12608
+ addAlert({ alert, type }) {
12609
+ this.alerts.push({
12610
+ begin: Date.now() - this.startTime.getTime(),
12611
+ end: Date.now() - this.startTime.getTime(),
12612
+ alert,
12613
+ type
12614
+ });
12615
+ }
12360
12616
  };
12361
12617
 
12362
12618
  // src/new-flow/recorders/AudioRecorder.ts
@@ -14845,19 +15101,38 @@ var ScreenRecorder = class {
14845
15101
  }
14846
15102
  async startRecording() {
14847
15103
  this.startTime = new Date(Date.now());
15104
+ if (isMobileDevice()) return;
14848
15105
  const { allowOnlyFirstMonitor, allowMultipleMonitors, onStopSharingScreenCallback, onBufferSizeErrorCallback } = this.options;
14849
- const displayMediaStreamConstraints = {
15106
+ const complexConstraints = {
14850
15107
  video: {
14851
15108
  cursor: "always",
14852
- width: 854,
14853
- height: 480,
15109
+ width: this.options.screenRecorderOptions.width,
15110
+ height: this.options.screenRecorderOptions.height,
14854
15111
  displaySurface: "monitor"
14855
15112
  },
14856
15113
  audio: false
14857
15114
  };
14858
- this.screenStream = await navigator.mediaDevices.getDisplayMedia(
14859
- displayMediaStreamConstraints
14860
- );
15115
+ const simpleConstraints = {
15116
+ video: {
15117
+ cursor: "always",
15118
+ width: 1280,
15119
+ height: 720,
15120
+ displaySurface: "monitor"
15121
+ },
15122
+ audio: false
15123
+ };
15124
+ let stream;
15125
+ try {
15126
+ stream = await navigator.mediaDevices.getDisplayMedia(complexConstraints);
15127
+ } catch (e3) {
15128
+ if (e3 instanceof TypeError && e3.message.includes("min constraints are not supported")) {
15129
+ console.warn("Navegador n\xE3o suporta restri\xE7\xF5es 'min/max/ideal'. Tentando com 720p fixo.");
15130
+ stream = await navigator.mediaDevices.getDisplayMedia(simpleConstraints);
15131
+ } else {
15132
+ throw e3;
15133
+ }
15134
+ }
15135
+ this.screenStream = stream;
14861
15136
  const tracks = this.screenStream.getVideoTracks();
14862
15137
  tracks[0].onended = onStopSharingScreenCallback;
14863
15138
  const isFirefox = navigator.userAgent.indexOf("Firefox") > -1;
@@ -17997,15 +18272,12 @@ var _ExternalCameraChecker = class _ExternalCameraChecker {
17997
18272
  }
17998
18273
  }
17999
18274
  async goToPositionGuide() {
18000
- if (this.connection) {
18001
- const actionMessage = new ActionMessage();
18002
- actionMessage.command = "Position_Guide";
18003
- console.log("Enviando comando 'Position_Guide' para o aplicativo...");
18004
- this.connection.invoke(
18005
- "SendAction",
18006
- this.externalSessionId,
18007
- actionMessage
18008
- );
18275
+ try {
18276
+ const response = await this.backend.goToExternalCameraPositionStep("" + this.externalSessionId);
18277
+ console.log(response);
18278
+ } catch (error) {
18279
+ console.error("Erro ao enviar comando de Position:", error);
18280
+ throw new Error("N\xE3o foi poss\xEDvel enviar comando de Position Guide.");
18009
18281
  }
18010
18282
  }
18011
18283
  async reset() {
@@ -18684,13 +18956,9 @@ var ExternalCameraChecker = _ExternalCameraChecker;
18684
18956
 
18685
18957
  // src/proctoring/proctoring.ts
18686
18958
  var Proctoring = class {
18687
- // private onProgress = (percentage: number) => {};
18688
- // public setOnProgress(cb: (percentage: number) => void) {
18689
- // console.log("proctoring.setOnProgress");
18690
- // this.onProgress = (percentage) => cb(percentage);
18691
- // }
18692
18959
  constructor(context) {
18693
18960
  this.context = context;
18961
+ this.deviceData = null;
18694
18962
  this.paramsConfig = {
18695
18963
  audioBehaviourParameters: {
18696
18964
  recordingBitrate: 128,
@@ -18743,11 +19011,17 @@ var Proctoring = class {
18743
19011
  this.appChecker = new ExternalCameraChecker(this.context, (response) => this.onRealtimeAlertsCallback(response));
18744
19012
  }
18745
19013
  setOnStopSharingScreenCallback(cb) {
18746
- this.onStopSharingScreenCallback = () => {
19014
+ this.onStopSharingScreenCallback = async () => {
19015
+ var _a2, _b, _c2, _d;
18747
19016
  trackers.registerStopSharingScreen(
18748
19017
  this.proctoringId,
18749
19018
  "Stop sharing screen"
18750
19019
  );
19020
+ (_b = (_a2 = this.allRecorders) == null ? void 0 : _a2.alertRecorder) == null ? void 0 : _b.addAlert({
19021
+ alert: 34 /* StopSharingScreen */,
19022
+ type: 3 /* Screen */
19023
+ });
19024
+ (_d = (_c2 = this.allRecorders) == null ? void 0 : _c2.screenRecorder) == null ? void 0 : _d.stopRecording();
18751
19025
  cb();
18752
19026
  };
18753
19027
  }
@@ -18760,17 +19034,71 @@ var Proctoring = class {
18760
19034
  async onChangeDevices(options = {}) {
18761
19035
  const onChange = new onChangeDevices(
18762
19036
  this.repositoryDevices,
18763
- this.proctoringId
19037
+ this.proctoringId,
19038
+ this.sessionOptions,
19039
+ this.allRecorders
18764
19040
  );
18765
19041
  onChange.startRecording(options);
18766
19042
  this.onChangeDevicesCallback = (devices) => options.status && options.status(devices);
18767
19043
  }
19044
+ convertRealtimeCategoryToAlertCategory(category) {
19045
+ switch (category) {
19046
+ case "no_face_detected":
19047
+ return 1 /* NoFace */;
19048
+ case "multiple_faces_detected":
19049
+ return 2 /* MultipleFaces */;
19050
+ case "multiple_persons_detected":
19051
+ return 28 /* EnvironmentMultiplePeople */;
19052
+ case "no_person_detected":
19053
+ return 29 /* EnvironmentNoPeople */;
19054
+ default:
19055
+ return null;
19056
+ }
19057
+ }
19058
+ convertRealtimeTypeToWarningType(category) {
19059
+ switch (category) {
19060
+ case "face_detection_on_stream":
19061
+ return 0 /* Face */;
19062
+ case "person_detection_on_stream":
19063
+ return 1 /* People */;
19064
+ default:
19065
+ return null;
19066
+ }
19067
+ }
18768
19068
  async onRealtimeAlerts(options = {}) {
18769
- this.onRealtimeAlertsCallback = (response) => options.data && options.data(response);
19069
+ this.onRealtimeAlertsCallback = async (response) => {
19070
+ options.data && options.data(response);
19071
+ if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream")) {
19072
+ if (response.status === "ALERT") {
19073
+ await this.backend.startRealtimeAlert({
19074
+ proctoringId: this.proctoringId,
19075
+ begin: response.begin,
19076
+ end: response.end,
19077
+ alert: this.convertRealtimeCategoryToAlertCategory(response.category)
19078
+ });
19079
+ } else if (response.status === "OK") {
19080
+ await this.backend.stopRealtimeAlert({
19081
+ proctoringId: this.proctoringId,
19082
+ begin: response.begin,
19083
+ end: response.end,
19084
+ warningCategoryEnum: this.convertRealtimeTypeToWarningType(response.type),
19085
+ alertImageBase64: await this.allRecorders.cameraRecorder.getCurrentImageBase64()
19086
+ });
19087
+ }
19088
+ }
19089
+ };
18770
19090
  }
18771
19091
  setOnBufferSizeErrorCallback(cb) {
18772
19092
  this.onBufferSizeErrorCallback = (cameraStream) => cb(cameraStream);
18773
19093
  }
19094
+ // private onProgress = (percentage: number) => {};
19095
+ // public setOnProgress(cb: (percentage: number) => void) {
19096
+ // console.log("proctoring.setOnProgress");
19097
+ // this.onProgress = (percentage) => cb(percentage);
19098
+ // }
19099
+ setDeviceCheckData(data) {
19100
+ this.deviceData = data;
19101
+ }
18774
19102
  createRecorders(options = getDefaultProctoringOptions) {
18775
19103
  var _a2, _b;
18776
19104
  this.onChangeDevices();
@@ -18797,6 +19125,7 @@ var Proctoring = class {
18797
19125
  const screenRecorder = this.sessionOptions.captureScreen ? new ScreenRecorder({
18798
19126
  allowOnlyFirstMonitor: (_a2 = this.sessionOptions.allowOnlyFirstMonitor) != null ? _a2 : true,
18799
19127
  allowMultipleMonitors: (_b = this.sessionOptions.allowMultipleMonitors) != null ? _b : true,
19128
+ screenRecorderOptions: this.sessionOptions.screenRecorderOptions,
18800
19129
  onStopSharingScreenCallback: () => this.onStopSharingScreenCallback(),
18801
19130
  onBufferSizeError: this.sessionOptions.onBufferSizeError,
18802
19131
  onBufferSizeErrorCallback: () => this.onBufferSizeErrorCallback()
@@ -18869,11 +19198,16 @@ var Proctoring = class {
18869
19198
  examId: this.context.examId,
18870
19199
  token: this.context.token
18871
19200
  },
18872
- this.sessionOptions.proctoringType,
19201
+ this.sessionOptions,
18873
19202
  this.geolocation ? this.geolocation.coords.latitude : void 0,
18874
19203
  this.geolocation ? this.geolocation.coords.longitude : void 0
18875
19204
  );
18876
19205
  this.proctoringId = startResponse.id;
19206
+ trackers.registerDevicesChecked(
19207
+ this.proctoringId,
19208
+ !!this.deviceData,
19209
+ `Devices checked: ${JSON.stringify(this.deviceData)} | Devices List: ${JSON.stringify(devices)}`
19210
+ );
18877
19211
  try {
18878
19212
  if (options == null ? void 0 : options.useExternalCamera) {
18879
19213
  await this.appChecker.startTransmission(this.proctoringId);
@@ -18907,6 +19241,11 @@ Navigator: ${JSON.stringify(_navigator2)}`
18907
19241
  startResponse.screenStream = this.allRecorders.screenRecorder.screenStream;
18908
19242
  }
18909
19243
  this.state = "Recording" /* Recording */;
19244
+ setTimeout(async () => {
19245
+ if (this.sessionOptions.proctoringType === "REALTIME") {
19246
+ await this.backend.verifyFace(this.proctoringId, await this.allRecorders.cameraRecorder.getCurrentImageBase64());
19247
+ }
19248
+ }, 1e3);
18910
19249
  return startResponse;
18911
19250
  } catch (error) {
18912
19251
  console.log(error);
@@ -19025,8 +19364,11 @@ Upload Services: ${uploaderServices}`,
19025
19364
  );
19026
19365
  });
19027
19366
  }
19028
- await this.backend.finishAndSendUrls(this.context, this.proctoringSession).then(() => {
19367
+ await this.backend.finishAndSendUrls(this.context).then((finishResponse) => {
19368
+ var _a2, _b;
19029
19369
  trackers.registerFinish(this.proctoringSession.id, true, "");
19370
+ console.log("finishResponse: ", finishResponse);
19371
+ options.onFinish && options.onFinish((_a2 = finishResponse == null ? void 0 : finishResponse.score) != null ? _a2 : 100, (_b = finishResponse == null ? void 0 : finishResponse.approved) != null ? _b : true);
19030
19372
  }).catch((error) => {
19031
19373
  trackers.registerFinish(
19032
19374
  this.proctoringSession.id,
@@ -19373,7 +19715,14 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
19373
19715
  const signTerm = new SignTerm(parameters);
19374
19716
  const photo = new CapturePhoto();
19375
19717
  const login = proctoring.login.bind(proctoring);
19376
- const start = proctoring.start.bind(proctoring);
19718
+ const originalStart = proctoring.start.bind(proctoring);
19719
+ const start = async (parameters2) => {
19720
+ const deviceResult = checker.getDeviceCheckResult();
19721
+ if (deviceResult) {
19722
+ proctoring.setDeviceCheckData(deviceResult);
19723
+ }
19724
+ return originalStart(parameters2);
19725
+ };
19377
19726
  const finish = proctoring.finish.bind(proctoring);
19378
19727
  const pause = proctoring.pause.bind(proctoring);
19379
19728
  const resume = proctoring.resume.bind(proctoring);