npm package: @skilliq/proctoring-sdk
Description: Complete proctoring SDK with video recording, S3 upload, and screen discipline for assessments
Latest version: 2.0.3

# 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

```bash
npm install @skilliq/proctoring-sdk
```

## Usage

### React

```tsx
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

```typescript
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();
  }
}
```

```typescript
// 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

```typescript
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 };
  },
};
```

```vue
<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

```typescript
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 changes
- `violation` - Emitted when violation occurs
- `warning` - Emitted when warning is shown
- `autoSubmit` - Emitted when auto-submit is triggered

## License

MIT