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/index.js CHANGED
@@ -23563,6 +23563,7 @@ var BaseDetection = class {
23563
23563
  this.modelAssetPath = modelAssetPath;
23564
23564
  this.classVideo = classVideo;
23565
23565
  this.classDiv = classDiv;
23566
+ this.startTime = new Date(Date.now());
23566
23567
  paramsConfig && (this.paramsConfig = paramsConfig);
23567
23568
  options && (this.options = options);
23568
23569
  }
@@ -23652,15 +23653,22 @@ var BaseDetection = class {
23652
23653
  this.options.onRealtimeAlertsCallback && this.options.onRealtimeAlertsCallback({
23653
23654
  status: "ALERT",
23654
23655
  description: this.alertTranslate(description),
23655
- type
23656
+ type,
23657
+ category: description,
23658
+ begin: Date.now() - this.startTime.getTime(),
23659
+ end: Date.now() - this.startTime.getTime()
23656
23660
  });
23657
- this.error && (this.error.innerText = description);
23661
+ if (this.options.onRealtimeAlertsCallback == null)
23662
+ this.error && (this.error.innerText = description);
23658
23663
  }
23659
23664
  handleOk(description, type) {
23660
23665
  this.options.onRealtimeAlertsCallback && this.options.onRealtimeAlertsCallback({
23661
23666
  status: "OK",
23662
23667
  description: this.alertTranslate(description),
23663
- type
23668
+ type,
23669
+ category: description,
23670
+ begin: Date.now() - this.startTime.getTime(),
23671
+ end: Date.now() - this.startTime.getTime()
23664
23672
  });
23665
23673
  this.error && (this.error.innerText = "");
23666
23674
  }
@@ -23690,6 +23698,18 @@ var BaseDetection = class {
23690
23698
  return "Face detectada";
23691
23699
  case "ok_position_face_detected":
23692
23700
  return "Face na posi\xE7\xE3o correta";
23701
+ case "wrong_face_size_detected":
23702
+ return "Face muito perto da c\xE2mera, afaste-se um pouco mais";
23703
+ case "wrong_face_position_edge_detected":
23704
+ return "Face muito pr\xF3xima da borda, mova-se para o centro da tela";
23705
+ case "wrong_face_position_move_right_detected":
23706
+ return "Face n\xE3o centralizada, mova-se para a direita";
23707
+ case "wrong_face_position_move_left_detected":
23708
+ return "Face n\xE3o centralizada, mova-se para a esquerda";
23709
+ case "wrong_face_position_move_top_detected":
23710
+ return "Face n\xE3o centralizada, mova-se para cima";
23711
+ case "wrong_face_position_move_bottom_detected":
23712
+ return "Face n\xE3o centralizada, mova-se para baixo";
23693
23713
  default:
23694
23714
  return description;
23695
23715
  }
@@ -23722,6 +23742,13 @@ var FaceDetection = class extends BaseDetection {
23722
23742
  constructor(options, paramsConfig, classVideo = "videoPreviewFrameDetection", classDiv = "liveViewFrameDetection") {
23723
23743
  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);
23724
23744
  this.emmitedPositionAlert = false;
23745
+ this.emmitedFaceAlert = false;
23746
+ }
23747
+ stopDetection() {
23748
+ super.stopDetection();
23749
+ if (this.emmitedFaceAlert) {
23750
+ this.handleOk("face_ok", "face_detection_on_stream");
23751
+ }
23725
23752
  }
