npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@muralidhar55/exam-proctor-sdk

v1.1.4

Published

A Node.js SDK for integrating exam proctoring features(Registration + verification + offline proctoring) into your applications.

Readme

@muralidhar55/exam-proctor-sdk

Lightweight Express middleware for in-page exam proctoring — no iframes, no sidecars, no separate processes.

npm version


Overview

@muralidhar55/exam-proctor-sdk mounts into your existing Express app and adds a complete browser-side proctoring flow:

  • face registration
  • identity verification
  • in-exam proctoring
  • violation collection
  • same-origin SDK routes

It does not require iframes, a sidecar process, or a separate Python service.

Features

  • Express middleware integration
  • Fast AI: Dual-layer caching (Service Worker + IndexedDB) for 0MB model downloads after first load.
  • Low Config Support: Motion-detection based AI throttling for old/weak CPUs.
  • Browser-based face registration and verification
  • In-page proctoring runtime
  • Same-origin routes for register, verify, worker, models, and violations
  • Database-agnostic persistence
  • Default model CDN with optional self-hosting
  • Graceful fallback when no database is configured

Install

npm install @muralidhar55/exam-proctor-sdk

Quick Start (30 Seconds)

1. Setup Environment

Add these to your .env file:

PORT=3000
PROCTOR_SESSION_JWT_SECRET=your_super_secret_key
# The internal API endpoint where violations will be persisted
PROCTOR_DATABASE_URL=http://localhost:3000/proctor/api/violations

2. Implementation

const express = require('express');
const examProctor = require('@muralidhar55/exam-proctor-sdk');
require('dotenv').config();

const app = express();
app.use(express.json());

// Mount the SDK
app.use(examProctor.middleware({
  databaseUrl: process.env.PROCTOR_DATABASE_URL,
  sessionJwtSecret: process.env.PROCTOR_SESSION_JWT_SECRET,
  // High-performance models pinned to v1.1.3
  modelsBaseUrl: 'https://cdn.jsdelivr.net/gh/muralidhar55/violation-detect@models-cdn/v1.1.3',
  allowedOrigins: ['http://localhost:3000'],
}));

app.listen(3000, () => {
  console.log('Proctoring enabled at http://localhost:3000/proctor/register');
});

What Gets Added To Your App

After app.use(examProctor.middleware(config)), these routes are available:

/proctor/register              Student face enrollment page
/proctor/verify                Student identity validation (Face Match)
/proctor/api/violations        GET, POST (Persistence layer)
/proctor/api/session-token     GET (Auth context)
/proctor/proctor-bundle.js     Main monitoring runtime script
/proctor/register-bundle.js    Registration logic
/proctor/auth-bundle.js        Authorization logic
/proctor/proctoringWorker.js   Background AI thread

How to use in your Frontend

Once you have mounted the middleware, you can trigger proctoring in your HTML pages.

1. Include the config and bundle

In your server-side template (EJS, Handlebars, etc.):

<head>
  <!-- Injects required metadata and SharedArrayBuffer settings -->
  ${examProctor.configScriptTag(config)}
</head>
<body>
  <!-- Include the proctoring runtime -->
  <script src="/proctor/proctor-bundle.js"></script>

  <script>
    async function startExam() {
      const sessionId = "student_sid_123";
      
      // Starts camera and real-time AI monitoring
      await window.ExamProctor.start(sessionId, "Student Full Name");
      
      console.log("AI Monitoring is active!");
    }
  </script>
</body>

2. Standard Student Flow (Recommended)

We recommend redirecting students through these steps to ensure identity:

  1. Register: Redirect new students to /proctor/register.
  2. Verify: On exam day, redirect to /proctor/verify?redirect=/exam-page.
  3. Proctor: On the /exam-page, call window.ExamProctor.start().

Quick Start

1. Mount the package in Express

const express = require('express');
const examProctor = require('@muralidhar55/exam-proctor-sdk');

const app = express();
app.use(express.json());

app.use(examProctor.middleware({
  databaseUrl: 'http://localhost:3000/api/proctor/violations',
  sessionJwtSecret: process.env.PROCTOR_SESSION_JWT_SECRET,
  modelsBaseUrl: 'https://cdn.jsdelivr.net/gh/muralidhar55/violation-detect@models-cdn/v1.1.3',
  allowedOrigins: ['http://localhost:3000'],
}));

2. Add your backend violations endpoint

databaseUrl should point to your backend API route, not a raw database connection string.

