Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#### <i>!NOTICE: This is majorly <a href="https://github.com/ansh-saini/react-proctoring">Ansh Saini's</a> work. I only added the cam detection bit. Major credit goes to him. I'm only publishing this for ease of use </i>

# Overview

This is a headless library which only gives you some flags. What you do with that information is totally upto you. The UI for handling various use cases is completely in your hands.
Expand Down
62 changes: 62 additions & 0 deletions package-old.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "react-proctoring",
"private": false,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:example": "tsc && vite build -c example.vite.config.ts"
},
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/react-proctoring.es.js",
"require": "./dist/react-proctoring.umd.js"
}
},
"main": "./dist/react-proctoring.umd.js",
"module": "./dist/react-proctoring.es.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@mediapipe/tasks-vision": "^0.10.18",
"react-device-detect": "^2.2.3"
},
"description": "A headless proctoring system that is built for React",
"peerDependencies": {
"react": "16.8.0 || >=17.x",
"react-dom": "16.8.0 || >=17.x"
},
"repository": {
"type": "git",
"url": "https://github.com/ansh-saini/react-proctoring"
},
"author": "Ansh Saini",
"bugs": {
"url": "https://github.com/ansh-saini/react-proctoring/issues"
},
"homepage": "https://github.com/ansh-saini/react-proctoring#readme",
"license": "MIT",
"devDependencies": {
"@types/node": "^18.13.0",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@vitejs/plugin-react": "^3.1.0",
"eslint": "^8.0.1",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.32.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "*",
"vite": "^4.1.0",
"vite-plugin-dts": "^1.7.2",
"vite-plugin-linter": "^2.0.2",
"vite-tsconfig-paths": "^4.0.5"
}
}
20 changes: 14 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-proctoring",
"name": "react-proctoring-1",
"private": false,
"version": "0.0.0",
"version": "1.0.3",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -21,22 +21,30 @@
"module": "./dist/react-proctoring.es.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@mediapipe/tasks-vision": "^0.10.18",
"react-device-detect": "^2.2.3"
},
"description": "A headless proctoring system for built for React",
"description": "A headless proctoring system that is built for React",
"peerDependencies": {
"react": "16.8.0 || >=17.x",
"react-dom": "16.8.0 || >=17.x"
},
"repository": {
"type": "git",
"url": "https://github.com/ansh-saini/react-proctoring"
"url": "git+https://github.com/4x3l3r8/react-proctoring.git"
},
"author": "Ansh Saini",
"contributors": [
{
"name": "Daniel Adesanya",
"email": "danieladesanya25@gmail.com",
"url": "https://github.com/4x3l3r8"
}
],
"bugs": {
"url": "https://github.com/ansh-saini/react-proctoring/issues"
"url": "https://github.com/4x3l3r8/react-proctoring/issues"
},
"homepage": "https://github.com/ansh-saini/react-proctoring#readme",
"homepage": "https://github.com/4x3l3r8/react-proctoring#readme",
"license": "MIT",
"devDependencies": {
"@types/node": "^18.13.0",
Expand Down
9 changes: 6 additions & 3 deletions src/example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import Exam from './components/Exam'
function App() {
const [examHasStarted, setExamHasStarted] = useState(false)

const { fullScreen, tabFocus } = useProctoring({
const { fullScreen, tabFocus, camDetection } = useProctoring({
forceFullScreen: true,
preventTabSwitch: true,
preventContextMenu: true,
preventUserSelection: true,
preventCopy: true,
monitorCam: true,
})

if (!examHasStarted) {
Expand All @@ -32,18 +33,20 @@ function App() {
}

const getContent = () => {
debugger
if (fullScreen.status === 'off') return <ExamPaused />
if (tabFocus.status === false) return <ExamPaused />

return <Exam />
return <Exam violationStatus={camDetection.violationStatus} />
}

return (
<>
{/* For debugging purpose */}
{/* <pre>{JSON.stringify({ fullScreen, tabFocus }, null, 2)}</pre> */}

<video ref={camDetection.videoRef} autoPlay playsInline></video>
<div className="test-container">{getContent()}</div>

<Alerts fullScreen={fullScreen} tabFocus={tabFocus} />
</>
)
Expand Down
75 changes: 46 additions & 29 deletions src/example/components/Exam/index.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
import React from 'react'
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react'
import questions from './questions'
import ProctorService from '../../../hooks/ProctorService'
import ExamPaused from './ExamPaused'

type Props = {
violationStatus: {
facesDetected: number;
objectDetected: string[];
}
}

const Exam = ({ violationStatus }: Props) => {


type Props = {}

const Exam = (props: Props) => {
return (
<div
style={{
padding: '12px 32px',
backgroundColor: '#cbffcc',
}}
>
<h1 style={{ textAlign: 'center' }}>Exam in progress!</h1>

{questions.map((q, i) => (
<div className="question">
<h4>Question {i + 1}</h4>
<p style={{ whiteSpace: 'pre-wrap' }}>{q.text}</p>
{q.options.map((option) => (
<>
<input
style={{ marginInlineEnd: 8, marginBottom: 8 }}
type="radio"
id={option}
name={`question${q.id}`}
value={option}
/>
<label htmlFor="html">{option}</label>
<br />
</>
<>
{/* <video ref={videoRef} autoPlay playsInline></video> */}
{/* {violationStatus.facesDetected > 1 && <span style={{ color: "red" }}>{violationStatus.facesDetected} faces detected</span>} */}

{violationStatus.facesDetected < 1 || violationStatus.facesDetected > 1 || violationStatus.objectDetected.filter((object) => object !== "person").length > 0 ? <ExamPaused /> :
<div
style={{
padding: '12px 32px',
backgroundColor: '#cbffcc',
}}
>
<h1 style={{ textAlign: 'center' }}>Exam in progress!</h1>

{questions.map((q, i) => (
<div className="question">
<h4>Question {i + 1}</h4>
<p style={{ whiteSpace: 'pre-wrap' }}>{q.text}</p>
{q.options.map((option) => (
<>
<input
style={{ marginInlineEnd: 8, marginBottom: 8 }}
type="radio"
id={option}
name={`question${q.id}`}
value={option}
/>
<label htmlFor="html">{option}</label>
<br />
</>
))}
</div>
))}
</div>
))}
</div>
}
</>
)
}

Expand Down
102 changes: 102 additions & 0 deletions src/hooks/ProctorService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
FaceDetector,
FaceLandmarker,
FilesetResolver,
ObjectDetector,
} from '@mediapipe/tasks-vision'

/**
* The `ProctorService` class in TypeScript initializes and manages instances of face detection, face
* landmarking, and object detection classes from the `@mediapipe/tasks-vision` library.
*/
class ProctorService {
/**
* The `faceDetector` property in the `ProctorService` class is used to store an instance of the
* FaceDetector class from the `@mediapipe/tasks-vision` library. This instance is created during the
* initialization of the `ProctorService` class and is used to detect faces in images or video
* streams. The `faceDetector` property allows the `ProctorService` class to perform face detection
* tasks using the functionalities provided by the FaceDetector class.
*/
faceDetector?: FaceDetector

/**
* The `faceLandmarker?: FaceLandmarker` line in the `ProctorService` class is declaring a property
* named `faceLandmarker` that can hold an instance of the `FaceLandmarker` class from the
* `@mediapipe/tasks-vision` library.
*/
faceLandmarker?: FaceLandmarker

/**
* The line `objectDetector?: ObjectDetector` in the `ProctorService` class is declaring a property
* named `objectDetector` that can hold an instance of the `ObjectDetector` class from the
* `@mediapipe/tasks-vision` library. This property is used to
* store an instance of the `ObjectDetector` class, which is responsible for detecting objects in
* images or video streams using the functionalities provided by the `ObjectDetector` class.
*/
objectDetector?: ObjectDetector

private eyeTracker: any
private vision: any

/**
* The `ProctorService` class in TypeScript initializes and manages instances of face detection, face
* landmarking, and object detection classes from the `@mediapipe/tasks-vision` library.
*/
static instance: ProctorService

violations = {
facesDetected: 0,
}

constructor() {
this.initVision()
}

private async initVision() {
this.vision = await FilesetResolver.forVisionTasks(
'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm',
)

this.faceDetector = await FaceDetector.createFromOptions(this.vision, {
baseOptions: {
modelAssetPath:
'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.tflite',
delegate: 'GPU',
},
runningMode: 'VIDEO',
})

this.faceLandmarker = await FaceLandmarker.createFromOptions(this.vision, {
baseOptions: {
modelAssetPath:
'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task',
delegate: 'CPU',
},
runningMode: 'VIDEO',
numFaces: 1,
})

this.objectDetector = await ObjectDetector.createFromOptions(this.vision, {
baseOptions: {
modelAssetPath:
'https://storage.googleapis.com/mediapipe-tasks/object_detector/efficientdet_lite0_uint8.tflite',
delegate: 'CPU',
},
runningMode: 'VIDEO',
scoreThreshold: 0.2,
})
}

/**
* Returns a singleton instance of the ProctorService class.
* @returns {ProctorService} Proctor - The singleton instance of ProctorService.
*/
static getInstance() {
if (!ProctorService.instance) {
ProctorService.instance = new ProctorService()
}
return ProctorService.instance
}
}

export default ProctorService.getInstance
Loading