@vectoral-labs/browser 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.
@@ -0,0 +1,189 @@
1
+ interface DeviceFingerprintOptions {
2
+ /** Your publishable site key (`pk_live_…`). Scopes the value to your tenant. */
3
+ siteKey: string;
4
+ }
5
+ interface DeviceFingerprint {
6
+ /** `fp_` + 32 hex chars. Send as `device_fingerprint`. */
7
+ fingerprint: string;
8
+ /**
9
+ * False when SubtleCrypto was unavailable (an insecure context) and a
10
+ * non-cryptographic fallback hash was used. The value is still stable and
11
+ * still comparable — it is just cheaper to reverse, so do not treat it as a
12
+ * privacy boundary.
13
+ */
14
+ strong: boolean;
15
+ /**
16
+ * How many of the components actually resolved, out of the total attempted.
17
+ * A very low count means a hardened or headless browser, which is itself
18
+ * worth forwarding.
19
+ */
20
+ coverage: {
21
+ present: number;
22
+ total: number;
23
+ };
24
+ /** The raw components, for debugging. Never send these anywhere. */
25
+ components: Record<string, string | null>;
26
+ }
27
+ /**
28
+ * Compute this browser's device fingerprint.
29
+ *
30
+ * Async because canvas and WebGL probing are, and because SubtleCrypto is.
31
+ * Call it once per page and reuse the result; it does not change within a
32
+ * session.
33
+ *
34
+ * Send the `fingerprint` to YOUR backend, which forwards it as
35
+ * `device_fingerprint`. It is the single highest-value optional field on a
36
+ * registration: one device across many signups is the strongest farm signal
37
+ * that exists, and it is invisible to Vectoral without it.
38
+ */
39
+ declare function deviceFingerprint(opts: DeviceFingerprintOptions): Promise<DeviceFingerprint>;
40
+
41
+ interface AutomationResult {
42
+ /** Noisy-OR combination of the weights that fired, in [0,1]. */
43
+ score: number;
44
+ /** `score >= threshold`. */
45
+ automated: boolean;
46
+ /** Names of the tells that fired, strongest first. */
47
+ reasons: string[];
48
+ /** Every tell that was evaluated, whether it fired or not. */
49
+ signals: Record<string, boolean>;
50
+ /**
51
+ * The environment-inconsistency subscore in [0,1] — the part of the evidence
52
+ * that comes from the browser contradicting itself rather than from an
53
+ * injected global. Send this as `client.fingerprint_anomaly`.
54
+ */
55
+ fingerprintAnomaly: number;
56
+ }
57
+ interface DetectAutomationOptions {
58
+ /** Decision point for `automated`. Default 0.6. */
59
+ threshold?: number;
60
+ }
61
+ /**
62
+ * Evaluate every automation tell against the current browser. Synchronous and
63
+ * cheap — safe to call on every protected action.
64
+ */
65
+ declare function detectAutomation(opts?: DetectAutomationOptions): AutomationResult;
66
+
67
+ interface FormFieldTelemetry {
68
+ pasted: boolean;
69
+ keystrokes: number;
70
+ corrections: number;
71
+ focus_ms: number;
72
+ }
73
+ interface FormTelemetrySnapshot {
74
+ load_to_submit_ms: number;
75
+ fields: Record<string, FormFieldTelemetry>;
76
+ }
77
+ interface FormTelemetryOptions {
78
+ /** Clock, in ms since page load. Defaults to `performance.now()`. */
79
+ now?: () => number;
80
+ /**
81
+ * Field names to report. When omitted, every field that received an event is
82
+ * reported. Never include a field whose NAME would reveal something you would
83
+ * not log — only names are sent, never values.
84
+ */
85
+ fields?: string[];
86
+ }
87
+ /**
88
+ * Accumulates fill behaviour for one form. Transport-free and DOM-free so it
89
+ * can be driven directly — `trackForm()` is the thin DOM wiring over it.
90
+ */
91
+ declare class FormTelemetry {
92
+ private readonly fields;
93
+ private readonly now;
94
+ private readonly allowed;
95
+ constructor(opts?: FormTelemetryOptions);
96
+ private state;
97
+ focus(field: string): void;
98
+ blur(field: string): void;
99
+ keystroke(field: string, key: string): void;
100
+ paste(field: string): void;
101
+ /**
102
+ * Current telemetry. Call at submit. Fields still focused have their
103
+ * in-progress focus time included without ending the focus, so calling this
104
+ * twice is safe.
105
+ */
106
+ snapshot(): FormTelemetrySnapshot;
107
+ }
108
+ interface ListenerTarget {
109
+ addEventListener(type: string, listener: (e: never) => void, options?: {
110
+ capture?: boolean;
111
+ passive?: boolean;
112
+ }): void;
113
+ removeEventListener(type: string, listener: (e: never) => void, options?: unknown): void;
114
+ }
115
+ interface FormTracker {
116
+ /** Telemetry so far. Call at submit and send it as the `form` block. */
117
+ snapshot(): FormTelemetrySnapshot;
118
+ /** Detach listeners. Safe to call twice. */
119
+ stop(): void;
120
+ }
121
+ /**
122
+ * Attach fill tracking to a form.
123
+ *
124
+ * Only field NAMES and the counts above are recorded — never values. Listeners
125
+ * are passive and capturing, so they never block typing.
126
+ *
127
+ * Buttons are not fields: submit, reset, plain and image buttons are skipped, so
128
+ * they never appear in `fields`. For tighter control over what is reported, pass
129
+ * an explicit `fields` allowlist.
130
+ *
131
+ * ```js
132
+ * const tracker = trackForm(document.querySelector("#signup"));
133
+ * // at submit:
134
+ * body.form = tracker.snapshot();
135
+ * ```
136
+ */
137
+ declare function trackForm(form: ListenerTarget, opts?: FormTelemetryOptions): FormTracker;
138
+
139
+ interface SignupSignalsOptions {
140
+ /** Your publishable site key (`pk_live_…`). */
141
+ siteKey: string;
142
+ /** A tracker from `trackForm()`, if you wired one up. */
143
+ form?: FormTracker;
144
+ /** Threshold for `automation.automated`. Does not affect the score. */
145
+ automationThreshold?: number;
146
+ }
147
+ /**
148
+ * Exactly the shape `registrations.score()` accepts for these fields — post it
149
+ * to your own endpoint and spread it into the request there.
150
+ */
151
+ interface SignupSignals {
152
+ device_fingerprint: string;
153
+ client: {
154
+ /** Present only when the browser actually reported `navigator.webdriver`. */
155
+ webdriver?: boolean;
156
+ fingerprint_anomaly: number;
157
+ load_to_submit_ms?: number;
158
+ timezone?: string;
159
+ };
160
+ form?: FormTelemetrySnapshot;
161
+ /** Diagnostics for your own logging. Not part of the wire contract. */
162
+ diagnostics: {
163
+ automation_score: number;
164
+ automation_reasons: string[];
165
+ fingerprint_strong: boolean;
166
+ fingerprint_coverage: {
167
+ present: number;
168
+ total: number;
169
+ };
170
+ };
171
+ }
172
+ /**
173
+ * Collect every signup signal available in the browser.
174
+ *
175
+ * **Post this to your own backend, not to Vectoral.** The API key is a secret
176
+ * and the IP must be read server-side. Your backend should also sanity-check
177
+ * what arrives — clamp `fingerprint_anomaly` to [0,1] and ignore absent fields
178
+ * — rather than trusting the client blindly.
179
+ *
180
+ * ```js
181
+ * const tracker = trackForm(formEl);
182
+ * // at submit:
183
+ * const signals = await signupSignals({ siteKey: "pk_live_…", form: tracker });
184
+ * await fetch("/api/signup", { method: "POST", body: JSON.stringify({ email, signals }) });
185
+ * ```
186
+ */
187
+ declare function signupSignals(opts: SignupSignalsOptions): Promise<SignupSignals>;
188
+
189
+ export { type AutomationResult, type DetectAutomationOptions, type DeviceFingerprint, type DeviceFingerprintOptions, type FormFieldTelemetry, FormTelemetry, type FormTelemetryOptions, type FormTelemetrySnapshot, type FormTracker, type SignupSignals, type SignupSignalsOptions, detectAutomation, deviceFingerprint, signupSignals, trackForm };
@@ -0,0 +1,189 @@
1
+ interface DeviceFingerprintOptions {
2
+ /** Your publishable site key (`pk_live_…`). Scopes the value to your tenant. */
3
+ siteKey: string;
4
+ }
5
+ interface DeviceFingerprint {
6
+ /** `fp_` + 32 hex chars. Send as `device_fingerprint`. */
7
+ fingerprint: string;
8
+ /**
9
+ * False when SubtleCrypto was unavailable (an insecure context) and a
10
+ * non-cryptographic fallback hash was used. The value is still stable and
11
+ * still comparable — it is just cheaper to reverse, so do not treat it as a
12
+ * privacy boundary.
13
+ */
14
+ strong: boolean;
15
+ /**
16
+ * How many of the components actually resolved, out of the total attempted.
17
+ * A very low count means a hardened or headless browser, which is itself
18
+ * worth forwarding.
19
+ */
20
+ coverage: {
21
+ present: number;
22
+ total: number;
23
+ };
24
+ /** The raw components, for debugging. Never send these anywhere. */
25
+ components: Record<string, string | null>;
26
+ }
27
+ /**
28
+ * Compute this browser's device fingerprint.
29
+ *
30
+ * Async because canvas and WebGL probing are, and because SubtleCrypto is.
31
+ * Call it once per page and reuse the result; it does not change within a
32
+ * session.
33
+ *
34
+ * Send the `fingerprint` to YOUR backend, which forwards it as
35
+ * `device_fingerprint`. It is the single highest-value optional field on a
36
+ * registration: one device across many signups is the strongest farm signal
37
+ * that exists, and it is invisible to Vectoral without it.
38
+ */
39
+ declare function deviceFingerprint(opts: DeviceFingerprintOptions): Promise<DeviceFingerprint>;
40
+
41
+ interface AutomationResult {
42
+ /** Noisy-OR combination of the weights that fired, in [0,1]. */
43
+ score: number;
44
+ /** `score >= threshold`. */
45
+ automated: boolean;
46
+ /** Names of the tells that fired, strongest first. */
47
+ reasons: string[];
48
+ /** Every tell that was evaluated, whether it fired or not. */
49
+ signals: Record<string, boolean>;
50
+ /**
51
+ * The environment-inconsistency subscore in [0,1] — the part of the evidence
52
+ * that comes from the browser contradicting itself rather than from an
53
+ * injected global. Send this as `client.fingerprint_anomaly`.
54
+ */
55
+ fingerprintAnomaly: number;
56
+ }
57
+ interface DetectAutomationOptions {
58
+ /** Decision point for `automated`. Default 0.6. */
59
+ threshold?: number;
60
+ }
61
+ /**
62
+ * Evaluate every automation tell against the current browser. Synchronous and
63
+ * cheap — safe to call on every protected action.
64
+ */
65
+ declare function detectAutomation(opts?: DetectAutomationOptions): AutomationResult;
66
+
67
+ interface FormFieldTelemetry {
68
+ pasted: boolean;
69
+ keystrokes: number;
70
+ corrections: number;
71
+ focus_ms: number;
72
+ }
73
+ interface FormTelemetrySnapshot {
74
+ load_to_submit_ms: number;
75
+ fields: Record<string, FormFieldTelemetry>;
76
+ }
77
+ interface FormTelemetryOptions {
78
+ /** Clock, in ms since page load. Defaults to `performance.now()`. */
79
+ now?: () => number;
80
+ /**
81
+ * Field names to report. When omitted, every field that received an event is
82
+ * reported. Never include a field whose NAME would reveal something you would
83
+ * not log — only names are sent, never values.
84
+ */
85
+ fields?: string[];
86
+ }
87
+ /**
88
+ * Accumulates fill behaviour for one form. Transport-free and DOM-free so it
89
+ * can be driven directly — `trackForm()` is the thin DOM wiring over it.
90
+ */
91
+ declare class FormTelemetry {
92
+ private readonly fields;
93
+ private readonly now;
94
+ private readonly allowed;
95
+ constructor(opts?: FormTelemetryOptions);
96
+ private state;
97
+ focus(field: string): void;
98
+ blur(field: string): void;
99
+ keystroke(field: string, key: string): void;
100
+ paste(field: string): void;
101
+ /**
102
+ * Current telemetry. Call at submit. Fields still focused have their
103
+ * in-progress focus time included without ending the focus, so calling this
104
+ * twice is safe.
105
+ */
106
+ snapshot(): FormTelemetrySnapshot;
107
+ }
108
+ interface ListenerTarget {
109
+ addEventListener(type: string, listener: (e: never) => void, options?: {
110
+ capture?: boolean;
111
+ passive?: boolean;
112
+ }): void;
113
+ removeEventListener(type: string, listener: (e: never) => void, options?: unknown): void;
114
+ }
115
+ interface FormTracker {
116
+ /** Telemetry so far. Call at submit and send it as the `form` block. */
117
+ snapshot(): FormTelemetrySnapshot;
118
+ /** Detach listeners. Safe to call twice. */
119
+ stop(): void;
120
+ }
121
+ /**
122
+ * Attach fill tracking to a form.
123
+ *
124
+ * Only field NAMES and the counts above are recorded — never values. Listeners
125
+ * are passive and capturing, so they never block typing.
126
+ *
127
+ * Buttons are not fields: submit, reset, plain and image buttons are skipped, so
128
+ * they never appear in `fields`. For tighter control over what is reported, pass
129
+ * an explicit `fields` allowlist.
130
+ *
131
+ * ```js
132
+ * const tracker = trackForm(document.querySelector("#signup"));
133
+ * // at submit:
134
+ * body.form = tracker.snapshot();
135
+ * ```
136
+ */
137
+ declare function trackForm(form: ListenerTarget, opts?: FormTelemetryOptions): FormTracker;
138
+
139
+ interface SignupSignalsOptions {
140
+ /** Your publishable site key (`pk_live_…`). */
141
+ siteKey: string;
142
+ /** A tracker from `trackForm()`, if you wired one up. */
143
+ form?: FormTracker;
144
+ /** Threshold for `automation.automated`. Does not affect the score. */
145
+ automationThreshold?: number;
146
+ }
147
+ /**
148
+ * Exactly the shape `registrations.score()` accepts for these fields — post it
149
+ * to your own endpoint and spread it into the request there.
150
+ */
151
+ interface SignupSignals {
152
+ device_fingerprint: string;
153
+ client: {
154
+ /** Present only when the browser actually reported `navigator.webdriver`. */
155
+ webdriver?: boolean;
156
+ fingerprint_anomaly: number;
157
+ load_to_submit_ms?: number;
158
+ timezone?: string;
159
+ };
160
+ form?: FormTelemetrySnapshot;
161
+ /** Diagnostics for your own logging. Not part of the wire contract. */
162
+ diagnostics: {
163
+ automation_score: number;
164
+ automation_reasons: string[];
165
+ fingerprint_strong: boolean;
166
+ fingerprint_coverage: {
167
+ present: number;
168
+ total: number;
169
+ };
170
+ };
171
+ }
172
+ /**
173
+ * Collect every signup signal available in the browser.
174
+ *
175
+ * **Post this to your own backend, not to Vectoral.** The API key is a secret
176
+ * and the IP must be read server-side. Your backend should also sanity-check
177
+ * what arrives — clamp `fingerprint_anomaly` to [0,1] and ignore absent fields
178
+ * — rather than trusting the client blindly.
179
+ *
180
+ * ```js
181
+ * const tracker = trackForm(formEl);
182
+ * // at submit:
183
+ * const signals = await signupSignals({ siteKey: "pk_live_…", form: tracker });
184
+ * await fetch("/api/signup", { method: "POST", body: JSON.stringify({ email, signals }) });
185
+ * ```
186
+ */
187
+ declare function signupSignals(opts: SignupSignalsOptions): Promise<SignupSignals>;
188
+
189
+ export { type AutomationResult, type DetectAutomationOptions, type DeviceFingerprint, type DeviceFingerprintOptions, type FormFieldTelemetry, FormTelemetry, type FormTelemetryOptions, type FormTelemetrySnapshot, type FormTracker, type SignupSignals, type SignupSignalsOptions, detectAutomation, deviceFingerprint, signupSignals, trackForm };