app.post('/api/proctor/violations', async (req, res) => {
  const violations = Array.isArray(req.body?.violations)
    ? req.body.violations
    : Array.isArray(req.body)
      ? req.body
      : [];

  // Save to PostgreSQL / MySQL / MongoDB / Supabase / Prisma / etc.

  res.status(201).json({ saved: violations.length });
});

app.get('/api/proctor/violations', async (req, res) => {
  const sessionId = String(req.query.sessionId || '').trim();

  // Read violations for this session from your database

  res.json([]);
});

3. Inject the runtime config and bundle on your exam page

app.get('/exam', (req, res) => {
  const proctorConfig = {
    databaseUrl: 'http://localhost:3000/api/proctor/violations',
    sessionJwtSecret: process.env.PROCTOR_SESSION_JWT_SECRET,
    modelsBaseUrl: 'https://cdn.jsdelivr.net/gh/Murali55-09/violation-detect@models-cdn/',
    allowedOrigins: ['http://localhost:3000'],
  };

  res.send(`
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        ${examProctor.configScriptTag(proctorConfig)}
      </head>
      <body>
        <h1>Exam</h1>
        <button id="start-exam">Start Proctoring</button>

        <script src="/proctor/proctor-bundle.js"></script>
        <script>
          document.getElementById('start-exam').addEventListener('click', async () => {
            const sessionId = 'session_' + crypto.randomUUID();
            await window.ExamProctor.start(sessionId, 'Student Name');
          });
        </script>
      </body>
    </html>
  `);
});

HTML Integration

Minimal HTML example:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    <h1>Exam Page</h1>
    <button id="start">Start Proctoring</button>

    <script src="/proctor/proctor-bundle.js"></script>
    <script>
      document.getElementById('start').addEventListener('click', async () => {
        const sessionId = 'session_' + crypto.randomUUID();
        await window.ExamProctor.start(sessionId, 'Student Name');
      });
    </script>
  </body>
</html>

Recommended Exam Flow

/  ->  /proctor/register  ->  /proctor/verify  ->  /exam  ->  /result

Helpers:

app.get('/', (_req, res) => {
  res.redirect(examProctor.registerUrl());
});

app.get('/verify-start', (req, res) => {
  res.redirect(examProctor.verifyUrl(String(req.query.sessionId || '')));
});

Database Configuration

This package is database-agnostic.

You can use:

  • PostgreSQL
  • MySQL
  • MongoDB
  • Supabase
  • Prisma
  • any other backend storage layer

Option A: databaseUrl

Use databaseUrl when you already have a backend route for persistence.

app.use(examProctor.middleware({
  databaseUrl: 'https://your-app.com/api/proctor/violations',
}));

Expected POST request body:

{
  "sessionId": "session-123",
  "violations": [
    {
      "id": "v1",
      "sessionId": "session-123",
      "type": "tab_switch",
      "timestamp": 1710000000000,
      "severity": "high",
      "metadata": {
        "reason": "visibility_hidden"
      },
      "studentName": "Student Name"
    }
  ],
  "source": "exam-proctor-sdk"
}

Recommended POST response:

{
  "saved": 1
}

Expected GET request:

GET /api/proctor/violations?sessionId=session-123

Recommended GET response:

[
  {
    "id": "v1",
    "sessionId": "session-123",
    "type": "tab_switch",
    "timestamp": 1710000000000,
    "severity": "high",
    "metadata": {
      "reason": "visibility_hidden"
    },
    "studentName": "Student Name"
  }
]

Option B: Persistence callbacks

Use callbacks when you want direct control inside Express:

app.use(examProctor.middleware({
  saveViolations: async (violations, ctx) => {
    return violations.length;
  },
  listViolations: async (sessionId, ctx) => {
    return [];
  },
}));

Available callbacks:

  • saveViolations(violations, ctx)
  • saveViolation(violation, ctx)
  • listViolations(sessionId, ctx)
  • issueSessionToken(sessionId, ctx)

Important Security Note

Do not expose raw database credentials in browser config.

Bad:

DATABASE_URL=postgresql://user:pass@host:5432/db

inside client-side config.

Good:

  • keep DB credentials only on your backend
  • expose your own backend route
  • pass that route to databaseUrl

Environment Variables

Example backend environment:

PORT=3000
DATABASE_URL=postgresql://user:pass@host:5432/dbname
PROCTOR_SESSION_JWT_SECRET=change-me

