@niksbanna/bot-detector 1.0.2 → 1.0.4

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.
Files changed (38) hide show
  1. package/README.md +22 -0
  2. package/dist/bot-detector.cjs.js +115 -75
  3. package/dist/bot-detector.cjs.js.map +2 -2
  4. package/dist/bot-detector.esm.js +115 -75
  5. package/dist/bot-detector.esm.js.map +2 -2
  6. package/dist/bot-detector.iife.js +115 -75
  7. package/dist/bot-detector.iife.js.map +2 -2
  8. package/dist/bot-detector.iife.min.js +3 -1
  9. package/package.json +1 -2
  10. package/src/core/BotDetector.js +0 -284
  11. package/src/core/ScoringEngine.js +0 -134
  12. package/src/core/Signal.js +0 -181
  13. package/src/core/VerdictEngine.js +0 -132
  14. package/src/index.js +0 -273
  15. package/src/signals/automation/PhantomJSSignal.js +0 -137
  16. package/src/signals/automation/PlaywrightSignal.js +0 -129
  17. package/src/signals/automation/PuppeteerSignal.js +0 -122
  18. package/src/signals/automation/SeleniumSignal.js +0 -151
  19. package/src/signals/automation/index.js +0 -8
  20. package/src/signals/behavior/InteractionTimingSignal.js +0 -170
  21. package/src/signals/behavior/KeyboardPatternSignal.js +0 -235
  22. package/src/signals/behavior/MouseMovementSignal.js +0 -215
  23. package/src/signals/behavior/ScrollBehaviorSignal.js +0 -236
  24. package/src/signals/behavior/index.js +0 -8
  25. package/src/signals/environment/HeadlessSignal.js +0 -97
  26. package/src/signals/environment/NavigatorAnomalySignal.js +0 -117
  27. package/src/signals/environment/PermissionsSignal.js +0 -76
  28. package/src/signals/environment/WebDriverSignal.js +0 -58
  29. package/src/signals/environment/index.js +0 -8
  30. package/src/signals/fingerprint/AudioContextSignal.js +0 -158
  31. package/src/signals/fingerprint/CanvasSignal.js +0 -133
  32. package/src/signals/fingerprint/PluginsSignal.js +0 -106
  33. package/src/signals/fingerprint/ScreenSignal.js +0 -157
  34. package/src/signals/fingerprint/WebGLSignal.js +0 -146
  35. package/src/signals/fingerprint/index.js +0 -9
  36. package/src/signals/timing/DOMContentTimingSignal.js +0 -159
  37. package/src/signals/timing/PageLoadSignal.js +0 -165
  38. package/src/signals/timing/index.js +0 -6
