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

@peepl/proctoring-browser

v0.1.0

Published

Proctoring SDK for client pages embedding the PEEPL online test via iframe

Downloads

306

Readme

@peepl/proctoring-browser

Embed the PEEPL Online Test with an iframe while proctoring runs on your page. Initialize the SDK once; the assessment controls when camera, screen sharing, and profile-photo capture are needed. Candidate login stays inside the iframe.

Install

bun add @peepl/proctoring-browser

For a script-tag integration, use the browser bundle shown below. To host it yourself, see self-hosting. Registry and CDN installation require the package to be published there.

Basic usage

<iframe id="online-test" src="https://test.peepl.tech/" allow="camera; microphone"></iframe>
<script src="https://unpkg.com/@peepl/proctoring-browser/dist/index.iife.js"></script>
<script>
  const proctor = NucleusProctor.init({ iframe: '#online-test' });
  window.addEventListener('pagehide', () => proctor.destroy());
</script>

Use your PEEPL assessment URL, HTTPS, desktop Chrome/Edge, and a parent-page origin registered with PEEPL. Keep the iframe visible during the assessment. allow="camera; microphone" permits interview and presentation media inside the iframe.

Use your own AI backend

PEEPL uses internal camera analysis by default. To use your own AI, PEEPL must enable external analysis for your client and issue an API key for your backend. Your backend runs inference and submits results using @peepl/proctoring-server.

Browser face detection, profile photos, periodic captures, screen evidence, and clipboard/window monitoring remain separate from final AI analysis. PEEPL still handles evidence storage, incident rules, and reporting.

Client page

import { init, createCameraAnalyzer } from '@peepl/proctoring-browser'

const proctor = init({
  iframe: '#online-test',
  analyzeCamera: createCameraAnalyzer('/api/proctoring/analyze'),
})

proctor.on('analysis-error', ({ evidenceId, code }) => {
  console.warn(evidenceId, code)
})

window.addEventListener('pagehide', () => proctor.destroy())

For script-tag integration, use NucleusProctor.createCameraAnalyzer('/api/proctoring/analyze').

createCameraAnalyzer sends JSON by same-origin POST with the page's session cookies. It requires HTTPS except for local loopback development and rejects cross-origin URLs, URL credentials, and redirects. Supply additional headers with its headers option when your endpoint requires CSRF protection.

You can also supply your own async analyzeCamera callback for a different authentication scheme or response format. It receives the request below plus an abort signal and must return { status: 'submitted' } after backend submission.

What your endpoint receives

{
  "image": "data:image/jpeg;base64,...",
  "evidenceId": "cam_0123456789abcdef",
  "sessionId": "emb_0123456789abcdef0123456789abcdef",
  "capturedAt": "2026-09-30T03:00:00.000Z"
}

Authenticate and authorize the candidate, enforce CSRF protection and request limits, and run inference on image on your server. Keep AI credentials and the PEEPL API key out of the browser. Do not trust browser-supplied detections.

After submitting to PEEPL, return this JSON response:

{ "status": "submitted" }

See the server guide for the model result contract and submission example.

Runtime behavior

  • Only camera frames selected for incident checking invoke analyzeCamera, not every video frame or ordinary periodic capture.
  • One analysis runs at a time. The default timeout is 15 seconds; analysisTimeoutMs accepts 1,000–60,000 ms.
  • Failure or an invalid receipt emits analysis-error. There is no automatic retry or fallback to PEEPL AI; periodic captures continue independently.
  • Stopping proctoring aborts in-flight analysis. Custom callbacks should pass the supplied signal to their network requests.
  • analysis-complete means backend submission succeeded, not that incident processing or storage has finished.

External analysis mode comes from the authenticated iframe handshake, not a browser setting. External face monitoring requires an analyzeCamera callback; otherwise it fails with client-analysis-required.

Use a browser model for the preliminary check

The built-in YuNet detector provides live face monitoring and profile-photo guidance. It loads lazily and is released when the SDK is destroyed. Browser results are advisory; they do not replace backend incident analysis.

Choose file replacement or an adapter

| Change | Supported approach | |---|---| | Host the built-in model elsewhere | Set assetsUrl to the complete detector assets directory. | | Use a compatible YuNet ONNX model | Replace the expected model file in your hosted assets. | | Change model architecture or preprocessing, such as MediaPipe, SCRFD, or YOLO | Supply a faceDetector adapter. |

There is no modelUrl option. assetsUrl is a directory, not a single model URL. The built-in engine requests models/face_detection_yunet_2026may.onnx inside that directory. Renaming an incompatible model does not make it work.

Compatible replacements must accept float32 [1, 3, 640, 640], planar BGR pixels without normalization, and top-left letterboxing. Outputs must match YuNet's cls, obj, bbox, and kps tensors for strides 8, 16, and 32, including names such as cls_8, shapes, layout, and decoding. The detector uses a score threshold of 0.6 and NMS IoU of 0.3.

