@skilliq/proctoring-sdk
v1.0.3
Published
Framework-agnostic SDK for enforcing screen discipline in proctored assessments
Readme
Screen Discipline SDK
Framework-agnostic SDK for enforcing screen discipline in proctored assessments. Monitors fullscreen mode, tab switching, network connectivity, and external device detection with configurable warnings and auto-submission.
Features
- ✅ Fullscreen enforcement with cross-browser support
- ✅ Tab switching detection
- ✅ Mouse leave detection
- ✅ Network connectivity monitoring
- ✅ External device violation handling
- ✅ Configurable warning system with countdowns
- ✅ Auto-submission on violations
- ✅ Framework adapters for React, Angular, and Vue
- ✅ TypeScript support with full type definitions
- ✅ Event-driven architecture
Installation
npm install @skilliq/proctoring-sdkUsage
React
import React, { useRef, useEffect } from 'react';
import { useScreenDisciplineSDK } from '@skilliq/proctoring-sdk/adapters/react';
function ExamComponent() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const { state, actions } = useScreenDisciplineSDK({
isEnabled: true,
iframeElement: iframeRef.current,
maxWarnings: 3,
countdowns: [15000, 15000, 15000], // 15s each
networkTimeoutMs: 180000, // 3 minutes
submitAssessment: async (event, reason) => {
await fetch('/api/submit-assessment', {
method: 'POST',
body: JSON.stringify({ event, reason }),
});
},
onViolation: async (source) => {
console.log('Violation detected:', source);
},
});
useEffect(() => {
if (iframeRef.current) {
actions.setIframeElement(iframeRef.current);
}
}, [actions]);
return (
<div>
<iframe ref={iframeRef} src="/exam-content" />
{state.fullscreenWarningOpen && (
<div className="warning-modal">
<h2>Please return to fullscreen</h2>
<p>Time remaining: {Math.ceil((state.fullscreenDeadline! - Date.now()) / 1000)}s</p>
<p>Warnings left: {state.warningsLeft}</p>
<button onClick={actions.acknowledgeFullscreenWarning}>
Return to Fullscreen
</button>
</div>
)}
{state.networkWarningOpen && (
<div className="warning-modal">
<h2>Network connection lost</h2>
<p>Reconnecting... {Math.ceil((state.networkDeadline! - Date.now()) / 1000)}s</p>
</div>
)}
{state.endOverlayOpen && (
<div className="end-overlay">
<h2>Assessment Ended</h2>
{state.isSubmitting && <p>Submitting...</p>}
</div>
)}
</div>
);
}Angular
import { Injectable } from '@angular/core';
import { ScreenDisciplineAngularAdapter } from '@skilliq/proctoring-sdk/adapters/angular';
@Injectable({ providedIn: 'root' })
export class ExamService {
private adapter = new ScreenDisciplineAngularAdapter({
isEnabled: true,
maxWarnings: 3,
submitAssessment: async (event, reason) => {
await this.http.post('/api/submit-assessment', { event, reason }).toPromise();
},
});
readonly state$ = this.adapter.state$;
readonly actions = this.adapter.actions;
start() {
this.adapter.start();
}
stop() {
this.adapter.stop();
}
ngOnDestroy() {
this.adapter.destroy();
}
}// In your component
export class ExamComponent implements OnInit {
state$ = this.examService.state$;
actions = this.examService.actions;
constructor(private examService: ExamService) {}
ngOnInit() {
this.examService.start();
this.state$.subscribe(state => {
if (state.fullscreenWarningOpen) {
// Show warning modal
}
});
}
}Vue
import { createVueScreenDisciplineAdapter } from '@skilliq/proctoring-sdk/adapters/vue';
import { reactive, toRefs, onMounted, onUnmounted } from 'vue';
const useScreenDisciplineSDK = createVueScreenDisciplineAdapter({
reactive,
toRefs,
onMounted,
onUnmounted,
});
export default {
setup() {
const { state, actions } = useScreenDisciplineSDK({
isEnabled: true,
maxWarnings: 3,
submitAssessment: async (event, reason) => {
await fetch('/api/submit-assessment', {
method: 'POST',
body: JSON.stringify({ event, reason }),
});
},
});
return { state, actions };
},
};<template>
<div>
<iframe ref="iframeRef" src="/exam-content" />
<div v-if="state.fullscreenWarningOpen" class="warning-modal">
<h2>Please return to fullscreen</h2>
<p>Time remaining: {{ Math.ceil((state.fullscreenDeadline - Date.now()) / 1000) }}s</p>
<button @click="actions.acknowledgeFullscreenWarning">
Return to Fullscreen
</button>
</div>
</div>
</template>Vanilla TypeScript / JavaScript
import { ScreenDisciplineSDK } from '@skilliq/proctoring-sdk';
const sdk = new ScreenDisciplineSDK({
isEnabled: true,
maxWarnings: 3,
submitAssessment: async (event, reason) => {
await fetch('/api/submit-assessment', {
method: 'POST',
body: JSON.stringify({ event, reason }),
});
},
onViolation: async (source) => {
console.log('Violation:', source);
},
onStateChange: (state) => {
console.log('State changed:', state);
},
});
// Start monitoring
sdk.start();
// Listen to events
sdk.on('violation', ({ source }) => {
console.log('Violation event:', source);
});
sdk.on('warning', ({ type }) => {
console.log('Warning shown:', type);
});
sdk.on('autoSubmit', ({ source, event, reason }) => {
console.log('Auto-submitted:', { source, event, reason });
});
// Manual actions
await sdk.enterFullscreen();
await sdk.endManually();
// Stop monitoring
sdk.stop();API Reference
ScreenDisciplineOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| isEnabled | boolean | true | Enable/disable screen discipline |
| isPractice | boolean | false | Practice mode (no auto-submit) |
| iframeElement | HTMLElement \| null | null | Iframe element to monitor |
| maxWarnings | number | 3 | Maximum warnings before auto-submit |
| countdowns | [number, number, number] | [15000, 15000, 15000] | Countdown durations in ms |
| networkTimeoutMs | number | 180000 | Network timeout duration (3 min) |
| proctorTimeoutMs | number | 120000 | Proctor warning timeout (2 min) |
| isLocalEnvironment | boolean | false | Skip enforcement in local dev |
| submitAssessment | (event: string, reason: string) => Promise<void> | - | Function to submit assessment |
| onViolation | (source: ViolationSource) => void | - | Callback on violation |
| onStateChange | (state: ScreenDisciplineState) => void | - | Callback on state change |
| onManualEnd | () => void | - | Callback on manual end |
ScreenDisciplineState
| Property | Type | Description |
|----------|------|-------------|
| isOnline | boolean | Network connectivity status |
| isFullscreen | boolean | Fullscreen mode status |
| warningsLeft | number | Remaining warnings |
| fullscreenWarningOpen | boolean | Fullscreen warning visible |
| fullscreenDeadline | number \| null | Fullscreen warning deadline timestamp |
| networkWarningOpen | boolean | Network warning visible |
| networkDeadline | number \| null | Network warning deadline timestamp |
| proctorWarningOpen | boolean | Proctor warning visible |
| proctorDeadline | number \| null | Proctor warning deadline timestamp |
| endOverlayOpen | boolean | End overlay visible |
| isSubmitting | boolean | Submission in progress |
ViolationSource
"EXIT_FULLSCREEN"- User exited fullscreen"TAB_SWITCH"- User switched tabs"MOUSE_LEAVE"- Mouse left window"EXTERNAL_DEVICE"- External device detected"NETWORK_TIMEOUT"- Network connection lost"DURATION_TIMEOUT"- Time limit exceeded
Events
stateChange- Emitted when state changesviolation- Emitted when violation occurswarning- Emitted when warning is shownautoSubmit- Emitted when auto-submit is triggered
License
MIT
