@a4anthony/proctorkit-types 0.0.0-canary.97dc0c42.20260708235358
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/api.d.ts +19 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +2 -0
- package/dist/api.js.map +1 -0
- package/dist/events.d.ts +31 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +317 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/production-safety.d.ts +15 -0
- package/dist/production-safety.d.ts.map +1 -0
- package/dist/production-safety.js +110 -0
- package/dist/production-safety.js.map +1 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +2 -0
- package/dist/session.js.map +1 -0
- package/dist/status.d.ts +65 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +2 -0
- package/dist/status.js.map +1 -0
- package/dist/worker-protocol.d.ts +79 -0
- package/dist/worker-protocol.d.ts.map +1 -0
- package/dist/worker-protocol.js +2 -0
- package/dist/worker-protocol.js.map +1 -0
- package/package.json +31 -0
- package/src/api.ts +20 -0
- package/src/events.ts +335 -0
- package/src/index.ts +6 -0
- package/src/production-safety.ts +155 -0
- package/src/session.ts +11 -0
- package/src/status.ts +93 -0
- package/src/worker-protocol.ts +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Gakro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SignedUploadUrl {
|
|
2
|
+
url: string;
|
|
3
|
+
method: "PUT";
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
objectKey: string;
|
|
7
|
+
}
|
|
8
|
+
export interface IngestBatchRequest {
|
|
9
|
+
batchId: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
candidate?: import("./worker-protocol.js").CandidateIdentity;
|
|
12
|
+
events: import("./events.js").SessionEvent[];
|
|
13
|
+
}
|
|
14
|
+
export interface IngestBatchResponse {
|
|
15
|
+
batchId: string;
|
|
16
|
+
accepted: string[];
|
|
17
|
+
rejected: string[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,KAAK,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,sBAAsB,EAAE,iBAAiB,CAAC;IAC7D,MAAM,EAAE,OAAO,aAAa,EAAE,YAAY,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB"}
|
package/dist/api.js
ADDED
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":""}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical list of every event kind, as a runtime value.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE SOURCE OF TRUTH for event kinds. The `EventKind`
|
|
5
|
+
* type is derived from it (`typeof EVENT_KINDS[number]`), and the
|
|
6
|
+
* server's ingest validation builds its zod enum from the same array
|
|
7
|
+
* (`z.enum(EVENT_KINDS)`) so the wire contract can never silently drift
|
|
8
|
+
* from the type. Add a new kind here and both the type and the ingest
|
|
9
|
+
* gate pick it up. (A hand-maintained copy of this list in the ingest
|
|
10
|
+
* DTO is exactly how `session.checkpoint` once 400'd every batch that
|
|
11
|
+
* carried it — don't reintroduce a second list.)
|
|
12
|
+
*/
|
|
13
|
+
export declare const EVENT_KINDS: readonly ["session.started", "session.heartbeat", "session.end_requested", "session.ended", "session.resumed", "session.abandoned", "session.checkpoint", "sdk.initialized", "sdk.ready", "sdk.error", "sdk.upload.failed", "sdk.media.failed", "sdk.permission.denied", "sdk.worker.failed", "sdk.storage.fallback", "sdk.recording.buffer-degraded", "sdk.recording.buffer-evicted", "sdk.stop.drain-timeout", "focus.lost", "focus.gained", "tab.hidden", "tab.visible", "page.viewed", "fullscreen.entered", "fullscreen.exited", "clipboard.copy", "clipboard.paste", "clipboard.cut", "contextmenu.opened", "keyboard.blocked", "screenshot.attempted", "screen-share.started", "screen-share.stopped", "screen-share.declined", "screen-share.wrong-surface", "screen-share.recording.started", "screen-share.recording.stopped", "screen-share.recording.unavailable", "screen-share.chunk-uploaded", "screen-share.chunk-dropped", "webcam.started", "webcam.stopped", "webcam.declined", "webcam.unavailable", "webcam.photo.captured", "webcam.photo.dropped", "webcam.recording.started", "webcam.recording.stopped", "webcam.recording.chunk-uploaded", "webcam.recording.chunk-dropped", "video-clip.started", "video-clip.stopped", "video-clip.uploaded", "video-clip.dropped", "audio-clip.started", "audio-clip.stopped", "audio-clip.uploaded", "audio-clip.dropped", "audio-playback.started", "audio-playback.paused", "audio-playback.stopped", "audio-playback.ended", "audio-playback.error", "audio-playback.sink-applied", "audio-playback.sink-unsupported", "audio-playback.sink-failed", "audio-playback.sink-disconnected", "video-playback.started", "video-playback.paused", "video-playback.stopped", "video-playback.ended", "video-playback.error", "video-playback.sink-applied", "video-playback.sink-unsupported", "video-playback.sink-failed", "video-playback.sink-disconnected", "pointer.left-window", "pointer.returned", "idle.started", "idle.ended", "session.fingerprint", "face.detector-ready", "face.detector-failed", "face.detector-degraded", "face.lost", "face.returned", "face.multiple", "face.multiple-cleared", "face.gaze-off-screen", "face.gaze-restored", "screen.window-switched", "screen.scene-stable", "screen.recording-paused", "webcam.phone-detected", "webcam.phone-cleared", "webcam.book-detected", "webcam.book-cleared", "webcam.second-device-detected", "webcam.second-device-cleared", "webcam.second-screen-detected", "webcam.second-screen-cleared", "face.lost-at-snapshot", "face.multiple-at-snapshot", "face.gaze-off-screen-at-snapshot", "face.identity-mismatch-at-snapshot", "webcam.phone-detected-at-snapshot", "webcam.book-detected-at-snapshot", "webcam.second-device-detected-at-snapshot", "webcam.second-screen-detected-at-snapshot", "media.frame.captured", "media.audio.chunk", "network.online", "network.offline", "preflight.started", "preflight.row-checking", "preflight.row-passed", "preflight.row-failed", "preflight.row-retried", "preflight.attestation", "preflight.device-picked", "preflight.devices-enumerated", "preflight.devices-changed", "preflight.face-detected", "preflight.camera-photo", "preflight.completed", "text.started", "text.paste", "text.copy", "text.cut", "text.keystroke-summary", "text.checkpoint", "text.focus-lost", "text.focus-regained", "text.synthetic-input", "text.saved", "text.save-failed", "text.submitted"];
|
|
14
|
+
export type EventKind = (typeof EVENT_KINDS)[number];
|
|
15
|
+
export interface SessionEvent {
|
|
16
|
+
id: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
kind: EventKind;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
payload?: Record<string, unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Per-device fingerprint stamped by the SDK at construction time
|
|
23
|
+
* (see `computeDeviceFingerprint`). Optional so legacy SDK
|
|
24
|
+
* builds + tests can still construct a SessionEvent without one.
|
|
25
|
+
* Promoted to a real column on the server's Event row so the
|
|
26
|
+
* dashboard can render multi-device timelines as parallel swim
|
|
27
|
+
* lanes without scanning the payload JSON.
|
|
28
|
+
*/
|
|
29
|
+
fingerprintId?: string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,6wGA+Sd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical list of every event kind, as a runtime value.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE SOURCE OF TRUTH for event kinds. The `EventKind`
|
|
5
|
+
* type is derived from it (`typeof EVENT_KINDS[number]`), and the
|
|
6
|
+
* server's ingest validation builds its zod enum from the same array
|
|
7
|
+
* (`z.enum(EVENT_KINDS)`) so the wire contract can never silently drift
|
|
8
|
+
* from the type. Add a new kind here and both the type and the ingest
|
|
9
|
+
* gate pick it up. (A hand-maintained copy of this list in the ingest
|
|
10
|
+
* DTO is exactly how `session.checkpoint` once 400'd every batch that
|
|
11
|
+
* carried it — don't reintroduce a second list.)
|
|
12
|
+
*/
|
|
13
|
+
export const EVENT_KINDS = [
|
|
14
|
+
"session.started",
|
|
15
|
+
"session.heartbeat",
|
|
16
|
+
"session.end_requested",
|
|
17
|
+
"session.ended",
|
|
18
|
+
/**
|
|
19
|
+
* Emitted once when a candidate resumes a live session after a
|
|
20
|
+
* mid-assessment page refresh — i.e. the attempt resolver returned
|
|
21
|
+
* `reuse_active_attempt` and the resume re-check (camera/mic/speaker/
|
|
22
|
+
* screen-share/fullscreen) passed. The payload carries the per-check
|
|
23
|
+
* verdicts and any device the candidate switched to during the
|
|
24
|
+
* re-check, so a proctor has an audit trail of the interruption
|
|
25
|
+
* (count of `session.resumed` events = number of resumes) and of any
|
|
26
|
+
* device change mid-exam. Distinct from `session.started`, which the
|
|
27
|
+
* SDK also re-emits on resume as a side effect of re-creating the
|
|
28
|
+
* client.
|
|
29
|
+
*/
|
|
30
|
+
"session.resumed",
|
|
31
|
+
/**
|
|
32
|
+
* Emitted server-side when a within-session attempt is retired without a
|
|
33
|
+
* clean `session.ended` — the candidate dropped (disconnect, stale backstop,
|
|
34
|
+
* or a different tab/device taking over) rather than finishing. Marks the
|
|
35
|
+
* close of an attempt on the review timeline; a resume opens the next one
|
|
36
|
+
* with `session.resumed`. Distinct from the `session.abandoned` *webhook*,
|
|
37
|
+
* which fires when the whole sitting ends abandoned. Server-derived because
|
|
38
|
+
* an abandoned client can't report its own departure.
|
|
39
|
+
*/
|
|
40
|
+
"session.abandoned",
|
|
41
|
+
/**
|
|
42
|
+
* Runtime integrity checkpoint: a re-verification, run on demand or on a
|
|
43
|
+
* schedule, that compares the live environment against the state that
|
|
44
|
+
* passed preflight. The payload carries `{ ok, changes[] }` — each change
|
|
45
|
+
* a drift signal (permission revoked, selected device gone, external
|
|
46
|
+
* monitor connected, camera track ended/black/frozen, mic track
|
|
47
|
+
* ended/muted). `ok: true` means "still matches preflight." A run is
|
|
48
|
+
* emitted every time so the count is an audit trail; the SDK never
|
|
49
|
+
* pauses or fails the session on drift — the host decides.
|
|
50
|
+
*/
|
|
51
|
+
"session.checkpoint",
|
|
52
|
+
"sdk.initialized",
|
|
53
|
+
"sdk.ready",
|
|
54
|
+
"sdk.error",
|
|
55
|
+
"sdk.upload.failed",
|
|
56
|
+
"sdk.media.failed",
|
|
57
|
+
"sdk.permission.denied",
|
|
58
|
+
"sdk.worker.failed",
|
|
59
|
+
"sdk.storage.fallback",
|
|
60
|
+
"sdk.recording.buffer-degraded",
|
|
61
|
+
"sdk.recording.buffer-evicted",
|
|
62
|
+
"sdk.stop.drain-timeout",
|
|
63
|
+
"focus.lost",
|
|
64
|
+
"focus.gained",
|
|
65
|
+
"tab.hidden",
|
|
66
|
+
"tab.visible",
|
|
67
|
+
/**
|
|
68
|
+
* Emitted once when the proctored runtime page loads — on first start
|
|
69
|
+
* and again on every resume (the SDK re-boots). Carries the page URL,
|
|
70
|
+
* path, referrer and document title, plus whether the page was
|
|
71
|
+
* backgrounded at load. Records *that the candidate landed on the exam
|
|
72
|
+
* page* (and from where), distinct from the presence signals
|
|
73
|
+
* (`tab.*` / `focus.*`) that track them leaving it. The browser cannot
|
|
74
|
+
* see which external page they navigate to, so this only ever reports
|
|
75
|
+
* the exam's own URL.
|
|
76
|
+
*/
|
|
77
|
+
"page.viewed",
|
|
78
|
+
"fullscreen.entered",
|
|
79
|
+
"fullscreen.exited",
|
|
80
|
+
"clipboard.copy",
|
|
81
|
+
"clipboard.paste",
|
|
82
|
+
"clipboard.cut",
|
|
83
|
+
"contextmenu.opened",
|
|
84
|
+
"keyboard.blocked",
|
|
85
|
+
"screenshot.attempted",
|
|
86
|
+
"screen-share.started",
|
|
87
|
+
"screen-share.stopped",
|
|
88
|
+
"screen-share.declined",
|
|
89
|
+
"screen-share.wrong-surface",
|
|
90
|
+
"screen-share.recording.started",
|
|
91
|
+
"screen-share.recording.stopped",
|
|
92
|
+
"screen-share.recording.unavailable",
|
|
93
|
+
"screen-share.chunk-uploaded",
|
|
94
|
+
"screen-share.chunk-dropped",
|
|
95
|
+
"webcam.started",
|
|
96
|
+
"webcam.stopped",
|
|
97
|
+
"webcam.declined",
|
|
98
|
+
"webcam.unavailable",
|
|
99
|
+
"webcam.photo.captured",
|
|
100
|
+
"webcam.photo.dropped",
|
|
101
|
+
"webcam.recording.started",
|
|
102
|
+
"webcam.recording.stopped",
|
|
103
|
+
"webcam.recording.chunk-uploaded",
|
|
104
|
+
"webcam.recording.chunk-dropped",
|
|
105
|
+
"video-clip.started",
|
|
106
|
+
"video-clip.stopped",
|
|
107
|
+
"video-clip.uploaded",
|
|
108
|
+
"video-clip.dropped",
|
|
109
|
+
"audio-clip.started",
|
|
110
|
+
"audio-clip.stopped",
|
|
111
|
+
"audio-clip.uploaded",
|
|
112
|
+
"audio-clip.dropped",
|
|
113
|
+
"audio-playback.started",
|
|
114
|
+
"audio-playback.paused",
|
|
115
|
+
"audio-playback.stopped",
|
|
116
|
+
"audio-playback.ended",
|
|
117
|
+
"audio-playback.error",
|
|
118
|
+
"audio-playback.sink-applied",
|
|
119
|
+
"audio-playback.sink-unsupported",
|
|
120
|
+
"audio-playback.sink-failed",
|
|
121
|
+
"audio-playback.sink-disconnected",
|
|
122
|
+
"video-playback.started",
|
|
123
|
+
"video-playback.paused",
|
|
124
|
+
"video-playback.stopped",
|
|
125
|
+
"video-playback.ended",
|
|
126
|
+
"video-playback.error",
|
|
127
|
+
"video-playback.sink-applied",
|
|
128
|
+
"video-playback.sink-unsupported",
|
|
129
|
+
"video-playback.sink-failed",
|
|
130
|
+
"video-playback.sink-disconnected",
|
|
131
|
+
"pointer.left-window",
|
|
132
|
+
"pointer.returned",
|
|
133
|
+
"idle.started",
|
|
134
|
+
"idle.ended",
|
|
135
|
+
"session.fingerprint",
|
|
136
|
+
"face.detector-ready",
|
|
137
|
+
"face.detector-failed",
|
|
138
|
+
"face.detector-degraded",
|
|
139
|
+
"face.lost",
|
|
140
|
+
"face.returned",
|
|
141
|
+
"face.multiple",
|
|
142
|
+
"face.multiple-cleared",
|
|
143
|
+
"face.gaze-off-screen",
|
|
144
|
+
"face.gaze-restored",
|
|
145
|
+
/**
|
|
146
|
+
* Post-hoc screen-share analysis events. Emitted by the
|
|
147
|
+
* packages/screen-detect Python sidecar after the stitched
|
|
148
|
+
* screen-share recording is assembled at session-end. Source
|
|
149
|
+
* column is set to "analyzer" and the analysisRunId points
|
|
150
|
+
* at the ScreenAnalysisRun row that produced this event,
|
|
151
|
+
* mirroring the face-analysis pattern.
|
|
152
|
+
*
|
|
153
|
+
* Why we need them: client-side focus.lost only fires when
|
|
154
|
+
* the candidate switches AWAY from the exam tab; it misses
|
|
155
|
+
* window-level switches that stay in-browser (a popover
|
|
156
|
+
* ChatGPT in another tab of the same Chrome instance, an
|
|
157
|
+
* OS-level overlay like Copilot, a second monitor in shared
|
|
158
|
+
* view, etc.). The screen recording itself is the only
|
|
159
|
+
* signal here — these events surface visual context changes
|
|
160
|
+
* that the live client never saw.
|
|
161
|
+
*/
|
|
162
|
+
"screen.window-switched",
|
|
163
|
+
"screen.scene-stable",
|
|
164
|
+
"screen.recording-paused",
|
|
165
|
+
/**
|
|
166
|
+
* Object-detection signals emitted by the post-hoc YOLO11
|
|
167
|
+
* pass that runs alongside face analysis on the stitched
|
|
168
|
+
* webcam recording. The YOLO model recognises 80 COCO
|
|
169
|
+
* classes; we surface only the subset that maps to
|
|
170
|
+
* "policy violation in a proctored exam":
|
|
171
|
+
*
|
|
172
|
+
* - phone-detected — a cell phone visible in frame
|
|
173
|
+
* - book-detected — a printed book / notebook visible
|
|
174
|
+
* - second-device-detected — a laptop other than the
|
|
175
|
+
* candidate's primary visible in frame
|
|
176
|
+
* - second-screen-detected — a TV / monitor other than
|
|
177
|
+
* the candidate's primary visible in frame
|
|
178
|
+
*
|
|
179
|
+
* The matching `*-cleared` event fires when the object
|
|
180
|
+
* stops being detected for a debounce window. Bounding box
|
|
181
|
+
* + confidence land in the payload.
|
|
182
|
+
*
|
|
183
|
+
* Sourced from the same FaceAnalysisRun as the face events
|
|
184
|
+
* (we run them in one pass over the stitched video) so
|
|
185
|
+
* `analysisRunId` is shared.
|
|
186
|
+
*/
|
|
187
|
+
"webcam.phone-detected",
|
|
188
|
+
"webcam.phone-cleared",
|
|
189
|
+
"webcam.book-detected",
|
|
190
|
+
"webcam.book-cleared",
|
|
191
|
+
"webcam.second-device-detected",
|
|
192
|
+
"webcam.second-device-cleared",
|
|
193
|
+
"webcam.second-screen-detected",
|
|
194
|
+
"webcam.second-screen-cleared",
|
|
195
|
+
/**
|
|
196
|
+
* Random-photo analysis events. The SDK uploads JPEG snapshots
|
|
197
|
+
* every 20-60 seconds during a proctored session; the photo-
|
|
198
|
+
* analysis worker runs the same detectors against each photo
|
|
199
|
+
* AFTER session-end as a backstop to the stitched-video pass.
|
|
200
|
+
*
|
|
201
|
+
* Why a separate set of kinds (`-at-snapshot`) rather than
|
|
202
|
+
* reusing `face.lost` / `webcam.phone-detected`:
|
|
203
|
+
*
|
|
204
|
+
* - The cert needs to prefer the photo's actual JPEG over
|
|
205
|
+
* a video-frame extraction as the evidence thumbnail —
|
|
206
|
+
* photos are 1080p singles, video frames are transcoded
|
|
207
|
+
* and compressed.
|
|
208
|
+
* - Reviewers reading the timeline benefit from knowing
|
|
209
|
+
* which signal came from continuous video analysis vs.
|
|
210
|
+
* intermittent snapshot sampling.
|
|
211
|
+
* - Source field is "photo-analyzer" (vs "analyzer" for
|
|
212
|
+
* the video pass) so audit queries can separate them.
|
|
213
|
+
*
|
|
214
|
+
* Payload always carries `photoId` (BigInt as string) so the
|
|
215
|
+
* dashboard can join back to webcam_photos and display the
|
|
216
|
+
* actual JPEG inline.
|
|
217
|
+
*/
|
|
218
|
+
"face.lost-at-snapshot",
|
|
219
|
+
"face.multiple-at-snapshot",
|
|
220
|
+
"face.gaze-off-screen-at-snapshot",
|
|
221
|
+
"face.identity-mismatch-at-snapshot",
|
|
222
|
+
"webcam.phone-detected-at-snapshot",
|
|
223
|
+
"webcam.book-detected-at-snapshot",
|
|
224
|
+
"webcam.second-device-detected-at-snapshot",
|
|
225
|
+
"webcam.second-screen-detected-at-snapshot",
|
|
226
|
+
"media.frame.captured",
|
|
227
|
+
"media.audio.chunk",
|
|
228
|
+
"network.online",
|
|
229
|
+
"network.offline",
|
|
230
|
+
// Preflight wizard lifecycle. Emitted by @a4anthony/proctorkit-vue's
|
|
231
|
+
// preflight telemetry composable. The server materialises these into
|
|
232
|
+
// the preflight_outcomes row keyed off the candidate's sessionId
|
|
233
|
+
// (the same opaque id the customer's host page assigns; correlationId
|
|
234
|
+
// on the Vue API surface, sessionId on the wire).
|
|
235
|
+
"preflight.started",
|
|
236
|
+
"preflight.row-checking",
|
|
237
|
+
"preflight.row-passed",
|
|
238
|
+
"preflight.row-failed",
|
|
239
|
+
"preflight.row-retried",
|
|
240
|
+
"preflight.attestation",
|
|
241
|
+
"preflight.device-picked",
|
|
242
|
+
/**
|
|
243
|
+
* Initial enumeration of available media devices for a row's
|
|
244
|
+
* picker. Fires once per row (microphone / speaker / camera)
|
|
245
|
+
* after permission resolves so device labels are populated.
|
|
246
|
+
* Payload: { row, devices: [{ deviceId, label, groupId, virtual }] }
|
|
247
|
+
*/
|
|
248
|
+
"preflight.devices-enumerated",
|
|
249
|
+
/**
|
|
250
|
+
* Fired in response to the browser's `navigator.mediaDevices
|
|
251
|
+
* .devicechange` event during preflight. The device list
|
|
252
|
+
* changed (plug/unplug) -- carries the new snapshot. Distinct
|
|
253
|
+
* from devices-enumerated so the dashboard timeline can mark
|
|
254
|
+
* "candidate plugged in headphones mid-flow."
|
|
255
|
+
* Payload: { row?, devices: [{ deviceId, label, groupId, virtual }] }
|
|
256
|
+
*/
|
|
257
|
+
"preflight.devices-changed",
|
|
258
|
+
"preflight.face-detected",
|
|
259
|
+
"preflight.camera-photo",
|
|
260
|
+
"preflight.completed",
|
|
261
|
+
/**
|
|
262
|
+
* Proctored writing-answer events, emitted by
|
|
263
|
+
* @a4anthony/proctorkit-vue's <ProctoredTextarea> via
|
|
264
|
+
* client.recordTextAnswer(). They capture HOW a written answer was
|
|
265
|
+
* produced (the integrity signals) — the answer TEXT itself is owned
|
|
266
|
+
* by the integrator (saved through their own backend via the
|
|
267
|
+
* component's saveAnswer hook); the proctoring server stores only
|
|
268
|
+
* these signals plus, when persistAnswer is on, content checkpoints.
|
|
269
|
+
*
|
|
270
|
+
* Every payload carries `questionId` so multiple writing questions in
|
|
271
|
+
* one session stay separable. The server materialises them into a
|
|
272
|
+
* per-answer outcome row (paste ratio, active typing time, synthetic-
|
|
273
|
+
* input flag, checkpoint history).
|
|
274
|
+
*
|
|
275
|
+
* - text.started — the candidate began (or resumed) an answer.
|
|
276
|
+
* - text.paste — a paste into the field. Payload: { questionId,
|
|
277
|
+
* length, pastedRatio, blocked, content? }. `content` only when
|
|
278
|
+
* the host opted into capturePasteContent (PII-gated).
|
|
279
|
+
* - text.copy / text.cut — the candidate copied or cut from the
|
|
280
|
+
* field. Payload: { questionId, length, blocked? } where `length`
|
|
281
|
+
* is the selection size. `blocked` when the host enforced
|
|
282
|
+
* blockCopy / blockCut (the attempt is still recorded).
|
|
283
|
+
* - text.keystroke-summary — a sampled window of typing dynamics.
|
|
284
|
+
* Payload: { questionId, windowMs, keystrokes, backspaces,
|
|
285
|
+
* charsAdded }. Never the keys themselves.
|
|
286
|
+
* - text.checkpoint — a periodic snapshot of the answer so a
|
|
287
|
+
* reviewer can replay how it grew, and so a refresh can rehydrate
|
|
288
|
+
* from the server. Payload: { questionId, savedAt, length,
|
|
289
|
+
* content? } (content only when persistAnswer is on).
|
|
290
|
+
* - text.focus-lost / text.focus-regained — the candidate left /
|
|
291
|
+
* returned to the field mid-answer (distinct from window-level
|
|
292
|
+
* focus). Payload carries { questionId, durationMs? }.
|
|
293
|
+
* - text.synthetic-input — the answer changed without a
|
|
294
|
+
* corresponding keystroke, an untrusted (isTrusted:false) input
|
|
295
|
+
* event fired, or a writing-assistant footprint (e.g. Grammarly's
|
|
296
|
+
* injected DOM) was seen. Records the footprint for review; it is
|
|
297
|
+
* never blocked and is not a guarantee. Payload: { questionId,
|
|
298
|
+
* source: "grammarly" | "no-keystroke" | "untrusted-event" }.
|
|
299
|
+
* - text.saved / text.save-failed — the host's saveAnswer resolved /
|
|
300
|
+
* rejected. save-failed carries a stable { code, reason }.
|
|
301
|
+
* - text.submitted — the candidate submitted. Payload: { questionId,
|
|
302
|
+
* length, pasteCount, typingMs, syntheticDetected }.
|
|
303
|
+
*/
|
|
304
|
+
"text.started",
|
|
305
|
+
"text.paste",
|
|
306
|
+
"text.copy",
|
|
307
|
+
"text.cut",
|
|
308
|
+
"text.keystroke-summary",
|
|
309
|
+
"text.checkpoint",
|
|
310
|
+
"text.focus-lost",
|
|
311
|
+
"text.focus-regained",
|
|
312
|
+
"text.synthetic-input",
|
|
313
|
+
"text.saved",
|
|
314
|
+
"text.save-failed",
|
|
315
|
+
"text.submitted",
|
|
316
|
+
];
|
|
317
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type EnvLike = Record<string, string | undefined>;
|
|
2
|
+
export declare const PRODUCTION_DESTRUCTIVE_ACTIONS_OVERRIDE = "ALLOW_PRODUCTION_DESTRUCTIVE_ACTIONS";
|
|
3
|
+
export declare const PRODUCTION_PUBLIC_SESSION_RESET_FLAG = "ALLOW_PRODUCTION_PUBLIC_SESSION_RESET";
|
|
4
|
+
export declare const PRODUCTION_PUBLIC_SESSION_DELETE_FLAG = "ALLOW_PRODUCTION_PUBLIC_SESSION_DELETE";
|
|
5
|
+
export declare const PRODUCTION_DASHBOARD_SESSION_DELETE_FLAG = "ALLOW_PRODUCTION_DASHBOARD_SESSION_DELETE";
|
|
6
|
+
export declare const PRODUCTION_ADMIN_DESTRUCTIVE_ACTIONS_FLAG = "ALLOW_PRODUCTION_ADMIN_DESTRUCTIVE_ACTIONS";
|
|
7
|
+
export declare const PRODUCTION_DEMO_SESSION_CLEANUP_FLAG = "ALLOW_PRODUCTION_DEMO_SESSION_CLEANUP";
|
|
8
|
+
export declare const LOCAL_PREVIEW_DATABASE_FLAG = "ALLOW_LOCAL_PREVIEW_DATABASE";
|
|
9
|
+
export declare function isProductionRuntime(env?: EnvLike): boolean;
|
|
10
|
+
export declare function envFlagEnabled(value: string | undefined): boolean;
|
|
11
|
+
export declare function productionActionAllowed(env: EnvLike, featureFlag: string): boolean;
|
|
12
|
+
export declare function productionActionBlockReason(env: EnvLike, action: string, featureFlag: string): string | null;
|
|
13
|
+
export declare function productionDatabaseSafetyIssues(env: EnvLike): string[];
|
|
14
|
+
export declare function assertProductionDatabaseSafety(env: EnvLike): void;
|
|
15
|
+
//# sourceMappingURL=production-safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-safety.d.ts","sourceRoot":"","sources":["../src/production-safety.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEzD,eAAO,MAAM,uCAAuC,yCACZ,CAAC;AACzC,eAAO,MAAM,oCAAoC,0CACR,CAAC;AAC1C,eAAO,MAAM,qCAAqC,2CACR,CAAC;AAC3C,eAAO,MAAM,wCAAwC,8CACR,CAAC;AAC9C,eAAO,MAAM,yCAAyC,+CACR,CAAC;AAC/C,eAAO,MAAM,oCAAoC,0CACR,CAAC;AAC1C,eAAO,MAAM,2BAA2B,iCAAiC,CAAC;AAE1E,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,OAAY,GAAG,OAAO,CAE9D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGjE;AAED,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAMT;AAED,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAOf;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAkDrE;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAIjE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export const PRODUCTION_DESTRUCTIVE_ACTIONS_OVERRIDE = "ALLOW_PRODUCTION_DESTRUCTIVE_ACTIONS";
|
|
2
|
+
export const PRODUCTION_PUBLIC_SESSION_RESET_FLAG = "ALLOW_PRODUCTION_PUBLIC_SESSION_RESET";
|
|
3
|
+
export const PRODUCTION_PUBLIC_SESSION_DELETE_FLAG = "ALLOW_PRODUCTION_PUBLIC_SESSION_DELETE";
|
|
4
|
+
export const PRODUCTION_DASHBOARD_SESSION_DELETE_FLAG = "ALLOW_PRODUCTION_DASHBOARD_SESSION_DELETE";
|
|
5
|
+
export const PRODUCTION_ADMIN_DESTRUCTIVE_ACTIONS_FLAG = "ALLOW_PRODUCTION_ADMIN_DESTRUCTIVE_ACTIONS";
|
|
6
|
+
export const PRODUCTION_DEMO_SESSION_CLEANUP_FLAG = "ALLOW_PRODUCTION_DEMO_SESSION_CLEANUP";
|
|
7
|
+
export const LOCAL_PREVIEW_DATABASE_FLAG = "ALLOW_LOCAL_PREVIEW_DATABASE";
|
|
8
|
+
export function isProductionRuntime(env = {}) {
|
|
9
|
+
return env["NODE_ENV"] === "production" || env["APP_ENV"] === "production";
|
|
10
|
+
}
|
|
11
|
+
export function envFlagEnabled(value) {
|
|
12
|
+
const v = value?.trim().toLowerCase();
|
|
13
|
+
return v === "true" || v === "1" || v === "yes" || v === "on";
|
|
14
|
+
}
|
|
15
|
+
export function productionActionAllowed(env, featureFlag) {
|
|
16
|
+
if (!isProductionRuntime(env))
|
|
17
|
+
return true;
|
|
18
|
+
return (envFlagEnabled(env[featureFlag]) ||
|
|
19
|
+
envFlagEnabled(env[PRODUCTION_DESTRUCTIVE_ACTIONS_OVERRIDE]));
|
|
20
|
+
}
|
|
21
|
+
export function productionActionBlockReason(env, action, featureFlag) {
|
|
22
|
+
if (productionActionAllowed(env, featureFlag))
|
|
23
|
+
return null;
|
|
24
|
+
return (`${action} is disabled in production. Set ${featureFlag}=true for this ` +
|
|
25
|
+
`specific path, or ${PRODUCTION_DESTRUCTIVE_ACTIONS_OVERRIDE}=true for a ` +
|
|
26
|
+
"short break-glass window.");
|
|
27
|
+
}
|
|
28
|
+
export function productionDatabaseSafetyIssues(env) {
|
|
29
|
+
if (!isProductionRuntime(env))
|
|
30
|
+
return [];
|
|
31
|
+
const raw = env["DATABASE_URL"];
|
|
32
|
+
if (!raw)
|
|
33
|
+
return ["DATABASE_URL must be set in production"];
|
|
34
|
+
const parsed = parseDatabaseUrl(raw);
|
|
35
|
+
if (!parsed) {
|
|
36
|
+
return ["DATABASE_URL must be a parseable PostgreSQL connection URL"];
|
|
37
|
+
}
|
|
38
|
+
const issues = [];
|
|
39
|
+
const host = parsed.hostname.toLowerCase();
|
|
40
|
+
const allowLocalPreviewDatabase = env["APP_ENV"] !== "production" &&
|
|
41
|
+
envFlagEnabled(env[LOCAL_PREVIEW_DATABASE_FLAG]);
|
|
42
|
+
if (!allowLocalPreviewDatabase &&
|
|
43
|
+
(host === "localhost" ||
|
|
44
|
+
host === "127.0.0.1" ||
|
|
45
|
+
host === "0.0.0.0" ||
|
|
46
|
+
host === "::1" ||
|
|
47
|
+
host === "[::1]")) {
|
|
48
|
+
issues.push("DATABASE_URL must not point at localhost in production");
|
|
49
|
+
}
|
|
50
|
+
if (parsed.database.endsWith("_test")) {
|
|
51
|
+
issues.push("DATABASE_URL must not point at a *_test database in production");
|
|
52
|
+
}
|
|
53
|
+
if (!allowLocalPreviewDatabase &&
|
|
54
|
+
parsed.username === "proctorkit" &&
|
|
55
|
+
parsed.password === "proctorkit") {
|
|
56
|
+
issues.push("DATABASE_URL must not use the default local credentials");
|
|
57
|
+
}
|
|
58
|
+
if (!allowLocalPreviewDatabase && envFlagEnabled(env["REQUIRE_DATABASE_SSL"])) {
|
|
59
|
+
const sslMode = parsed.searchParams["sslmode"]?.toLowerCase();
|
|
60
|
+
if (!sslMode || sslMode === "disable") {
|
|
61
|
+
issues.push("DATABASE_URL must include sslmode=require or stronger when REQUIRE_DATABASE_SSL=true");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return issues;
|
|
65
|
+
}
|
|
66
|
+
export function assertProductionDatabaseSafety(env) {
|
|
67
|
+
const issues = productionDatabaseSafetyIssues(env);
|
|
68
|
+
if (issues.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
throw new Error(`Unsafe production database configuration:\n- ${issues.join("\n- ")}`);
|
|
71
|
+
}
|
|
72
|
+
function parseDatabaseUrl(raw) {
|
|
73
|
+
const match = raw.match(/^[a-z][a-z0-9+.-]*:\/\/(?:(?<auth>[^@/?#]*)@)?(?<host>\[[^\]]+\]|[^/:?#]+)(?::\d+)?\/(?<path>[^?#]*)(?:\?(?<query>[^#]*))?/i);
|
|
74
|
+
if (!match?.groups?.["host"] || !match.groups["path"])
|
|
75
|
+
return null;
|
|
76
|
+
const auth = match.groups["auth"] ?? "";
|
|
77
|
+
const authParts = auth.split(":");
|
|
78
|
+
const username = decodeUrlPart(authParts[0] ?? "");
|
|
79
|
+
const password = decodeUrlPart(authParts.slice(1).join(":"));
|
|
80
|
+
const database = decodeUrlPart(match.groups["path"].replace(/^\/+/, ""));
|
|
81
|
+
return {
|
|
82
|
+
hostname: match.groups["host"],
|
|
83
|
+
username,
|
|
84
|
+
password,
|
|
85
|
+
database,
|
|
86
|
+
searchParams: parseQuery(match.groups["query"] ?? ""),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function parseQuery(raw) {
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const part of raw.split("&")) {
|
|
92
|
+
if (!part)
|
|
93
|
+
continue;
|
|
94
|
+
const eq = part.indexOf("=");
|
|
95
|
+
const key = decodeUrlPart(eq === -1 ? part : part.slice(0, eq));
|
|
96
|
+
const value = decodeUrlPart(eq === -1 ? "" : part.slice(eq + 1));
|
|
97
|
+
if (key)
|
|
98
|
+
out[key] = value;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function decodeUrlPart(value) {
|
|
103
|
+
try {
|
|
104
|
+
return decodeURIComponent(value);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=production-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-safety.js","sourceRoot":"","sources":["../src/production-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SessionStatus = "pending" | "active" | "ended" | "abandoned";
|
|
2
|
+
export interface Session {
|
|
3
|
+
id: string;
|
|
4
|
+
candidateId: string;
|
|
5
|
+
orgId: string;
|
|
6
|
+
status: SessionStatus;
|
|
7
|
+
startedAt: number | null;
|
|
8
|
+
endedAt: number | null;
|
|
9
|
+
metadata?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AAEzE,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":""}
|