@@ -1,134 +0,0 @@
1
- /**
2
- * @fileoverview Scoring engine that calculates bot probability from signals.
3
- */
4
-
5
- /**
6
- * Calculates a weighted score from signal results.
7
- * Score represents the probability of the visitor being a bot.
8
- */
9
- class ScoringEngine {
10
- /**
11
- * Creates a new ScoringEngine instance.
12
- * @param {Object} [options={}] - Configuration options
13
- * @param {Object.<string, number>} [options.weightOverrides={}] - Override weights by signal ID
14
- * @param {number} [options.maxScore=100] - Maximum possible score
15
- */
16
- constructor(options = {}) {
17
- this.weightOverrides = options.weightOverrides || {};
18
- this.maxScore = options.maxScore || 100;
19
- this._results = new Map();
20
- }
21
-
22
- /**
23
- * Get the effective weight for a signal.
24
- * @param {string} signalId - Signal identifier
25
- * @param {number} defaultWeight - Default weight from signal definition
26
- * @returns {number}
27
- */
28
- getWeight(signalId, defaultWeight) {
29
- return this.weightOverrides[signalId] ?? defaultWeight;
30
- }
31
-
32
- /**
33
- * Add a signal result to the scoring calculation.
34
- * @param {string} signalId - Signal identifier
35
- * @param {Object} result - Signal detection result
36
- * @param {boolean} result.triggered - Whether signal was triggered
37
- * @param {number} result.confidence - Confidence level (0-1)
38
- * @param {number} weight - Signal weight (0.1-1.0)
39
- */
40
- addResult(signalId, result, weight) {
41
- const effectiveWeight = this.getWeight(signalId, weight);
42
- this._results.set(signalId, {
43
- ...result,
44
- weight: effectiveWeight,
45
- contribution: result.triggered ? effectiveWeight * result.confidence : 0,
46
- });
47
- }
48
-
49
- /**
50
- * Calculate the final score from all added results.
51
- * Formula: (sum of contributions / sum of weights) * maxScore
52
- *
53
- * @returns {number} Score between 0 and maxScore
54
- */
55
- calculate() {
56
- if (this._results.size === 0) {
57
- return 0;
58
- }
59
-
60
- let totalWeight = 0;
61
- let totalContribution = 0;
62
-
63
- for (const [, data] of this._results) {
64
- totalWeight += data.weight;
65
- totalContribution += data.contribution;
66
- }
67
-
68
- if (totalWeight === 0) {
69
- return 0;
70
- }
71
-
72
- const score = (totalContribution / totalWeight) * this.maxScore;
73
- return Math.round(score * 100) / 100; // Round to 2 decimal places
74
- }
75
-
76
- /**
77
- * Get detailed breakdown of score contributions.
78
- * @returns {Array<Object>} Array of signal contributions
79
- */
80
- getBreakdown() {
81
- const breakdown = [];
82
-
83
- for (const [signalId, data] of this._results) {
84
- breakdown.push({
85
- signalId,
86
- triggered: data.triggered,
87
- confidence: data.confidence,
88
- weight: data.weight,
89
- contribution: data.contribution,
90
- percentOfScore: this.calculate() > 0
91
- ? (data.contribution / this.calculate() * 100).toFixed(1)
92
- : '0.0',
93
- });
94
- }
95
-
96
- // Sort by contribution (highest first)
97
- return breakdown.sort((a, b) => b.contribution - a.contribution);
98
- }
99
-
100
- /**
101
- * Get all triggered signals.
102
- * @returns {Array<string>} Array of triggered signal IDs
103
- */
104
- getTriggeredSignals() {
105
- const triggered = [];
106
- for (const [signalId, data] of this._results) {
107
- if (data.triggered) {
108
- triggered.push(signalId);
109
- }
110
- }
111
- return triggered;
112
- }
113
-
114
- /**
115
- * Get the number of triggered signals.
116
- * @returns {number}
117
- */
118
- getTriggeredCount() {
119
- let count = 0;
120
- for (const [, data] of this._results) {
121
- if (data.triggered) count++;
122
- }
123
- return count;
124
- }
125
-
126
- /**
127
- * Reset all stored results.
128
- */
129
- reset() {
130
- this._results.clear();
131
- }
132
- }
133
-
134
- export { ScoringEngine };
@@ -1,181 +0,0 @@
1
- /**
2
- * @fileoverview Base Signal class for bot detection signals.
3
- * All signal detectors must extend this class.
4
- */
5
-
6
- /**
7
- * Base class for all signal detectors.
8
- * Signals detect specific indicators of automated browser behavior.
9
- *
10
- * @abstract
11
- * @example
12
- * class CustomSignal extends Signal {
13
- * static id = 'custom-signal';
14
- * static category = 'custom';
15
- * static weight = 0.5;
16
- * static description = 'Detects custom bot behavior';
17
- *
18
- * async detect() {
19
- * const isSuspicious = // ... detection logic
20
- * return {
21
- * triggered: isSuspicious,
22
- * value: someValue,
23
- * confidence: 0.8
24
- * };
25
- * }
26
- * }
27
- */
28
- class Signal {
29
- /**
30
- * Unique identifier for this signal.
31
- * @type {string}
32
- */
33
- static id = 'base-signal';
34
-
35
- /**
36
- * Category this signal belongs to.
37
- * Categories: 'environment', 'behavior', 'fingerprint', 'timing', 'automation'
38
- * @type {string}
39
- */
40
- static category = 'uncategorized';
41
-
42
- /**
43
- * Weight of this signal in the scoring calculation.
44
- * Range: 0.1 (low importance) to 1.0 (high importance)
45
- * @type {number}
46
- */
47
- static weight = 0.5;
48
-
49
- /**
50
- * Human-readable description of what this signal detects.
51
- * @type {string}
52
- */
53
- static description = 'Base signal class';
54
-
55
- /**
56
- * Whether this signal requires user interaction before it can detect.
57
- * @type {boolean}
58
- */
59
- static requiresInteraction = false;
60
-
61
- /**
62
- * Creates a new Signal instance.
63
- * @param {Object} [options={}] - Configuration options for this signal.
64
- */
65
- constructor(options = {}) {
66
- this.options = options;
67
- this._lastResult = null;
68
- }
69
-
70
- /**
71
- * Get the signal's unique identifier.
72
- * @returns {string}
73
- */
74
- get id() {
75
- return this.constructor.id;
76
- }
77
-
78
- /**
79
- * Get the signal's category.
80
- * @returns {string}
81
- */
82
- get category() {
83
- return this.constructor.category;
84
- }
85
-
86
- /**
87
- * Get the signal's weight.
88
- * @returns {number}
89
- */
90
- get weight() {
91
- return this.options.weight ?? this.constructor.weight;
92
- }
93
-
94
- /**
95
- * Get the signal's description.
96
- * @returns {string}
97
- */
98
- get description() {
99
- return this.constructor.description;
100
- }
101
-
102
- /**
103
- * Check if this signal requires interaction.
104
- * @returns {boolean}
105
- */
106
- get requiresInteraction() {
107
- return this.constructor.requiresInteraction;
108
- }
109
-
110
- /**
111
- * Get the last detection result.
112
- * @returns {SignalResult|null}
113
- */
114
- get lastResult() {
115
- return this._lastResult;
116
- }
117
-
118
- /**
119
- * Perform the detection check.
120
- * Must be overridden by subclasses.
121
- *
122
- * @abstract
123
- * @returns {Promise<SignalResult>} Detection result
124
- * @throws {Error} If not implemented by subclass
125
- */
126
- async detect() {
127
- throw new Error(`Signal.detect() must be implemented by ${this.constructor.name}`);
128
- }
129
-
130
- /**
131
- * Run detection and cache the result.
132
- * @returns {Promise<SignalResult>}
133
- */
134
- async run() {
135
- try {
136
- this._lastResult = await this.detect();
137
- return this._lastResult;
138
- } catch (error) {
139
- // Fail-safe: if detection throws, treat as not triggered
140
- this._lastResult = {
141
- triggered: false,
142
- value: null,
143
- confidence: 0,
144
- error: error.message,
145
- };
146
- return this._lastResult;
147
- }
148
- }
149
-
150
- /**
151
- * Reset the signal state.
152
- */
153
- reset() {
154
- this._lastResult = null;
155
- }
156
-
157
- /**
158
- * Create a result object with defaults.
159
- * @param {boolean} triggered - Whether the signal was triggered
160
- * @param {*} [value=null] - Optional value associated with the detection
161
- * @param {number} [confidence=1] - Confidence level (0-1)
162
- * @returns {SignalResult}
163
- */
164
- createResult(triggered, value = null, confidence = 1) {
165
- return {
166
- triggered: Boolean(triggered),
167
- value,
168
- confidence: Math.max(0, Math.min(1, confidence)),
169
- };
170
- }
171
- }
172
-
173
- /**
174
- * @typedef {Object} SignalResult
175
- * @property {boolean} triggered - Whether the signal detected bot behavior
176
- * @property {*} value - Associated value or evidence
177
- * @property {number} confidence - Confidence level between 0 and 1
178
- * @property {string} [error] - Error message if detection failed
179
- */
180
-
181
- export { Signal };
@@ -1,132 +0,0 @@
1
- /**
2
- * @fileoverview Verdict engine that determines bot/human classification.
3
- */
4
-
5
- /**
6
- * Possible verdicts from detection.
7
- * @enum {string}
8
- */
9
- const Verdict = {
10
- HUMAN: 'human',
11
- SUSPICIOUS: 'suspicious',
12
- BOT: 'bot',
13
- };
14
-
15
- /**
16
- * Determines the final verdict based on score and triggered signals.
17
- */
18
- class VerdictEngine {
19
- /**
20
- * Default threshold configuration.
21
- * @type {Object}
22
- */
23
- static DEFAULT_THRESHOLDS = {
24
- human: 20, // score < 20 = human
25
- suspicious: 50, // 20 <= score < 50 = suspicious
26
- // score >= 50 = bot
27
- };
28
-
29
- /**
30
- * Creates a new VerdictEngine instance.
31
- * @param {Object} [options={}] - Configuration options
32
- * @param {number} [options.humanThreshold=20] - Max score for human verdict
33
- * @param {number} [options.suspiciousThreshold=50] - Max score for suspicious verdict
34
- * @param {Array<string>} [options.instantBotSignals=[]] - Signal IDs that instantly flag as bot
35
- */
36
- constructor(options = {}) {
37
- this.humanThreshold = options.humanThreshold ?? VerdictEngine.DEFAULT_THRESHOLDS.human;
38
- this.suspiciousThreshold = options.suspiciousThreshold ?? VerdictEngine.DEFAULT_THRESHOLDS.suspicious;
39
- this.instantBotSignals = new Set(options.instantBotSignals || []);
40
- }
41
-
42
- /**
43
- * Get the verdict based on score and triggered signals.
44
- * @param {number} score - Calculated score (0-100)
45
- * @param {Array<string>} triggeredSignals - List of triggered signal IDs
46
- * @returns {VerdictResult}
47
- */
48
- getVerdict(score, triggeredSignals = []) {
49
- // Check for instant bot signals first
50
- for (const signalId of triggeredSignals) {
51
- if (this.instantBotSignals.has(signalId)) {
52
- return {
53
- verdict: Verdict.BOT,
54
- score,
55
- confidence: 'high',
56
- reason: `Instant bot signal triggered: ${signalId}`,
57
- triggeredCount: triggeredSignals.length,
58
- };
59
- }
60
- }
61
-
62
- // Score-based verdict
63
- let verdict;
64
- let confidence;
65
- let reason;
66
-
67
- if (score < this.humanThreshold) {
68
- verdict = Verdict.HUMAN;
69
- confidence = score < 10 ? 'high' : 'medium';
70
- reason = 'Low bot score';
71
- } else if (score < this.suspiciousThreshold) {
72
- verdict = Verdict.SUSPICIOUS;
73
- confidence = 'medium';
74
- reason = 'Moderate bot indicators detected';
75
- } else {
76
- verdict = Verdict.BOT;
77
- confidence = score >= 75 ? 'high' : 'medium';
78
- reason = 'High accumulation of bot indicators';
79
- }
80
-
81
- return {
82
- verdict,
83
- score,
84
- confidence,
85
- reason,
86
- triggeredCount: triggeredSignals.length,
87
- };
88
- }
89
-
90
- /**
91
- * Check if signal should instantly flag as bot.
92
- * @param {string} signalId - Signal identifier
93
- * @returns {boolean}
94
- */
95
- isInstantBotSignal(signalId) {
96
- return this.instantBotSignals.has(signalId);
97
- }
98
-
99
- /**
100
- * Add a signal to the instant bot list.
101
- * @param {string} signalId - Signal identifier
102
- */
103
- addInstantBotSignal(signalId) {
104
- this.instantBotSignals.add(signalId);
105
- }
106
-
107
- /**
108
- * Update thresholds.
109
- * @param {Object} thresholds - New threshold values
110
- * @param {number} [thresholds.human] - New human threshold
111
- * @param {number} [thresholds.suspicious] - New suspicious threshold
112
- */
113
- setThresholds(thresholds) {
114
- if (thresholds.human !== undefined) {
115
- this.humanThreshold = thresholds.human;
116
- }
117
- if (thresholds.suspicious !== undefined) {
118
- this.suspiciousThreshold = thresholds.suspicious;
119
- }
120
- }
121
- }
122
-
123
- /**
124
- * @typedef {Object} VerdictResult
125
- * @property {string} verdict - 'human', 'suspicious', or 'bot'
126
- * @property {number} score - The calculated score
127
- * @property {string} confidence - 'low', 'medium', or 'high'
128
- * @property {string} reason - Human-readable reason for verdict
129
- * @property {number} triggeredCount - Number of triggered signals
130
- */
131
-
132
- export { VerdictEngine, Verdict };
package/src/index.js DELETED
@@ -1,273 +0,0 @@
1
- /**
2
- * @fileoverview Main entry point for the bot detection library.
3
- * @module @niksbanna/bot-detector
4
- */
5
-
6
- // Core classes
7
- import { BotDetector, Verdict } from './core/BotDetector.js';
8
- import { Signal } from './core/Signal.js';
9
- import { ScoringEngine } from './core/ScoringEngine.js';
10
- import { VerdictEngine } from './core/VerdictEngine.js';
11
-
12
- // Environment signals
13
- import {
14
- WebDriverSignal,
15
- HeadlessSignal,
16
- NavigatorAnomalySignal,
17
- PermissionsSignal,
18
- } from './signals/environment/index.js';
19
-
20
- // Behavioral signals
21
- import {
22
- MouseMovementSignal,
23
- KeyboardPatternSignal,
24
- InteractionTimingSignal,
25
- ScrollBehaviorSignal,
26
- } from './signals/behavior/index.js';
27
-
28
- // Fingerprint signals
29
- import {
30
- PluginsSignal,
31
- WebGLSignal,
32
- CanvasSignal,
33
- AudioContextSignal,
34
- ScreenSignal,
35
- } from './signals/fingerprint/index.js';
36
-
37
- // Timing signals
38
- import {
39
- PageLoadSignal,
40
- DOMContentTimingSignal,
41
- } from './signals/timing/index.js';
42
-
43
- // Automation framework signals
44
- import {
45
- PuppeteerSignal,
46
- PlaywrightSignal,
47
- SeleniumSignal,
48
- PhantomJSSignal,
49
- } from './signals/automation/index.js';
50
-
51
- /**
52
- * All built-in signal classes organized by category.
53
- */
54
- const Signals = {
55
- // Environment signals
56
- WebDriverSignal,
57
- HeadlessSignal,
58
- NavigatorAnomalySignal,
59
- PermissionsSignal,
60
-
61
- // Behavioral signals
62
- MouseMovementSignal,
63
- KeyboardPatternSignal,
64
- InteractionTimingSignal,
65
- ScrollBehaviorSignal,
66
-
67
- // Fingerprint signals
68
- PluginsSignal,
69
- WebGLSignal,
70
- CanvasSignal,
71
- AudioContextSignal,
72
- ScreenSignal,
73
-
74
- // Timing signals
75
- PageLoadSignal,
76
- DOMContentTimingSignal,
77
-
78
- // Automation framework signals
79
- PuppeteerSignal,
80
- PlaywrightSignal,
81
- SeleniumSignal,
82
- PhantomJSSignal,
83
- };
84
-
85
- /**
86
- * Default signal instances that don't require user interaction.
87
- * These are suitable for immediate detection on page load.
88
- */
89
- const defaultInstantSignals = [
90
- new WebDriverSignal(),
91
- new HeadlessSignal(),
92
- new NavigatorAnomalySignal(),
93
- new PermissionsSignal(),
94
- new PluginsSignal(),
95
- new WebGLSignal(),
96
- new CanvasSignal(),
97
- new AudioContextSignal(),
98
- new ScreenSignal(),
99
- new PageLoadSignal(),
100
- new DOMContentTimingSignal(),
101
- new PuppeteerSignal(),
102
- new PlaywrightSignal(),
103
- new SeleniumSignal(),
104
- new PhantomJSSignal(),
105
- ];
106
-
107
- /**
108
- * Signal instances that require user interaction to be meaningful.
109
- * These track mouse, keyboard, and scroll behavior over time.
110
- */
111
- const defaultInteractionSignals = [
112
- new MouseMovementSignal(),
113
- new KeyboardPatternSignal(),
114
- new InteractionTimingSignal(),
115
- new ScrollBehaviorSignal(),
116
- ];
117
-
118
- /**
119
- * All default signal instances.
120
- */
121
- const defaultSignals = [...defaultInstantSignals, ...defaultInteractionSignals];
122
-
123
- /**
124
- * Create a BotDetector with all default signals registered.
125
- * This is the recommended way to create a detector for most use cases.
126
- *
127
- * @param {Object} [options={}] - Configuration options
128
- * @param {Object.<string, number>} [options.weightOverrides={}] - Override signal weights
129
- * @param {number} [options.humanThreshold=20] - Score threshold for human verdict
130
- * @param {number} [options.suspiciousThreshold=50] - Score threshold for suspicious verdict
131
- * @param {Array<string>} [options.instantBotSignals=['webdriver', 'puppeteer', 'playwright', 'selenium', 'phantomjs']] - Signals that instantly flag as bot
132
- * @param {boolean} [options.includeInteractionSignals=true] - Include signals requiring interaction
133
- * @param {number} [options.detectionTimeout=5000] - Timeout for detection in ms
134
- * @returns {BotDetector}
135
- *
136
- * @example
137
- * // Basic usage
138
- * const detector = createDetector();
139
- * const result = await detector.detect();
140
- *
141
- * @example
142
- * // Custom thresholds
143
- * const detector = createDetector({
144
- * humanThreshold: 15,
145
- * suspiciousThreshold: 40,
146
- * });
147
- *
148
- * @example
149
- * // Skip interaction signals for instant detection
150
- * const detector = createDetector({
151
- * includeInteractionSignals: false,
152
- * });
153
- */
154
- function createDetector(options = {}) {
155
- const {
156
- includeInteractionSignals = true,
157
- instantBotSignals = ['webdriver', 'puppeteer', 'playwright', 'selenium', 'phantomjs'],
158
- ...detectorOptions
159
- } = options;
160
-
161
- const signals = includeInteractionSignals
162
- ? [...defaultInstantSignals, ...defaultInteractionSignals.map(s => {
163
- // Create fresh instances for interaction signals
164
- const SignalClass = s.constructor;
165
- return new SignalClass(s.options);
166
- })]
167
- : defaultInstantSignals.map(s => {
168
- const SignalClass = s.constructor;
169
- return new SignalClass(s.options);
170
- });
171
-
172
- return new BotDetector({
173
- signals,
174
- instantBotSignals,
175
- ...detectorOptions,
176
- });
177
- }
178
-
179
- /**
180
- * Quick detection function for simple use cases.
181
- * Creates a detector, runs detection, and returns result.
182
- *
183
- * @param {Object} [options={}] - Detection options
184
- * @param {boolean} [options.skipInteractionSignals=false] - Skip signals requiring interaction
185
- * @returns {Promise<DetectionResult>}
186
- *
187
- * @example
188
- * const result = await detect();
189
- * if (result.verdict === 'bot') {
190
- * console.log('Bot detected!', result.score);
191
- * }
192
- */
193
- async function detect(options = {}) {
194
- const detector = createDetector({
195
- includeInteractionSignals: !options.skipInteractionSignals,
196
- });
197
- return detector.detect(options);
198
- }
199
-
200
- /**
201
- * Quick detection that only runs instant signals (no interaction required).
202
- * Suitable for immediate detection on page load.
203
- *
204
- * @returns {Promise<DetectionResult>}
205
- *
206
- * @example
207
- * document.addEventListener('DOMContentLoaded', async () => {
208
- * const result = await detectInstant();
209
- * console.log('Verdict:', result.verdict);
210
- * });
211
- */
212
- async function detectInstant() {
213
- return detect({ skipInteractionSignals: true });
214
- }
215
-
216
- // Export everything
217
- export {
218
- // Main class
219
- BotDetector,
220
-
221
- // Factory functions
222
- createDetector,
223
- detect,
224
- detectInstant,
225
-
226
- // Base classes
227
- Signal,
228
- ScoringEngine,
229
- VerdictEngine,
230
-
231
- // Enums
232
- Verdict,
233
-
234
- // Signal classes (for custom registration)
235
- Signals,
236
-
237
- // Individual signals
238
- WebDriverSignal,
239
- HeadlessSignal,
240
- NavigatorAnomalySignal,
241
- PermissionsSignal,
242
- MouseMovementSignal,
243
- KeyboardPatternSignal,
244
- InteractionTimingSignal,
245
- ScrollBehaviorSignal,
246
- PluginsSignal,
247
- WebGLSignal,
248
- CanvasSignal,
249
- AudioContextSignal,
250
- ScreenSignal,
251
- PageLoadSignal,
252
- DOMContentTimingSignal,
253
- PuppeteerSignal,
254
- PlaywrightSignal,
255
- SeleniumSignal,
256
- PhantomJSSignal,
257
-
258
- // Default signal instances
259
- defaultSignals,
260
- defaultInstantSignals,
261
- defaultInteractionSignals,
262
- };
263
-
264
- // Default export for convenience
265
- export default {
266
- BotDetector,
267
- createDetector,
268
- detect,
269
- detectInstant,
270
- Signal,
271
- Signals,
272
- Verdict,
273
- };