import type { EventKind } from "@a4anthony/proctorkit-types"; import { type ScreenshotObserverConfig } from "./screenshot-observer.js"; import type { ScreenShareObserverConfig } from "./screen-share-observer.js"; import type { WebcamObserverConfig } from "./webcam-observer.js"; export type { ScreenshotObserverConfig } from "./screenshot-observer.js"; export type { ScreenShareObserverConfig } from "./screen-share-observer.js"; export type { WebcamObserverConfig } from "./webcam-observer.js"; /** * Minimal contract the observers need from the host's ProctoringClient. * Decoupled from the concrete client so we can unit-test in isolation * without spinning up a worker. */ export interface ObserverEmitter { emit(kind: EventKind, payload?: Record): void; } export interface ClipboardBlockConfig { /** Block Cmd/Ctrl+C from updating the clipboard. The attempt is still logged. */ copy?: boolean; /** Block Cmd/Ctrl+X. The attempt is still logged. */ cut?: boolean; /** * Block paste *except* on elements that opt back in via the * `data-proctoring-allow-clipboard` attribute (set on the element or * any ancestor). Useful for blocking paste into instructions while * still letting candidates paste into their own answer field. */ paste?: boolean; /** Block the right-click / context menu globally. */ contextmenu?: boolean; } export interface ClipboardObserverConfig { /** * Capture the actual text content of copy/paste/cut events. * * SECURITY: candidates' clipboards routinely contain unrelated * personal data (passwords, OTPs, addresses). Capturing it has * material legal and ethical consequences — only turn this on after * obtaining explicit consent from candidates, typically via a * disclosure screen before the test starts. Default: false. */ captureContent?: boolean; /** * When captureContent is on, the maximum number of UTF-8 bytes of * clipboard text included per event. Content is truncated past this. * Default: 2000. */ maxBytes?: number; /** * Block clipboard actions during the proctored test. The attempted * action is always still emitted to the timeline (with * `blocked: true`) so proctors see the cheating signal regardless. * * Pass `true` to block everything. Pass an object to be granular. * Default: nothing blocked. */ block?: boolean | ClipboardBlockConfig; } export interface KeyboardObserverConfig { /** * Shortcut strings to block. Each is "Mod+P", "Mod+Shift+I", "F12" * etc. `Mod` resolves to Cmd on Mac, Ctrl elsewhere. Other modifier * tokens: `Ctrl`, `Cmd`, `Alt`, `Shift`. Letters are case-insensitive. * * When a blocked shortcut fires, event.preventDefault() runs and a * `keyboard.blocked` event lands in the timeline with the shortcut * name in the payload. * * Default: a sensible proctoring list (print, save, find, reload, * F12, view source, DevTools). Pass `[]` to disable entirely. */ block?: string[]; } export interface IdleObserverConfig { /** * Seconds of no keystrokes / mouse movement / pointer / wheel / * touch input before `idle.started` fires. The timer pauses while * the tab is hidden — a candidate who switched tabs is "away", not * "idle on this page", and we already emit `tab.hidden` for that * case. * * Default: 60s. Tuned for proctoring: under 30s gives false positives * for thinking pauses; over 2min misses real "candidate walked off" * cases. */ thresholdSeconds?: number; } export interface DomObserversConfig { focus?: boolean; visibility?: boolean; fullscreen?: boolean; network?: boolean; /** * Detect when the cursor leaves the viewport (candidate moves the * mouse off the page to interact with another window / monitor / OS * notification). Emits `pointer.left-window` on `mouseleave` of the * document element and `pointer.returned` on the matching * `mouseenter`. Pass `false` to disable. Default: on. * * False-positives to be aware of: the browser also fires mouseleave * when the candidate switches to another tab via keyboard. We do not * emit if the page is already hidden (visibilityState !== "visible") * to avoid double-flagging the same disengagement that `tab.hidden` * already captures. */ pointer?: boolean; /** * Detect "candidate appears to have walked away" — no keyboard or * pointer activity for N seconds while the tab is still visible. * Pass `false` to disable, `true` for defaults, or an object to tune * the threshold. Default: on, 60s threshold. */ idle?: boolean | IdleObserverConfig; /** * Clipboard signals (copy / paste / cut) and right-click. Pass `true` * (default) to record fact-only payloads. Pass `false` to disable * entirely. Pass a {@link ClipboardObserverConfig} to opt into * capturing content, which has privacy implications. */ clipboard?: boolean | ClipboardObserverConfig; /** * Block in-page keyboard shortcuts. Pass `false` to disable, `true` * to use the default list, or an object for custom shortcuts. * * CAVEAT: browser-owned shortcuts (Cmd+T new tab, Cmd+L address bar, * Cmd+Tab app switch, OS screenshot shortcuts) cannot be intercepted * by a web page — those are filtered by the browser before keydown * reaches your listener. This option blocks the in-page subset only. */ keyboard?: boolean | KeyboardObserverConfig; /** * Detect screenshot attempts (keyboard shortcuts + visibility change * + window blur), emitting `screenshot.attempted` for each. Pass `true` * to enable detection-only, or an object to opt into the on-page blur * overlay deterrent. Defaults to off because the blur overlay changes * the candidate's UX — opt in deliberately. */ screenshot?: boolean | ScreenshotObserverConfig; /** * Request the candidate's screen via `getDisplayMedia` when the * client starts, and record the stream into chunks. Pass `true` to * use defaults (entire screen enforced, 10s VP9 WebM chunks at * 500kbps), or an object to customise. Default: off. * * Prefer a stream returned by `requestScreenShare()` from the candidate's * button click. When `stream` is omitted, the client opens the picker * synchronously during construction for compatibility, so construction * must itself happen in that user-activation handler. */ screenShare?: boolean | ScreenShareObserverConfig; /** * Acquire the candidate's webcam via `getUserMedia` when the client * starts. Pass `true` to grab the camera with no photo loop, or an * object with `photos: true` to capture random snapshots at intervals. * * IMPORTANT: when enabled, construct the `ProctoringClient` inside a * user-activation handler (your "Start session" click). Browsers * reject `getUserMedia` outside that window. Failures are routed via * the `onWebcamError(kind)` callback on the client. */ webcam?: boolean | WebcamObserverConfig; } export declare class DomObservers { private readonly emitter; private listening; private readonly config; private readonly listeners; private readonly screenshotConfig; private screenshotObserver; private idleTimer; private idleSince; private isIdle; constructor(emitter: ObserverEmitter, config?: DomObserversConfig); start(): void; stop(): void; private bind; /** * Mark "the candidate is actively using the page right now." If the * idle observer previously fired `idle.started`, also fire * `idle.ended` with the duration. Either way, re-arm the timer that * will fire `idle.started` after `thresholdMs` of further silence. */ private recordActivity; private clearIdleTimer; } //# sourceMappingURL=dom-observers.d.ts.map