Self-host or replace a compatible model file

Build the SDK, then copy the contents of packages/client/dist/ into your web server's public/proctoring/ directory. These browser URLs must resolve:

/proctoring/index.iife.js
/proctoring/face-detector.iife.js
/proctoring/models/face_detection_yunet_2026may.onnx
/proctoring/wasm/ort-wasm-simd-threaded.mjs
/proctoring/wasm/ort-wasm-simd-threaded.wasm

Serve .mjs as JavaScript and .wasm as application/wasm, not an HTML fallback. For assets on another origin, allow CORS from your page. Serve the page over HTTPS or local loopback HTTP, not file://.

<iframe id="online-test" src="https://test.peepl.tech/" allow="camera; microphone"></iframe>
<script src="/proctoring/index.iife.js"></script>
<script>
  const proctor = NucleusProctor.init({
    iframe: '#online-test',
    assetsUrl: '/proctoring/',
  })

  window.addEventListener('pagehide', () => proctor.destroy())
</script>

For a CDN, use the full assets directory URL for assetsUrl, such as https://cdn.client.example/proctoring/.

A compatible alternative is OpenCV Zoo's face_detection_yunet_2023mar.onnx. Run this command inside your hosted public/proctoring/ directory:

curl --fail --location \
  'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx' \
  --output models/face_detection_yunet_2026may.onnx

The destination filename stays the same; its contents are the compatible 2023 model. The HTML integration is unchanged. Preserve the upstream model license.

Different architecture: MediaPipe adapter

This example uses MediaPipe BlazeFace instead of YuNet. Use it instead of the previous initialization, with /proctoring/index.iife.js hosted as above.

<iframe id="online-test" src="https://test.peepl.tech/" allow="camera; microphone"></iframe>
<script src="/proctoring/index.iife.js"></script>
<script type="module">
  import {
    FaceDetector as MediaPipeDetector,
    FilesetResolver,
  } from 'https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/vision_bundle.mjs'

  async function createMediaPipeDetector() {
    const vision = await FilesetResolver.forVisionTasks(
      'https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm'
    )
    const detector = await MediaPipeDetector.createFromOptions(vision, {
      baseOptions: {
        modelAssetPath:
          'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite',
        delegate: 'CPU',
      },
      runningMode: 'IMAGE',
      minDetectionConfidence: 0.6,
      minSuppressionThreshold: 0.3,
    })
    let disposed = false

    return {
      async detect(input) {
        if (disposed) throw new Error('face-detector-disposed')
        return detector.detect(input).detections.map(({ boundingBox }) => {
          if (!boundingBox) throw new Error('face-bounding-box-missing')
          return {
            x: boundingBox.originX,
            y: boundingBox.originY,
            width: boundingBox.width,
            height: boundingBox.height,
          }
        })
      },
      dispose() {
        if (disposed) return
        disposed = true
        detector.close()
      },
    }
  }

  const proctor = NucleusProctor.init({
    iframe: '#online-test',
    faceDetector: createMediaPipeDetector,
  })

  window.addEventListener('pagehide', () => proctor.destroy())
</script>

IMAGE mode accepts both webcam video frames and profile-photo images. MediaPipe handles its own preprocessing and assets; the built-in YuNet assets are not loaded. Allow the CDN, model, and runtime URLs in your CSP, or host matching assets yourself. Inference is synchronous and can block the UI thread.

For external backend analysis, configure analyzeCamera independently of either browser model example.

Adapter contract

  • faceDetector is an async factory returning { detect, dispose }.
  • detect accepts an HTMLVideoElement or HTMLImageElement and returns a promise of face boxes in original-image pixels: [{ x, y, width, height }].
  • Return [] for successful inference with no face; throw on detection failure.
  • The SDK loads the factory lazily, reuses the detector, and calls dispose when destroyed.

This browser contract needs only boxes. The backend contract also needs scores and five landmarks. MediaPipe Face Detector lacks separate mouth corners, so this example is not a backend-analysis adapter.

API

| Member | Description | |---|---| | init({ iframe, childOrigin?, assetsUrl?, faceDetector?, analyzeCamera?, analysisTimeoutMs? }) | Initialize once per page. iframe is a selector or element; childOrigin defaults to its src origin. | | assetsUrl | Directory containing the built-in detector assets. | | faceDetector | Lazy factory for a custom browser detector. | | analyzeCamera | Async callback returning a submitted receipt in external mode. | | createCameraAnalyzer(endpoint, { headers? }?) | Same-origin JSON endpoint adapter. | | destroy() | Stop proctoring, cancel analysis, release media and models, and remove listeners. | | status() | Current { state, sid, camera, screen, proctoring, cameraAnalysis }; analysis mode is internal or external. | | on(event, handler) | Subscribe to SDK events. |

Events: state, handshake, stop, error, connection-lost, protocol, analysis-error, analysis-complete.

Common errors: hello-timeout, camera-denied, screen-denied, heartbeat-dead, and client-analysis-required.