23726
23753
  displayVideoDetections(result) {
23727
23754
  for (const child of this.children) {
@@ -23784,31 +23811,71 @@ var FaceDetection = class extends BaseDetection {
23784
23811
  }
23785
23812
  }
23786
23813
  verify(result) {
23787
- var _a2, _b, _c2, _d, _e3, _f, _g, _h, _i3;
23814
+ var _a2;
23788
23815
  if (((_a2 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _a2.detectFace) && result.detections.length !== this.numFacesSent) {
23789
23816
  this.numFacesSent = result.detections.length;
23790
23817
  if (result.detections.length === 0) {
23791
23818
  this.handleAlert("no_face_detected", "face_detection_on_stream");
23819
+ this.emmitedFaceAlert = true;
23820
+ return;
23792
23821
  } else if (result.detections.length > 1) {
23793
23822
  this.handleAlert("multiple_faces_detected", "face_detection_on_stream");
23823
+ this.emmitedFaceAlert = true;
23824
+ return;
23794
23825
  } else {
23795
23826
  this.handleOk("face_ok", "face_detection_on_stream");
23796
- }
23797
- }
23798
- const faceSize = ((_b = result.detections[0]) == null ? void 0 : _b.keypoints[3].y) - ((_c2 = result.detections[0]) == null ? void 0 : _c2.keypoints[0].y);
23799
- 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) {
23800
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
23827
+ this.emmitedFaceAlert = false;
23828
+ }
23829
+ }
23830
+ if (result.detections.length === 0) return;
23831
+ let face = result.detections[0].boundingBox;
23832
+ let video = document.getElementById(this.classVideo);
23833
+ const imageWidth = video.videoWidth;
23834
+ const imageHeight = video.videoHeight;
23835
+ let failedFacePosition = false;
23836
+ if (imageWidth > imageHeight) {
23837
+ if (face.height / imageHeight > 0.7) {
23838
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_size_detected", "position_detection_on_stream");
23839
+ failedFacePosition = true;
23840
+ this.emmitedPositionAlert = true;
23841
+ } else if (face.width / imageWidth > 0.7) {
23842
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_size_detected", "position_detection_on_stream");
23843
+ this.emmitedPositionAlert = true;
23844
+ failedFacePosition = true;
23845
+ }
23846
+ }
23847
+ let start = [face.originX, face.originY];
23848
+ let end = [face.originX + face.width, face.originY + face.height];
23849
+ if (start[0] < 0.1 * face.width || start[1] < 0.2 * face.height || end[0] > imageWidth - 0.1 * face.width || end[1] > imageHeight - 5) {
23850
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_edge_detected", "position_detection_on_stream");
23801
23851
  this.emmitedPositionAlert = true;
23802
- } 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) {
23803
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
23852
+ failedFacePosition = true;
23853
+ }
23854
+ let leftGap = start[0];
23855
+ let rightGap = imageWidth - end[0];
23856
+ let topGap = start[1];
23857
+ let bottomGap = imageHeight - end[1];
23858
+ if (leftGap > 2 * rightGap) {
23859
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_right_detected", "position_detection_on_stream");
23804
23860
  this.emmitedPositionAlert = true;
23805
- } 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) {
23806
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
23861
+ failedFacePosition = true;
23862
+ }
23863
+ if (topGap > 4 * bottomGap) {
23864
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_top_detected", "position_detection_on_stream");
23807
23865
  this.emmitedPositionAlert = true;
23808
- } else if (faceSize > 0.35) {
23809
- !this.emmitedPositionAlert && this.handleAlert("wrong_position_face_detected", "position_detection_on_stream");
23866
+ failedFacePosition = true;
23867
+ }
23868
+ if (rightGap > 2 * leftGap) {
23869
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_left_detected", "position_detection_on_stream");
23810
23870
  this.emmitedPositionAlert = true;
23811
- } else {
23871
+ failedFacePosition = true;
23872
+ }
23873
+ if (bottomGap > 3 * topGap) {
23874
+ !this.emmitedPositionAlert && this.handleAlert("wrong_face_position_move_bottom_detected", "position_detection_on_stream");
23875
+ this.emmitedPositionAlert = true;
23876
+ failedFacePosition = true;
23877
+ }
23878
+ if (failedFacePosition == false) {
23812
23879
  this.emmitedPositionAlert && this.handleOk("ok_position_face_detected", "position_detection_on_stream");
23813
23880
  this.emmitedPositionAlert = false;
23814
23881
  }
@@ -27183,6 +27250,14 @@ var BackendService = class {
27183
27250
  jwt: this.token
27184
27251
  });
27185
27252
  }
27253
+ async goToExternalCameraPositionStep(externalSessionId) {
27254
+ return await this.makeRequest({
27255
+ path: `/ExternalCamera/go-to-position-step/${externalSessionId}`,
27256
+ method: "POST",
27257
+ body: {},
27258
+ jwt: this.token
27259
+ });
27260
+ }
27186
27261
  async externalCameraFinish(externalSessionId) {
27187
27262
  return await this.makeRequest({
27188
27263
  path: `/ExternalCamera/finish/${externalSessionId}`,
@@ -27191,15 +27266,16 @@ var BackendService = class {
27191
27266
  jwt: this.token
27192
27267
  });
27193
27268
  }
27194
- async confirmStart(proctoringOptions, proctoringType, latitude, longitude) {
27269
+ async confirmStart(proctoringOptions, sessionOptions, latitude, longitude) {
27195
27270
  return await this.makeRequest({
27196
27271
  path: `/proctoring/start/${proctoringOptions.examId}`,
27197
27272
  method: "POST",
27198
27273
  body: {
27199
27274
  clientId: proctoringOptions.clientId,
27200
- proctoringType,
27275
+ proctoringType: sessionOptions.proctoringType,
27201
27276
  latitude,
27202
- longitude
27277
+ longitude,
27278
+ captureScreen: sessionOptions.captureScreen
27203
27279
  },
27204
27280
  jwt: proctoringOptions.token
27205
27281
  });
@@ -27246,8 +27322,8 @@ var BackendService = class {
27246
27322
  }
27247
27323
  });
27248
27324
  }
