devn-bot-detector-core 0.1.0
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/README.md +20 -0
- package/dist/index.cjs +522 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +312 -0
- package/dist/index.d.ts +312 -0
- package/dist/index.js +497 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 devn
|
|
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/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# devn-bot-detector-core
|
|
2
|
+
|
|
3
|
+
Shared types, constants, validation, and protocol helpers.
|
|
4
|
+
|
|
5
|
+
**No browser APIs. No Node APIs.** Safe to import from both client and server packages.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add devn-bot-detector-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- Types: `Telemetry`, `RiskResult`, `SessionData`, `ClientConfig`, `ServerConfig`, …
|
|
16
|
+
- Constants: thresholds, API paths, error codes, payload limits
|
|
17
|
+
- Validation: `validateTelemetry`, `validateAnalyzeRequest`, `validateCreateSessionRequest`
|
|
18
|
+
- Helpers: `decisionFromScore`, `hostnameAllowed`, `BotDetectorError`
|
|
19
|
+
|
|
20
|
+
This package never collects data or scores risk — it only defines the contract.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
var DEFAULT_SESSION_DURATION_MS = 30 * 60 * 1e3;
|
|
5
|
+
var DEFAULT_TELEMETRY_INTERVAL_MS = 5e3;
|
|
6
|
+
var MAX_TELEMETRY_BYTES = 32 * 1024;
|
|
7
|
+
var DEFAULT_RISK_TOKEN_DURATION_MS = 5 * 60 * 1e3;
|
|
8
|
+
var TELEMETRY_VERSION = 1;
|
|
9
|
+
var SITE_KEY_HEADER = "X-Bot-Site-Key";
|
|
10
|
+
var API_PATHS = {
|
|
11
|
+
session: "/v1/session",
|
|
12
|
+
analyze: "/v1/analyze"
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_RISK_THRESHOLDS = {
|
|
15
|
+
allow: 70,
|
|
16
|
+
monitor: 50,
|
|
17
|
+
challenge: 30
|
|
18
|
+
};
|
|
19
|
+
var ERROR_CODES = [
|
|
20
|
+
"INVALID_SITE_KEY",
|
|
21
|
+
"INVALID_HOSTNAME",
|
|
22
|
+
"INVALID_SESSION",
|
|
23
|
+
"SESSION_EXPIRED",
|
|
24
|
+
"INVALID_NONCE",
|
|
25
|
+
"INVALID_SEQUENCE",
|
|
26
|
+
"RATE_LIMITED",
|
|
27
|
+
"INVALID_TELEMETRY",
|
|
28
|
+
"MALFORMED_REQUEST",
|
|
29
|
+
"INTERNAL_ERROR",
|
|
30
|
+
"IP_BLOCKED",
|
|
31
|
+
"TOKEN_ALREADY_USED",
|
|
32
|
+
"ACTION_MISMATCH"
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// src/errors.ts
|
|
36
|
+
var BotDetectorError = class extends Error {
|
|
37
|
+
constructor(code, message, requestId) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "BotDetectorError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.requestId = requestId;
|
|
42
|
+
}
|
|
43
|
+
toJSON() {
|
|
44
|
+
return {
|
|
45
|
+
error: {
|
|
46
|
+
code: this.code,
|
|
47
|
+
message: this.message,
|
|
48
|
+
requestId: this.requestId
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function isBotDetectorError(value) {
|
|
54
|
+
return value instanceof BotDetectorError;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/utils.ts
|
|
58
|
+
function clamp(value, min, max) {
|
|
59
|
+
return Math.min(max, Math.max(min, value));
|
|
60
|
+
}
|
|
61
|
+
function decisionFromScore(score, thresholds = DEFAULT_RISK_THRESHOLDS) {
|
|
62
|
+
if (score >= thresholds.allow) return "allow";
|
|
63
|
+
if (score >= thresholds.monitor) return "monitor";
|
|
64
|
+
if (score >= thresholds.challenge) return "challenge";
|
|
65
|
+
return "block";
|
|
66
|
+
}
|
|
67
|
+
function riskLevelFromScore(score) {
|
|
68
|
+
if (score >= 70) return "low";
|
|
69
|
+
if (score >= 40) return "medium";
|
|
70
|
+
return "high";
|
|
71
|
+
}
|
|
72
|
+
function mergeRiskThresholds(partial) {
|
|
73
|
+
return {
|
|
74
|
+
allow: partial?.allow ?? DEFAULT_RISK_THRESHOLDS.allow,
|
|
75
|
+
monitor: partial?.monitor ?? DEFAULT_RISK_THRESHOLDS.monitor,
|
|
76
|
+
challenge: partial?.challenge ?? DEFAULT_RISK_THRESHOLDS.challenge
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isPlainObject(value) {
|
|
80
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81
|
+
}
|
|
82
|
+
function utf8ByteLength(value) {
|
|
83
|
+
if (typeof TextEncoder !== "undefined") {
|
|
84
|
+
return new TextEncoder().encode(value).length;
|
|
85
|
+
}
|
|
86
|
+
let bytes = 0;
|
|
87
|
+
for (let i = 0; i < value.length; i++) {
|
|
88
|
+
const code = value.charCodeAt(i);
|
|
89
|
+
if (code < 128) bytes += 1;
|
|
90
|
+
else if (code < 2048) bytes += 2;
|
|
91
|
+
else if (code >= 55296 && code <= 56319) {
|
|
92
|
+
bytes += 4;
|
|
93
|
+
i++;
|
|
94
|
+
} else bytes += 3;
|
|
95
|
+
}
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
function normalizeHostname(hostname) {
|
|
99
|
+
return hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
100
|
+
}
|
|
101
|
+
function hostnameAllowed(hostname, allowedDomains) {
|
|
102
|
+
const host = normalizeHostname(hostname);
|
|
103
|
+
if (!host) return false;
|
|
104
|
+
for (const raw of allowedDomains) {
|
|
105
|
+
const allowed = normalizeHostname(raw);
|
|
106
|
+
if (!allowed) continue;
|
|
107
|
+
if (host === allowed) return true;
|
|
108
|
+
if (host.endsWith(`.${allowed}`)) return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function extractHostnameFromOrigin(origin) {
|
|
113
|
+
if (!origin) return null;
|
|
114
|
+
try {
|
|
115
|
+
const url = new URL(origin);
|
|
116
|
+
return normalizeHostname(url.hostname);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function ipMatches(ip, patterns) {
|
|
122
|
+
if (!ip || !patterns || patterns.length === 0) return false;
|
|
123
|
+
const cleanIp = ip.trim().toLowerCase();
|
|
124
|
+
for (const raw of patterns) {
|
|
125
|
+
const pattern = raw.trim().toLowerCase();
|
|
126
|
+
if (!pattern) continue;
|
|
127
|
+
if (pattern === cleanIp || pattern === "*") return true;
|
|
128
|
+
if (pattern.includes("*")) {
|
|
129
|
+
const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
|
|
130
|
+
if (regex.test(cleanIp)) return true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (pattern.includes("/")) {
|
|
134
|
+
const [range, prefixStr] = pattern.split("/");
|
|
135
|
+
const prefix = parseInt(prefixStr, 10);
|
|
136
|
+
if (isNaN(prefix) || prefix < 0 || prefix > 32) continue;
|
|
137
|
+
const ipParts = cleanIp.split(".").map(Number);
|
|
138
|
+
const rangeParts = range.split(".").map(Number);
|
|
139
|
+
if (ipParts.length === 4 && rangeParts.length === 4 && ipParts.every((n) => !isNaN(n) && n >= 0 && n <= 255) && rangeParts.every((n) => !isNaN(n) && n >= 0 && n <= 255)) {
|
|
140
|
+
const ipInt = (ipParts[0] << 24 | ipParts[1] << 16 | ipParts[2] << 8 | ipParts[3]) >>> 0;
|
|
141
|
+
const rangeInt = (rangeParts[0] << 24 | rangeParts[1] << 16 | rangeParts[2] << 8 | rangeParts[3]) >>> 0;
|
|
142
|
+
const mask = prefix === 0 ? 0 : -1 << 32 - prefix >>> 0;
|
|
143
|
+
if ((ipInt & mask) === (rangeInt & mask)) return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/validation.ts
|
|
151
|
+
function asFiniteNumber(value) {
|
|
152
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
153
|
+
}
|
|
154
|
+
function asOptionalFiniteNumber(value) {
|
|
155
|
+
if (value === void 0) return void 0;
|
|
156
|
+
if (value === null) return null;
|
|
157
|
+
return asFiniteNumber(value);
|
|
158
|
+
}
|
|
159
|
+
function asString(value) {
|
|
160
|
+
return typeof value === "string" ? value : null;
|
|
161
|
+
}
|
|
162
|
+
function asBoolean(value) {
|
|
163
|
+
return typeof value === "boolean" ? value : null;
|
|
164
|
+
}
|
|
165
|
+
function validateBrowser(raw) {
|
|
166
|
+
if (!isPlainObject(raw)) return { ok: false, message: "browser must be an object" };
|
|
167
|
+
const userAgent = asString(raw.userAgent);
|
|
168
|
+
const language = asString(raw.language);
|
|
169
|
+
const timezone = asString(raw.timezone);
|
|
170
|
+
const platform = asString(raw.platform);
|
|
171
|
+
const languagesLength = asFiniteNumber(raw.languagesLength);
|
|
172
|
+
const timezoneOffset = asFiniteNumber(raw.timezoneOffset);
|
|
173
|
+
const screenWidth = asFiniteNumber(raw.screenWidth);
|
|
174
|
+
const screenHeight = asFiniteNumber(raw.screenHeight);
|
|
175
|
+
const colorDepth = asFiniteNumber(raw.colorDepth);
|
|
176
|
+
const devicePixelRatio = asFiniteNumber(raw.devicePixelRatio);
|
|
177
|
+
const maxTouchPoints = asFiniteNumber(raw.maxTouchPoints);
|
|
178
|
+
const cookieEnabled = asBoolean(raw.cookieEnabled);
|
|
179
|
+
const webdriver = asBoolean(raw.webdriver);
|
|
180
|
+
const hardwareConcurrency = raw.hardwareConcurrency === null ? null : asFiniteNumber(raw.hardwareConcurrency);
|
|
181
|
+
const webglRenderer = raw.webglRenderer === null ? null : raw.webglRenderer !== void 0 ? asString(raw.webglRenderer) : void 0;
|
|
182
|
+
const webglVendor = raw.webglVendor === null ? null : raw.webglVendor !== void 0 ? asString(raw.webglVendor) : void 0;
|
|
183
|
+
const hasChrome = raw.hasChrome !== void 0 ? asBoolean(raw.hasChrome) ?? void 0 : void 0;
|
|
184
|
+
const pluginsCount = raw.pluginsCount !== void 0 ? asFiniteNumber(raw.pluginsCount) ?? void 0 : void 0;
|
|
185
|
+
const screenOrientation = raw.screenOrientation === null ? null : raw.screenOrientation !== void 0 ? asString(raw.screenOrientation) : void 0;
|
|
186
|
+
if (userAgent == null || language == null || timezone == null || platform == null || languagesLength == null || timezoneOffset == null || screenWidth == null || screenHeight == null || colorDepth == null || devicePixelRatio == null || maxTouchPoints == null || cookieEnabled == null || webdriver == null || hardwareConcurrency === void 0) {
|
|
187
|
+
return { ok: false, message: "browser signals incomplete" };
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
ok: true,
|
|
191
|
+
value: {
|
|
192
|
+
userAgent: userAgent.slice(0, 512),
|
|
193
|
+
language: language.slice(0, 32),
|
|
194
|
+
languagesLength,
|
|
195
|
+
timezone: timezone.slice(0, 64),
|
|
196
|
+
timezoneOffset,
|
|
197
|
+
platform: platform.slice(0, 64),
|
|
198
|
+
screenWidth,
|
|
199
|
+
screenHeight,
|
|
200
|
+
colorDepth,
|
|
201
|
+
devicePixelRatio,
|
|
202
|
+
hardwareConcurrency,
|
|
203
|
+
maxTouchPoints,
|
|
204
|
+
cookieEnabled,
|
|
205
|
+
webdriver,
|
|
206
|
+
...webglRenderer !== void 0 ? { webglRenderer: webglRenderer ? webglRenderer.slice(0, 128) : null } : {},
|
|
207
|
+
...webglVendor !== void 0 ? { webglVendor: webglVendor ? webglVendor.slice(0, 128) : null } : {},
|
|
208
|
+
...hasChrome !== void 0 ? { hasChrome } : {},
|
|
209
|
+
...pluginsCount !== void 0 ? { pluginsCount } : {},
|
|
210
|
+
...screenOrientation !== void 0 ? { screenOrientation: screenOrientation ? screenOrientation.slice(0, 32) : null } : {}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function validateIntervalStats(raw, label) {
|
|
215
|
+
const averageInterval = asOptionalFiniteNumber(raw.averageInterval);
|
|
216
|
+
const minInterval = asOptionalFiniteNumber(raw.minInterval);
|
|
217
|
+
const maxInterval = asOptionalFiniteNumber(raw.maxInterval);
|
|
218
|
+
if (averageInterval === void 0 || minInterval === void 0 || maxInterval === void 0) {
|
|
219
|
+
return { ok: false, message: `${label} intervals incomplete` };
|
|
220
|
+
}
|
|
221
|
+
return { ok: true, value: { averageInterval, minInterval, maxInterval } };
|
|
222
|
+
}
|
|
223
|
+
function validatePointer(raw) {
|
|
224
|
+
if (!isPlainObject(raw)) return { ok: false, message: "pointer must be an object" };
|
|
225
|
+
const eventCount = asFiniteNumber(raw.eventCount);
|
|
226
|
+
const movementDistance = asFiniteNumber(raw.movementDistance);
|
|
227
|
+
const directionChanges = asFiniteNumber(raw.directionChanges);
|
|
228
|
+
const idlePeriods = asFiniteNumber(raw.idlePeriods);
|
|
229
|
+
const activeDuration = asFiniteNumber(raw.activeDuration);
|
|
230
|
+
const clickCount = asFiniteNumber(raw.clickCount);
|
|
231
|
+
const intervals = validateIntervalStats(raw, "pointer");
|
|
232
|
+
if (!intervals.ok) return intervals;
|
|
233
|
+
if (eventCount == null || movementDistance == null || directionChanges == null || idlePeriods == null || activeDuration == null || clickCount == null) {
|
|
234
|
+
return { ok: false, message: "pointer signals incomplete" };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
value: {
|
|
239
|
+
eventCount,
|
|
240
|
+
movementDistance,
|
|
241
|
+
...intervals.value,
|
|
242
|
+
directionChanges,
|
|
243
|
+
idlePeriods,
|
|
244
|
+
activeDuration,
|
|
245
|
+
clickCount
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function validateKeyboard(raw) {
|
|
250
|
+
if (!isPlainObject(raw)) return { ok: false, message: "keyboard must be an object" };
|
|
251
|
+
const keydownCount = asFiniteNumber(raw.keydownCount);
|
|
252
|
+
const keyupCount = asFiniteNumber(raw.keyupCount);
|
|
253
|
+
const pasteCount = asFiniteNumber(raw.pasteCount);
|
|
254
|
+
const timingVariance = asOptionalFiniteNumber(raw.timingVariance);
|
|
255
|
+
const intervals = validateIntervalStats(raw, "keyboard");
|
|
256
|
+
if (!intervals.ok) return intervals;
|
|
257
|
+
if (keydownCount == null || keyupCount == null || pasteCount == null || timingVariance === void 0) {
|
|
258
|
+
return { ok: false, message: "keyboard signals incomplete" };
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
ok: true,
|
|
262
|
+
value: {
|
|
263
|
+
keydownCount,
|
|
264
|
+
keyupCount,
|
|
265
|
+
...intervals.value,
|
|
266
|
+
timingVariance,
|
|
267
|
+
pasteCount
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function validateTouch(raw) {
|
|
272
|
+
if (!isPlainObject(raw)) return { ok: false, message: "touch must be an object" };
|
|
273
|
+
const touchCount = asFiniteNumber(raw.touchCount);
|
|
274
|
+
const averageInterval = asOptionalFiniteNumber(raw.averageInterval);
|
|
275
|
+
const activeDuration = asFiniteNumber(raw.activeDuration);
|
|
276
|
+
const multiTouchCount = asFiniteNumber(raw.multiTouchCount);
|
|
277
|
+
if (touchCount == null || averageInterval === void 0 || activeDuration == null || multiTouchCount == null) {
|
|
278
|
+
return { ok: false, message: "touch signals incomplete" };
|
|
279
|
+
}
|
|
280
|
+
return { ok: true, value: { touchCount, averageInterval, activeDuration, multiTouchCount } };
|
|
281
|
+
}
|
|
282
|
+
function validateScroll(raw) {
|
|
283
|
+
if (!isPlainObject(raw)) return { ok: false, message: "scroll must be an object" };
|
|
284
|
+
const scrollCount = asFiniteNumber(raw.scrollCount);
|
|
285
|
+
const averageInterval = asOptionalFiniteNumber(raw.averageInterval);
|
|
286
|
+
const totalDistance = asFiniteNumber(raw.totalDistance);
|
|
287
|
+
const directionChanges = asFiniteNumber(raw.directionChanges);
|
|
288
|
+
const activeDuration = asFiniteNumber(raw.activeDuration);
|
|
289
|
+
if (scrollCount == null || averageInterval === void 0 || totalDistance == null || directionChanges == null || activeDuration == null) {
|
|
290
|
+
return { ok: false, message: "scroll signals incomplete" };
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
ok: true,
|
|
294
|
+
value: { scrollCount, averageInterval, totalDistance, directionChanges, activeDuration }
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function validateFocus(raw) {
|
|
298
|
+
if (!isPlainObject(raw)) return { ok: false, message: "focus must be an object" };
|
|
299
|
+
const focusCount = asFiniteNumber(raw.focusCount);
|
|
300
|
+
const blurCount = asFiniteNumber(raw.blurCount);
|
|
301
|
+
if (focusCount == null || blurCount == null) {
|
|
302
|
+
return { ok: false, message: "focus signals incomplete" };
|
|
303
|
+
}
|
|
304
|
+
return { ok: true, value: { focusCount, blurCount } };
|
|
305
|
+
}
|
|
306
|
+
function validateVisibility(raw) {
|
|
307
|
+
if (!isPlainObject(raw)) return { ok: false, message: "visibility must be an object" };
|
|
308
|
+
const visibilityChanges = asFiniteNumber(raw.visibilityChanges);
|
|
309
|
+
const visibleDuration = asFiniteNumber(raw.visibleDuration);
|
|
310
|
+
const hiddenDuration = asFiniteNumber(raw.hiddenDuration);
|
|
311
|
+
if (visibilityChanges == null || visibleDuration == null || hiddenDuration == null) {
|
|
312
|
+
return { ok: false, message: "visibility signals incomplete" };
|
|
313
|
+
}
|
|
314
|
+
return { ok: true, value: { visibilityChanges, visibleDuration, hiddenDuration } };
|
|
315
|
+
}
|
|
316
|
+
function validateInteraction(raw) {
|
|
317
|
+
if (!isPlainObject(raw)) return { ok: false, message: "interaction must be an object" };
|
|
318
|
+
const pageLoadTime = asFiniteNumber(raw.pageLoadTime);
|
|
319
|
+
const firstInteractionTime = asOptionalFiniteNumber(raw.firstInteractionTime);
|
|
320
|
+
const timeToFirstInteraction = asOptionalFiniteNumber(raw.timeToFirstInteraction);
|
|
321
|
+
const clickCount = asFiniteNumber(raw.clickCount);
|
|
322
|
+
const interactionCount = asFiniteNumber(raw.interactionCount);
|
|
323
|
+
const formInteractionCount = asFiniteNumber(raw.formInteractionCount);
|
|
324
|
+
const scrollCount = asFiniteNumber(raw.scrollCount);
|
|
325
|
+
const focusCount = asFiniteNumber(raw.focusCount);
|
|
326
|
+
if (pageLoadTime == null || firstInteractionTime === void 0 || timeToFirstInteraction === void 0 || clickCount == null || interactionCount == null || formInteractionCount == null || scrollCount == null || focusCount == null) {
|
|
327
|
+
return { ok: false, message: "interaction signals incomplete" };
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
ok: true,
|
|
331
|
+
value: {
|
|
332
|
+
pageLoadTime,
|
|
333
|
+
firstInteractionTime,
|
|
334
|
+
timeToFirstInteraction,
|
|
335
|
+
clickCount,
|
|
336
|
+
interactionCount,
|
|
337
|
+
formInteractionCount,
|
|
338
|
+
scrollCount,
|
|
339
|
+
focusCount
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
var FORBIDDEN_TELEMETRY_KEYS = /* @__PURE__ */ new Set([
|
|
344
|
+
"password",
|
|
345
|
+
"passwords",
|
|
346
|
+
"value",
|
|
347
|
+
"values",
|
|
348
|
+
"key",
|
|
349
|
+
"keys",
|
|
350
|
+
"code",
|
|
351
|
+
"codes",
|
|
352
|
+
"clipboard",
|
|
353
|
+
"cookie",
|
|
354
|
+
"cookies",
|
|
355
|
+
"localStorage",
|
|
356
|
+
"sessionStorage",
|
|
357
|
+
"token",
|
|
358
|
+
"tokens",
|
|
359
|
+
"authorization",
|
|
360
|
+
"text",
|
|
361
|
+
"innerText",
|
|
362
|
+
"innerHTML",
|
|
363
|
+
"coordinates",
|
|
364
|
+
"path",
|
|
365
|
+
"points"
|
|
366
|
+
]);
|
|
367
|
+
function assertNoForbiddenKeys(value, path) {
|
|
368
|
+
if (Array.isArray(value)) {
|
|
369
|
+
for (let i = 0; i < value.length; i++) {
|
|
370
|
+
const err = assertNoForbiddenKeys(value[i], `${path}[${i}]`);
|
|
371
|
+
if (err) return err;
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
if (!isPlainObject(value)) return null;
|
|
376
|
+
for (const key of Object.keys(value)) {
|
|
377
|
+
if (FORBIDDEN_TELEMETRY_KEYS.has(key)) {
|
|
378
|
+
return `forbidden telemetry field: ${path}.${key}`;
|
|
379
|
+
}
|
|
380
|
+
const err = assertNoForbiddenKeys(value[key], `${path}.${key}`);
|
|
381
|
+
if (err) return err;
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
function validateTelemetry(raw, maxBytes = MAX_TELEMETRY_BYTES) {
|
|
386
|
+
if (!isPlainObject(raw)) return { ok: false, message: "telemetry must be an object" };
|
|
387
|
+
const forbidden = assertNoForbiddenKeys(raw, "telemetry");
|
|
388
|
+
if (forbidden) return { ok: false, message: forbidden };
|
|
389
|
+
const serialized = JSON.stringify(raw);
|
|
390
|
+
if (utf8ByteLength(serialized) > maxBytes) {
|
|
391
|
+
return { ok: false, message: `telemetry exceeds ${maxBytes} bytes` };
|
|
392
|
+
}
|
|
393
|
+
const version = asFiniteNumber(raw.version);
|
|
394
|
+
if (version !== TELEMETRY_VERSION) {
|
|
395
|
+
return { ok: false, message: "unsupported telemetry version" };
|
|
396
|
+
}
|
|
397
|
+
const telemetry = { version: 1 };
|
|
398
|
+
if (raw.browser !== void 0) {
|
|
399
|
+
const r = validateBrowser(raw.browser);
|
|
400
|
+
if (!r.ok) return r;
|
|
401
|
+
telemetry.browser = r.value;
|
|
402
|
+
}
|
|
403
|
+
if (raw.pointer !== void 0) {
|
|
404
|
+
const r = validatePointer(raw.pointer);
|
|
405
|
+
if (!r.ok) return r;
|
|
406
|
+
telemetry.pointer = r.value;
|
|
407
|
+
}
|
|
408
|
+
if (raw.keyboard !== void 0) {
|
|
409
|
+
const r = validateKeyboard(raw.keyboard);
|
|
410
|
+
if (!r.ok) return r;
|
|
411
|
+
telemetry.keyboard = r.value;
|
|
412
|
+
}
|
|
413
|
+
if (raw.touch !== void 0) {
|
|
414
|
+
const r = validateTouch(raw.touch);
|
|
415
|
+
if (!r.ok) return r;
|
|
416
|
+
telemetry.touch = r.value;
|
|
417
|
+
}
|
|
418
|
+
if (raw.scroll !== void 0) {
|
|
419
|
+
const r = validateScroll(raw.scroll);
|
|
420
|
+
if (!r.ok) return r;
|
|
421
|
+
telemetry.scroll = r.value;
|
|
422
|
+
}
|
|
423
|
+
if (raw.focus !== void 0) {
|
|
424
|
+
const r = validateFocus(raw.focus);
|
|
425
|
+
if (!r.ok) return r;
|
|
426
|
+
telemetry.focus = r.value;
|
|
427
|
+
}
|
|
428
|
+
if (raw.visibility !== void 0) {
|
|
429
|
+
const r = validateVisibility(raw.visibility);
|
|
430
|
+
if (!r.ok) return r;
|
|
431
|
+
telemetry.visibility = r.value;
|
|
432
|
+
}
|
|
433
|
+
if (raw.interaction !== void 0) {
|
|
434
|
+
const r = validateInteraction(raw.interaction);
|
|
435
|
+
if (!r.ok) return r;
|
|
436
|
+
telemetry.interaction = r.value;
|
|
437
|
+
}
|
|
438
|
+
return { ok: true, value: telemetry };
|
|
439
|
+
}
|
|
440
|
+
function validateCreateSessionRequest(raw) {
|
|
441
|
+
if (!isPlainObject(raw)) return { ok: false, message: "body must be an object" };
|
|
442
|
+
const hostname = asString(raw.hostname);
|
|
443
|
+
const timestamp = asFiniteNumber(raw.timestamp);
|
|
444
|
+
const userAgent = raw.userAgent === void 0 ? void 0 : asString(raw.userAgent) ?? void 0;
|
|
445
|
+
if (!hostname || timestamp == null) {
|
|
446
|
+
return { ok: false, message: "hostname and timestamp are required" };
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
ok: true,
|
|
450
|
+
value: {
|
|
451
|
+
hostname: hostname.slice(0, 253),
|
|
452
|
+
timestamp,
|
|
453
|
+
...userAgent !== void 0 ? { userAgent: userAgent.slice(0, 512) } : {}
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function validateAnalyzeRequest(raw, maxBytes = MAX_TELEMETRY_BYTES) {
|
|
458
|
+
if (!isPlainObject(raw)) return { ok: false, message: "body must be an object" };
|
|
459
|
+
const sessionId = asString(raw.sessionId);
|
|
460
|
+
const sequence = asFiniteNumber(raw.sequence);
|
|
461
|
+
const timestamp = asFiniteNumber(raw.timestamp);
|
|
462
|
+
const hostname = asString(raw.hostname);
|
|
463
|
+
const nonce = raw.nonce === void 0 ? void 0 : asString(raw.nonce) ?? void 0;
|
|
464
|
+
const action = raw.action === void 0 ? void 0 : asString(raw.action) ?? void 0;
|
|
465
|
+
let metadata;
|
|
466
|
+
if (raw.metadata !== void 0) {
|
|
467
|
+
if (!isPlainObject(raw.metadata)) {
|
|
468
|
+
return { ok: false, message: "metadata must be an object" };
|
|
469
|
+
}
|
|
470
|
+
const forbidden = assertNoForbiddenKeys(raw.metadata, "metadata");
|
|
471
|
+
if (forbidden) return { ok: false, message: forbidden };
|
|
472
|
+
metadata = raw.metadata;
|
|
473
|
+
}
|
|
474
|
+
if (!sessionId || sequence == null || timestamp == null || !hostname) {
|
|
475
|
+
return { ok: false, message: "sessionId, sequence, timestamp, and hostname are required" };
|
|
476
|
+
}
|
|
477
|
+
if (!Number.isInteger(sequence) || sequence < 1) {
|
|
478
|
+
return { ok: false, message: "sequence must be a positive integer" };
|
|
479
|
+
}
|
|
480
|
+
const telemetry = validateTelemetry(raw.telemetry, maxBytes);
|
|
481
|
+
if (!telemetry.ok) return telemetry;
|
|
482
|
+
return {
|
|
483
|
+
ok: true,
|
|
484
|
+
value: {
|
|
485
|
+
sessionId,
|
|
486
|
+
sequence,
|
|
487
|
+
timestamp,
|
|
488
|
+
hostname: hostname.slice(0, 253),
|
|
489
|
+
telemetry: telemetry.value,
|
|
490
|
+
...nonce !== void 0 ? { nonce } : {},
|
|
491
|
+
...action !== void 0 ? { action: action.slice(0, 64) } : {},
|
|
492
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
exports.API_PATHS = API_PATHS;
|
|
498
|
+
exports.BotDetectorError = BotDetectorError;
|
|
499
|
+
exports.DEFAULT_RISK_THRESHOLDS = DEFAULT_RISK_THRESHOLDS;
|
|
500
|
+
exports.DEFAULT_RISK_TOKEN_DURATION_MS = DEFAULT_RISK_TOKEN_DURATION_MS;
|
|
501
|
+
exports.DEFAULT_SESSION_DURATION_MS = DEFAULT_SESSION_DURATION_MS;
|
|
502
|
+
exports.DEFAULT_TELEMETRY_INTERVAL_MS = DEFAULT_TELEMETRY_INTERVAL_MS;
|
|
503
|
+
exports.ERROR_CODES = ERROR_CODES;
|
|
504
|
+
exports.MAX_TELEMETRY_BYTES = MAX_TELEMETRY_BYTES;
|
|
505
|
+
exports.SITE_KEY_HEADER = SITE_KEY_HEADER;
|
|
506
|
+
exports.TELEMETRY_VERSION = TELEMETRY_VERSION;
|
|
507
|
+
exports.clamp = clamp;
|
|
508
|
+
exports.decisionFromScore = decisionFromScore;
|
|
509
|
+
exports.extractHostnameFromOrigin = extractHostnameFromOrigin;
|
|
510
|
+
exports.hostnameAllowed = hostnameAllowed;
|
|
511
|
+
exports.ipMatches = ipMatches;
|
|
512
|
+
exports.isBotDetectorError = isBotDetectorError;
|
|
513
|
+
exports.isPlainObject = isPlainObject;
|
|
514
|
+
exports.mergeRiskThresholds = mergeRiskThresholds;
|
|
515
|
+
exports.normalizeHostname = normalizeHostname;
|
|
516
|
+
exports.riskLevelFromScore = riskLevelFromScore;
|
|
517
|
+
exports.utf8ByteLength = utf8ByteLength;
|
|
518
|
+
exports.validateAnalyzeRequest = validateAnalyzeRequest;
|
|
519
|
+
exports.validateCreateSessionRequest = validateCreateSessionRequest;
|
|
520
|
+
exports.validateTelemetry = validateTelemetry;
|
|
521
|
+
//# sourceMappingURL=index.cjs.map
|
|
522
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/validation.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** Default session lifetime (30 minutes). */\nexport const DEFAULT_SESSION_DURATION_MS = 30 * 60 * 1000\n\n/** Default client telemetry flush interval. */\nexport const DEFAULT_TELEMETRY_INTERVAL_MS = 5000\n\n/** Soft max size for a telemetry JSON payload. */\nexport const MAX_TELEMETRY_BYTES = 32 * 1024\n\n/** Default risk token lifetime (5 minutes). */\nexport const DEFAULT_RISK_TOKEN_DURATION_MS = 5 * 60 * 1000\n\n/** Protocol version embedded in telemetry. */\nexport const TELEMETRY_VERSION = 1 as const\n\n/** HTTP header for the public site key. */\nexport const SITE_KEY_HEADER = 'X-Bot-Site-Key'\n\n/** API path suffixes (relative to endpoint base). */\nexport const API_PATHS = {\n session: '/v1/session',\n analyze: '/v1/analyze',\n} as const\n\n/**\n * Default human-score thresholds (higher = more human-like).\n * allow >= 70, monitor >= 50, challenge >= 30, block < 30\n */\nexport const DEFAULT_RISK_THRESHOLDS = {\n allow: 70,\n monitor: 50,\n challenge: 30,\n} as const\n\nexport const ERROR_CODES = [\n 'INVALID_SITE_KEY',\n 'INVALID_HOSTNAME',\n 'INVALID_SESSION',\n 'SESSION_EXPIRED',\n 'INVALID_NONCE',\n 'INVALID_SEQUENCE',\n 'RATE_LIMITED',\n 'INVALID_TELEMETRY',\n 'MALFORMED_REQUEST',\n 'INTERNAL_ERROR',\n 'IP_BLOCKED',\n 'TOKEN_ALREADY_USED',\n 'ACTION_MISMATCH',\n] as const\n\nexport type ErrorCode = (typeof ERROR_CODES)[number]\n","import type { ErrorCode } from './constants'\nimport type { ApiErrorBody } from './types'\n\nexport class BotDetectorError extends Error {\n readonly code: ErrorCode\n readonly requestId: string\n\n constructor(code: ErrorCode, message: string, requestId: string) {\n super(message)\n this.name = 'BotDetectorError'\n this.code = code\n this.requestId = requestId\n }\n\n toJSON(): ApiErrorBody {\n return {\n error: {\n code: this.code,\n message: this.message,\n requestId: this.requestId,\n },\n }\n }\n}\n\nexport function isBotDetectorError(value: unknown): value is BotDetectorError {\n return value instanceof BotDetectorError\n}\n","import type { RiskConfig, RiskDecision, RiskLevel } from './types'\nimport { DEFAULT_RISK_THRESHOLDS } from './constants'\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\nexport function decisionFromScore(score: number, thresholds: RiskConfig = DEFAULT_RISK_THRESHOLDS): RiskDecision {\n if (score >= thresholds.allow) return 'allow'\n if (score >= thresholds.monitor) return 'monitor'\n if (score >= thresholds.challenge) return 'challenge'\n return 'block'\n}\n\nexport function riskLevelFromScore(score: number): RiskLevel {\n if (score >= 70) return 'low'\n if (score >= 40) return 'medium'\n return 'high'\n}\n\nexport function mergeRiskThresholds(partial?: Partial<RiskConfig>): RiskConfig {\n return {\n allow: partial?.allow ?? DEFAULT_RISK_THRESHOLDS.allow,\n monitor: partial?.monitor ?? DEFAULT_RISK_THRESHOLDS.monitor,\n challenge: partial?.challenge ?? DEFAULT_RISK_THRESHOLDS.challenge,\n }\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nexport function utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') {\n return new TextEncoder().encode(value).length\n }\n // Fallback for unusual environments\n let bytes = 0\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i)\n if (code < 0x80) bytes += 1\n else if (code < 0x800) bytes += 2\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4\n i++\n } else bytes += 3\n }\n return bytes\n}\n\n/** Normalize hostname for allowlist comparison. */\nexport function normalizeHostname(hostname: string): string {\n return hostname.trim().toLowerCase().replace(/\\.$/, '')\n}\n\n/** Check whether hostname matches an allowed domain (exact or subdomain). */\nexport function hostnameAllowed(hostname: string, allowedDomains: readonly string[]): boolean {\n const host = normalizeHostname(hostname)\n if (!host) return false\n for (const raw of allowedDomains) {\n const allowed = normalizeHostname(raw)\n if (!allowed) continue\n if (host === allowed) return true\n if (host.endsWith(`.${allowed}`)) return true\n }\n return false\n}\n\nexport function extractHostnameFromOrigin(origin: string | undefined): string | null {\n if (!origin) return null\n try {\n const url = new URL(origin)\n return normalizeHostname(url.hostname)\n } catch {\n return null\n }\n}\n\n/** Check whether an IP matches any entry in a list of exact IPs, wildcards, or IPv4 CIDR blocks. */\nexport function ipMatches(ip: string | undefined, patterns: readonly string[]): boolean {\n if (!ip || !patterns || patterns.length === 0) return false\n const cleanIp = ip.trim().toLowerCase()\n\n for (const raw of patterns) {\n const pattern = raw.trim().toLowerCase()\n if (!pattern) continue\n if (pattern === cleanIp || pattern === '*') return true\n\n // Wildcard match (e.g. 192.168.*)\n if (pattern.includes('*')) {\n const regex = new RegExp('^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*') + '$')\n if (regex.test(cleanIp)) return true\n continue\n }\n\n // CIDR match (IPv4)\n if (pattern.includes('/')) {\n const [range, prefixStr] = pattern.split('/')\n const prefix = parseInt(prefixStr, 10)\n if (isNaN(prefix) || prefix < 0 || prefix > 32) continue\n\n const ipParts = cleanIp.split('.').map(Number)\n const rangeParts = range.split('.').map(Number)\n\n if (\n ipParts.length === 4 &&\n rangeParts.length === 4 &&\n ipParts.every((n) => !isNaN(n) && n >= 0 && n <= 255) &&\n rangeParts.every((n) => !isNaN(n) && n >= 0 && n <= 255)\n ) {\n const ipInt =\n ((ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3]) >>> 0\n const rangeInt =\n ((rangeParts[0] << 24) | (rangeParts[1] << 16) | (rangeParts[2] << 8) | rangeParts[3]) >>>\n 0\n const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0\n if ((ipInt & mask) === (rangeInt & mask)) return true\n }\n }\n }\n return false\n}\n\n","import { MAX_TELEMETRY_BYTES, TELEMETRY_VERSION } from './constants'\nimport type {\n AnalyzeRequest,\n BrowserSignals,\n CreateSessionRequest,\n FocusSignals,\n InteractionSignals,\n KeyboardSignals,\n PointerSignals,\n ScrollSignals,\n Telemetry,\n TouchSignals,\n VisibilitySignals,\n} from './types'\nimport { isPlainObject, utf8ByteLength } from './utils'\n\nexport type ValidationResult<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly message: string }\n\nfunction asFiniteNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\nfunction asOptionalFiniteNumber(value: unknown): number | null | undefined {\n if (value === undefined) return undefined\n if (value === null) return null\n return asFiniteNumber(value)\n}\n\nfunction asString(value: unknown): string | null {\n return typeof value === 'string' ? value : null\n}\n\nfunction asBoolean(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null\n}\n\nfunction validateBrowser(raw: unknown): ValidationResult<BrowserSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'browser must be an object' }\n const userAgent = asString(raw.userAgent)\n const language = asString(raw.language)\n const timezone = asString(raw.timezone)\n const platform = asString(raw.platform)\n const languagesLength = asFiniteNumber(raw.languagesLength)\n const timezoneOffset = asFiniteNumber(raw.timezoneOffset)\n const screenWidth = asFiniteNumber(raw.screenWidth)\n const screenHeight = asFiniteNumber(raw.screenHeight)\n const colorDepth = asFiniteNumber(raw.colorDepth)\n const devicePixelRatio = asFiniteNumber(raw.devicePixelRatio)\n const maxTouchPoints = asFiniteNumber(raw.maxTouchPoints)\n const cookieEnabled = asBoolean(raw.cookieEnabled)\n const webdriver = asBoolean(raw.webdriver)\n const hardwareConcurrency =\n raw.hardwareConcurrency === null ? null : asFiniteNumber(raw.hardwareConcurrency)\n\n const webglRenderer =\n raw.webglRenderer === null\n ? null\n : raw.webglRenderer !== undefined\n ? asString(raw.webglRenderer)\n : undefined\n const webglVendor =\n raw.webglVendor === null\n ? null\n : raw.webglVendor !== undefined\n ? asString(raw.webglVendor)\n : undefined\n const hasChrome =\n raw.hasChrome !== undefined ? asBoolean(raw.hasChrome) ?? undefined : undefined\n const pluginsCount =\n raw.pluginsCount !== undefined ? asFiniteNumber(raw.pluginsCount) ?? undefined : undefined\n const screenOrientation =\n raw.screenOrientation === null\n ? null\n : raw.screenOrientation !== undefined\n ? asString(raw.screenOrientation)\n : undefined\n\n if (\n userAgent == null ||\n language == null ||\n timezone == null ||\n platform == null ||\n languagesLength == null ||\n timezoneOffset == null ||\n screenWidth == null ||\n screenHeight == null ||\n colorDepth == null ||\n devicePixelRatio == null ||\n maxTouchPoints == null ||\n cookieEnabled == null ||\n webdriver == null ||\n (hardwareConcurrency === undefined)\n ) {\n return { ok: false, message: 'browser signals incomplete' }\n }\n\n return {\n ok: true,\n value: {\n userAgent: userAgent.slice(0, 512),\n language: language.slice(0, 32),\n languagesLength,\n timezone: timezone.slice(0, 64),\n timezoneOffset,\n platform: platform.slice(0, 64),\n screenWidth,\n screenHeight,\n colorDepth,\n devicePixelRatio,\n hardwareConcurrency,\n maxTouchPoints,\n cookieEnabled,\n webdriver,\n ...(webglRenderer !== undefined\n ? { webglRenderer: webglRenderer ? webglRenderer.slice(0, 128) : null }\n : {}),\n ...(webglVendor !== undefined\n ? { webglVendor: webglVendor ? webglVendor.slice(0, 128) : null }\n : {}),\n ...(hasChrome !== undefined ? { hasChrome } : {}),\n ...(pluginsCount !== undefined ? { pluginsCount } : {}),\n ...(screenOrientation !== undefined\n ? { screenOrientation: screenOrientation ? screenOrientation.slice(0, 32) : null }\n : {}),\n },\n }\n}\n\nfunction validateIntervalStats(raw: Record<string, unknown>, label: string): ValidationResult<{\n averageInterval: number | null\n minInterval: number | null\n maxInterval: number | null\n}> {\n const averageInterval = asOptionalFiniteNumber(raw.averageInterval)\n const minInterval = asOptionalFiniteNumber(raw.minInterval)\n const maxInterval = asOptionalFiniteNumber(raw.maxInterval)\n if (averageInterval === undefined || minInterval === undefined || maxInterval === undefined) {\n return { ok: false, message: `${label} intervals incomplete` }\n }\n return { ok: true, value: { averageInterval, minInterval, maxInterval } }\n}\n\nfunction validatePointer(raw: unknown): ValidationResult<PointerSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'pointer must be an object' }\n const eventCount = asFiniteNumber(raw.eventCount)\n const movementDistance = asFiniteNumber(raw.movementDistance)\n const directionChanges = asFiniteNumber(raw.directionChanges)\n const idlePeriods = asFiniteNumber(raw.idlePeriods)\n const activeDuration = asFiniteNumber(raw.activeDuration)\n const clickCount = asFiniteNumber(raw.clickCount)\n const intervals = validateIntervalStats(raw, 'pointer')\n if (!intervals.ok) return intervals\n if (\n eventCount == null ||\n movementDistance == null ||\n directionChanges == null ||\n idlePeriods == null ||\n activeDuration == null ||\n clickCount == null\n ) {\n return { ok: false, message: 'pointer signals incomplete' }\n }\n return {\n ok: true,\n value: {\n eventCount,\n movementDistance,\n ...intervals.value,\n directionChanges,\n idlePeriods,\n activeDuration,\n clickCount,\n },\n }\n}\n\nfunction validateKeyboard(raw: unknown): ValidationResult<KeyboardSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'keyboard must be an object' }\n const keydownCount = asFiniteNumber(raw.keydownCount)\n const keyupCount = asFiniteNumber(raw.keyupCount)\n const pasteCount = asFiniteNumber(raw.pasteCount)\n const timingVariance = asOptionalFiniteNumber(raw.timingVariance)\n const intervals = validateIntervalStats(raw, 'keyboard')\n if (!intervals.ok) return intervals\n if (keydownCount == null || keyupCount == null || pasteCount == null || timingVariance === undefined) {\n return { ok: false, message: 'keyboard signals incomplete' }\n }\n return {\n ok: true,\n value: {\n keydownCount,\n keyupCount,\n ...intervals.value,\n timingVariance,\n pasteCount,\n },\n }\n}\n\nfunction validateTouch(raw: unknown): ValidationResult<TouchSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'touch must be an object' }\n const touchCount = asFiniteNumber(raw.touchCount)\n const averageInterval = asOptionalFiniteNumber(raw.averageInterval)\n const activeDuration = asFiniteNumber(raw.activeDuration)\n const multiTouchCount = asFiniteNumber(raw.multiTouchCount)\n if (\n touchCount == null ||\n averageInterval === undefined ||\n activeDuration == null ||\n multiTouchCount == null\n ) {\n return { ok: false, message: 'touch signals incomplete' }\n }\n return { ok: true, value: { touchCount, averageInterval, activeDuration, multiTouchCount } }\n}\n\nfunction validateScroll(raw: unknown): ValidationResult<ScrollSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'scroll must be an object' }\n const scrollCount = asFiniteNumber(raw.scrollCount)\n const averageInterval = asOptionalFiniteNumber(raw.averageInterval)\n const totalDistance = asFiniteNumber(raw.totalDistance)\n const directionChanges = asFiniteNumber(raw.directionChanges)\n const activeDuration = asFiniteNumber(raw.activeDuration)\n if (\n scrollCount == null ||\n averageInterval === undefined ||\n totalDistance == null ||\n directionChanges == null ||\n activeDuration == null\n ) {\n return { ok: false, message: 'scroll signals incomplete' }\n }\n return {\n ok: true,\n value: { scrollCount, averageInterval, totalDistance, directionChanges, activeDuration },\n }\n}\n\nfunction validateFocus(raw: unknown): ValidationResult<FocusSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'focus must be an object' }\n const focusCount = asFiniteNumber(raw.focusCount)\n const blurCount = asFiniteNumber(raw.blurCount)\n if (focusCount == null || blurCount == null) {\n return { ok: false, message: 'focus signals incomplete' }\n }\n return { ok: true, value: { focusCount, blurCount } }\n}\n\nfunction validateVisibility(raw: unknown): ValidationResult<VisibilitySignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'visibility must be an object' }\n const visibilityChanges = asFiniteNumber(raw.visibilityChanges)\n const visibleDuration = asFiniteNumber(raw.visibleDuration)\n const hiddenDuration = asFiniteNumber(raw.hiddenDuration)\n if (visibilityChanges == null || visibleDuration == null || hiddenDuration == null) {\n return { ok: false, message: 'visibility signals incomplete' }\n }\n return { ok: true, value: { visibilityChanges, visibleDuration, hiddenDuration } }\n}\n\nfunction validateInteraction(raw: unknown): ValidationResult<InteractionSignals> {\n if (!isPlainObject(raw)) return { ok: false, message: 'interaction must be an object' }\n const pageLoadTime = asFiniteNumber(raw.pageLoadTime)\n const firstInteractionTime = asOptionalFiniteNumber(raw.firstInteractionTime)\n const timeToFirstInteraction = asOptionalFiniteNumber(raw.timeToFirstInteraction)\n const clickCount = asFiniteNumber(raw.clickCount)\n const interactionCount = asFiniteNumber(raw.interactionCount)\n const formInteractionCount = asFiniteNumber(raw.formInteractionCount)\n const scrollCount = asFiniteNumber(raw.scrollCount)\n const focusCount = asFiniteNumber(raw.focusCount)\n if (\n pageLoadTime == null ||\n firstInteractionTime === undefined ||\n timeToFirstInteraction === undefined ||\n clickCount == null ||\n interactionCount == null ||\n formInteractionCount == null ||\n scrollCount == null ||\n focusCount == null\n ) {\n return { ok: false, message: 'interaction signals incomplete' }\n }\n return {\n ok: true,\n value: {\n pageLoadTime,\n firstInteractionTime,\n timeToFirstInteraction,\n clickCount,\n interactionCount,\n formInteractionCount,\n scrollCount,\n focusCount,\n },\n }\n}\n\n/** Forbidden keys that must never appear in telemetry (defense in depth). */\nconst FORBIDDEN_TELEMETRY_KEYS = new Set([\n 'password',\n 'passwords',\n 'value',\n 'values',\n 'key',\n 'keys',\n 'code',\n 'codes',\n 'clipboard',\n 'cookie',\n 'cookies',\n 'localStorage',\n 'sessionStorage',\n 'token',\n 'tokens',\n 'authorization',\n 'text',\n 'innerText',\n 'innerHTML',\n 'coordinates',\n 'path',\n 'points',\n])\n\nfunction assertNoForbiddenKeys(value: unknown, path: string): string | null {\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const err = assertNoForbiddenKeys(value[i], `${path}[${i}]`)\n if (err) return err\n }\n return null\n }\n if (!isPlainObject(value)) return null\n for (const key of Object.keys(value)) {\n if (FORBIDDEN_TELEMETRY_KEYS.has(key)) {\n return `forbidden telemetry field: ${path}.${key}`\n }\n const err = assertNoForbiddenKeys(value[key], `${path}.${key}`)\n if (err) return err\n }\n return null\n}\n\nexport function validateTelemetry(\n raw: unknown,\n maxBytes: number = MAX_TELEMETRY_BYTES,\n): ValidationResult<Telemetry> {\n if (!isPlainObject(raw)) return { ok: false, message: 'telemetry must be an object' }\n\n const forbidden = assertNoForbiddenKeys(raw, 'telemetry')\n if (forbidden) return { ok: false, message: forbidden }\n\n const serialized = JSON.stringify(raw)\n if (utf8ByteLength(serialized) > maxBytes) {\n return { ok: false, message: `telemetry exceeds ${maxBytes} bytes` }\n }\n\n const version = asFiniteNumber(raw.version)\n if (version !== TELEMETRY_VERSION) {\n return { ok: false, message: 'unsupported telemetry version' }\n }\n\n const telemetry: {\n version: 1\n browser?: BrowserSignals\n pointer?: PointerSignals\n keyboard?: KeyboardSignals\n touch?: TouchSignals\n scroll?: ScrollSignals\n focus?: FocusSignals\n visibility?: VisibilitySignals\n interaction?: InteractionSignals\n } = { version: 1 }\n\n if (raw.browser !== undefined) {\n const r = validateBrowser(raw.browser)\n if (!r.ok) return r\n telemetry.browser = r.value\n }\n if (raw.pointer !== undefined) {\n const r = validatePointer(raw.pointer)\n if (!r.ok) return r\n telemetry.pointer = r.value\n }\n if (raw.keyboard !== undefined) {\n const r = validateKeyboard(raw.keyboard)\n if (!r.ok) return r\n telemetry.keyboard = r.value\n }\n if (raw.touch !== undefined) {\n const r = validateTouch(raw.touch)\n if (!r.ok) return r\n telemetry.touch = r.value\n }\n if (raw.scroll !== undefined) {\n const r = validateScroll(raw.scroll)\n if (!r.ok) return r\n telemetry.scroll = r.value\n }\n if (raw.focus !== undefined) {\n const r = validateFocus(raw.focus)\n if (!r.ok) return r\n telemetry.focus = r.value\n }\n if (raw.visibility !== undefined) {\n const r = validateVisibility(raw.visibility)\n if (!r.ok) return r\n telemetry.visibility = r.value\n }\n if (raw.interaction !== undefined) {\n const r = validateInteraction(raw.interaction)\n if (!r.ok) return r\n telemetry.interaction = r.value\n }\n\n return { ok: true, value: telemetry }\n}\n\nexport function validateCreateSessionRequest(raw: unknown): ValidationResult<CreateSessionRequest> {\n if (!isPlainObject(raw)) return { ok: false, message: 'body must be an object' }\n const hostname = asString(raw.hostname)\n const timestamp = asFiniteNumber(raw.timestamp)\n const userAgent = raw.userAgent === undefined ? undefined : asString(raw.userAgent) ?? undefined\n if (!hostname || timestamp == null) {\n return { ok: false, message: 'hostname and timestamp are required' }\n }\n return {\n ok: true,\n value: {\n hostname: hostname.slice(0, 253),\n timestamp,\n ...(userAgent !== undefined ? { userAgent: userAgent.slice(0, 512) } : {}),\n },\n }\n}\n\nexport function validateAnalyzeRequest(\n raw: unknown,\n maxBytes: number = MAX_TELEMETRY_BYTES,\n): ValidationResult<AnalyzeRequest> {\n if (!isPlainObject(raw)) return { ok: false, message: 'body must be an object' }\n const sessionId = asString(raw.sessionId)\n const sequence = asFiniteNumber(raw.sequence)\n const timestamp = asFiniteNumber(raw.timestamp)\n const hostname = asString(raw.hostname)\n const nonce = raw.nonce === undefined ? undefined : asString(raw.nonce) ?? undefined\n const action = raw.action === undefined ? undefined : asString(raw.action) ?? undefined\n let metadata: Record<string, unknown> | undefined\n if (raw.metadata !== undefined) {\n if (!isPlainObject(raw.metadata)) {\n return { ok: false, message: 'metadata must be an object' }\n }\n const forbidden = assertNoForbiddenKeys(raw.metadata, 'metadata')\n if (forbidden) return { ok: false, message: forbidden }\n metadata = raw.metadata\n }\n\n if (!sessionId || sequence == null || timestamp == null || !hostname) {\n return { ok: false, message: 'sessionId, sequence, timestamp, and hostname are required' }\n }\n if (!Number.isInteger(sequence) || sequence < 1) {\n return { ok: false, message: 'sequence must be a positive integer' }\n }\n\n const telemetry = validateTelemetry(raw.telemetry, maxBytes)\n if (!telemetry.ok) return telemetry\n\n return {\n ok: true,\n value: {\n sessionId,\n sequence,\n timestamp,\n hostname: hostname.slice(0, 253),\n telemetry: telemetry.value,\n ...(nonce !== undefined ? { nonce } : {}),\n ...(action !== undefined ? { action: action.slice(0, 64) } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n },\n }\n}\n"]}
|