@a4anthony/proctorkit-types 0.0.0-canary.485d0f63.20260716091407

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 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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":""}
@@ -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", "session.integrity-hold", "session.integrity-cleared", "sdk.initialized", "sdk.delivery.ready", "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", "session.policy", "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", "face.identity-mismatch", "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.microphone-verification", "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,26GA0Ud,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,344 @@
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
+ /**
53
+ * Mid-session integrity hold. Emitted when a runtime checkpoint fails
54
+ * mid-exam (triggered by a device disconnect, tab-return, or heartbeat)
55
+ * and the SDK raises its recovery gate — the candidate is blocked from
56
+ * the exam until the environment is fixed. `session.integrity-hold`
57
+ * opens the hold; `session.integrity-cleared` closes it once the
58
+ * re-check passes. They are always paired and correlated by `holdId`,
59
+ * so a proctor (and the host's exam timer, which pauses on hold and
60
+ * resumes on clear) can reconcile the interruption window.
61
+ *
62
+ * - integrity-hold payload: { holdId, reason, changes: CheckpointChange[] }
63
+ * - integrity-cleared payload: { holdId, resolvedMs }
64
+ *
65
+ * Distinct from `session.checkpoint` (which emits on EVERY run, pass or
66
+ * fail, as a raw audit record): the hold pair marks only the drifts that
67
+ * actually gated the candidate, and brackets the recovery window.
68
+ */
69
+ "session.integrity-hold",
70
+ "session.integrity-cleared",
71
+ "sdk.initialized",
72
+ "sdk.delivery.ready",
73
+ "sdk.ready",
74
+ "sdk.error",
75
+ "sdk.upload.failed",
76
+ "sdk.media.failed",
77
+ "sdk.permission.denied",
78
+ "sdk.worker.failed",
79
+ "sdk.storage.fallback",
80
+ "sdk.recording.buffer-degraded",
81
+ "sdk.recording.buffer-evicted",
82
+ "sdk.stop.drain-timeout",
83
+ "focus.lost",
84
+ "focus.gained",
85
+ "tab.hidden",
86
+ "tab.visible",
87
+ /**
88
+ * Emitted once when the proctored runtime page loads — on first start
89
+ * and again on every resume (the SDK re-boots). Carries only the customer
90
+ * application origin and whether the page was backgrounded at load. Full
91
+ * URLs, paths, queries, fragments, referrers, and document titles are never
92
+ * included. This is distinct from the presence signals (`tab.*` / `focus.*`)
93
+ * that track the candidate leaving the assessment.
94
+ */
95
+ "page.viewed",
96
+ "fullscreen.entered",
97
+ "fullscreen.exited",
98
+ "clipboard.copy",
99
+ "clipboard.paste",
100
+ "clipboard.cut",
101
+ "contextmenu.opened",
102
+ "keyboard.blocked",
103
+ "screenshot.attempted",
104
+ "screen-share.started",
105
+ "screen-share.stopped",
106
+ "screen-share.declined",
107
+ "screen-share.wrong-surface",
108
+ "screen-share.recording.started",
109
+ "screen-share.recording.stopped",
110
+ "screen-share.recording.unavailable",
111
+ "screen-share.chunk-uploaded",
112
+ "screen-share.chunk-dropped",
113
+ "webcam.started",
114
+ "webcam.stopped",
115
+ "webcam.declined",
116
+ "webcam.unavailable",
117
+ "webcam.photo.captured",
118
+ "webcam.photo.dropped",
119
+ "webcam.recording.started",
120
+ "webcam.recording.stopped",
121
+ "webcam.recording.chunk-uploaded",
122
+ "webcam.recording.chunk-dropped",
123
+ "video-clip.started",
124
+ "video-clip.stopped",
125
+ "video-clip.uploaded",
126
+ "video-clip.dropped",
127
+ "audio-clip.started",
128
+ "audio-clip.stopped",
129
+ "audio-clip.uploaded",
130
+ "audio-clip.dropped",
131
+ "audio-playback.started",
132
+ "audio-playback.paused",
133
+ "audio-playback.stopped",
134
+ "audio-playback.ended",
135
+ "audio-playback.error",
136
+ "audio-playback.sink-applied",
137
+ "audio-playback.sink-unsupported",
138
+ "audio-playback.sink-failed",
139
+ "audio-playback.sink-disconnected",
140
+ "video-playback.started",
141
+ "video-playback.paused",
142
+ "video-playback.stopped",
143
+ "video-playback.ended",
144
+ "video-playback.error",
145
+ "video-playback.sink-applied",
146
+ "video-playback.sink-unsupported",
147
+ "video-playback.sink-failed",
148
+ "video-playback.sink-disconnected",
149
+ "pointer.left-window",
150
+ "pointer.returned",
151
+ "idle.started",
152
+ "idle.ended",
153
+ "session.fingerprint",
154
+ "session.policy",
155
+ "face.detector-ready",
156
+ "face.detector-failed",
157
+ "face.detector-degraded",
158
+ "face.lost",
159
+ "face.returned",
160
+ "face.multiple",
161
+ "face.multiple-cleared",
162
+ "face.gaze-off-screen",
163
+ "face.gaze-restored",
164
+ "face.identity-mismatch",
165
+ /**
166
+ * Post-hoc screen-share analysis events. Emitted by the
167
+ * packages/screen-detect Python sidecar after the stitched
168
+ * screen-share recording is assembled at session-end. Source
169
+ * column is set to "analyzer" and the analysisRunId points
170
+ * at the ScreenAnalysisRun row that produced this event,
171
+ * mirroring the face-analysis pattern.
172
+ *
173
+ * Why we need them: client-side focus.lost only fires when
174
+ * the candidate switches AWAY from the exam tab; it misses
175
+ * window-level switches that stay in-browser (a popover
176
+ * ChatGPT in another tab of the same Chrome instance, an
177
+ * OS-level overlay like Copilot, a second monitor in shared
178
+ * view, etc.). The screen recording itself is the only
179
+ * signal here — these events surface visual context changes
180
+ * that the live client never saw.
181
+ */
182
+ "screen.window-switched",
183
+ "screen.scene-stable",
184
+ "screen.recording-paused",
185
+ /**
186
+ * Object-detection signals emitted by the post-hoc YOLO11
187
+ * pass that runs alongside face analysis on the stitched
188
+ * webcam recording. The YOLO model recognises 80 COCO
189
+ * classes; we surface only the subset that maps to
190
+ * "policy violation in a proctored exam":
191
+ *
192
+ * - phone-detected — a cell phone visible in frame
193
+ * - book-detected — a printed book / notebook visible
194
+ * - second-device-detected — a laptop other than the
195
+ * candidate's primary visible in frame
196
+ * - second-screen-detected — a TV / monitor other than
197
+ * the candidate's primary visible in frame
198
+ *
199
+ * The matching `*-cleared` event fires when the object
200
+ * stops being detected for a debounce window. Bounding box
201
+ * + confidence land in the payload.
202
+ *
203
+ * Sourced from the same FaceAnalysisRun as the face events
204
+ * (we run them in one pass over the stitched video) so
205
+ * `analysisRunId` is shared.
206
+ */
207
+ "webcam.phone-detected",
208
+ "webcam.phone-cleared",
209
+ "webcam.book-detected",
210
+ "webcam.book-cleared",
211
+ "webcam.second-device-detected",
212
+ "webcam.second-device-cleared",
213
+ "webcam.second-screen-detected",
214
+ "webcam.second-screen-cleared",
215
+ /**
216
+ * Random-photo analysis events. The SDK uploads JPEG snapshots
217
+ * every 20-60 seconds during a proctored session; the photo-
218
+ * analysis worker runs the same detectors against each photo
219
+ * AFTER session-end as a backstop to the stitched-video pass.
220
+ *
221
+ * Why a separate set of kinds (`-at-snapshot`) rather than
222
+ * reusing `face.lost` / `webcam.phone-detected`:
223
+ *
224
+ * - The cert needs to prefer the photo's actual JPEG over
225
+ * a video-frame extraction as the evidence thumbnail —
226
+ * photos are 1080p singles, video frames are transcoded
227
+ * and compressed.
228
+ * - Reviewers reading the timeline benefit from knowing
229
+ * which signal came from continuous video analysis vs.
230
+ * intermittent snapshot sampling.
231
+ * - Source field is "photo-analyzer" (vs "analyzer" for
232
+ * the video pass) so audit queries can separate them.
233
+ *
234
+ * Payload always carries `photoId` (BigInt as string) so the
235
+ * dashboard can join back to webcam_photos and display the
236
+ * actual JPEG inline.
237
+ */
238
+ "face.lost-at-snapshot",
239
+ "face.multiple-at-snapshot",
240
+ "face.gaze-off-screen-at-snapshot",
241
+ "face.identity-mismatch-at-snapshot",
242
+ "webcam.phone-detected-at-snapshot",
243
+ "webcam.book-detected-at-snapshot",
244
+ "webcam.second-device-detected-at-snapshot",
245
+ "webcam.second-screen-detected-at-snapshot",
246
+ "media.frame.captured",
247
+ "media.audio.chunk",
248
+ "network.online",
249
+ "network.offline",
250
+ // Preflight wizard lifecycle. Emitted by @a4anthony/proctorkit-vue's
251
+ // preflight telemetry composable. The server materialises these into
252
+ // the preflight_outcomes row keyed off the candidate's sessionId
253
+ // (the same opaque id the customer's host page assigns; correlationId
254
+ // on the Vue API surface, sessionId on the wire).
255
+ "preflight.started",
256
+ "preflight.row-checking",
257
+ "preflight.row-passed",
258
+ "preflight.row-failed",
259
+ "preflight.row-retried",
260
+ "preflight.attestation",
261
+ "preflight.device-picked",
262
+ /**
263
+ * Initial enumeration of available media devices for a row's
264
+ * picker. Fires once per row (microphone / speaker / camera)
265
+ * after permission resolves so device labels are populated.
266
+ * Payload: { row, devices: [{ deviceId, label, groupId, virtual }] }
267
+ */
268
+ "preflight.devices-enumerated",
269
+ /**
270
+ * Fired in response to the browser's `navigator.mediaDevices
271
+ * .devicechange` event during preflight. The device list
272
+ * changed (plug/unplug) -- carries the new snapshot. Distinct
273
+ * from devices-enumerated so the dashboard timeline can mark
274
+ * "candidate plugged in headphones mid-flow."
275
+ * Payload: { row?, devices: [{ deviceId, label, groupId, virtual }] }
276
+ */
277
+ "preflight.devices-changed",
278
+ /**
279
+ * Internal microphone-verification diagnostic. Records whether Silero VAD
280
+ * verified speech or the bounded RMS reliability fallback did, plus the VAD
281
+ * preparation duration and fallback reason. It is emitted by the wizard and
282
+ * is never accepted as policy or customer configuration.
283
+ */
284
+ "preflight.microphone-verification",
285
+ "preflight.face-detected",
286
+ "preflight.camera-photo",
287
+ "preflight.completed",
288
+ /**
289
+ * Proctored writing-answer events, emitted by
290
+ * @a4anthony/proctorkit-vue's <ProctoredTextarea> via
291
+ * client.recordTextAnswer(). They capture HOW a written answer was
292
+ * produced (the integrity signals) — the answer TEXT itself is owned
293
+ * by the integrator (saved through their own backend via the
294
+ * component's saveAnswer hook); the proctoring server stores only
295
+ * these signals plus, when persistAnswer is on, content checkpoints.
296
+ *
297
+ * Every payload carries `questionId` so multiple writing questions in
298
+ * one session stay separable. The server materialises them into a
299
+ * per-answer outcome row (paste ratio, active typing time, synthetic-
300
+ * input flag, checkpoint history).
301
+ *
302
+ * - text.started — the candidate began (or resumed) an answer.
303
+ * - text.paste — a paste into the field. Payload: { questionId,
304
+ * length, pastedRatio, blocked, content? }. `content` only when
305
+ * the host opted into capturePasteContent (PII-gated).
306
+ * - text.copy / text.cut — the candidate copied or cut from the
307
+ * field. Payload: { questionId, length, blocked? } where `length`
308
+ * is the selection size. `blocked` when the host enforced
309
+ * blockCopy / blockCut (the attempt is still recorded).
310
+ * - text.keystroke-summary — a sampled window of typing dynamics.
311
+ * Payload: { questionId, windowMs, keystrokes, backspaces,
312
+ * charsAdded }. Never the keys themselves.
313
+ * - text.checkpoint — a periodic snapshot of the answer so a
314
+ * reviewer can replay how it grew, and so a refresh can rehydrate
315
+ * from the server. Payload: { questionId, savedAt, length,
316
+ * content? } (content only when persistAnswer is on).
317
+ * - text.focus-lost / text.focus-regained — the candidate left /
318
+ * returned to the field mid-answer (distinct from window-level
319
+ * focus). Payload carries { questionId, durationMs? }.
320
+ * - text.synthetic-input — the answer changed without a
321
+ * corresponding keystroke, an untrusted (isTrusted:false) input
322
+ * event fired, or a writing-assistant footprint (e.g. Grammarly's
323
+ * injected DOM) was seen. Records the footprint for review; it is
324
+ * never blocked and is not a guarantee. Payload: { questionId,
325
+ * source: "grammarly" | "no-keystroke" | "untrusted-event" }.
326
+ * - text.saved / text.save-failed — the host's saveAnswer resolved /
327
+ * rejected. save-failed carries a stable { code, reason }.
328
+ * - text.submitted — the candidate submitted. Payload: { questionId,
329
+ * length, pasteCount, typingMs, syntheticDetected }.
330
+ */
331
+ "text.started",
332
+ "text.paste",
333
+ "text.copy",
334
+ "text.cut",
335
+ "text.keystroke-summary",
336
+ "text.checkpoint",
337
+ "text.focus-lost",
338
+ "text.focus-regained",
339
+ "text.synthetic-input",
340
+ "text.saved",
341
+ "text.save-failed",
342
+ "text.submitted",
343
+ ];
344
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from "./events.js";
2
+ export * from "./session.js";
3
+ export * from "./api.js";
4
+ export * from "./worker-protocol.js";
5
+ export * from "./production-safety.js";
6
+ export * from "./status.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -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,7 @@
1
+ export * from "./events.js";
2
+ export * from "./session.js";
3
+ export * from "./api.js";
4
+ export * from "./worker-protocol.js";
5
+ export * from "./production-safety.js";
6
+ export * from "./status.js";
7
+ //# sourceMappingURL=index.js.map
@@ -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":"AAEA,MAAM,CAAC,MAAM,uCAAuC,GAClD,sCAAsC,CAAC;AACzC,MAAM,CAAC,MAAM,oCAAoC,GAC/C,uCAAuC,CAAC;AAC1C,MAAM,CAAC,MAAM,qCAAqC,GAChD,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,wCAAwC,GACnD,2CAA2C,CAAC;AAC9C,MAAM,CAAC,MAAM,yCAAyC,GACpD,4CAA4C,CAAC;AAC/C,MAAM,CAAC,MAAM,oCAAoC,GAC/C,uCAAuC,CAAC;AAC1C,MAAM,CAAC,MAAM,2BAA2B,GAAG,8BAA8B,CAAC;AAE1E,MAAM,UAAU,mBAAmB,CAAC,MAAe,EAAE;IACnD,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,YAAY,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAyB;IACtD,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,GAAY,EACZ,WAAmB;IAEnB,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,CACL,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAChC,cAAc,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,GAAY,EACZ,MAAc,EACd,WAAmB;IAEnB,IAAI,uBAAuB,CAAC,GAAG,EAAE,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,OAAO,CACL,GAAG,MAAM,mCAAmC,WAAW,iBAAiB;QACxE,qBAAqB,uCAAuC,cAAc;QAC1E,2BAA2B,CAC5B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,GAAY;IACzD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzC,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,wCAAwC,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,4DAA4D,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,yBAAyB,GAC7B,GAAG,CAAC,SAAS,CAAC,KAAK,YAAY;QAC/B,cAAc,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,CAAC;IAEnD,IACE,CAAC,yBAAyB;QAC1B,CAAC,IAAI,KAAK,WAAW;YACnB,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,SAAS;YAClB,IAAI,KAAK,KAAK;YACd,IAAI,KAAK,OAAO,CAAC,EACnB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;IAChF,CAAC;IAED,IACE,CAAC,yBAAyB;QAC1B,MAAM,CAAC,QAAQ,KAAK,YAAY;QAChC,MAAM,CAAC,QAAQ,KAAK,YAAY,EAChC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,yBAAyB,IAAI,cAAc,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;QAC9D,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CACT,sFAAsF,CACvF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,GAAY;IACzD,MAAM,MAAM,GAAG,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAChC,MAAM,IAAI,KAAK,CAAC,gDAAgD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACzF,CAAC;AAUD,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CACrB,6HAA6H,CAC9H,CAAC;IACF,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAEzE,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;QAC9B,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,YAAY,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,GAAG;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":""}