guardian-risk-browser 0.1.1 → 0.2.1

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 CHANGED
@@ -6,31 +6,44 @@
6
6
  npm install guardian-risk guardian-risk-browser
7
7
  ```
8
8
 
9
- > **Stub package** API may change before `1.0.0`.
9
+ Browser-side behavioral signal collection for [guardian-risk](https://www.npmjs.com/package/guardian-risk).
10
10
 
11
- Browser integration for [guardian-risk](https://www.npmjs.com/package/guardian-risk). Collects client-side behavioral and fingerprint signals.
12
-
13
- ## Planned signals
11
+ ## Signals
14
12
 
15
13
  | Signal | Source |
16
14
  |--------|--------|
17
- | `mouseLinearity` | Mouse movement patterns |
18
- | `keystrokeInterval` | Typing rhythm |
19
- | `headlessUA` | User agent heuristics |
20
- | `canvasFingerprint` | Canvas hash |
15
+ | `mouseLinearity` | Pointer movement linearity (0–1) |
16
+ | `hasPointerActivity` | Mouse or touch activity detected |
17
+ | `keystrokeCount` | Key events in sample window |
18
+ | `headlessUA` | User-agent heuristics |
21
19
 
22
- ## Usage (stub)
20
+ ## Usage
23
21
 
24
22
  ```typescript
25
23
  import { Guardian } from 'guardian-risk';
26
- import { browserPlugin, collectSignals } from 'guardian-risk-browser';
24
+ import { browserPlugin, BrowserCollector } from 'guardian-risk-browser';
27
25
 
28
26
  const guardian = new Guardian().use(browserPlugin());
29
27
 