Usage:

  • DATABASE_URL used by your backend code to connect to SQL or NoSQL storage
  • PROCTOR_SESSION_JWT_SECRET used by the SDK when signing generic session JWTs

If No Database Is Configured

The package still works.

That means:

  • models still load
  • registration and verification still work
  • proctoring runtime still runs
  • the SDK logs a warning
  • violations stay in memory for the current process only

This is useful for demos and local testing.

Model Loading

The package fetches model assets at runtime.

Default model base:

https://cdn.jsdelivr.net/gh/Murali55-09/violation-detect@models-cdn/

Runtime assets include:

  • MobileFaceNet.onnx
  • yolov8n_tunned.onnx
  • face_mesh.js
  • ort-wasm-simd-threaded.wasm

If you want to self-host models:

app.use(examProctor.middleware({
  modelsBaseUrl: 'https://your-cdn.example.com/models',
}));

If you do not set modelsBaseUrl, the default CDN is used.

Frontend API

After loading /proctor/proctor-bundle.js, the browser gets:

await window.ExamProctor.start(sessionId, studentName);
await window.ExamProctor.stop();
window.ExamProctor.getMode();

Modes:

  • full
  • basic
  • tab-only
  • stopped

Recommended Headers

Serve pages with:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin

PostgreSQL Example

const express = require('express');
const { Pool } = require('pg');
const examProctor = require('exam-proctor-sdk');

const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

app.use(express.json());

app.post('/api/proctor/violations', async (req, res) => {
  const violations = Array.isArray(req.body?.violations) ? req.body.violations : [];

  for (const item of violations) {
    await pool.query(
      `insert into violations (id, session_id, type, timestamp, severity, metadata, student_name)
       values ($1, $2, $3, to_timestamp($4 / 1000.0), $5, $6::jsonb, $7)
       on conflict (id) do update set
         session_id = excluded.session_id,
         type = excluded.type,
         timestamp = excluded.timestamp,
         severity = excluded.severity,
         metadata = excluded.metadata,
         student_name = excluded.student_name`,
      [
        item.id,
        item.sessionId,
        item.type,
        item.timestamp,
        item.severity,
        JSON.stringify(item.metadata || {}),
        item.studentName || '',
      ]
    );
  }

  res.status(201).json({ saved: violations.length });
});

app.get('/api/proctor/violations', async (req, res) => {
  const sessionId = String(req.query.sessionId || '').trim();
  const result = await pool.query(
    'select * from violations where session_id = $1 order by timestamp asc',
    [sessionId]
  );
  res.json(result.rows);
});

app.use(examProctor.middleware({
  databaseUrl: 'http://localhost:3000/api/proctor/violations',
  sessionJwtSecret: process.env.PROCTOR_SESSION_JWT_SECRET,
}));

Configuration Reference

| Key | Type | Description | |---|---|---| | mountPath | string | Base route for SDK endpoints. Default: /proctor | | modelsBaseUrl | string | Base URL for runtime model assets | | modelsPath | string | Alternative model path under the mounted route | | databaseUrl | string | Your backend API endpoint for violation writes and reads | | saveViolations | function | Batch persistence callback | | saveViolation | function | Single-row persistence callback | | listViolations | function | Read violations for a session | | sessionJwtSecret | string | Secret used for generic session JWT signing | | issueSessionToken | function | Custom token issuer callback | | sessionTokenUrl | string | Override the default session token route | | violationsApiUrl | string | Override the default violations API route | | verifySuccessUrl | string | Redirect target after verification | | workerPath | string | Override worker route | | allowedOrigins | string[] \| string | Allowed origins for SDK CORS |

Exports

| Export | Description | |---|---| | middleware(config) | Mounts SDK routes into your Express app | | configScriptTag(config) | Returns an inline config <script> tag | | registerUrl() | Returns the registration route | | verifyUrl(sessionId) | Returns the verification route for a session | | DEFAULT_CONFIG | Default SDK config values |

Troubleshooting

Models do not load

Check:

  • modelsBaseUrl
  • network access to model files
  • browser console for blocked assets

Proctoring runs but violations are not saved

Check:

  • databaseUrl points to your backend route
  • your backend accepts JSON
  • your backend returns { "saved": number }
  • your database is configured on the backend

I only installed the npm package and nothing happens

Installing the package alone is not enough.

You still must:

  1. mount the middleware
  2. inject the SDK script on your exam page
  3. configure persistence if you want stored violations

License

MIT