usehuma 1.0.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/README.md +188 -0
- package/dist/index.d.mts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +148 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +157 -0
- package/dist/react.d.ts +157 -0
- package/dist/react.js +330 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +310 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# usehuma
|
|
2
|
+
|
|
3
|
+
**Privacy-first human verification SDK.**
|
|
4
|
+
No CAPTCHA. No Cloudflare. No PII collected. Just invisible behavioral biometrics.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/usehuma)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install usehuma
|
|
15
|
+
# or
|
|
16
|
+
yarn add usehuma
|
|
17
|
+
# or
|
|
18
|
+
pnpm add usehuma
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick start β Vanilla JS
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { init, verify } from 'usehuma';
|
|
27
|
+
|
|
28
|
+
// Start collecting signals as early as possible
|
|
29
|
+
init();
|
|
30
|
+
|
|
31
|
+
// Later β on form submit, signup, checkout:
|
|
32
|
+
const result = await verify({
|
|
33
|
+
apiKey: 'huma_live_...',
|
|
34
|
+
userId: 'user_123',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (result.human) {
|
|
38
|
+
// β
proceed
|
|
39
|
+
} else {
|
|
40
|
+
// π« block or challenge
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## React hook
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useHuma } from 'usehuma/react';
|
|
50
|
+
|
|
51
|
+
function SignupForm() {
|
|
52
|
+
const { verify, loading, isHuman, score } = useHuma({
|
|
53
|
+
apiKey: 'huma_live_...',
|
|
54
|
+
userId: session.userId,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const handleSubmit = async (e) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const result = await verify();
|
|
60
|
+
if (result?.human) {
|
|
61
|
+
// proceed with signup
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<form onSubmit={handleSubmit}>
|
|
67
|
+
{/* your form fields */}
|
|
68
|
+
<button type="submit" disabled={loading}>
|
|
69
|
+
{loading ? 'Verifyingβ¦' : 'Create account'}
|
|
70
|
+
</button>
|
|
71
|
+
{score !== null && <p>Trust score: {score}%</p>}
|
|
72
|
+
</form>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## HumaGate β conditional rendering
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { HumaGate } from 'usehuma/react';
|
|
83
|
+
|
|
84
|
+
<HumaGate
|
|
85
|
+
apiKey="huma_live_..."
|
|
86
|
+
userId={userId}
|
|
87
|
+
fallback={<div>Checking...</div>}
|
|
88
|
+
blocked={<div>Access denied.</div>}
|
|
89
|
+
>
|
|
90
|
+
<SensitiveContent />
|
|
91
|
+
</HumaGate>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## HumaProvider β global context
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { HumaProvider, useHumaContext } from 'usehuma/react';
|
|
100
|
+
|
|
101
|
+
// Wrap your app:
|
|
102
|
+
<HumaProvider apiKey="huma_live_..." userId={userId} autoVerify>
|
|
103
|
+
<App />
|
|
104
|
+
</HumaProvider>
|
|
105
|
+
|
|
106
|
+
// Use anywhere inside:
|
|
107
|
+
function AnyChild() {
|
|
108
|
+
const { isHuman, score } = useHumaContext();
|
|
109
|
+
return <div>Human: {String(isHuman)} | Score: {score}%</div>;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## HumaVerifyButton
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { HumaVerifyButton } from 'usehuma/react';
|
|
119
|
+
|
|
120
|
+
<HumaVerifyButton
|
|
121
|
+
apiKey="huma_live_..."
|
|
122
|
+
userId={userId}
|
|
123
|
+
onVerified={(result) => console.log('Human!', result.confidence)}
|
|
124
|
+
onBlocked={() => alert('Bot detected')}
|
|
125
|
+
>
|
|
126
|
+
Submit Order
|
|
127
|
+
</HumaVerifyButton>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Next.js App Router
|
|
133
|
+
|
|
134
|
+
Works out of the box. All React exports include `"use client"`.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// app/signup/page.tsx
|
|
138
|
+
import { HumaGate } from 'usehuma/react';
|
|
139
|
+
|
|
140
|
+
export default function SignupPage() {
|
|
141
|
+
return (
|
|
142
|
+
<HumaGate apiKey={process.env.NEXT_PUBLIC_HUMA_KEY!} userId="anonymous">
|
|
143
|
+
<SignupForm />
|
|
144
|
+
</HumaGate>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## API response
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
{
|
|
155
|
+
human: boolean; // true = human verified
|
|
156
|
+
confidence: number; // 0β1 trust score
|
|
157
|
+
threshold: number; // your configured threshold
|
|
158
|
+
token: string; // h_verified_... verification proof
|
|
159
|
+
pii_stored: false; // we never store personal data
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## What signals are collected?
|
|
166
|
+
|
|
167
|
+
| Signal | What we measure |
|
|
168
|
+
|---|---|
|
|
169
|
+
| Mouse movement | Speed variance, direction changes |
|
|
170
|
+
| Keyboard | Keystroke interval variance |
|
|
171
|
+
| Scroll | Rhythm and timing variance |
|
|
172
|
+
| Clicks | Interval variance |
|
|
173
|
+
| Tab focus | Focus/blur events |
|
|
174
|
+
| Time on page | Dwell time |
|
|
175
|
+
|
|
176
|
+
**No IP address. No device fingerprint. No personal data. Ever.**
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Get your API key
|
|
181
|
+
|
|
182
|
+
β [humaverify.com](https://humaverify.com)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT Β© [UseHUMA](https://humaverify.com)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHUMA β TypeScript Types
|
|
3
|
+
* https://humaverify.com
|
|
4
|
+
*/
|
|
5
|
+
type SessionData = {
|
|
6
|
+
time_on_page_ms?: number;
|
|
7
|
+
key_interval_cv?: number;
|
|
8
|
+
key_count?: number;
|
|
9
|
+
scroll_interval_cv?: number;
|
|
10
|
+
scroll_count?: number;
|
|
11
|
+
mouse_speed_cv?: number;
|
|
12
|
+
mouse_direction_changes?: number;
|
|
13
|
+
mouse_sample_count?: number;
|
|
14
|
+
click_interval_cv?: number;
|
|
15
|
+
click_count?: number;
|
|
16
|
+
tab_switches?: number;
|
|
17
|
+
accessibility?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type HumaVerifyResult = {
|
|
20
|
+
human: boolean;
|
|
21
|
+
confidence: number;
|
|
22
|
+
threshold: number;
|
|
23
|
+
token: string;
|
|
24
|
+
pii_stored: false;
|
|
25
|
+
};
|
|
26
|
+
type HumaError = {
|
|
27
|
+
error: string;
|
|
28
|
+
code?: string;
|
|
29
|
+
upgrade_url?: string;
|
|
30
|
+
retry_after?: number;
|
|
31
|
+
};
|
|
32
|
+
type HumaOptions = {
|
|
33
|
+
/** Your HUMA API key (huma_live_...) */
|
|
34
|
+
apiKey: string;
|
|
35
|
+
/** Your internal user ID */
|
|
36
|
+
userId: string;
|
|
37
|
+
/** Override the API endpoint (default: https://humaverify.com/api/v1/verify) */
|
|
38
|
+
endpoint?: string;
|
|
39
|
+
};
|
|
40
|
+
type HumaState = {
|
|
41
|
+
loading: boolean;
|
|
42
|
+
result: HumaVerifyResult | null;
|
|
43
|
+
error: HumaError | null;
|
|
44
|
+
/** true = verified human, false = bot, null = not yet verified */
|
|
45
|
+
isHuman: boolean | null;
|
|
46
|
+
/** 0β100 confidence score */
|
|
47
|
+
score: number | null;
|
|
48
|
+
/** Call manually to trigger verification */
|
|
49
|
+
verify: () => Promise<HumaVerifyResult | null>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* useHUMA β Core Signal Collector
|
|
54
|
+
* Invisible behavioral biometrics β no PII collected.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
declare class HumaCollector {
|
|
58
|
+
private signals;
|
|
59
|
+
private listening;
|
|
60
|
+
private onMouseMove;
|
|
61
|
+
private onKeyDown;
|
|
62
|
+
private onScroll;
|
|
63
|
+
private onClick;
|
|
64
|
+
private onFocus;
|
|
65
|
+
private onBlur;
|
|
66
|
+
start(): void;
|
|
67
|
+
stop(): void;
|
|
68
|
+
extract(): SessionData;
|
|
69
|
+
debug(): Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/** Send extracted signals to the HUMA API and return a result. */
|
|
72
|
+
declare function callHumaApi(opts: HumaOptions, sessionData: SessionData): Promise<HumaVerifyResult>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* useHUMA JavaScript SDK
|
|
76
|
+
* Privacy-first human verification β no PII, no Cloudflare dependency.
|
|
77
|
+
* https://humaverify.com
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/** Auto-start signal collection (call once, early in your app). */
|
|
81
|
+
declare function init(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Verify the current user as human using collected behavioral signals.
|
|
84
|
+
* Calls init() automatically if not already started.
|
|
85
|
+
*/
|
|
86
|
+
declare function verify(opts: HumaOptions): Promise<HumaVerifyResult>;
|
|
87
|
+
/** Get raw collected signals β useful for debugging. */
|
|
88
|
+
declare function debug(): Record<string, unknown> | null;
|
|
89
|
+
|
|
90
|
+
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, verify };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHUMA β TypeScript Types
|
|
3
|
+
* https://humaverify.com
|
|
4
|
+
*/
|
|
5
|
+
type SessionData = {
|
|
6
|
+
time_on_page_ms?: number;
|
|
7
|
+
key_interval_cv?: number;
|
|
8
|
+
key_count?: number;
|
|
9
|
+
scroll_interval_cv?: number;
|
|
10
|
+
scroll_count?: number;
|
|
11
|
+
mouse_speed_cv?: number;
|
|
12
|
+
mouse_direction_changes?: number;
|
|
13
|
+
mouse_sample_count?: number;
|
|
14
|
+
click_interval_cv?: number;
|
|
15
|
+
click_count?: number;
|
|
16
|
+
tab_switches?: number;
|
|
17
|
+
accessibility?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type HumaVerifyResult = {
|
|
20
|
+
human: boolean;
|
|
21
|
+
confidence: number;
|
|
22
|
+
threshold: number;
|
|
23
|
+
token: string;
|
|
24
|
+
pii_stored: false;
|
|
25
|
+
};
|
|
26
|
+
type HumaError = {
|
|
27
|
+
error: string;
|
|
28
|
+
code?: string;
|
|
29
|
+
upgrade_url?: string;
|
|
30
|
+
retry_after?: number;
|
|
31
|
+
};
|
|
32
|
+
type HumaOptions = {
|
|
33
|
+
/** Your HUMA API key (huma_live_...) */
|
|
34
|
+
apiKey: string;
|
|
35
|
+
/** Your internal user ID */
|
|
36
|
+
userId: string;
|
|
37
|
+
/** Override the API endpoint (default: https://humaverify.com/api/v1/verify) */
|
|
38
|
+
endpoint?: string;
|
|
39
|
+
};
|
|
40
|
+
type HumaState = {
|
|
41
|
+
loading: boolean;
|
|
42
|
+
result: HumaVerifyResult | null;
|
|
43
|
+
error: HumaError | null;
|
|
44
|
+
/** true = verified human, false = bot, null = not yet verified */
|
|
45
|
+
isHuman: boolean | null;
|
|
46
|
+
/** 0β100 confidence score */
|
|
47
|
+
score: number | null;
|
|
48
|
+
/** Call manually to trigger verification */
|
|
49
|
+
verify: () => Promise<HumaVerifyResult | null>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* useHUMA β Core Signal Collector
|
|
54
|
+
* Invisible behavioral biometrics β no PII collected.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
declare class HumaCollector {
|
|
58
|
+
private signals;
|
|
59
|
+
private listening;
|
|
60
|
+
private onMouseMove;
|
|
61
|
+
private onKeyDown;
|
|
62
|
+
private onScroll;
|
|
63
|
+
private onClick;
|
|
64
|
+
private onFocus;
|
|
65
|
+
private onBlur;
|
|
66
|
+
start(): void;
|
|
67
|
+
stop(): void;
|
|
68
|
+
extract(): SessionData;
|
|
69
|
+
debug(): Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/** Send extracted signals to the HUMA API and return a result. */
|
|
72
|
+
declare function callHumaApi(opts: HumaOptions, sessionData: SessionData): Promise<HumaVerifyResult>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* useHUMA JavaScript SDK
|
|
76
|
+
* Privacy-first human verification β no PII, no Cloudflare dependency.
|
|
77
|
+
* https://humaverify.com
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/** Auto-start signal collection (call once, early in your app). */
|
|
81
|
+
declare function init(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Verify the current user as human using collected behavioral signals.
|
|
84
|
+
* Calls init() automatically if not already started.
|
|
85
|
+
*/
|
|
86
|
+
declare function verify(opts: HumaOptions): Promise<HumaVerifyResult>;
|
|
87
|
+
/** Get raw collected signals β useful for debugging. */
|
|
88
|
+
declare function debug(): Record<string, unknown> | null;
|
|
89
|
+
|
|
90
|
+
export { HumaCollector, type HumaError, type HumaOptions, type HumaState, type HumaVerifyResult, type SessionData, callHumaApi, debug, init, verify };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
HumaCollector: () => HumaCollector,
|
|
24
|
+
callHumaApi: () => callHumaApi,
|
|
25
|
+
debug: () => debug,
|
|
26
|
+
init: () => init,
|
|
27
|
+
verify: () => verify
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
|
|
31
|
+
// src/core.ts
|
|
32
|
+
function cv(arr) {
|
|
33
|
+
if (arr.length < 2) return 0;
|
|
34
|
+
const mean = arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
35
|
+
if (mean === 0) return 0;
|
|
36
|
+
const variance = arr.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / arr.length;
|
|
37
|
+
return Math.sqrt(variance) / mean;
|
|
38
|
+
}
|
|
39
|
+
function mouseFeatures(pts) {
|
|
40
|
+
if (pts.length < 5) return { speed_cv: 0, direction_changes: 0, sample_count: 0 };
|
|
41
|
+
const speeds = [];
|
|
42
|
+
const directions = [];
|
|
43
|
+
for (let i = 1; i < pts.length; i++) {
|
|
44
|
+
const dx = pts[i].x - pts[i - 1].x;
|
|
45
|
+
const dy = pts[i].y - pts[i - 1].y;
|
|
46
|
+
const dt = Math.max(pts[i].t - pts[i - 1].t, 1);
|
|
47
|
+
speeds.push(Math.sqrt(dx * dx + dy * dy) / dt);
|
|
48
|
+
directions.push(Math.atan2(dy, dx));
|
|
49
|
+
}
|
|
50
|
+
let dirChanges = 0;
|
|
51
|
+
for (let j = 1; j < directions.length; j++) {
|
|
52
|
+
if (Math.abs(directions[j] - directions[j - 1]) > 0.3) dirChanges++;
|
|
53
|
+
}
|
|
54
|
+
return { speed_cv: cv(speeds), direction_changes: dirChanges, sample_count: pts.length };
|
|
55
|
+
}
|
|
56
|
+
var HumaCollector = class {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.signals = {
|
|
59
|
+
mouse: [],
|
|
60
|
+
keys: [],
|
|
61
|
+
scrolls: [],
|
|
62
|
+
clicks: [],
|
|
63
|
+
focusEvents: [],
|
|
64
|
+
startTime: Date.now(),
|
|
65
|
+
lastKeyTime: null,
|
|
66
|
+
lastScrollTime: null
|
|
67
|
+
};
|
|
68
|
+
this.listening = false;
|
|
69
|
+
this.onMouseMove = (e) => {
|
|
70
|
+
if (this.signals.mouse.length < 200)
|
|
71
|
+
this.signals.mouse.push({ x: e.clientX, y: e.clientY, t: Date.now() });
|
|
72
|
+
};
|
|
73
|
+
this.onKeyDown = () => {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
if (this.signals.lastKeyTime !== null && this.signals.keys.length < 100)
|
|
76
|
+
this.signals.keys.push({ interval: now - this.signals.lastKeyTime });
|
|
77
|
+
this.signals.lastKeyTime = now;
|
|
78
|
+
};
|
|
79
|
+
this.onScroll = () => {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
if (this.signals.lastScrollTime !== null && this.signals.scrolls.length < 50)
|
|
82
|
+
this.signals.scrolls.push({ interval: now - this.signals.lastScrollTime });
|
|
83
|
+
this.signals.lastScrollTime = now;
|
|
84
|
+
};
|
|
85
|
+
this.onClick = () => {
|
|
86
|
+
if (this.signals.clicks.length < 50)
|
|
87
|
+
this.signals.clicks.push({ t: Date.now() });
|
|
88
|
+
};
|
|
89
|
+
this.onFocus = () => this.signals.focusEvents.push({ type: "focus", t: Date.now() });
|
|
90
|
+
this.onBlur = () => this.signals.focusEvents.push({ type: "blur", t: Date.now() });
|
|
91
|
+
}
|
|
92
|
+
start() {
|
|
93
|
+
if (this.listening || typeof window === "undefined") return;
|
|
94
|
+
this.listening = true;
|
|
95
|
+
document.addEventListener("mousemove", this.onMouseMove, { passive: true });
|
|
96
|
+
document.addEventListener("keydown", this.onKeyDown, { passive: true });
|
|
97
|
+
document.addEventListener("scroll", this.onScroll, { passive: true });
|
|
98
|
+
document.addEventListener("click", this.onClick, { passive: true });
|
|
99
|
+
window.addEventListener("focus", this.onFocus, { passive: true });
|
|
100
|
+
window.addEventListener("blur", this.onBlur, { passive: true });
|
|
101
|
+
}
|
|
102
|
+
stop() {
|
|
103
|
+
if (!this.listening) return;
|
|
104
|
+
this.listening = false;
|
|
105
|
+
document.removeEventListener("mousemove", this.onMouseMove);
|
|
106
|
+
document.removeEventListener("keydown", this.onKeyDown);
|
|
107
|
+
document.removeEventListener("scroll", this.onScroll);
|
|
108
|
+
document.removeEventListener("click", this.onClick);
|
|
109
|
+
window.removeEventListener("focus", this.onFocus);
|
|
110
|
+
window.removeEventListener("blur", this.onBlur);
|
|
111
|
+
}
|
|
112
|
+
extract() {
|
|
113
|
+
const { keys, scrolls, clicks, focusEvents, startTime, mouse } = this.signals;
|
|
114
|
+
const m = mouseFeatures(mouse);
|
|
115
|
+
const clickIntervals = [];
|
|
116
|
+
for (let i = 1; i < clicks.length; i++)
|
|
117
|
+
clickIntervals.push(clicks[i].t - clicks[i - 1].t);
|
|
118
|
+
return {
|
|
119
|
+
time_on_page_ms: Date.now() - startTime,
|
|
120
|
+
key_interval_cv: cv(keys.map((k) => k.interval)),
|
|
121
|
+
key_count: keys.length,
|
|
122
|
+
scroll_interval_cv: cv(scrolls.map((s) => s.interval)),
|
|
123
|
+
scroll_count: scrolls.length,
|
|
124
|
+
mouse_speed_cv: m.speed_cv,
|
|
125
|
+
mouse_direction_changes: m.direction_changes,
|
|
126
|
+
mouse_sample_count: m.sample_count,
|
|
127
|
+
click_interval_cv: cv(clickIntervals),
|
|
128
|
+
click_count: clicks.length,
|
|
129
|
+
tab_switches: focusEvents.length
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
debug() {
|
|
133
|
+
return { signals: this.signals, features: this.extract() };
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
async function callHumaApi(opts, sessionData) {
|
|
137
|
+
var _a;
|
|
138
|
+
const endpoint = (_a = opts.endpoint) != null ? _a : "https://humaverify.com/api/v1/verify";
|
|
139
|
+
const res = await fetch(endpoint, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
143
|
+
"Content-Type": "application/json"
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({ userId: opts.userId, sessionData })
|
|
146
|
+
});
|
|
147
|
+
if (!res.ok) {
|
|
148
|
+
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
return res.json();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/index.ts
|
|
155
|
+
var _globalCollector = null;
|
|
156
|
+
function init() {
|
|
157
|
+
if (typeof window === "undefined") return;
|
|
158
|
+
if (_globalCollector) return;
|
|
159
|
+
_globalCollector = new HumaCollector();
|
|
160
|
+
_globalCollector.start();
|
|
161
|
+
}
|
|
162
|
+
async function verify(opts) {
|
|
163
|
+
if (!_globalCollector) init();
|
|
164
|
+
const sessionData = _globalCollector.extract();
|
|
165
|
+
return callHumaApi(opts, sessionData);
|
|
166
|
+
}
|
|
167
|
+
function debug() {
|
|
168
|
+
var _a;
|
|
169
|
+
return (_a = _globalCollector == null ? void 0 : _globalCollector.debug()) != null ? _a : null;
|
|
170
|
+
}
|
|
171
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
172
|
+
0 && (module.exports = {
|
|
173
|
+
HumaCollector,
|
|
174
|
+
callHumaApi,
|
|
175
|
+
debug,
|
|
176
|
+
init,
|
|
177
|
+
verify
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|