27249
- async finishAndSendUrls(proctoringOptions, proctoringSession) {
27250
- await this.makeRequest({
27325
+ async finishAndSendUrls(proctoringOptions) {
27326
+ return await this.makeRequest({
27251
27327
  path: `/proctoring/finish/${proctoringOptions.examId}`,
27252
27328
  method: "POST",
27253
27329
  body: {
@@ -27293,6 +27369,51 @@ var BackendService = class {
27293
27369
  });
27294
27370
  return result.data;
27295
27371
  }
27372
+ async startChallenge(body) {
27373
+ const result = await this.makeRequestAxios({
27374
+ path: `/Challenge/start`,
27375
+ method: "POST",
27376
+ jwt: this.token,
27377
+ body
27378
+ });
27379
+ return result.data;
27380
+ }
27381
+ async stopChallenge(challengeId, body) {
27382
+ const result = await this.makeRequestAxios({
27383
+ path: `/Challenge/stop/${challengeId}`,
27384
+ method: "POST",
27385
+ jwt: this.token,
27386
+ body
27387
+ });
27388
+ return result.data;
27389
+ }
27390
+ async startRealtimeAlert(body) {
27391
+ const result = await this.makeRequestAxios({
27392
+ path: `/Realtime/start-warning`,
27393
+ method: "POST",
27394
+ jwt: this.token,
27395
+ body
27396
+ });
27397
+ return result.data;
27398
+ }
27399
+ async stopRealtimeAlert(body) {
27400
+ const result = await this.makeRequestAxios({
27401
+ path: `/Realtime/stop-warning`,
27402
+ method: "POST",
27403
+ jwt: this.token,
27404
+ body
27405
+ });
27406
+ return result.data;
27407
+ }
27408
+ async verifyFace(proctoringId2, faceImage) {
27409
+ const result = await this.makeRequestAxios({
27410
+ path: `/Realtime/verify-face`,
27411
+ method: "POST",
27412
+ jwt: this.token,
27413
+ body: { "proctoringId": proctoringId2, "base64": faceImage }
27414
+ });
27415
+ return result.data;
27416
+ }
27296
27417
  async getServerHour(token) {
27297
27418
  return await this.makeRequest({
27298
27419
  path: `/Proctoring/server-hour`,
@@ -27620,7 +27741,9 @@ var getDefaultProctoringOptions = {
27620
27741
  onBufferSizeError: false,
27621
27742
  useGeolocation: false,
27622
27743
  useSpyScan: false,
27623
- useExternalCamera: false
27744
+ useExternalCamera: false,
27745
+ useChallenge: false,
27746
+ screenRecorderOptions: { width: 1280, height: 720 }
27624
27747
  };
27625
27748
 
27626
27749
  // src/proctoring/options/ProctoringVideoOptions.ts
@@ -27679,6 +27802,7 @@ var init = (backend) => {
27679
27802
  return backendService;
27680
27803
  };
27681
27804
  var eventNames = {
27805
+ DEVICES_CHECKED: "devices_checked",
27682
27806
  START: "start",
27683
27807
  FINISH: "finish",
27684
27808
  ERROR: "error",
@@ -27695,6 +27819,7 @@ var eventNames = {
27695
27819
  };
27696
27820
  var log = (eventName, properties) => backendService && backendService.log(eventName, properties);
27697
27821
  var trackers = {
27822
+ registerDevicesChecked: (proctoringId2, success, description) => log(eventNames.DEVICES_CHECKED, { proctoringId: proctoringId2, success, description }),
27698
27823
  registerStart: (proctoringId2, success, description) => log(eventNames.START, { proctoringId: proctoringId2, success, description }),
27699
27824
  registerFinish: (proctoringId2, success, description) => log(eventNames.FINISH, { proctoringId: proctoringId2, success, description }),
27700
27825
  registerError: (proctoringId2, description) => log(eventNames.ERROR, { proctoringId: proctoringId2, description }),
@@ -27985,6 +28110,12 @@ var ObjectDetection = class extends BaseDetection {
27985
28110
  constructor(options, paramsConfig, classVideo = "videoPreviewFrameDetection", classDiv = "liveViewFrameDetection") {
27986
28111
  super("ObjectDetector", `https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float16/1/efficientdet_lite0.tflite`, options, paramsConfig, classVideo, classDiv);
27987
28112
  }
28113
+ stopDetection() {
28114
+ super.stopDetection();
28115
+ if (this.numPersonsSent > 0) {
28116
+ this.handleOk("person_ok", "person_detection_on_stream");
28117
+ }
28118
+ }
27988
28119
  displayVideoDetections(result) {
27989
28120
  for (const child of this.children) {
27990
28121
  this.liveView.removeChild(child);
@@ -28159,6 +28290,10 @@ var CameraRecorder = class {
28159
28290
  this.blobsRTC = [];
28160
28291
  this.imageCount = 0;
28161
28292
  this.filesToUpload = [];
28293
+ this.animationFrameId = null;
28294
+ this.isCanvasLoopActive = false;
28295
+ this.hardwareStream = null;
28296
+ this.internalClonedStream = null;
28162
28297
  this.currentRetries = 0;
28163
28298
  this.noiseWait = 20;
28164
28299
  this.options = options;
@@ -28220,7 +28355,7 @@ var CameraRecorder = class {
28220
28355
  }
28221
28356
  }
28222
28357
  async startRecording(options) {
28223
- var _a2, _b, _c2, _d, _e3, _f, _g, _h;
28358
+ var _a2, _b, _c2, _d, _e3, _f, _g;
28224
28359
  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)) {
28225
28360
  await this.initializeDetectors();
28226
28361
  }
@@ -28235,7 +28370,7 @@ var CameraRecorder = class {
28235
28370
  }
28236
28371
  };
28237
28372
  try {
28238
- this.cameraStream = await navigator.mediaDevices.getUserMedia(
28373
+ this.hardwareStream = await navigator.mediaDevices.getUserMedia(
28239
28374
  constraints
28240
28375
  );
28241
28376
  } catch (error) {
@@ -28243,6 +28378,16 @@ var CameraRecorder = class {
28243
28378
  throw "N\xE3o foi poss\xEDvel conectar a camera, ela pode estar sendo utilizada por outro programa";
28244
28379
  throw error;
28245
28380
  }
28381
+ this.cameraStream = this.hardwareStream;
28382
+ const track = this.cameraStream.getVideoTracks()[0];
28383
+ const settings = track.getSettings();
28384
+ const { width = 0, height = 0 } = settings;
28385
+ const needsRotationFix = isMobileDevice() && width > height;
28386
+ this.isCanvasLoopActive = true;
28387
+ if (needsRotationFix) {
28388
+ console.log("Aplicando corre\xE7\xE3o e substituindo stream p\xFAblico...");
28389
+ this.cameraStream = this.createRotatedStream(this.hardwareStream);
28390
+ }
28246
28391
  const {
28247
28392
  startRecording,
28248
28393
  stopRecording,
@@ -28252,6 +28397,7 @@ var CameraRecorder = class {
28252
28397
  getBufferSize
28253
28398
  } = recorder(
28254
28399
  this.cameraStream,
28400
+ // streamToRecord,
28255
28401
  this.blobs,
28256
28402
  this.options.onBufferSizeError,
28257
28403
  (e3) => this.bufferError(e3),
@@ -28264,25 +28410,6 @@ var CameraRecorder = class {
28264
28410
  this.recorderOptions = recorderOptions;
28265
28411
  this.getBufferSize = getBufferSize;
28266
28412
  this.recordingStart();
28267
- const tracks = this.cameraStream.getVideoTracks();
28268
- const settings = tracks[0].getSettings();
28269
- let { width = 0, height = 0 } = settings;
28270
- const isPortrait = (_d = screen.orientation) == null ? void 0 : _d.type.includes("portrait");
28271
- if (isPortrait && isMobileDevice()) {
28272
- if (this.videoOptions.width == height && this.videoOptions.height == width) {
28273
- [width, height] = [height, width];
28274
- }
28275
- }
28276
- console.log("isPortrait -> ", isPortrait);
28277
- console.log("this.isMobileDevice() -> ", isMobileDevice());
28278
- console.log("width -> ", width);
28279
- console.log("height -> ", height);
28280
- console.log("minWidth -> ", this.videoOptions.minWidth);
28281
- console.log("minHeight -> ", this.videoOptions.minHeight);
28282
- console.log("this.videoOptions.width -> ", this.videoOptions.width);
28283
- console.log("this.videoOptions.height -> ", this.videoOptions.height);
28284
- console.log("settings.width -> ", settings.width);
28285
- console.log("settings.height -> ", settings.height);
28286
28413
  if (this.videoOptions.minWidth > width || this.videoOptions.minHeight > height) {
28287
28414
  throw STREAM_UNDER_MINIMUM_PERMITTED;
28288
28415
  } else if (this.videoOptions.width !== width || this.videoOptions.height !== height) {
@@ -28298,18 +28425,37 @@ Setting: ${JSON.stringify(settings, null, 2)}`
28298
28425
  );
28299
28426
  throw ANOTHER_STREAM_ACTIVE;
28300
28427
  }
28301
- ((_e3 = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _e3.useUploadImage) && this.options.proctoringType == "IMAGE" && this.photoShotsCycle();
28302
- if ((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectFace) {
28428
+ ((_d = this.paramsConfig.imageBehaviourParameters) == null ? void 0 : _d.useUploadImage) && this.options.proctoringType == "IMAGE" && this.photoShotsCycle();
28429
+ if ((_e3 = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _e3.detectFace) {
28303
28430
  await this.faceDetection.enableCam(this.cameraStream);
28304
28431
  }
28305
- if (((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectPerson) || ((_h = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _h.detectCellPhone)) {
28432
+ if (((_f = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _f.detectPerson) || ((_g = this.paramsConfig.videoBehaviourParameters) == null ? void 0 : _g.detectCellPhone)) {
28306
28433
  await this.objectDetection.enableCam(this.cameraStream);
28307
28434
  }
28308
28435
  this.filesToUpload = [];
28309
- this.options.proctoringType == "REALTIME" && this.captureFrame();
28310
28436
  }
28311
28437
  async stopRecording() {
28438
+ this.isCanvasLoopActive = false;
28312
28439
  this.recordingStop && await this.recordingStop();
28440
+ try {
28441
+ if (this.animationFrameId) {
28442
+ cancelAnimationFrame(this.animationFrameId);
28443
+ this.animationFrameId = null;
28444
+ }
28445
+ if (this.cameraStream) {
28446
+ this.cameraStream.getTracks().forEach((track) => track.stop());
28447
+ }
28448
+ if (this.internalClonedStream) {
28449
+ this.internalClonedStream.getTracks().forEach((track) => track.stop());
28450
+ this.internalClonedStream = null;
28451
+ }
28452
+ if (this.hardwareStream) {
28453
+ this.hardwareStream.getTracks().forEach((track) => track.stop());
28454
+ this.hardwareStream = null;
28455
+ }
28456
+ } catch {
28457
+ console.error("Erro ao parar os streams de m\xEDdia.");
28458
+ }
28313
28459
  this.faceDetection && this.faceDetection.detecting && this.faceDetection.stopDetection();
28314
28460
  this.objectDetection && this.objectDetection.detecting && this.objectDetection.stopDetection();
28315
28461
  clearInterval(this.imageInterval);
@@ -28376,6 +28522,36 @@ Setting: ${JSON.stringify(settings, null, 2)}`
28376
28522
  }
28377
28523
  }, this.paramsConfig.imageBehaviourParameters.uploadInterval * 1e3);
28378
28524
  }
28525
+ async getCurrentImageBase64() {
28526
+ if (!this.video || !this.canvas) {
28527
+ this.configImageCapture();
28528
+ } else {
28529
+ if (this.video.srcObject !== this.cameraStream) {
28530
+ this.video.srcObject = this.cameraStream;
28531
+ await this.video.play();
28532
+ }
28533
+ }
28534
+ if (this.video.paused) {
28535
+ await this.video.play();
28536
+ }
28537
+ await new Promise((resolve) => {
28538
+ if (this.video.readyState >= 2) {
28539
+ resolve();
28540
+ return;
28541
+ }
28542
+ const onLoadedMetadata = () => {
28543
+ this.video.removeEventListener("loadedmetadata", onLoadedMetadata);
28544
+ setTimeout(() => resolve(), 50);
28545
+ };
28546
+ this.video.addEventListener("loadedmetadata", onLoadedMetadata);
28547
+ setTimeout(() => {
28548
+ this.video.removeEventListener("loadedmetadata", onLoadedMetadata);
28549
+ resolve();
28550
+ }, 500);
28551
+ });
28552
+ this.canvas.getContext("2d").drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
28553
+ return this.canvas.toDataURL("image/jpeg");
28554
+ }
28379
28555
  // De um em um segundo captura um frame
28380
28556
  captureFrame() {
28381
28557
  let imageFile;
@@ -28510,6 +28686,46 @@ Setting: ${JSON.stringify(settings, null, 2)}`
28510
28686
  }
28511
28687
  this.noiseWait++;
28512
28688
  }
28689
+ /**
28690
+ * Cria um stream processado onde os frames são rotacionados via Canvas.
28691
+ * Isso corrige o problema de gravação deitada em iOS/Mobile.
28692
+ */
28693
+ createRotatedStream(originalStream) {
28694
+ this.internalClonedStream = originalStream.clone();
28695
+ const video = document.createElement("video");
28696
+ video.srcObject = this.internalClonedStream;
28697
+ video.muted = true;
28698
+ video.playsInline = true;
28699
+ video.play();
28700
+ const canvas = document.createElement("canvas");
28701
+ const ctx = canvas.getContext("2d");
28702
+ const track = originalStream.getVideoTracks()[0];
28703
+ const settings = track.getSettings();
28704
+ const width = settings.width || 640;
28705
+ const height = settings.height || 480;
28706
+ canvas.width = height;
28707
+ canvas.height = width;
28708
+ const draw = () => {
28709
+ if (video.paused || video.ended) return;
28710
+ if (!this.isCanvasLoopActive) return;
28711
+ if (ctx) {
28712
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
28713
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
28714
+ }
28715
+ if (this.isCanvasLoopActive) {
28716
+ this.animationFrameId = requestAnimationFrame(draw);
28717
+ }
28718
+ };
28719
+ video.onplaying = () => {
28720
+ this.isCanvasLoopActive = true;
28721
+ draw();
28722
+ };
28723
+ const canvasStream = canvas.captureStream(30);
28724
+ originalStream.getAudioTracks().forEach((track2) => {
28725
+ canvasStream.addTrack(track2);
28726
+ });
28727
+ return canvasStream;
28728
+ }
28513
28729
  };
28514
28730
 
28515
28731
  // src/new-flow/checkers/DeviceCheckerUI.ts
@@ -28894,6 +29110,9 @@ var DeviceCheckerUI = class {
28894
29110
  const center = document.createElement("div");
28895
29111
  video.setAttribute("id", "cameraStream");
28896
29112
  video.muted = true;
29113
+ video.setAttribute("playsinline", "true");
29114
+ video.setAttribute("webkit-playsinline", "true");
29115
+ video.autoplay = true;
28897
29116
  const divCameraStyles = {
28898
29117
  width: "100%",
28899
29118
  display: "flex",
@@ -29322,7 +29541,7 @@ var DeviceCheckerUI = class {
29322
29541
  checkmark_kick_AmbientVerify.setAttribute("class", "checkmark_kick");
29323
29542
  }
29324
29543
  } else {
29325
- if (checkmark_FacePosition && (response.description === "Nenhuma face encontrada" || response.description === "Face na posi\xE7\xE3o errada")) {
29544
+ if (checkmark_FacePosition && (response.type === "position_detection_on_stream" || response.type === "face_detection_on_stream")) {
29326
29545
  facePositionAlert && (facePositionAlert.style.color = "#FF0000");
29327
29546
  checkmark_FacePosition.setAttribute("class", "checkmark_error");
29328
29547
  checkmark_stem_FacePosition.setAttribute("class", "checkmark_stem_error");
@@ -29427,7 +29646,9 @@ Para iniciar um exame utilize uma outra c\xE2mera.`);
29427
29646
  const cameraStream = document.querySelector("#cameraStream");
29428
29647
  if (cameraStream) {
29429
29648
  cameraStream.srcObject = stream4;
29430
- cameraStream.play();
29649
+ cameraStream.play().catch((e3) => {
29650
+ console.warn("Erro ao iniciar preview de v\xEDdeo:", e3);
29651
+ });
29431
29652
  }
29432
29653
  }
29433
29654
  audioDeviceInterfaceUIAllowedAmbient(allowedAmbient) {
@@ -30213,9 +30434,11 @@ var Extension = class {
30213
30434
 
30214
30435
  // src/modules/onChangeDevices.ts
30215
30436
  var onChangeDevices = class {
30216
- constructor(repositoryDevices, proctoringId2) {
30437
+ constructor(repositoryDevices, proctoringId2, sessionOptions, allRecorders) {
30217
30438
  this.repositoryDevices = repositoryDevices;
30218
30439
  this.proctoringId = proctoringId2;
30440
+ this.sessionOptions = sessionOptions;
30441
+ this.allRecorders = allRecorders;
30219
30442
  }
30220
30443
  startRecording(options) {
30221
30444
  navigator.mediaDevices.ondevicechange = () => {
@@ -30229,24 +30452,49 @@ var onChangeDevices = class {
30229
30452
  const response = await this.repositoryDevices.getDevices("devices");
30230
30453
  const defaultDevice = { label: "", id: "" };
30231
30454
  const copy = { cameras: (response == null ? void 0 : response.cameras) || [defaultDevice], microphones: (response == null ? void 0 : response.microphones) || [defaultDevice] };
30232
- let resultCameras;
30233
- let resultMicrophones;
30455
+ const status = devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) || devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? "in" : "out";
30456
+ let resultCameras = [];
30457
+ let resultMicrophones = [];
30458
+ let isActiveDeviceRemoved = false;
30234
30459
  if (devices.cameras.length != (copy == null ? void 0 : copy.cameras.length)) {
30235
- 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);
30460
+ const removedCameras = status === "out" ? onlyInLeft(copy == null ? void 0 : copy.cameras, devices.cameras, isSameDevice) : [];
30461
+ const addedCameras = status === "in" ? onlyInLeft(devices.cameras, copy == null ? void 0 : copy.cameras, isSameDevice) : [];
30462
+ resultCameras = status === "out" ? removedCameras : addedCameras;
30236
30463
  resultCameras = resultCameras.filter((item) => item.id != "default");
30464
+ console.log(removedCameras);
30465
+ console.log(this.sessionOptions.cameraId);
30466
+ if (status === "out") {
30467
+ isActiveDeviceRemoved = removedCameras.some(
30468
+ (device) => device.id === this.sessionOptions.cameraId
30469
+ );
30470
+ }
30237
30471
  }
30238
30472
  if (devices.microphones.length != (copy == null ? void 0 : copy.microphones.length)) {
30239
- 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);
30473
+ const removedMicrophones = status === "out" ? onlyInLeft(copy == null ? void 0 : copy.microphones, devices.microphones, isSameDevice) : [];
30474
+ const addedMicrophones = status === "in" ? onlyInLeft(devices.microphones, copy == null ? void 0 : copy.microphones, isSameDevice) : [];
30475
+ resultMicrophones = status === "out" ? removedMicrophones : addedMicrophones;
30240
30476
  resultMicrophones = resultMicrophones.filter((item) => item.id != "default" && item.id != "communications");
30477
+ if (status === "out" && !isActiveDeviceRemoved) {
30478
+ isActiveDeviceRemoved = removedMicrophones.some(
30479
+ (device) => device.id === this.sessionOptions.microphoneId
30480
+ );
30481
+ }
30241
30482
  }
30242
30483
  const devicesChanged = {
30243
30484
  cameras: resultCameras || [],
30244
30485
  microphones: resultMicrophones || [],
30245
- status: devices.cameras.length > (copy == null ? void 0 : copy.cameras.length) || devices.microphones.length > (copy == null ? void 0 : copy.microphones.length) ? "in" : "out"
30486
+ status,
30487
+ isActiveDevice: isActiveDeviceRemoved
30246
30488
  };
30247
- 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" });
30489
+ await this.repositoryDevices.save({ ...devices, id: "devices", status });
30248
30490
  if (options.status && (devicesChanged.cameras.length != 0 || devicesChanged.microphones.length != 0)) {
30249
30491
  trackers.registerChangeDevice(this.proctoringId, devicesChanged.status, JSON.stringify(devicesChanged, null, 2));
30492
+ if (devicesChanged.isActiveDevice) {
30493
+ this.allRecorders.alertRecorder.addAlert({
30494
+ alert: 39 /* ChangeDevices */,
30495
+ type: 2 /* Video */
30496
+ });
30497
+ }
30250
30498
  options.status(devicesChanged);
30251
30499
  }
30252
30500
  }
@@ -30454,6 +30702,14 @@ var AlertRecorder = class {
30454
30702
  lastAlert.end = Date.now() - this.startTime.getTime();
30455
30703
  }
30456
30704
  }
30705
+ addAlert({ alert, type }) {
30706
+ this.alerts.push({
30707
+ begin: Date.now() - this.startTime.getTime(),
30708
+ end: Date.now() - this.startTime.getTime(),
30709
+ alert,
30710
+ type
30711
+ });
30712
+ }
30457
30713
  };
30458
30714
 
30459
30715
  // src/new-flow/recorders/AudioRecorder.ts
@@ -32942,19 +33198,38 @@ var ScreenRecorder = class {
32942
33198
  }
32943
33199
  async startRecording() {
32944
33200
  this.startTime = new Date(Date.now());
33201
+ if (isMobileDevice()) return;
32945
33202
  const { allowOnlyFirstMonitor, allowMultipleMonitors, onStopSharingScreenCallback, onBufferSizeErrorCallback } = this.options;
32946
- const displayMediaStreamConstraints = {
33203
+ const complexConstraints = {
32947
33204
  video: {
32948
33205
  cursor: "always",
32949
- width: 854,
32950
- height: 480,
33206
+ width: this.options.screenRecorderOptions.width,
33207
+ height: this.options.screenRecorderOptions.height,
32951
33208
  displaySurface: "monitor"
32952
33209
  },
32953
33210
  audio: false
32954
33211
  };
32955
- this.screenStream = await navigator.mediaDevices.getDisplayMedia(
32956
- displayMediaStreamConstraints
32957
- );
33212
+ const simpleConstraints = {
33213
+ video: {
33214
+ cursor: "always",
33215
+ width: 1280,
33216
+ height: 720,
33217
+ displaySurface: "monitor"
33218
+ },
33219
+ audio: false
33220
+ };
33221
+ let stream4;
33222
+ try {
33223
+ stream4 = await navigator.mediaDevices.getDisplayMedia(complexConstraints);
33224
+ } catch (e3) {
33225
+ if (e3 instanceof TypeError && e3.message.includes("min constraints are not supported")) {
33226
+ console.warn("Navegador n\xE3o suporta restri\xE7\xF5es 'min/max/ideal'. Tentando com 720p fixo.");
33227
+ stream4 = await navigator.mediaDevices.getDisplayMedia(simpleConstraints);
33228
+ } else {
33229
+ throw e3;
33230
+ }
33231
+ }
33232
+ this.screenStream = stream4;
32958
33233
  const tracks = this.screenStream.getVideoTracks();
32959
33234
  tracks[0].onended = onStopSharingScreenCallback;
32960
33235
  const isFirefox = navigator.userAgent.indexOf("Firefox") > -1;
@@ -33246,15 +33521,12 @@ var _ExternalCameraChecker = class _ExternalCameraChecker {
33246
33521
  }
33247
33522
  }
33248
33523
  async goToPositionGuide() {
33249
- if (this.connection) {
33250
- const actionMessage = new ActionMessage();
33251
- actionMessage.command = "Position_Guide";
33252
- console.log("Enviando comando 'Position_Guide' para o aplicativo...");
33253
- this.connection.invoke(
33254
- "SendAction",
33255
- this.externalSessionId,
33256
- actionMessage
33257
- );
33524
+ try {
33525
+ const response = await this.backend.goToExternalCameraPositionStep("" + this.externalSessionId);
33526
+ console.log(response);
33527
+ } catch (error) {
33528
+ console.error("Erro ao enviar comando de Position:", error);
33529
+ throw new Error("N\xE3o foi poss\xEDvel enviar comando de Position Guide.");
33258
33530
  }
33259
33531
  }
33260
33532
  async reset() {
@@ -33933,13 +34205,9 @@ var ExternalCameraChecker = _ExternalCameraChecker;
33933
34205
 
33934
34206
  // src/proctoring/proctoring.ts
33935
34207
  var Proctoring = class {
33936
- // private onProgress = (percentage: number) => {};
33937
- // public setOnProgress(cb: (percentage: number) => void) {
33938
- // console.log("proctoring.setOnProgress");
33939
- // this.onProgress = (percentage) => cb(percentage);
33940
- // }
33941
34208
  constructor(context) {
33942
34209
  this.context = context;
34210
+ this.deviceData = null;
33943
34211
  this.paramsConfig = {
33944
34212
  audioBehaviourParameters: {
33945
34213
  recordingBitrate: 128,
@@ -33992,11 +34260,17 @@ var Proctoring = class {
33992
34260
  this.appChecker = new ExternalCameraChecker(this.context, (response) => this.onRealtimeAlertsCallback(response));
33993
34261
  }
33994
34262
  setOnStopSharingScreenCallback(cb) {
33995
- this.onStopSharingScreenCallback = () => {
34263
+ this.onStopSharingScreenCallback = async () => {
34264
+ var _a2, _b, _c2, _d;
33996
34265
  trackers.registerStopSharingScreen(
33997
34266
  this.proctoringId,
33998
34267
  "Stop sharing screen"
33999
34268
  );
34269
+ (_b = (_a2 = this.allRecorders) == null ? void 0 : _a2.alertRecorder) == null ? void 0 : _b.addAlert({
34270
+ alert: 34 /* StopSharingScreen */,
34271
+ type: 3 /* Screen */
34272
+ });
34273
+ (_d = (_c2 = this.allRecorders) == null ? void 0 : _c2.screenRecorder) == null ? void 0 : _d.stopRecording();
34000
34274
  cb();
34001
34275
  };
34002
34276
  }
@@ -34009,17 +34283,71 @@ var Proctoring = class {
34009
34283
  async onChangeDevices(options = {}) {
34010
34284
  const onChange = new onChangeDevices(
34011
34285
  this.repositoryDevices,
34012
- this.proctoringId
34286
+ this.proctoringId,
34287
+ this.sessionOptions,
34288
+ this.allRecorders
34013
34289
  );
34014
34290
  onChange.startRecording(options);
34015
34291
  this.onChangeDevicesCallback = (devices) => options.status && options.status(devices);
34016
34292
  }
34293
+ convertRealtimeCategoryToAlertCategory(category) {
34294
+ switch (category) {
34295
+ case "no_face_detected":
34296
+ return 1 /* NoFace */;
34297
+ case "multiple_faces_detected":
34298
+ return 2 /* MultipleFaces */;
34299
+ case "multiple_persons_detected":
34300
+ return 28 /* EnvironmentMultiplePeople */;
34301
+ case "no_person_detected":
34302
+ return 29 /* EnvironmentNoPeople */;
34303
+ default:
34304
+ return null;
34305
+ }
34306
+ }
34307
+ convertRealtimeTypeToWarningType(category) {
34308
+ switch (category) {
34309
+ case "face_detection_on_stream":
34310
+ return 0 /* Face */;
34311
+ case "person_detection_on_stream":
34312
+ return 1 /* People */;
34313
+ default:
34314
+ return null;
34315
+ }
34316
+ }
34017
34317
  async onRealtimeAlerts(options = {}) {
34018
- this.onRealtimeAlertsCallback = (response) => options.data && options.data(response);
34318
+ this.onRealtimeAlertsCallback = async (response) => {
34319
+ options.data && options.data(response);
34320
+ if (this.sessionOptions.proctoringType === "REALTIME" && (response.type === "face_detection_on_stream" || response.type === "person_detection_on_stream")) {
34321
+ if (response.status === "ALERT") {
34322
+ await this.backend.startRealtimeAlert({
34323
+ proctoringId: this.proctoringId,
34324
+ begin: response.begin,
34325
+ end: response.end,
34326
+ alert: this.convertRealtimeCategoryToAlertCategory(response.category)
34327
+ });
34328
+ } else if (response.status === "OK") {
34329
+ await this.backend.stopRealtimeAlert({
34330
+ proctoringId: this.proctoringId,
34331
+ begin: response.begin,
34332
+ end: response.end,
34333
+ warningCategoryEnum: this.convertRealtimeTypeToWarningType(response.type),
34334
+ alertImageBase64: await this.allRecorders.cameraRecorder.getCurrentImageBase64()
34335
+ });
34336
+ }
34337
+ }
34338
+ };
34019
34339
  }
34020
34340
  setOnBufferSizeErrorCallback(cb) {
34021
34341
  this.onBufferSizeErrorCallback = (cameraStream) => cb(cameraStream);
34022
34342
  }
34343
+ // private onProgress = (percentage: number) => {};
34344
+ // public setOnProgress(cb: (percentage: number) => void) {
34345
+ // console.log("proctoring.setOnProgress");
34346
+ // this.onProgress = (percentage) => cb(percentage);
34347
+ // }
34348
+ setDeviceCheckData(data) {
34349
+ this.deviceData = data;
34350
+ }
34023
34351
  createRecorders(options = getDefaultProctoringOptions) {
34024
34352
  var _a2, _b;
34025
34353
  this.onChangeDevices();
@@ -34046,6 +34374,7 @@ var Proctoring = class {
34046
34374
  const screenRecorder = this.sessionOptions.captureScreen ? new ScreenRecorder({
34047
34375
  allowOnlyFirstMonitor: (_a2 = this.sessionOptions.allowOnlyFirstMonitor) != null ? _a2 : true,
34048
34376
  allowMultipleMonitors: (_b = this.sessionOptions.allowMultipleMonitors) != null ? _b : true,
34377
+ screenRecorderOptions: this.sessionOptions.screenRecorderOptions,
34049
34378
  onStopSharingScreenCallback: () => this.onStopSharingScreenCallback(),
34050
34379
  onBufferSizeError: this.sessionOptions.onBufferSizeError,
34051
34380
  onBufferSizeErrorCallback: () => this.onBufferSizeErrorCallback()
@@ -34118,11 +34447,16 @@ var Proctoring = class {
34118
34447
  examId: this.context.examId,
34119
34448
  token: this.context.token
34120
34449
  },
34121
- this.sessionOptions.proctoringType,
34450
+ this.sessionOptions,
34122
34451
  this.geolocation ? this.geolocation.coords.latitude : void 0,
34123
34452
  this.geolocation ? this.geolocation.coords.longitude : void 0
34124
34453
  );
34125
34454
  this.proctoringId = startResponse.id;
34455
+ trackers.registerDevicesChecked(
34456
+ this.proctoringId,
34457
+ !!this.deviceData,
34458
+ `Devices checked: ${JSON.stringify(this.deviceData)} | Devices List: ${JSON.stringify(devices)}`
34459
+ );
34126
34460
  try {
34127
34461
  if (options == null ? void 0 : options.useExternalCamera) {
34128
34462
  await this.appChecker.startTransmission(this.proctoringId);
@@ -34156,6 +34490,11 @@ Navigator: ${JSON.stringify(_navigator2)}`
34156
34490
  startResponse.screenStream = this.allRecorders.screenRecorder.screenStream;
34157
34491
  }
34158
34492
  this.state = "Recording" /* Recording */;
34493
+ setTimeout(async () => {
34494
+ if (this.sessionOptions.proctoringType === "REALTIME") {
34495
+ await this.backend.verifyFace(this.proctoringId, await this.allRecorders.cameraRecorder.getCurrentImageBase64());
34496
+ }
34497
+ }, 1e3);
34159
34498
  return startResponse;
34160
34499
  } catch (error) {
34161
34500
  console.log(error);
@@ -34274,8 +34613,11 @@ Upload Services: ${uploaderServices}`,
34274
34613
  );
34275
34614
  });
34276
34615
  }
34277
- await this.backend.finishAndSendUrls(this.context, this.proctoringSession).then(() => {
34616
+ await this.backend.finishAndSendUrls(this.context).then((finishResponse) => {
34617
+ var _a2, _b;
34278
34618
  trackers.registerFinish(this.proctoringSession.id, true, "");
34619
+ console.log("finishResponse: ", finishResponse);
34620
+ options.onFinish && options.onFinish((_a2 = finishResponse == null ? void 0 : finishResponse.score) != null ? _a2 : 100, (_b = finishResponse == null ? void 0 : finishResponse.approved) != null ? _b : true);
34279
34621
  }).catch((error) => {
34280
34622
  trackers.registerFinish(
34281
34623
  this.proctoringSession.id,
@@ -34622,7 +34964,14 @@ function useProctoring(proctoringOptions, enviromentConfig = "prod") {
34622
34964
  const signTerm = new SignTerm(parameters);
34623
34965
  const photo = new CapturePhoto();
34624
34966
  const login = proctoring.login.bind(proctoring);
34625
- const start = proctoring.start.bind(proctoring);
34967
+ const originalStart = proctoring.start.bind(proctoring);
34968
+ const start = async (parameters2) => {
34969
+ const deviceResult = checker.getDeviceCheckResult();
34970
+ if (deviceResult) {
34971
+ proctoring.setDeviceCheckData(deviceResult);
34972
+ }
34973
+ return originalStart(parameters2);
34974
+ };
34626
34975
  const finish = proctoring.finish.bind(proctoring);
34627
34976
  const pause = proctoring.pause.bind(proctoring);
34628
34977
  const resume = proctoring.resume.bind(proctoring);