usehuma 1.0.0 → 1.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/dist/index.d.mts +66 -7
- package/dist/index.d.ts +66 -7
- package/dist/index.js +112 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -4
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +49 -1
- package/dist/react.d.ts +49 -1
- package/dist/react.js +242 -3
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +242 -4
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -14,6 +14,13 @@ type SessionData = {
|
|
|
14
14
|
click_interval_cv?: number;
|
|
15
15
|
click_count?: number;
|
|
16
16
|
tab_switches?: number;
|
|
17
|
+
tap_count?: number;
|
|
18
|
+
tap_interval_cv?: number;
|
|
19
|
+
tap_duration_cv?: number;
|
|
20
|
+
tap_force_cv?: number;
|
|
21
|
+
touch_move_count?: number;
|
|
22
|
+
touch_speed_cv?: number;
|
|
23
|
+
multi_touch_count?: number;
|
|
17
24
|
accessibility?: boolean;
|
|
18
25
|
};
|
|
19
26
|
type HumaVerifyResult = {
|
|
@@ -63,6 +70,9 @@ declare class HumaCollector {
|
|
|
63
70
|
private onClick;
|
|
64
71
|
private onFocus;
|
|
65
72
|
private onBlur;
|
|
73
|
+
private onTouchStart;
|
|
74
|
+
private onTouchEnd;
|
|
75
|
+
private onTouchMove;
|
|
66
76
|
start(): void;
|
|
67
77
|
stop(): void;
|
|
68
78
|
extract(): SessionData;
|
|
@@ -71,12 +81,6 @@ declare class HumaCollector {
|
|
|
71
81
|
/** Send extracted signals to the HUMA API and return a result. */
|
|
72
82
|
declare function callHumaApi(opts: HumaOptions, sessionData: SessionData): Promise<HumaVerifyResult>;
|
|
73
83
|
|
|
74
|
-
/**
|
|
75
|
-
* useHUMA JavaScript SDK
|
|
76
|
-
* Privacy-first human verification — no PII, no Cloudflare dependency.
|
|
77
|
-
* https://humaverify.com
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
84
|
/** Auto-start signal collection (call once, early in your app). */
|
|
81
85
|
declare function init(): void;
|
|
82
86
|
/**
|
|
@@ -86,5 +90,60 @@ declare function init(): void;
|
|
|
86
90
|
declare function verify(opts: HumaOptions): Promise<HumaVerifyResult>;
|
|
87
91
|
/** Get raw collected signals — useful for debugging. */
|
|
88
92
|
declare function debug(): Record<string, unknown> | null;
|
|
93
|
+
/**
|
|
94
|
+
* Server-side verify — pass pre-collected signals forwarded from the browser.
|
|
95
|
+
* Use this in your API routes / server actions when you want the final
|
|
96
|
+
* verify call to happen on your backend, not the browser.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // In your Next.js API route:
|
|
100
|
+
* import { verifyWithSignals } from 'usehuma';
|
|
101
|
+
*
|
|
102
|
+
* const result = await verifyWithSignals({
|
|
103
|
+
* apiKey: process.env.HUMA_API_KEY!,
|
|
104
|
+
* userId: req.body.userId,
|
|
105
|
+
* signals: req.body.humaSignals, // forwarded from browser via Huma.debug()
|
|
106
|
+
* });
|
|
107
|
+
*/
|
|
108
|
+
declare function verifyWithSignals(opts: {
|
|
109
|
+
apiKey: string;
|
|
110
|
+
userId: string;
|
|
111
|
+
signals: SessionData;
|
|
112
|
+
endpoint?: string;
|
|
113
|
+
}): Promise<HumaVerifyResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Request a step-up challenge when verify() confidence is borderline (30–70%).
|
|
116
|
+
* Returns challenge data to render to the user.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const result = await verify({ apiKey, userId });
|
|
120
|
+
* if (!result.human && result.confidence > 0.3) {
|
|
121
|
+
* const ch = await requestChallenge({ apiKey, userId });
|
|
122
|
+
* // Render ch.payload to user, collect answer, then:
|
|
123
|
+
* const solved = await solveChallenge({ apiKey, challengeId: ch.challenge_id, answer });
|
|
124
|
+
* if (solved.passed) continueLogin(solved.token);
|
|
125
|
+
* }
|
|
126
|
+
*/
|
|
127
|
+
declare function requestChallenge(opts: {
|
|
128
|
+
apiKey: string;
|
|
129
|
+
userId: string;
|
|
130
|
+
endpoint?: string;
|
|
131
|
+
}): Promise<{
|
|
132
|
+
challenge_id: string;
|
|
133
|
+
type: string;
|
|
134
|
+
payload: Record<string, unknown>;
|
|
135
|
+
expires_in_seconds: number;
|
|
136
|
+
}>;
|
|
137
|
+
declare function solveChallenge(opts: {
|
|
138
|
+
apiKey: string;
|
|
139
|
+
challengeId: string;
|
|
140
|
+
answer: string;
|
|
141
|
+
endpoint?: string;
|
|
142
|
+
}): Promise<{
|
|
143
|
+
passed: boolean;
|
|
144
|
+
token?: string;
|
|
145
|
+
message?: string;
|
|
146
|
+
attempts_remaining?: number;
|
|
147
|
+
}>;
|
|
89
148
|
|
|
90
|
-
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, verify };
|
|
149
|
+
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, requestChallenge, solveChallenge, verify, verifyWithSignals };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ type SessionData = {
|
|
|
14
14
|
click_interval_cv?: number;
|
|
15
15
|
click_count?: number;
|
|
16
16
|
tab_switches?: number;
|
|
17
|
+
tap_count?: number;
|
|
18
|
+
tap_interval_cv?: number;
|
|
19
|
+
tap_duration_cv?: number;
|
|
20
|
+
tap_force_cv?: number;
|
|
21
|
+
touch_move_count?: number;
|
|
22
|
+
touch_speed_cv?: number;
|
|
23
|
+
multi_touch_count?: number;
|
|
17
24
|
accessibility?: boolean;
|
|
18
25
|
};
|
|
19
26
|
type HumaVerifyResult = {
|
|
@@ -63,6 +70,9 @@ declare class HumaCollector {
|
|
|
63
70
|
private onClick;
|
|
64
71
|
private onFocus;
|
|
65
72
|
private onBlur;
|
|
73
|
+
private onTouchStart;
|
|
74
|
+
private onTouchEnd;
|
|
75
|
+
private onTouchMove;
|
|
66
76
|
start(): void;
|
|
67
77
|
stop(): void;
|
|
68
78
|
extract(): SessionData;
|
|
@@ -71,12 +81,6 @@ declare class HumaCollector {
|
|
|
71
81
|
/** Send extracted signals to the HUMA API and return a result. */
|
|
72
82
|
declare function callHumaApi(opts: HumaOptions, sessionData: SessionData): Promise<HumaVerifyResult>;
|
|
73
83
|
|
|
74
|
-
/**
|
|
75
|
-
* useHUMA JavaScript SDK
|
|
76
|
-
* Privacy-first human verification — no PII, no Cloudflare dependency.
|
|
77
|
-
* https://humaverify.com
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
84
|
/** Auto-start signal collection (call once, early in your app). */
|
|
81
85
|
declare function init(): void;
|
|
82
86
|
/**
|
|
@@ -86,5 +90,60 @@ declare function init(): void;
|
|
|
86
90
|
declare function verify(opts: HumaOptions): Promise<HumaVerifyResult>;
|
|
87
91
|
/** Get raw collected signals — useful for debugging. */
|
|
88
92
|
declare function debug(): Record<string, unknown> | null;
|
|
93
|
+
/**
|
|
94
|
+
* Server-side verify — pass pre-collected signals forwarded from the browser.
|
|
95
|
+
* Use this in your API routes / server actions when you want the final
|
|
96
|
+
* verify call to happen on your backend, not the browser.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // In your Next.js API route:
|
|
100
|
+
* import { verifyWithSignals } from 'usehuma';
|
|
101
|
+
*
|
|
102
|
+
* const result = await verifyWithSignals({
|
|
103
|
+
* apiKey: process.env.HUMA_API_KEY!,
|
|
104
|
+
* userId: req.body.userId,
|
|
105
|
+
* signals: req.body.humaSignals, // forwarded from browser via Huma.debug()
|
|
106
|
+
* });
|
|
107
|
+
*/
|
|
108
|
+
declare function verifyWithSignals(opts: {
|
|
109
|
+
apiKey: string;
|
|
110
|
+
userId: string;
|
|
111
|
+
signals: SessionData;
|
|
112
|
+
endpoint?: string;
|
|
113
|
+
}): Promise<HumaVerifyResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Request a step-up challenge when verify() confidence is borderline (30–70%).
|
|
116
|
+
* Returns challenge data to render to the user.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const result = await verify({ apiKey, userId });
|
|
120
|
+
* if (!result.human && result.confidence > 0.3) {
|
|
121
|
+
* const ch = await requestChallenge({ apiKey, userId });
|
|
122
|
+
* // Render ch.payload to user, collect answer, then:
|
|
123
|
+
* const solved = await solveChallenge({ apiKey, challengeId: ch.challenge_id, answer });
|
|
124
|
+
* if (solved.passed) continueLogin(solved.token);
|
|
125
|
+
* }
|
|
126
|
+
*/
|
|
127
|
+
declare function requestChallenge(opts: {
|
|
128
|
+
apiKey: string;
|
|
129
|
+
userId: string;
|
|
130
|
+
endpoint?: string;
|
|
131
|
+
}): Promise<{
|
|
132
|
+
challenge_id: string;
|
|
133
|
+
type: string;
|
|
134
|
+
payload: Record<string, unknown>;
|
|
135
|
+
expires_in_seconds: number;
|
|
136
|
+
}>;
|
|
137
|
+
declare function solveChallenge(opts: {
|
|
138
|
+
apiKey: string;
|
|
139
|
+
challengeId: string;
|
|
140
|
+
answer: string;
|
|
141
|
+
endpoint?: string;
|
|
142
|
+
}): Promise<{
|
|
143
|
+
passed: boolean;
|
|
144
|
+
token?: string;
|
|
145
|
+
message?: string;
|
|
146
|
+
attempts_remaining?: number;
|
|
147
|
+
}>;
|
|
89
148
|
|
|
90
|
-
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, verify };
|
|
149
|
+
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, requestChallenge, solveChallenge, verify, verifyWithSignals };
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,10 @@ __export(src_exports, {
|
|
|
24
24
|
callHumaApi: () => callHumaApi,
|
|
25
25
|
debug: () => debug,
|
|
26
26
|
init: () => init,
|
|
27
|
-
|
|
27
|
+
requestChallenge: () => requestChallenge,
|
|
28
|
+
solveChallenge: () => solveChallenge,
|
|
29
|
+
verify: () => verify,
|
|
30
|
+
verifyWithSignals: () => verifyWithSignals
|
|
28
31
|
});
|
|
29
32
|
module.exports = __toCommonJS(src_exports);
|
|
30
33
|
|
|
@@ -53,6 +56,22 @@ function mouseFeatures(pts) {
|
|
|
53
56
|
}
|
|
54
57
|
return { speed_cv: cv(speeds), direction_changes: dirChanges, sample_count: pts.length };
|
|
55
58
|
}
|
|
59
|
+
function touchFeatures(taps, moves, multiTouchCount) {
|
|
60
|
+
const tapIntervals = [];
|
|
61
|
+
for (let i = 1; i < taps.length; i++) tapIntervals.push(taps[i].t - taps[i - 1].t);
|
|
62
|
+
const durations = taps.map((t) => t.duration_ms);
|
|
63
|
+
const forces = taps.filter((t) => t.force > 0).map((t) => t.force);
|
|
64
|
+
const speeds = moves.map((m) => m.speed);
|
|
65
|
+
return {
|
|
66
|
+
tap_count: taps.length,
|
|
67
|
+
tap_interval_cv: cv(tapIntervals),
|
|
68
|
+
tap_duration_cv: cv(durations),
|
|
69
|
+
tap_force_cv: cv(forces),
|
|
70
|
+
touch_move_count: moves.length,
|
|
71
|
+
touch_speed_cv: cv(speeds),
|
|
72
|
+
multi_touch_count: multiTouchCount
|
|
73
|
+
};
|
|
74
|
+
}
|
|
56
75
|
var HumaCollector = class {
|
|
57
76
|
constructor() {
|
|
58
77
|
this.signals = {
|
|
@@ -61,9 +80,13 @@ var HumaCollector = class {
|
|
|
61
80
|
scrolls: [],
|
|
62
81
|
clicks: [],
|
|
63
82
|
focusEvents: [],
|
|
83
|
+
taps: [],
|
|
84
|
+
touchMoves: [],
|
|
85
|
+
multiTouchCount: 0,
|
|
64
86
|
startTime: Date.now(),
|
|
65
87
|
lastKeyTime: null,
|
|
66
|
-
lastScrollTime: null
|
|
88
|
+
lastScrollTime: null,
|
|
89
|
+
activeTouches: {}
|
|
67
90
|
};
|
|
68
91
|
this.listening = false;
|
|
69
92
|
this.onMouseMove = (e) => {
|
|
@@ -88,6 +111,46 @@ var HumaCollector = class {
|
|
|
88
111
|
};
|
|
89
112
|
this.onFocus = () => this.signals.focusEvents.push({ type: "focus", t: Date.now() });
|
|
90
113
|
this.onBlur = () => this.signals.focusEvents.push({ type: "blur", t: Date.now() });
|
|
114
|
+
this.onTouchStart = (e) => {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (e.touches.length > 1) this.signals.multiTouchCount++;
|
|
117
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
118
|
+
const t = e.changedTouches[i];
|
|
119
|
+
this.signals.activeTouches[t.identifier] = { t: now, x: t.clientX, y: t.clientY };
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
this.onTouchEnd = (e) => {
|
|
123
|
+
var _a;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
126
|
+
const touch = e.changedTouches[i];
|
|
127
|
+
const start = this.signals.activeTouches[touch.identifier];
|
|
128
|
+
if (!start) continue;
|
|
129
|
+
delete this.signals.activeTouches[touch.identifier];
|
|
130
|
+
if (this.signals.taps.length < 80) {
|
|
131
|
+
this.signals.taps.push({
|
|
132
|
+
t: now,
|
|
133
|
+
duration_ms: now - start.t,
|
|
134
|
+
force: (_a = touch.force) != null ? _a : 0,
|
|
135
|
+
touch_count: e.touches.length + 1
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
this.onTouchMove = (e) => {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
143
|
+
const touch = e.changedTouches[i];
|
|
144
|
+
const start = this.signals.activeTouches[touch.identifier];
|
|
145
|
+
if (!start) continue;
|
|
146
|
+
const dx = touch.clientX - start.x;
|
|
147
|
+
const dy = touch.clientY - start.y;
|
|
148
|
+
const dt = Math.max(now - start.t, 1);
|
|
149
|
+
if (this.signals.touchMoves.length < 100)
|
|
150
|
+
this.signals.touchMoves.push({ speed: Math.sqrt(dx * dx + dy * dy) / dt, t: now });
|
|
151
|
+
this.signals.activeTouches[touch.identifier] = { t: now, x: touch.clientX, y: touch.clientY };
|
|
152
|
+
}
|
|
153
|
+
};
|
|
91
154
|
}
|
|
92
155
|
start() {
|
|
93
156
|
if (this.listening || typeof window === "undefined") return;
|
|
@@ -96,6 +159,9 @@ var HumaCollector = class {
|
|
|
96
159
|
document.addEventListener("keydown", this.onKeyDown, { passive: true });
|
|
97
160
|
document.addEventListener("scroll", this.onScroll, { passive: true });
|
|
98
161
|
document.addEventListener("click", this.onClick, { passive: true });
|
|
162
|
+
document.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
163
|
+
document.addEventListener("touchend", this.onTouchEnd, { passive: true });
|
|
164
|
+
document.addEventListener("touchmove", this.onTouchMove, { passive: true });
|
|
99
165
|
window.addEventListener("focus", this.onFocus, { passive: true });
|
|
100
166
|
window.addEventListener("blur", this.onBlur, { passive: true });
|
|
101
167
|
}
|
|
@@ -106,12 +172,16 @@ var HumaCollector = class {
|
|
|
106
172
|
document.removeEventListener("keydown", this.onKeyDown);
|
|
107
173
|
document.removeEventListener("scroll", this.onScroll);
|
|
108
174
|
document.removeEventListener("click", this.onClick);
|
|
175
|
+
document.removeEventListener("touchstart", this.onTouchStart);
|
|
176
|
+
document.removeEventListener("touchend", this.onTouchEnd);
|
|
177
|
+
document.removeEventListener("touchmove", this.onTouchMove);
|
|
109
178
|
window.removeEventListener("focus", this.onFocus);
|
|
110
179
|
window.removeEventListener("blur", this.onBlur);
|
|
111
180
|
}
|
|
112
181
|
extract() {
|
|
113
|
-
const { keys, scrolls, clicks, focusEvents, startTime, mouse } = this.signals;
|
|
182
|
+
const { keys, scrolls, clicks, focusEvents, startTime, mouse, taps, touchMoves, multiTouchCount } = this.signals;
|
|
114
183
|
const m = mouseFeatures(mouse);
|
|
184
|
+
const t = touchFeatures(taps, touchMoves, multiTouchCount);
|
|
115
185
|
const clickIntervals = [];
|
|
116
186
|
for (let i = 1; i < clicks.length; i++)
|
|
117
187
|
clickIntervals.push(clicks[i].t - clicks[i - 1].t);
|
|
@@ -126,7 +196,14 @@ var HumaCollector = class {
|
|
|
126
196
|
mouse_sample_count: m.sample_count,
|
|
127
197
|
click_interval_cv: cv(clickIntervals),
|
|
128
198
|
click_count: clicks.length,
|
|
129
|
-
tab_switches: focusEvents.length
|
|
199
|
+
tab_switches: focusEvents.length,
|
|
200
|
+
tap_count: t.tap_count,
|
|
201
|
+
tap_interval_cv: t.tap_interval_cv,
|
|
202
|
+
tap_duration_cv: t.tap_duration_cv,
|
|
203
|
+
tap_force_cv: t.tap_force_cv,
|
|
204
|
+
touch_move_count: t.touch_move_count,
|
|
205
|
+
touch_speed_cv: t.touch_speed_cv,
|
|
206
|
+
multi_touch_count: t.multi_touch_count
|
|
130
207
|
};
|
|
131
208
|
}
|
|
132
209
|
debug() {
|
|
@@ -168,12 +245,42 @@ function debug() {
|
|
|
168
245
|
var _a;
|
|
169
246
|
return (_a = _globalCollector == null ? void 0 : _globalCollector.debug()) != null ? _a : null;
|
|
170
247
|
}
|
|
248
|
+
async function verifyWithSignals(opts) {
|
|
249
|
+
return callHumaApi(
|
|
250
|
+
{ apiKey: opts.apiKey, userId: opts.userId, endpoint: opts.endpoint },
|
|
251
|
+
opts.signals
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
async function requestChallenge(opts) {
|
|
255
|
+
var _a;
|
|
256
|
+
const base = ((_a = opts.endpoint) != null ? _a : "https://humaverify.com/api/v1/verify").replace(/\/api\/v1\/verify$/, "").replace(/\/$/, "");
|
|
257
|
+
const res = await fetch(`${base}/api/v1/challenge/create`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, "Content-Type": "application/json" },
|
|
260
|
+
body: JSON.stringify({ userId: opts.userId })
|
|
261
|
+
});
|
|
262
|
+
if (!res.ok) throw await res.json().catch(() => ({ error: "Failed to create challenge" }));
|
|
263
|
+
return res.json();
|
|
264
|
+
}
|
|
265
|
+
async function solveChallenge(opts) {
|
|
266
|
+
var _a;
|
|
267
|
+
const base = ((_a = opts.endpoint) != null ? _a : "https://humaverify.com/api/v1/verify").replace(/\/api\/v1\/verify$/, "").replace(/\/$/, "");
|
|
268
|
+
const res = await fetch(`${base}/api/v1/challenge/verify`, {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, "Content-Type": "application/json" },
|
|
271
|
+
body: JSON.stringify({ challenge_id: opts.challengeId, answer: opts.answer })
|
|
272
|
+
});
|
|
273
|
+
return res.json();
|
|
274
|
+
}
|
|
171
275
|
// Annotate the CommonJS export names for ESM import in node:
|
|
172
276
|
0 && (module.exports = {
|
|
173
277
|
HumaCollector,
|
|
174
278
|
callHumaApi,
|
|
175
279
|
debug,
|
|
176
280
|
init,
|
|
177
|
-
|
|
281
|
+
requestChallenge,
|
|
282
|
+
solveChallenge,
|
|
283
|
+
verify,
|
|
284
|
+
verifyWithSignals
|
|
178
285
|
});
|
|
179
286
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["/**\n * useHUMA JavaScript SDK\n * Privacy-first human verification — no PII, no Cloudflare dependency.\n * https://humaverify.com\n */\n\nexport { HumaCollector, callHumaApi } from \"./core\";\nexport type {\n SessionData,\n HumaVerifyResult,\n HumaError,\n HumaOptions,\n HumaState,\n} from \"./types\";\n\n/**\n * Vanilla JS verify — for non-React apps.\n *\n * @example\n * import { verify } from 'usehuma';\n *\n * const result = await verify({\n * apiKey: 'huma_live_...',\n * userId: 'user_123',\n * });\n * if (result.human) { ... }\n */\nimport { HumaCollector, callHumaApi } from \"./core\";\nimport type { HumaOptions, HumaVerifyResult } from \"./types\";\n\nlet _globalCollector: HumaCollector | null = null;\n\n/** Auto-start signal collection (call once, early in your app). */\nexport function init() {\n if (typeof window === \"undefined\") return;\n if (_globalCollector) return;\n _globalCollector = new HumaCollector();\n _globalCollector.start();\n}\n\n/**\n * Verify the current user as human using collected behavioral signals.\n * Calls init() automatically if not already started.\n */\nexport async function verify(opts: HumaOptions): Promise<HumaVerifyResult> {\n if (!_globalCollector) init();\n const sessionData = _globalCollector!.extract();\n return callHumaApi(opts, sessionData);\n}\n\n/** Get raw collected signals — useful for debugging. */\nexport function debug(): Record<string, unknown> | null {\n return _globalCollector?.debug() as Record<string, unknown> | null ?? null;\n}\n","/**\n * useHUMA — Core Signal Collector\n * Invisible behavioral biometrics — no PII collected.\n */\n\nimport type { SessionData, HumaVerifyResult, HumaError, HumaOptions } from \"./types\";\n\ntype MousePoint = { x: number; y: number; t: number };\ntype KeyEntry = { interval: number };\ntype ScrollEntry = { interval: number };\ntype ClickEntry = { t: number };\ntype FocusEvent = { type: \"focus\" | \"blur\"; t: number };\n\ninterface Signals {\n mouse: MousePoint[];\n keys: KeyEntry[];\n scrolls: ScrollEntry[];\n clicks: ClickEntry[];\n focusEvents: FocusEvent[];\n startTime: number;\n lastKeyTime: number | null;\n lastScrollTime: number | null;\n}\n\nfunction cv(arr: number[]): number {\n if (arr.length < 2) return 0;\n const mean = arr.reduce((a, b) => a + b, 0) / arr.length;\n if (mean === 0) return 0;\n const variance = arr.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / arr.length;\n return Math.sqrt(variance) / mean;\n}\n\nfunction mouseFeatures(pts: MousePoint[]) {\n if (pts.length < 5) return { speed_cv: 0, direction_changes: 0, sample_count: 0 };\n const speeds: number[] = [];\n const directions: number[] = [];\n for (let i = 1; i < pts.length; i++) {\n const dx = pts[i].x - pts[i - 1].x;\n const dy = pts[i].y - pts[i - 1].y;\n const dt = Math.max(pts[i].t - pts[i - 1].t, 1);\n speeds.push(Math.sqrt(dx * dx + dy * dy) / dt);\n directions.push(Math.atan2(dy, dx));\n }\n let dirChanges = 0;\n for (let j = 1; j < directions.length; j++) {\n if (Math.abs(directions[j] - directions[j - 1]) > 0.3) dirChanges++;\n }\n return { speed_cv: cv(speeds), direction_changes: dirChanges, sample_count: pts.length };\n}\n\nexport class HumaCollector {\n private signals: Signals = {\n mouse: [], keys: [], scrolls: [], clicks: [], focusEvents: [],\n startTime: Date.now(), lastKeyTime: null, lastScrollTime: null,\n };\n private listening = false;\n\n private onMouseMove = (e: MouseEvent) => {\n if (this.signals.mouse.length < 200)\n this.signals.mouse.push({ x: e.clientX, y: e.clientY, t: Date.now() });\n };\n private onKeyDown = () => {\n const now = Date.now();\n if (this.signals.lastKeyTime !== null && this.signals.keys.length < 100)\n this.signals.keys.push({ interval: now - this.signals.lastKeyTime });\n this.signals.lastKeyTime = now;\n };\n private onScroll = () => {\n const now = Date.now();\n if (this.signals.lastScrollTime !== null && this.signals.scrolls.length < 50)\n this.signals.scrolls.push({ interval: now - this.signals.lastScrollTime });\n this.signals.lastScrollTime = now;\n };\n private onClick = () => {\n if (this.signals.clicks.length < 50)\n this.signals.clicks.push({ t: Date.now() });\n };\n private onFocus = () => this.signals.focusEvents.push({ type: \"focus\", t: Date.now() });\n private onBlur = () => this.signals.focusEvents.push({ type: \"blur\", t: Date.now() });\n\n start() {\n if (this.listening || typeof window === \"undefined\") return;\n this.listening = true;\n document.addEventListener(\"mousemove\", this.onMouseMove, { passive: true });\n document.addEventListener(\"keydown\", this.onKeyDown, { passive: true });\n document.addEventListener(\"scroll\", this.onScroll, { passive: true });\n document.addEventListener(\"click\", this.onClick, { passive: true });\n window.addEventListener(\"focus\", this.onFocus, { passive: true });\n window.addEventListener(\"blur\", this.onBlur, { passive: true });\n }\n\n stop() {\n if (!this.listening) return;\n this.listening = false;\n document.removeEventListener(\"mousemove\", this.onMouseMove);\n document.removeEventListener(\"keydown\", this.onKeyDown);\n document.removeEventListener(\"scroll\", this.onScroll);\n document.removeEventListener(\"click\", this.onClick);\n window.removeEventListener(\"focus\", this.onFocus);\n window.removeEventListener(\"blur\", this.onBlur);\n }\n\n extract(): SessionData {\n const { keys, scrolls, clicks, focusEvents, startTime, mouse } = this.signals;\n const m = mouseFeatures(mouse);\n const clickIntervals: number[] = [];\n for (let i = 1; i < clicks.length; i++)\n clickIntervals.push(clicks[i].t - clicks[i - 1].t);\n return {\n time_on_page_ms: Date.now() - startTime,\n key_interval_cv: cv(keys.map(k => k.interval)),\n key_count: keys.length,\n scroll_interval_cv: cv(scrolls.map(s => s.interval)),\n scroll_count: scrolls.length,\n mouse_speed_cv: m.speed_cv,\n mouse_direction_changes: m.direction_changes,\n mouse_sample_count: m.sample_count,\n click_interval_cv: cv(clickIntervals),\n click_count: clicks.length,\n tab_switches: focusEvents.length,\n };\n }\n\n debug(): Record<string, unknown> {\n return { signals: this.signals as unknown, features: this.extract() };\n }\n}\n\n/** Send extracted signals to the HUMA API and return a result. */\nexport async function callHumaApi(\n opts: HumaOptions,\n sessionData: SessionData\n): Promise<HumaVerifyResult> {\n const endpoint = opts.endpoint ?? \"https://humaverify.com/api/v1/verify\";\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ userId: opts.userId, sessionData }),\n });\n if (!res.ok) {\n const err: HumaError = await res.json().catch(() => ({ error: \"Unknown error\" }));\n throw err;\n }\n return res.json() as Promise<HumaVerifyResult>;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["/**\n * useHUMA JavaScript SDK\n * Privacy-first human verification — no PII, no Cloudflare dependency.\n * https://humaverify.com\n */\n\nexport { HumaCollector, callHumaApi } from \"./core\";\nexport type {\n SessionData,\n HumaVerifyResult,\n HumaError,\n HumaOptions,\n HumaState,\n} from \"./types\";\n\n/**\n * Vanilla JS verify — for non-React apps.\n *\n * @example\n * import { verify } from 'usehuma';\n *\n * const result = await verify({\n * apiKey: 'huma_live_...',\n * userId: 'user_123',\n * });\n * if (result.human) { ... }\n */\nimport { HumaCollector, callHumaApi } from \"./core\";\nimport type { HumaOptions, HumaVerifyResult } from \"./types\";\n\nlet _globalCollector: HumaCollector | null = null;\n\n/** Auto-start signal collection (call once, early in your app). */\nexport function init() {\n if (typeof window === \"undefined\") return;\n if (_globalCollector) return;\n _globalCollector = new HumaCollector();\n _globalCollector.start();\n}\n\n/**\n * Verify the current user as human using collected behavioral signals.\n * Calls init() automatically if not already started.\n */\nexport async function verify(opts: HumaOptions): Promise<HumaVerifyResult> {\n if (!_globalCollector) init();\n const sessionData = _globalCollector!.extract();\n return callHumaApi(opts, sessionData);\n}\n\n/** Get raw collected signals — useful for debugging. */\nexport function debug(): Record<string, unknown> | null {\n return _globalCollector?.debug() as Record<string, unknown> | null ?? null;\n}\n\n/**\n * Server-side verify — pass pre-collected signals forwarded from the browser.\n * Use this in your API routes / server actions when you want the final\n * verify call to happen on your backend, not the browser.\n *\n * @example\n * // In your Next.js API route:\n * import { verifyWithSignals } from 'usehuma';\n *\n * const result = await verifyWithSignals({\n * apiKey: process.env.HUMA_API_KEY!,\n * userId: req.body.userId,\n * signals: req.body.humaSignals, // forwarded from browser via Huma.debug()\n * });\n */\nexport async function verifyWithSignals(opts: {\n apiKey: string;\n userId: string;\n signals: import(\"./types\").SessionData;\n endpoint?: string;\n}): Promise<import(\"./types\").HumaVerifyResult> {\n return callHumaApi(\n { apiKey: opts.apiKey, userId: opts.userId, endpoint: opts.endpoint },\n opts.signals\n );\n}\n\n/**\n * Request a step-up challenge when verify() confidence is borderline (30–70%).\n * Returns challenge data to render to the user.\n *\n * @example\n * const result = await verify({ apiKey, userId });\n * if (!result.human && result.confidence > 0.3) {\n * const ch = await requestChallenge({ apiKey, userId });\n * // Render ch.payload to user, collect answer, then:\n * const solved = await solveChallenge({ apiKey, challengeId: ch.challenge_id, answer });\n * if (solved.passed) continueLogin(solved.token);\n * }\n */\nexport async function requestChallenge(opts: {\n apiKey: string;\n userId: string;\n endpoint?: string;\n}): Promise<{ challenge_id: string; type: string; payload: Record<string, unknown>; expires_in_seconds: number }> {\n const base = (opts.endpoint ?? \"https://humaverify.com/api/v1/verify\")\n .replace(/\\/api\\/v1\\/verify$/, \"\")\n .replace(/\\/$/, \"\");\n const res = await fetch(`${base}/api/v1/challenge/create`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${opts.apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ userId: opts.userId }),\n });\n if (!res.ok) throw await res.json().catch(() => ({ error: \"Failed to create challenge\" }));\n return res.json();\n}\n\nexport async function solveChallenge(opts: {\n apiKey: string;\n challengeId: string;\n answer: string;\n endpoint?: string;\n}): Promise<{ passed: boolean; token?: string; message?: string; attempts_remaining?: number }> {\n const base = (opts.endpoint ?? \"https://humaverify.com/api/v1/verify\")\n .replace(/\\/api\\/v1\\/verify$/, \"\")\n .replace(/\\/$/, \"\");\n const res = await fetch(`${base}/api/v1/challenge/verify`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${opts.apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ challenge_id: opts.challengeId, answer: opts.answer }),\n });\n return res.json();\n}\n","/**\n * useHUMA — Core Signal Collector\n * Invisible behavioral biometrics — no PII collected.\n */\n\nimport type { SessionData, HumaVerifyResult, HumaError, HumaOptions } from \"./types\";\n\ntype MousePoint = { x: number; y: number; t: number };\ntype KeyEntry = { interval: number };\ntype ScrollEntry = { interval: number };\ntype ClickEntry = { t: number };\ntype FocusEvent = { type: \"focus\" | \"blur\"; t: number };\ntype TapEntry = { t: number; duration_ms: number; force: number; touch_count: number };\ntype TouchMove = { speed: number; t: number };\ntype ActiveTouch = { t: number; x: number; y: number };\n\ninterface Signals {\n mouse: MousePoint[];\n keys: KeyEntry[];\n scrolls: ScrollEntry[];\n clicks: ClickEntry[];\n focusEvents: FocusEvent[];\n taps: TapEntry[];\n touchMoves: TouchMove[];\n multiTouchCount: number;\n startTime: number;\n lastKeyTime: number | null;\n lastScrollTime: number | null;\n activeTouches: Record<number, ActiveTouch>;\n}\n\nfunction cv(arr: number[]): number {\n if (arr.length < 2) return 0;\n const mean = arr.reduce((a, b) => a + b, 0) / arr.length;\n if (mean === 0) return 0;\n const variance = arr.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / arr.length;\n return Math.sqrt(variance) / mean;\n}\n\nfunction mouseFeatures(pts: MousePoint[]) {\n if (pts.length < 5) return { speed_cv: 0, direction_changes: 0, sample_count: 0 };\n const speeds: number[] = [];\n const directions: number[] = [];\n for (let i = 1; i < pts.length; i++) {\n const dx = pts[i].x - pts[i - 1].x;\n const dy = pts[i].y - pts[i - 1].y;\n const dt = Math.max(pts[i].t - pts[i - 1].t, 1);\n speeds.push(Math.sqrt(dx * dx + dy * dy) / dt);\n directions.push(Math.atan2(dy, dx));\n }\n let dirChanges = 0;\n for (let j = 1; j < directions.length; j++) {\n if (Math.abs(directions[j] - directions[j - 1]) > 0.3) dirChanges++;\n }\n return { speed_cv: cv(speeds), direction_changes: dirChanges, sample_count: pts.length };\n}\n\nfunction touchFeatures(taps: TapEntry[], moves: TouchMove[], multiTouchCount: number) {\n const tapIntervals: number[] = [];\n for (let i = 1; i < taps.length; i++) tapIntervals.push(taps[i].t - taps[i - 1].t);\n const durations = taps.map(t => t.duration_ms);\n const forces = taps.filter(t => t.force > 0).map(t => t.force);\n const speeds = moves.map(m => m.speed);\n return {\n tap_count: taps.length,\n tap_interval_cv: cv(tapIntervals),\n tap_duration_cv: cv(durations),\n tap_force_cv: cv(forces),\n touch_move_count: moves.length,\n touch_speed_cv: cv(speeds),\n multi_touch_count: multiTouchCount,\n };\n}\n\nexport class HumaCollector {\n private signals: Signals = {\n mouse: [], keys: [], scrolls: [], clicks: [], focusEvents: [],\n taps: [], touchMoves: [], multiTouchCount: 0,\n startTime: Date.now(), lastKeyTime: null, lastScrollTime: null,\n activeTouches: {},\n };\n private listening = false;\n\n private onMouseMove = (e: MouseEvent) => {\n if (this.signals.mouse.length < 200)\n this.signals.mouse.push({ x: e.clientX, y: e.clientY, t: Date.now() });\n };\n private onKeyDown = () => {\n const now = Date.now();\n if (this.signals.lastKeyTime !== null && this.signals.keys.length < 100)\n this.signals.keys.push({ interval: now - this.signals.lastKeyTime });\n this.signals.lastKeyTime = now;\n };\n private onScroll = () => {\n const now = Date.now();\n if (this.signals.lastScrollTime !== null && this.signals.scrolls.length < 50)\n this.signals.scrolls.push({ interval: now - this.signals.lastScrollTime });\n this.signals.lastScrollTime = now;\n };\n private onClick = () => {\n if (this.signals.clicks.length < 50)\n this.signals.clicks.push({ t: Date.now() });\n };\n private onFocus = () => this.signals.focusEvents.push({ type: \"focus\", t: Date.now() });\n private onBlur = () => this.signals.focusEvents.push({ type: \"blur\", t: Date.now() });\n\n private onTouchStart = (e: TouchEvent) => {\n const now = Date.now();\n if (e.touches.length > 1) this.signals.multiTouchCount++;\n for (let i = 0; i < e.changedTouches.length; i++) {\n const t = e.changedTouches[i];\n this.signals.activeTouches[t.identifier] = { t: now, x: t.clientX, y: t.clientY };\n }\n };\n\n private onTouchEnd = (e: TouchEvent) => {\n const now = Date.now();\n for (let i = 0; i < e.changedTouches.length; i++) {\n const touch = e.changedTouches[i];\n const start = this.signals.activeTouches[touch.identifier];\n if (!start) continue;\n delete this.signals.activeTouches[touch.identifier];\n if (this.signals.taps.length < 80) {\n this.signals.taps.push({\n t: now,\n duration_ms: now - start.t,\n force: (touch as Touch & { force?: number }).force ?? 0,\n touch_count: e.touches.length + 1,\n });\n }\n }\n };\n\n private onTouchMove = (e: TouchEvent) => {\n const now = Date.now();\n for (let i = 0; i < e.changedTouches.length; i++) {\n const touch = e.changedTouches[i];\n const start = this.signals.activeTouches[touch.identifier];\n if (!start) continue;\n const dx = touch.clientX - start.x;\n const dy = touch.clientY - start.y;\n const dt = Math.max(now - start.t, 1);\n if (this.signals.touchMoves.length < 100)\n this.signals.touchMoves.push({ speed: Math.sqrt(dx * dx + dy * dy) / dt, t: now });\n this.signals.activeTouches[touch.identifier] = { t: now, x: touch.clientX, y: touch.clientY };\n }\n };\n\n start() {\n if (this.listening || typeof window === \"undefined\") return;\n this.listening = true;\n document.addEventListener(\"mousemove\", this.onMouseMove, { passive: true });\n document.addEventListener(\"keydown\", this.onKeyDown, { passive: true });\n document.addEventListener(\"scroll\", this.onScroll, { passive: true });\n document.addEventListener(\"click\", this.onClick, { passive: true });\n document.addEventListener(\"touchstart\", this.onTouchStart, { passive: true });\n document.addEventListener(\"touchend\", this.onTouchEnd, { passive: true });\n document.addEventListener(\"touchmove\", this.onTouchMove, { passive: true });\n window.addEventListener(\"focus\", this.onFocus, { passive: true });\n window.addEventListener(\"blur\", this.onBlur, { passive: true });\n }\n\n stop() {\n if (!this.listening) return;\n this.listening = false;\n document.removeEventListener(\"mousemove\", this.onMouseMove);\n document.removeEventListener(\"keydown\", this.onKeyDown);\n document.removeEventListener(\"scroll\", this.onScroll);\n document.removeEventListener(\"click\", this.onClick);\n document.removeEventListener(\"touchstart\", this.onTouchStart);\n document.removeEventListener(\"touchend\", this.onTouchEnd);\n document.removeEventListener(\"touchmove\", this.onTouchMove);\n window.removeEventListener(\"focus\", this.onFocus);\n window.removeEventListener(\"blur\", this.onBlur);\n }\n\n extract(): SessionData {\n const { keys, scrolls, clicks, focusEvents, startTime, mouse, taps, touchMoves, multiTouchCount } = this.signals;\n const m = mouseFeatures(mouse);\n const t = touchFeatures(taps, touchMoves, multiTouchCount);\n const clickIntervals: number[] = [];\n for (let i = 1; i < clicks.length; i++)\n clickIntervals.push(clicks[i].t - clicks[i - 1].t);\n return {\n time_on_page_ms: Date.now() - startTime,\n key_interval_cv: cv(keys.map(k => k.interval)),\n key_count: keys.length,\n scroll_interval_cv: cv(scrolls.map(s => s.interval)),\n scroll_count: scrolls.length,\n mouse_speed_cv: m.speed_cv,\n mouse_direction_changes: m.direction_changes,\n mouse_sample_count: m.sample_count,\n click_interval_cv: cv(clickIntervals),\n click_count: clicks.length,\n tab_switches: focusEvents.length,\n tap_count: t.tap_count,\n tap_interval_cv: t.tap_interval_cv,\n tap_duration_cv: t.tap_duration_cv,\n tap_force_cv: t.tap_force_cv,\n touch_move_count: t.touch_move_count,\n touch_speed_cv: t.touch_speed_cv,\n multi_touch_count: t.multi_touch_count,\n };\n }\n\n debug(): Record<string, unknown> {\n return { signals: this.signals as unknown, features: this.extract() };\n }\n}\n\n/** Send extracted signals to the HUMA API and return a result. */\nexport async function callHumaApi(\n opts: HumaOptions,\n sessionData: SessionData\n): Promise<HumaVerifyResult> {\n const endpoint = opts.endpoint ?? \"https://humaverify.com/api/v1/verify\";\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ userId: opts.userId, sessionData }),\n });\n if (!res.ok) {\n const err: HumaError = await res.json().catch(() => ({ error: \"Unknown error\" }));\n throw err;\n }\n return res.json() as Promise<HumaVerifyResult>;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -23,6 +23,22 @@ function mouseFeatures(pts) {
|
|
|
23
23
|
}
|
|
24
24
|
return { speed_cv: cv(speeds), direction_changes: dirChanges, sample_count: pts.length };
|
|
25
25
|
}
|
|
26
|
+
function touchFeatures(taps, moves, multiTouchCount) {
|
|
27
|
+
const tapIntervals = [];
|
|
28
|
+
for (let i = 1; i < taps.length; i++) tapIntervals.push(taps[i].t - taps[i - 1].t);
|
|
29
|
+
const durations = taps.map((t) => t.duration_ms);
|
|
30
|
+
const forces = taps.filter((t) => t.force > 0).map((t) => t.force);
|
|
31
|
+
const speeds = moves.map((m) => m.speed);
|
|
32
|
+
return {
|
|
33
|
+
tap_count: taps.length,
|
|
34
|
+
tap_interval_cv: cv(tapIntervals),
|
|
35
|
+
tap_duration_cv: cv(durations),
|
|
36
|
+
tap_force_cv: cv(forces),
|
|
37
|
+
touch_move_count: moves.length,
|
|
38
|
+
touch_speed_cv: cv(speeds),
|
|
39
|
+
multi_touch_count: multiTouchCount
|
|
40
|
+
};
|
|
41
|
+
}
|
|
26
42
|
var HumaCollector = class {
|
|
27
43
|
constructor() {
|
|
28
44
|
this.signals = {
|
|
@@ -31,9 +47,13 @@ var HumaCollector = class {
|
|
|
31
47
|
scrolls: [],
|
|
32
48
|
clicks: [],
|
|
33
49
|
focusEvents: [],
|
|
50
|
+
taps: [],
|
|
51
|
+
touchMoves: [],
|
|
52
|
+
multiTouchCount: 0,
|
|
34
53
|
startTime: Date.now(),
|
|
35
54
|
lastKeyTime: null,
|
|
36
|
-
lastScrollTime: null
|
|
55
|
+
lastScrollTime: null,
|
|
56
|
+
activeTouches: {}
|
|
37
57
|
};
|
|
38
58
|
this.listening = false;
|
|
39
59
|
this.onMouseMove = (e) => {
|
|
@@ -58,6 +78,46 @@ var HumaCollector = class {
|
|
|
58
78
|
};
|
|
59
79
|
this.onFocus = () => this.signals.focusEvents.push({ type: "focus", t: Date.now() });
|
|
60
80
|
this.onBlur = () => this.signals.focusEvents.push({ type: "blur", t: Date.now() });
|
|
81
|
+
this.onTouchStart = (e) => {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (e.touches.length > 1) this.signals.multiTouchCount++;
|
|
84
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
85
|
+
const t = e.changedTouches[i];
|
|
86
|
+
this.signals.activeTouches[t.identifier] = { t: now, x: t.clientX, y: t.clientY };
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
this.onTouchEnd = (e) => {
|
|
90
|
+
var _a;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
93
|
+
const touch = e.changedTouches[i];
|
|
94
|
+
const start = this.signals.activeTouches[touch.identifier];
|
|
95
|
+
if (!start) continue;
|
|
96
|
+
delete this.signals.activeTouches[touch.identifier];
|
|
97
|
+
if (this.signals.taps.length < 80) {
|
|
98
|
+
this.signals.taps.push({
|
|
99
|
+
t: now,
|
|
100
|
+
duration_ms: now - start.t,
|
|
101
|
+
force: (_a = touch.force) != null ? _a : 0,
|
|
102
|
+
touch_count: e.touches.length + 1
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
this.onTouchMove = (e) => {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
110
|
+
const touch = e.changedTouches[i];
|
|
111
|
+
const start = this.signals.activeTouches[touch.identifier];
|
|
112
|
+
if (!start) continue;
|
|
113
|
+
const dx = touch.clientX - start.x;
|
|
114
|
+
const dy = touch.clientY - start.y;
|
|
115
|
+
const dt = Math.max(now - start.t, 1);
|
|
116
|
+
if (this.signals.touchMoves.length < 100)
|
|
117
|
+
this.signals.touchMoves.push({ speed: Math.sqrt(dx * dx + dy * dy) / dt, t: now });
|
|
118
|
+
this.signals.activeTouches[touch.identifier] = { t: now, x: touch.clientX, y: touch.clientY };
|
|
119
|
+
}
|
|
120
|
+
};
|
|
61
121
|
}
|
|
62
122
|
start() {
|
|
63
123
|
if (this.listening || typeof window === "undefined") return;
|
|
@@ -66,6 +126,9 @@ var HumaCollector = class {
|
|
|
66
126
|
document.addEventListener("keydown", this.onKeyDown, { passive: true });
|
|
67
127
|
document.addEventListener("scroll", this.onScroll, { passive: true });
|
|
68
128
|
document.addEventListener("click", this.onClick, { passive: true });
|
|
129
|
+
document.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
130
|
+
document.addEventListener("touchend", this.onTouchEnd, { passive: true });
|
|
131
|
+
document.addEventListener("touchmove", this.onTouchMove, { passive: true });
|
|
69
132
|
window.addEventListener("focus", this.onFocus, { passive: true });
|
|
70
133
|
window.addEventListener("blur", this.onBlur, { passive: true });
|
|
71
134
|
}
|
|
@@ -76,12 +139,16 @@ var HumaCollector = class {
|
|
|
76
139
|
document.removeEventListener("keydown", this.onKeyDown);
|
|
77
140
|
document.removeEventListener("scroll", this.onScroll);
|
|
78
141
|
document.removeEventListener("click", this.onClick);
|
|
142
|
+
document.removeEventListener("touchstart", this.onTouchStart);
|
|
143
|
+
document.removeEventListener("touchend", this.onTouchEnd);
|
|
144
|
+
document.removeEventListener("touchmove", this.onTouchMove);
|
|
79
145
|
window.removeEventListener("focus", this.onFocus);
|
|
80
146
|
window.removeEventListener("blur", this.onBlur);
|
|
81
147
|
}
|
|
82
148
|
extract() {
|
|
83
|
-
const { keys, scrolls, clicks, focusEvents, startTime, mouse } = this.signals;
|
|
149
|
+
const { keys, scrolls, clicks, focusEvents, startTime, mouse, taps, touchMoves, multiTouchCount } = this.signals;
|
|
84
150
|
const m = mouseFeatures(mouse);
|
|
151
|
+
const t = touchFeatures(taps, touchMoves, multiTouchCount);
|
|
85
152
|
const clickIntervals = [];
|
|
86
153
|
for (let i = 1; i < clicks.length; i++)
|
|
87
154
|
clickIntervals.push(clicks[i].t - clicks[i - 1].t);
|
|
@@ -96,7 +163,14 @@ var HumaCollector = class {
|
|
|
96
163
|
mouse_sample_count: m.sample_count,
|
|
97
164
|
click_interval_cv: cv(clickIntervals),
|
|
98
165
|
click_count: clicks.length,
|
|
99
|
-
tab_switches: focusEvents.length
|
|
166
|
+
tab_switches: focusEvents.length,
|
|
167
|
+
tap_count: t.tap_count,
|
|
168
|
+
tap_interval_cv: t.tap_interval_cv,
|
|
169
|
+
tap_duration_cv: t.tap_duration_cv,
|
|
170
|
+
tap_force_cv: t.tap_force_cv,
|
|
171
|
+
touch_move_count: t.touch_move_count,
|
|
172
|
+
touch_speed_cv: t.touch_speed_cv,
|
|
173
|
+
multi_touch_count: t.multi_touch_count
|
|
100
174
|
};
|
|
101
175
|
}
|
|
102
176
|
debug() {
|
|
@@ -138,11 +212,41 @@ function debug() {
|
|
|
138
212
|
var _a;
|
|
139
213
|
return (_a = _globalCollector == null ? void 0 : _globalCollector.debug()) != null ? _a : null;
|
|
140
214
|
}
|
|
215
|
+
async function verifyWithSignals(opts) {
|
|
216
|
+
return callHumaApi(
|
|
217
|
+
{ apiKey: opts.apiKey, userId: opts.userId, endpoint: opts.endpoint },
|
|
218
|
+
opts.signals
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async function requestChallenge(opts) {
|
|
222
|
+
var _a;
|
|
223
|
+
const base = ((_a = opts.endpoint) != null ? _a : "https://humaverify.com/api/v1/verify").replace(/\/api\/v1\/verify$/, "").replace(/\/$/, "");
|
|
224
|
+
const res = await fetch(`${base}/api/v1/challenge/create`, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, "Content-Type": "application/json" },
|
|
227
|
+
body: JSON.stringify({ userId: opts.userId })
|
|
228
|
+
});
|
|
229
|
+
if (!res.ok) throw await res.json().catch(() => ({ error: "Failed to create challenge" }));
|
|
230
|
+
return res.json();
|
|
231
|
+
}
|
|
232
|
+
async function solveChallenge(opts) {
|
|
233
|
+
var _a;
|
|
234
|
+
const base = ((_a = opts.endpoint) != null ? _a : "https://humaverify.com/api/v1/verify").replace(/\/api\/v1\/verify$/, "").replace(/\/$/, "");
|
|
235
|
+
const res = await fetch(`${base}/api/v1/challenge/verify`, {
|
|
236
|
+
method: "POST",
|
|
237
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, "Content-Type": "application/json" },
|
|
238
|
+
body: JSON.stringify({ challenge_id: opts.challengeId, answer: opts.answer })
|
|
239
|
+
});
|
|
240
|
+
return res.json();
|
|
241
|
+
}
|
|
141
242
|
export {
|
|
142
243
|
HumaCollector,
|
|
143
244
|
callHumaApi,
|
|
144
245
|
debug,
|
|
145
246
|
init,
|
|
146
|
-
|
|
247
|
+
requestChallenge,
|
|
248
|
+
solveChallenge,
|
|
249
|
+
verify,
|
|
250
|
+
verifyWithSignals
|
|
147
251
|
};
|
|
148
252
|
//# sourceMappingURL=index.mjs.map
|