30
- collectSignals(guardian);
31
- const report = guardian.analyze();
28
+ const collector = new BrowserCollector();
29
+ const stop = collector.start();
30
+ // ... user interacts ...
31
+ collector.applyTo(guardian);
32
+ stop();
32
33
  ```
33
34
 
34
- ## Status
35
+ ## Security notes
36
+
37
+ - **All browser signals are client-controlled** — attackers can spoof or omit them.
38
+ - Use for **defense in depth** only; never as sole auth or blocking factor.
39
+ - Pair with server-side signals (IP rate limits, session age, VPN checks).
40
+ - Mobile users: `hasPointerActivity` includes touch events.
41
+
42
+ ## API
43
+
44
+ - `browserPlugin()` — registers default behavioral rules
45
+ - `BrowserCollector` — tracks pointer/keyboard activity
46
+ - `collectSignals(guardian, options?)` — timed sampling helper
47
+ - `computeMouseLinearity(points)` — standalone metric
35
48
 
36
- Not yet published. Implementation in progress.
49
+ See [SECURITY.md](../../SECURITY.md).
package/dist/index.cjs CHANGED
@@ -1,17 +1,190 @@
1
1
  'use strict';
2
2
 
3
+ // src/collector.ts
4
+ function computeMouseLinearity(points) {
5
+ if (points.length < 3) {
6
+ return 0;
7
+ }
8
+ const first = points[0];
9
+ const last = points[points.length - 1];
10
+ const straightDistance = distance(first, last);
11
+ if (straightDistance === 0) {
12
+ return 1;
13
+ }
14
+ let pathDistance = 0;
15
+ for (let i = 1; i < points.length; i++) {
16
+ pathDistance += distance(points[i - 1], points[i]);
17
+ }
18
+ if (pathDistance === 0) {
19
+ return 0;
20
+ }
21
+ const linearity = straightDistance / pathDistance;
22
+ return Math.min(1, Math.max(0, linearity));
23
+ }
24
+ function distance(a, b) {
25
+ const dx = b.x - a.x;
26
+ const dy = b.y - a.y;
27
+ return Math.sqrt(dx * dx + dy * dy);
28
+ }
29
+ var BrowserCollector = class {
30
+ maxMouseSamples;
31
+ target;
32
+ mousePoints = [];
33
+ clickTimes = [];
34
+ keyStrokeCount = 0;
35
+ pointerActivity = false;
36
+ startedAt = 0;
37
+ listeners = [];
38
+ constructor(options = {}) {
39
+ this.maxMouseSamples = options.maxMouseSamples ?? 100;
40
+ this.target = options.target ?? getDefaultTarget();
41
+ }
42
+ /** Start listening for user events. Returns a stop function. */
43
+ start() {
44
+ if (!this.target) {
45
+ return () => {
46
+ };
47
+ }
48
+ this.startedAt = Date.now();
49
+ const onMouseMove = (event) => {
50
+ const e = event;
51
+ this.mousePoints.push({ x: e.clientX, y: e.clientY, t: Date.now() });
52
+ if (this.mousePoints.length > this.maxMouseSamples) {
53
+ this.mousePoints.shift();
54
+ }
55
+ };
56
+ const onClick = () => {
57
+ this.clickTimes.push(Date.now());
58
+ };
59
+ const onKeyDown = () => {
60
+ this.keyStrokeCount += 1;
61
+ };
62
+ const onPointer = () => {
63
+ this.pointerActivity = true;
64
+ };
65
+ this.addListener("mousemove", onMouseMove);
66
+ this.addListener("click", onClick);
67
+ this.addListener("keydown", onKeyDown);
68
+ this.addListener("pointerdown", onPointer);
69
+ this.addListener("touchstart", onPointer);
70
+ return () => this.stop();
71
+ }
72
+ stop() {
73
+ if (!this.target) {
74
+ return;
75
+ }
76
+ for (const { type, handler } of this.listeners) {
77
+ this.target.removeEventListener(type, handler);
78
+ }
79
+ this.listeners = [];
80
+ }
81
+ /** Build a snapshot of collected signals. */
82
+ getSnapshot() {
83
+ const intervals = [];
84
+ for (let i = 1; i < this.clickTimes.length; i++) {
85
+ intervals.push(this.clickTimes[i] - this.clickTimes[i - 1]);
86
+ }
87
+ const avgClickIntervalMs = intervals.length > 0 ? intervals.reduce((sum, n) => sum + n, 0) / intervals.length : 0;
88
+ return {
89
+ mouseSampleCount: this.mousePoints.length,
90
+ mouseLinearity: computeMouseLinearity(this.mousePoints),
91
+ clickCount: this.clickTimes.length,
92
+ avgClickIntervalMs,
93
+ keyStrokeCount: this.keyStrokeCount,
94
+ hasPointerActivity: this.pointerActivity || this.mousePoints.length > 0,
95
+ collectionDurationMs: this.startedAt > 0 ? Date.now() - this.startedAt : 0
96
+ };
97
+ }
98
+ /** Push collected signals onto a Guardian instance. */
99
+ applyTo(guardian) {
100
+ const snapshot = this.getSnapshot();
101
+ return guardian.signal("mouseSampleCount", snapshot.mouseSampleCount).signal("mouseLinearity", round(snapshot.mouseLinearity, 4)).signal("clickCount", snapshot.clickCount).signal("avgClickIntervalMs", Math.round(snapshot.avgClickIntervalMs)).signal("keyStrokeCount", snapshot.keyStrokeCount).signal("hasPointerActivity", snapshot.hasPointerActivity).signal("collectionDurationMs", snapshot.collectionDurationMs).signal("signalSource", "browser");
102
+ }
103
+ addListener(type, handler) {
104
+ if (!this.target) {
105
+ return;
106
+ }
107
+ this.target.addEventListener(type, handler, { passive: true });
108
+ this.listeners.push({ type, handler });
109
+ }
110
+ };
111
+ function getDefaultTarget() {
112
+ if (typeof document !== "undefined") {
113
+ return document;
114
+ }
115
+ return null;
116
+ }
117
+ function round(value, digits) {
118
+ const factor = 10 ** digits;
119
+ return Math.round(value * factor) / factor;
120
+ }
121
+
3
122
  // src/index.ts
4
123
  function browserPlugin(options = {}) {
5
- const { container = "document" } = options;
124
+ const { autoStart = false, ...collectorOptions } = options;
6
125
  return {
7
126
  name: "guardian-risk-browser",
8
- install(_guardian) {
127
+ install(guardian) {
128
+ guardian.ruleGroup({
129
+ name: "behavior",
130
+ maxScore: 50,
131
+ rules: [
132
+ {
133
+ name: "LinearMouse",
134
+ reason: "Mouse movement is unnaturally linear",
135
+ when: (s) => s.mouseLinearity > 0.92,
136
+ score: 25
137
+ },
138
+ {
139
+ name: "NoMouseActivity",
140
+ reason: "No pointer or mouse activity detected before submit",
141
+ when: (s) => s.mouseSampleCount === 0 && s.hasPointerActivity !== true,
142
+ score: 20
143
+ },
144
+ {
145
+ name: "RoboticClicks",
146
+ reason: "Click timing is unnaturally regular",
147
+ when: (s) => {
148
+ const interval = s.avgClickIntervalMs;
149
+ const count = s.clickCount;
150
+ return count >= 3 && interval > 0 && interval < 50;
151
+ },
152
+ score: 30
153
+ }
154
+ ]
155
+ });
156
+ if (autoStart && typeof document !== "undefined") {
157
+ const collector = new BrowserCollector(collectorOptions);
158
+ const stop = collector.start();
159
+ guardian.beforeAnalyze(({ guardian: g }) => {
160
+ collector.applyTo(g);
161
+ stop();
162
+ });
163
+ }
9
164
  }
10
165
  };
11
166
  }
12
- function collectSignals(guardian) {
13
- return guardian.signal("signalSource", "browser").signal("browserPlugin", "stub");
167
+ function createBrowserCollector(options = {}) {
168
+ return new BrowserCollector(options);
169
+ }
170
+ function collectSignals(guardian, options = {}) {
171
+ const { durationMs = 0, ...collectorOptions } = options;
172
+ const collector = new BrowserCollector(collectorOptions);
173
+ const stop = collector.start();
174
+ const finish = () => {
175
+ stop();
176
+ return collector.applyTo(guardian);
177
+ };
178
+ if (durationMs <= 0) {
179
+ return Promise.resolve(finish());
180
+ }
181
+ return new Promise((resolve) => {
182
+ setTimeout(() => resolve(finish()), durationMs);
183
+ });
14
184
  }
15
185
 
186
+ exports.BrowserCollector = BrowserCollector;
16
187
  exports.browserPlugin = browserPlugin;
17
188
  exports.collectSignals = collectSignals;
189
+ exports.computeMouseLinearity = computeMouseLinearity;
190
+ exports.createBrowserCollector = createBrowserCollector;
package/dist/index.d.cts CHANGED
@@ -1,21 +1,75 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
2
  import { Plugin } from 'guardian-risk';
3
3
 
4
- /** Options for the browser plugin (stub). */
5
- interface BrowserPluginOptions {
6
- /** DOM element to attach behavioral collectors. Defaults to document. */
7
- readonly container?: string;
4
+ /** Collected behavioral signals from the browser. */
5
+ interface BrowserSignalSnapshot {
6
+ readonly mouseSampleCount: number;
7
+ readonly mouseLinearity: number;
8
+ readonly clickCount: number;
9
+ readonly avgClickIntervalMs: number;
10
+ readonly keyStrokeCount: number;
11
+ readonly hasPointerActivity: boolean;
12
+ readonly collectionDurationMs: number;
8
13
  }
14
+ /** Options for browser signal collection. */
15
+ interface BrowserCollectorOptions {
16
+ /** Max mouse samples to retain. */
17
+ readonly maxMouseSamples?: number;
18
+ /** Target element or document root. */
19
+ readonly target?: EventTarget;
20
+ }
21
+ interface Point {
22
+ x: number;
23
+ y: number;
24
+ t: number;
25
+ }
26
+ /**
27
+ * Compute how linear a mouse path is (0 = erratic, 1 = perfectly straight).
28
+ * High values often indicate scripted movement.
29
+ */
30
+ declare function computeMouseLinearity(points: readonly Point[]): number;
9
31
  /**
10
- * Browser plugin for guardian-risk.
11
- *
12
- * @stub Future versions will collect mouse, keyboard, and fingerprint signals
13
- * in the page and add them to Guardian before analysis.
32
+ * Collects mouse, click, and keyboard behavioral signals in the browser.
33
+ */
34
+ declare class BrowserCollector {
35
+ private readonly maxMouseSamples;
36
+ private readonly target;
37
+ private readonly mousePoints;
38
+ private readonly clickTimes;
39
+ private keyStrokeCount;
40
+ private pointerActivity;
41
+ private startedAt;
42
+ private listeners;
43
+ constructor(options?: BrowserCollectorOptions);
44
+ /** Start listening for user events. Returns a stop function. */
45
+ start(): () => void;
46
+ stop(): void;
47
+ /** Build a snapshot of collected signals. */
48
+ getSnapshot(): BrowserSignalSnapshot;
49
+ /** Push collected signals onto a Guardian instance. */
50
+ applyTo(guardian: guardian_risk.Guardian): guardian_risk.Guardian;
51
+ private addListener;
52
+ }
53
+
54
+ /** Options for the browser plugin. */
55
+ interface BrowserPluginOptions extends BrowserCollectorOptions {
56
+ /** Auto-start collection on install (browser only). */
57
+ readonly autoStart?: boolean;
58
+ }
59
+ /**
60
+ * Browser plugin — behavioral rules with a capped `behavior` group.
61
+ * Use {@link createBrowserCollector} to collect signals before analyze.
14
62
  */
15
63
  declare function browserPlugin(options?: BrowserPluginOptions): Plugin;
16
64
  /**
17
- * @stub Future helper to start client-side signal collection.
65
+ * Create a browser signal collector. Call `start()`, then `applyTo(guardian)` before analyze.
66
+ */
67
+ declare function createBrowserCollector(options?: BrowserCollectorOptions): BrowserCollector;
68
+ /**
69
+ * Collect signals then apply to Guardian. Optional `durationMs` to wait before sampling.
18
70
  */
19
- declare function collectSignals(guardian: guardian_risk.Guardian): guardian_risk.Guardian;
71
+ declare function collectSignals(guardian: guardian_risk.Guardian, options?: BrowserCollectorOptions & {
72
+ durationMs?: number;
73
+ }): Promise<guardian_risk.Guardian>;
20
74
 
21
- export { type BrowserPluginOptions, browserPlugin, collectSignals };
75
+ export { BrowserCollector, type BrowserCollectorOptions, type BrowserPluginOptions, type BrowserSignalSnapshot, browserPlugin, collectSignals, computeMouseLinearity, createBrowserCollector };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,75 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
2
  import { Plugin } from 'guardian-risk';
3
3
 
4
- /** Options for the browser plugin (stub). */
5
- interface BrowserPluginOptions {
6
- /** DOM element to attach behavioral collectors. Defaults to document. */
7
- readonly container?: string;
4
+ /** Collected behavioral signals from the browser. */
5
+ interface BrowserSignalSnapshot {
6
+ readonly mouseSampleCount: number;
7
+ readonly mouseLinearity: number;
8
+ readonly clickCount: number;
9
+ readonly avgClickIntervalMs: number;
10
+ readonly keyStrokeCount: number;
11
+ readonly hasPointerActivity: boolean;
12
+ readonly collectionDurationMs: number;
8
13
  }
14
+ /** Options for browser signal collection. */
15
+ interface BrowserCollectorOptions {
16
+ /** Max mouse samples to retain. */
17
+ readonly maxMouseSamples?: number;
18
+ /** Target element or document root. */
19
+ readonly target?: EventTarget;
20
+ }
21
+ interface Point {
22
+ x: number;
23
+ y: number;
24
+ t: number;
25
+ }
26
+ /**
27
+ * Compute how linear a mouse path is (0 = erratic, 1 = perfectly straight).
28
+ * High values often indicate scripted movement.
29
+ */
30
+ declare function computeMouseLinearity(points: readonly Point[]): number;
9
31
  /**
10
- * Browser plugin for guardian-risk.
11
- *
12
- * @stub Future versions will collect mouse, keyboard, and fingerprint signals
13
- * in the page and add them to Guardian before analysis.
32
+ * Collects mouse, click, and keyboard behavioral signals in the browser.
33
+ */
34
+ declare class BrowserCollector {
35
+ private readonly maxMouseSamples;
36
+ private readonly target;
37
+ private readonly mousePoints;
38
+ private readonly clickTimes;
39
+ private keyStrokeCount;
40
+ private pointerActivity;
41
+ private startedAt;
42
+ private listeners;
43
+ constructor(options?: BrowserCollectorOptions);
44
+ /** Start listening for user events. Returns a stop function. */
45
+ start(): () => void;
46
+ stop(): void;
47
+ /** Build a snapshot of collected signals. */
48
+ getSnapshot(): BrowserSignalSnapshot;
49
+ /** Push collected signals onto a Guardian instance. */
50
+ applyTo(guardian: guardian_risk.Guardian): guardian_risk.Guardian;
51
+ private addListener;
52
+ }
53
+
54
+ /** Options for the browser plugin. */
55
+ interface BrowserPluginOptions extends BrowserCollectorOptions {
56
+ /** Auto-start collection on install (browser only). */
57
+ readonly autoStart?: boolean;
58
+ }
59
+ /**
60
+ * Browser plugin — behavioral rules with a capped `behavior` group.
61
+ * Use {@link createBrowserCollector} to collect signals before analyze.
14
62
  */
15
63
  declare function browserPlugin(options?: BrowserPluginOptions): Plugin;
16
64
  /**
17
- * @stub Future helper to start client-side signal collection.
65
+ * Create a browser signal collector. Call `start()`, then `applyTo(guardian)` before analyze.
66
+ */
67
+ declare function createBrowserCollector(options?: BrowserCollectorOptions): BrowserCollector;
68
+ /**
69
+ * Collect signals then apply to Guardian. Optional `durationMs` to wait before sampling.
18
70
  */
19
- declare function collectSignals(guardian: guardian_risk.Guardian): guardian_risk.Guardian;
71
+ declare function collectSignals(guardian: guardian_risk.Guardian, options?: BrowserCollectorOptions & {
72
+ durationMs?: number;
73
+ }): Promise<guardian_risk.Guardian>;
20
74
 
21
- export { type BrowserPluginOptions, browserPlugin, collectSignals };
75
+ export { BrowserCollector, type BrowserCollectorOptions, type BrowserPluginOptions, type BrowserSignalSnapshot, browserPlugin, collectSignals, computeMouseLinearity, createBrowserCollector };
package/dist/index.js CHANGED
@@ -1,14 +1,184 @@
1
+ // src/collector.ts
2
+ function computeMouseLinearity(points) {
3
+ if (points.length < 3) {
4
+ return 0;
5
+ }
6
+ const first = points[0];
7
+ const last = points[points.length - 1];
8
+ const straightDistance = distance(first, last);
9
+ if (straightDistance === 0) {
10
+ return 1;
11
+ }
12
+ let pathDistance = 0;
13
+ for (let i = 1; i < points.length; i++) {
14
+ pathDistance += distance(points[i - 1], points[i]);
15
+ }
16
+ if (pathDistance === 0) {
17
+ return 0;
18
+ }
19
+ const linearity = straightDistance / pathDistance;
20
+ return Math.min(1, Math.max(0, linearity));
21
+ }
22
+ function distance(a, b) {
23
+ const dx = b.x - a.x;
24
+ const dy = b.y - a.y;
25
+ return Math.sqrt(dx * dx + dy * dy);
26
+ }
27
+ var BrowserCollector = class {
28
+ maxMouseSamples;
29
+ target;
30
+ mousePoints = [];
31
+ clickTimes = [];
32
+ keyStrokeCount = 0;
33
+ pointerActivity = false;
34
+ startedAt = 0;
35
+ listeners = [];
36
+ constructor(options = {}) {
37
+ this.maxMouseSamples = options.maxMouseSamples ?? 100;
38
+ this.target = options.target ?? getDefaultTarget();
39
+ }
40
+ /** Start listening for user events. Returns a stop function. */
41
+ start() {
42
+ if (!this.target) {
43
+ return () => {
44
+ };
45
+ }
46
+ this.startedAt = Date.now();
47
+ const onMouseMove = (event) => {
48
+ const e = event;
49
+ this.mousePoints.push({ x: e.clientX, y: e.clientY, t: Date.now() });
50
+ if (this.mousePoints.length > this.maxMouseSamples) {
51
+ this.mousePoints.shift();
52
+ }
53
+ };
54
+ const onClick = () => {
55
+ this.clickTimes.push(Date.now());
56
+ };
57
+ const onKeyDown = () => {
58
+ this.keyStrokeCount += 1;
59
+ };
60
+ const onPointer = () => {
61
+ this.pointerActivity = true;
62
+ };
63
+ this.addListener("mousemove", onMouseMove);
64
+ this.addListener("click", onClick);
65
+ this.addListener("keydown", onKeyDown);
66
+ this.addListener("pointerdown", onPointer);
67
+ this.addListener("touchstart", onPointer);
68
+ return () => this.stop();
69
+ }
70
+ stop() {
71
+ if (!this.target) {
72
+ return;
73
+ }
74
+ for (const { type, handler } of this.listeners) {
75
+ this.target.removeEventListener(type, handler);
76
+ }
77
+ this.listeners = [];
78
+ }
79
+ /** Build a snapshot of collected signals. */
80
+ getSnapshot() {
81
+ const intervals = [];
82
+ for (let i = 1; i < this.clickTimes.length; i++) {
83
+ intervals.push(this.clickTimes[i] - this.clickTimes[i - 1]);
84
+ }
85
+ const avgClickIntervalMs = intervals.length > 0 ? intervals.reduce((sum, n) => sum + n, 0) / intervals.length : 0;
86
+ return {
87
+ mouseSampleCount: this.mousePoints.length,
88
+ mouseLinearity: computeMouseLinearity(this.mousePoints),
89
+ clickCount: this.clickTimes.length,
90
+ avgClickIntervalMs,
91
+ keyStrokeCount: this.keyStrokeCount,
92
+ hasPointerActivity: this.pointerActivity || this.mousePoints.length > 0,
93
+ collectionDurationMs: this.startedAt > 0 ? Date.now() - this.startedAt : 0
94
+ };
95
+ }
96
+ /** Push collected signals onto a Guardian instance. */
97
+ applyTo(guardian) {
98
+ const snapshot = this.getSnapshot();
99
+ return guardian.signal("mouseSampleCount", snapshot.mouseSampleCount).signal("mouseLinearity", round(snapshot.mouseLinearity, 4)).signal("clickCount", snapshot.clickCount).signal("avgClickIntervalMs", Math.round(snapshot.avgClickIntervalMs)).signal("keyStrokeCount", snapshot.keyStrokeCount).signal("hasPointerActivity", snapshot.hasPointerActivity).signal("collectionDurationMs", snapshot.collectionDurationMs).signal("signalSource", "browser");
100
+ }
101
+ addListener(type, handler) {
102
+ if (!this.target) {
103
+ return;
104
+ }
105
+ this.target.addEventListener(type, handler, { passive: true });
106
+ this.listeners.push({ type, handler });
107
+ }
108
+ };
109
+ function getDefaultTarget() {
110
+ if (typeof document !== "undefined") {
111
+ return document;
112
+ }
113
+ return null;
114
+ }
115
+ function round(value, digits) {
116
+ const factor = 10 ** digits;
117
+ return Math.round(value * factor) / factor;
118
+ }
119
+
1
120
  // src/index.ts
2
121
  function browserPlugin(options = {}) {
3
- const { container = "document" } = options;
122
+ const { autoStart = false, ...collectorOptions } = options;
4
123
  return {
5
124
  name: "guardian-risk-browser",
6
- install(_guardian) {
125
+ install(guardian) {
126
+ guardian.ruleGroup({
127
+ name: "behavior",
128
+ maxScore: 50,
129
+ rules: [
130
+ {
131
+ name: "LinearMouse",
132
+ reason: "Mouse movement is unnaturally linear",
133
+ when: (s) => s.mouseLinearity > 0.92,
134
+ score: 25
135
+ },
136
+ {
137
+ name: "NoMouseActivity",
138
+ reason: "No pointer or mouse activity detected before submit",
139
+ when: (s) => s.mouseSampleCount === 0 && s.hasPointerActivity !== true,
140
+ score: 20
141
+ },
142
+ {
143
+ name: "RoboticClicks",
144
+ reason: "Click timing is unnaturally regular",
145
+ when: (s) => {
146
+ const interval = s.avgClickIntervalMs;
147
+ const count = s.clickCount;
148
+ return count >= 3 && interval > 0 && interval < 50;
149
+ },
150
+ score: 30
151
+ }
152
+ ]
153
+ });
154
+ if (autoStart && typeof document !== "undefined") {
155
+ const collector = new BrowserCollector(collectorOptions);
156
+ const stop = collector.start();
157
+ guardian.beforeAnalyze(({ guardian: g }) => {
158
+ collector.applyTo(g);
159
+ stop();
160
+ });
161
+ }
7
162
  }
8
163
  };
9
164
  }
10
- function collectSignals(guardian) {
11
- return guardian.signal("signalSource", "browser").signal("browserPlugin", "stub");
165
+ function createBrowserCollector(options = {}) {
166
+ return new BrowserCollector(options);
167
+ }
168
+ function collectSignals(guardian, options = {}) {
169
+ const { durationMs = 0, ...collectorOptions } = options;
170
+ const collector = new BrowserCollector(collectorOptions);
171
+ const stop = collector.start();
172
+ const finish = () => {
173
+ stop();
174
+ return collector.applyTo(guardian);
175
+ };
176
+ if (durationMs <= 0) {
177
+ return Promise.resolve(finish());
178
+ }
179
+ return new Promise((resolve) => {
180
+ setTimeout(() => resolve(finish()), durationMs);
181
+ });
12
182
  }
13
183
 
14
- export { browserPlugin, collectSignals };
184
+ export { BrowserCollector, browserPlugin, collectSignals, computeMouseLinearity, createBrowserCollector };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-risk-browser",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Browser plugin for guardian-risk — collects behavioral and fingerprint signals",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -54,15 +54,17 @@
54
54
  "access": "public"
55
55
  },
56
56
  "peerDependencies": {
57
- "guardian-risk": "^0.2.0"
57
+ "guardian-risk": "^0.3.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "tsup": "^8.3.5",
61
61
  "typescript": "^5.7.2",
62
- "guardian-risk": "0.2.1"
62
+ "vitest": "^4.1.9",
63
+ "guardian-risk": "0.3.1"
63
64
  },
64
65
  "scripts": {
65
66
  "build": "tsup",
67
+ "test": "vitest run",
66
68
  "typecheck": "tsc --noEmit"
67
69
  }
68
70
  }