@purespectrum1/purescore-client 0.7.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 +55 -0
- package/lib/application/auto-init.d.ts +44 -0
- package/lib/application/auto-init.js +245 -0
- package/lib/application/device-identifier.d.ts +116 -0
- package/lib/application/device-identifier.js +169 -0
- package/lib/domain/models/crosscutting/device-intelligence.d.ts +11 -0
- package/lib/domain/models/crosscutting/device-intelligence.js +18 -0
- package/lib/domain/models/crosscutting/index.d.ts +1 -0
- package/lib/domain/models/crosscutting/index.js +17 -0
- package/lib/domain/protocols/cache/get-storage.d.ts +8 -0
- package/lib/domain/protocols/cache/get-storage.js +2 -0
- package/lib/domain/protocols/cache/index.d.ts +3 -0
- package/lib/domain/protocols/cache/index.js +19 -0
- package/lib/domain/protocols/cache/remove-storage.d.ts +6 -0
- package/lib/domain/protocols/cache/remove-storage.js +2 -0
- package/lib/domain/protocols/cache/set-storage.d.ts +7 -0
- package/lib/domain/protocols/cache/set-storage.js +2 -0
- package/lib/domain/protocols/http/http-client.d.ts +23 -0
- package/lib/domain/protocols/http/http-client.js +13 -0
- package/lib/domain/protocols/services/behavioural-biometrics/cursor-behavior.d.ts +63 -0
- package/lib/domain/protocols/services/behavioural-biometrics/cursor-behavior.js +2 -0
- package/lib/domain/protocols/services/behavioural-biometrics/index.d.ts +3 -0
- package/lib/domain/protocols/services/behavioural-biometrics/index.js +19 -0
- package/lib/domain/protocols/services/behavioural-biometrics/keystorke-metrics.d.ts +35 -0
- package/lib/domain/protocols/services/behavioural-biometrics/keystorke-metrics.js +2 -0
- package/lib/domain/protocols/services/behavioural-biometrics/touch-behavior.d.ts +88 -0
- package/lib/domain/protocols/services/behavioural-biometrics/touch-behavior.js +2 -0
- package/lib/domain/protocols/services/device-fingerprint/display.d.ts +22 -0
- package/lib/domain/protocols/services/device-fingerprint/display.js +2 -0
- package/lib/domain/protocols/services/device-fingerprint/index.d.ts +3 -0
- package/lib/domain/protocols/services/device-fingerprint/index.js +19 -0
- package/lib/domain/protocols/services/device-fingerprint/navigator.d.ts +63 -0
- package/lib/domain/protocols/services/device-fingerprint/navigator.js +2 -0
- package/lib/domain/protocols/services/device-fingerprint/utils.d.ts +40 -0
- package/lib/domain/protocols/services/device-fingerprint/utils.js +2 -0
- package/lib/domain/protocols/services/device-identifier.d.ts +96 -0
- package/lib/domain/protocols/services/device-identifier.js +2 -0
- package/lib/domain/protocols/services/device-intelligence.d.ts +5 -0
- package/lib/domain/protocols/services/device-intelligence.js +2 -0
- package/lib/domain/protocols/services/listeners.d.ts +9 -0
- package/lib/domain/protocols/services/listeners.js +2 -0
- package/lib/domain/protocols/services/single-script-injection/auto-init.d.ts +120 -0
- package/lib/domain/protocols/services/single-script-injection/auto-init.js +2 -0
- package/lib/domain/protocols/services/single-script-injection/index.d.ts +1 -0
- package/lib/domain/protocols/services/single-script-injection/index.js +17 -0
- package/lib/domain/services/behavioural-biometrics/collector.d.ts +94 -0
- package/lib/domain/services/behavioural-biometrics/collector.js +229 -0
- package/lib/domain/services/behavioural-biometrics/cursor-behavior.d.ts +87 -0
- package/lib/domain/services/behavioural-biometrics/cursor-behavior.js +369 -0
- package/lib/domain/services/behavioural-biometrics/index.d.ts +4 -0
- package/lib/domain/services/behavioural-biometrics/index.js +20 -0
- package/lib/domain/services/behavioural-biometrics/keystroke-metrics.d.ts +60 -0
- package/lib/domain/services/behavioural-biometrics/keystroke-metrics.js +213 -0
- package/lib/domain/services/behavioural-biometrics/touch-behavior.d.ts +125 -0
- package/lib/domain/services/behavioural-biometrics/touch-behavior.js +413 -0
- package/lib/domain/services/device-fingerprint/audio.d.ts +14 -0
- package/lib/domain/services/device-fingerprint/audio.js +109 -0
- package/lib/domain/services/device-fingerprint/canvas.d.ts +10 -0
- package/lib/domain/services/device-fingerprint/canvas.js +33 -0
- package/lib/domain/services/device-fingerprint/display.d.ts +49 -0
- package/lib/domain/services/device-fingerprint/display.js +88 -0
- package/lib/domain/services/device-fingerprint/hash.d.ts +14 -0
- package/lib/domain/services/device-fingerprint/hash.js +60 -0
- package/lib/domain/services/device-fingerprint/index.d.ts +8 -0
- package/lib/domain/services/device-fingerprint/index.js +24 -0
- package/lib/domain/services/device-fingerprint/navigator.d.ts +110 -0
- package/lib/domain/services/device-fingerprint/navigator.js +213 -0
- package/lib/domain/services/device-fingerprint/pointer-capability.d.ts +14 -0
- package/lib/domain/services/device-fingerprint/pointer-capability.js +42 -0
- package/lib/domain/services/device-fingerprint/storage.d.ts +44 -0
- package/lib/domain/services/device-fingerprint/storage.js +64 -0
- package/lib/domain/services/device-fingerprint/utils.d.ts +42 -0
- package/lib/domain/services/device-fingerprint/utils.js +95 -0
- package/lib/domain/services/device-intelligence.d.ts +10 -0
- package/lib/domain/services/device-intelligence.js +59 -0
- package/lib/domain/services/single-script-injection/checkpoint-scheduler.d.ts +37 -0
- package/lib/domain/services/single-script-injection/checkpoint-scheduler.js +94 -0
- package/lib/domain/services/single-script-injection/index.d.ts +3 -0
- package/lib/domain/services/single-script-injection/index.js +19 -0
- package/lib/domain/services/single-script-injection/response-delivery.d.ts +47 -0
- package/lib/domain/services/single-script-injection/response-delivery.js +115 -0
- package/lib/domain/services/single-script-injection/schedule-store.d.ts +69 -0
- package/lib/domain/services/single-script-injection/schedule-store.js +146 -0
- package/lib/domain/services/snapshot-store.d.ts +19 -0
- package/lib/domain/services/snapshot-store.js +36 -0
- package/lib/domain/services/spa-navigation.d.ts +15 -0
- package/lib/domain/services/spa-navigation.js +40 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.js +27 -0
- package/lib/infra/cache/index.d.ts +3 -0
- package/lib/infra/cache/index.js +19 -0
- package/lib/infra/cache/local-storage-adapter.d.ts +8 -0
- package/lib/infra/cache/local-storage-adapter.js +14 -0
- package/lib/infra/cache/session-storage-adapter.d.ts +8 -0
- package/lib/infra/cache/session-storage-adapter.js +14 -0
- package/lib/infra/cache/web-storage-adapter.d.ts +13 -0
- package/lib/infra/cache/web-storage-adapter.js +33 -0
- package/lib/infra/http/axios-http-client.d.ts +4 -0
- package/lib/infra/http/axios-http-client.js +39 -0
- package/lib/validation/builder.d.ts +43 -0
- package/lib/validation/builder.js +85 -0
- package/lib/validation/composite.d.ts +13 -0
- package/lib/validation/composite.js +23 -0
- package/lib/validation/errors/index.d.ts +2 -0
- package/lib/validation/errors/index.js +18 -0
- package/lib/validation/errors/invalid-field-error.d.ts +7 -0
- package/lib/validation/errors/invalid-field-error.js +14 -0
- package/lib/validation/errors/required-field-error.d.ts +3 -0
- package/lib/validation/errors/required-field-error.js +10 -0
- package/lib/validation/index.d.ts +5 -0
- package/lib/validation/index.js +21 -0
- package/lib/validation/protocols/index.d.ts +1 -0
- package/lib/validation/protocols/index.js +17 -0
- package/lib/validation/protocols/validator.d.ts +11 -0
- package/lib/validation/protocols/validator.js +2 -0
- package/lib/validation/validators/behavioural-signal.d.ts +27 -0
- package/lib/validation/validators/behavioural-signal.js +56 -0
- package/lib/validation/validators/country-code.d.ts +14 -0
- package/lib/validation/validators/country-code.js +24 -0
- package/lib/validation/validators/global-function.d.ts +14 -0
- package/lib/validation/validators/global-function.js +29 -0
- package/lib/validation/validators/index.d.ts +9 -0
- package/lib/validation/validators/index.js +25 -0
- package/lib/validation/validators/interpolated-template.d.ts +14 -0
- package/lib/validation/validators/interpolated-template.js +28 -0
- package/lib/validation/validators/json-object.d.ts +14 -0
- package/lib/validation/validators/json-object.js +33 -0
- package/lib/validation/validators/language-code.d.ts +13 -0
- package/lib/validation/validators/language-code.js +23 -0
- package/lib/validation/validators/positive-number.d.ts +13 -0
- package/lib/validation/validators/positive-number.js +24 -0
- package/lib/validation/validators/required-string.d.ts +10 -0
- package/lib/validation/validators/required-string.js +19 -0
- package/lib/validation/validators/schedule-record.d.ts +26 -0
- package/lib/validation/validators/schedule-record.js +33 -0
- package/package.json +51 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { SwipeGesture, SwipeMovement, TouchBehaviorBiometrics } from '../../protocols/services/behavioural-biometrics/touch-behavior';
|
|
2
|
+
import { BehaviouralBiometricsOptions } from '../../protocols/services/device-identifier';
|
|
3
|
+
/**
|
|
4
|
+
* TouchBehavior class for capturing and analyzing touch/gesture behavioral signals.
|
|
5
|
+
*
|
|
6
|
+
* This class tracks various touchscreen interactions including:
|
|
7
|
+
* - Touch pressure (force sensitivity)
|
|
8
|
+
* - Swipe gestures (speed, angle, length)
|
|
9
|
+
* - Finger size/contact area
|
|
10
|
+
*/
|
|
11
|
+
export declare class TouchBehavior {
|
|
12
|
+
private pressureData;
|
|
13
|
+
private touchSizes;
|
|
14
|
+
private swipeGestures;
|
|
15
|
+
private swipeMovements;
|
|
16
|
+
private touchSwipeStart;
|
|
17
|
+
private touchLastPosition;
|
|
18
|
+
private pointerSwipeStart;
|
|
19
|
+
private pointerLastPosition;
|
|
20
|
+
private isTracking;
|
|
21
|
+
private useTouchForSwipes;
|
|
22
|
+
private minSwipeLength;
|
|
23
|
+
private maxSwipeDuration;
|
|
24
|
+
private pointerDownHandler;
|
|
25
|
+
private pointerMoveHandler;
|
|
26
|
+
private pointerUpHandler;
|
|
27
|
+
private pointerCancelHandler;
|
|
28
|
+
private touchStartHandler;
|
|
29
|
+
private touchMoveHandler;
|
|
30
|
+
private touchEndHandler;
|
|
31
|
+
constructor();
|
|
32
|
+
/**
|
|
33
|
+
* Starts tracking touch behavior.
|
|
34
|
+
*/
|
|
35
|
+
startTracking(options?: BehaviouralBiometricsOptions): void;
|
|
36
|
+
/**
|
|
37
|
+
* Stops tracking touch behavior without clearing collected data.
|
|
38
|
+
*/
|
|
39
|
+
stopTracking(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Returns true if this tracker currently has its DOM listeners attached
|
|
42
|
+
* (i.e., it is actively collecting touch/pointer events).
|
|
43
|
+
*/
|
|
44
|
+
hasActiveListeners(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Snapshot of the raw event buffers this tracker has accumulated.
|
|
47
|
+
* Used to persist state across page reloads / SSR navigations.
|
|
48
|
+
*/
|
|
49
|
+
getState(): {
|
|
50
|
+
pressureData: number[];
|
|
51
|
+
touchSizes: Array<{
|
|
52
|
+
radiusX: number;
|
|
53
|
+
radiusY: number;
|
|
54
|
+
}>;
|
|
55
|
+
swipeGestures: SwipeGesture[];
|
|
56
|
+
swipeMovements: SwipeMovement[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Restores raw event buffers from a previously captured snapshot. Transient
|
|
60
|
+
* mid-gesture state (swipe-start maps, last positions) is intentionally not
|
|
61
|
+
* restored — it is only meaningful within a single continuous interaction.
|
|
62
|
+
*/
|
|
63
|
+
restoreState(state: {
|
|
64
|
+
pressureData?: number[];
|
|
65
|
+
touchSizes?: Array<{
|
|
66
|
+
radiusX: number;
|
|
67
|
+
radiusY: number;
|
|
68
|
+
}>;
|
|
69
|
+
swipeGestures?: SwipeGesture[];
|
|
70
|
+
swipeMovements?: SwipeMovement[];
|
|
71
|
+
}): void;
|
|
72
|
+
/**
|
|
73
|
+
* Resets all collected touch behavioral data.
|
|
74
|
+
*/
|
|
75
|
+
resetData(): void;
|
|
76
|
+
private handlePointerDown;
|
|
77
|
+
private handlePointerMove;
|
|
78
|
+
/**
|
|
79
|
+
* Handles pointer up events and calculates swipe metrics.
|
|
80
|
+
*/
|
|
81
|
+
private handlePointerUp;
|
|
82
|
+
/**
|
|
83
|
+
* Handles pointer cancel events (non-touch devices only).
|
|
84
|
+
*/
|
|
85
|
+
private handlePointerCancel;
|
|
86
|
+
/**
|
|
87
|
+
* Handles touch start events.
|
|
88
|
+
*/
|
|
89
|
+
private handleTouchStart;
|
|
90
|
+
/**
|
|
91
|
+
* touchmove - track the latest position so we know where the finger is.
|
|
92
|
+
*/
|
|
93
|
+
private handleTouchMove;
|
|
94
|
+
/**
|
|
95
|
+
* touchend - finalize the swipe using the real touchend coordinates.
|
|
96
|
+
* This always fires, even if the browser took over scrolling.
|
|
97
|
+
*/
|
|
98
|
+
private handleTouchEnd;
|
|
99
|
+
/**
|
|
100
|
+
* Records a swipe from start data to the given end coordinates.
|
|
101
|
+
*/
|
|
102
|
+
private recordSwipe;
|
|
103
|
+
/**
|
|
104
|
+
* Finalizes a pointer-based swipe (non-touch devices only).
|
|
105
|
+
*/
|
|
106
|
+
private finalizePointerSwipe;
|
|
107
|
+
/**
|
|
108
|
+
* Gets the complete touch behavioral biometrics.
|
|
109
|
+
*/
|
|
110
|
+
getTouchBehaviorBiometrics(): TouchBehaviorBiometrics;
|
|
111
|
+
/**
|
|
112
|
+
* Returns all recorded swipe movements with direction and coordinates.
|
|
113
|
+
*/
|
|
114
|
+
getSwipeMovements(): SwipeMovement[];
|
|
115
|
+
private calculateAngle;
|
|
116
|
+
private calculateDirection;
|
|
117
|
+
private getPressureData;
|
|
118
|
+
private getSwipeMetrics;
|
|
119
|
+
private getFingerSizeData;
|
|
120
|
+
private isPointerSupported;
|
|
121
|
+
private isTouchSupported;
|
|
122
|
+
private isTrackablePointer;
|
|
123
|
+
private isPointerPressureSupported;
|
|
124
|
+
private isPointerSizeSupported;
|
|
125
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TouchBehavior = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* TouchBehavior class for capturing and analyzing touch/gesture behavioral signals.
|
|
6
|
+
*
|
|
7
|
+
* This class tracks various touchscreen interactions including:
|
|
8
|
+
* - Touch pressure (force sensitivity)
|
|
9
|
+
* - Swipe gestures (speed, angle, length)
|
|
10
|
+
* - Finger size/contact area
|
|
11
|
+
*/
|
|
12
|
+
class TouchBehavior {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.pressureData = [];
|
|
15
|
+
this.touchSizes = [];
|
|
16
|
+
this.swipeGestures = [];
|
|
17
|
+
this.swipeMovements = [];
|
|
18
|
+
// Touch-based swipe tracking (primary on touch devices)
|
|
19
|
+
this.touchSwipeStart = new Map();
|
|
20
|
+
this.touchLastPosition = new Map();
|
|
21
|
+
// Pointer-based swipe tracking (used only on non-touch-pointer devices)
|
|
22
|
+
this.pointerSwipeStart = new Map();
|
|
23
|
+
this.pointerLastPosition = new Map();
|
|
24
|
+
this.isTracking = false;
|
|
25
|
+
this.useTouchForSwipes = false;
|
|
26
|
+
this.minSwipeLength = 15;
|
|
27
|
+
this.maxSwipeDuration = 2000;
|
|
28
|
+
this.pointerDownHandler = this.handlePointerDown.bind(this);
|
|
29
|
+
this.pointerMoveHandler = this.handlePointerMove.bind(this);
|
|
30
|
+
this.pointerUpHandler = this.handlePointerUp.bind(this);
|
|
31
|
+
this.pointerCancelHandler = this.handlePointerCancel.bind(this);
|
|
32
|
+
this.touchStartHandler = this.handleTouchStart.bind(this);
|
|
33
|
+
this.touchMoveHandler = this.handleTouchMove.bind(this);
|
|
34
|
+
this.touchEndHandler = this.handleTouchEnd.bind(this);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Starts tracking touch behavior.
|
|
38
|
+
*/
|
|
39
|
+
startTracking(options) {
|
|
40
|
+
var _a, _b;
|
|
41
|
+
if (this.isTracking || typeof window === 'undefined' || typeof document === 'undefined') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.minSwipeLength = (_a = options === null || options === void 0 ? void 0 : options.minSwipeLength) !== null && _a !== void 0 ? _a : 15;
|
|
45
|
+
this.maxSwipeDuration = (_b = options === null || options === void 0 ? void 0 : options.maxSwipeDuration) !== null && _b !== void 0 ? _b : 2000;
|
|
46
|
+
this.isTracking = true;
|
|
47
|
+
this.useTouchForSwipes = this.isTouchSupported();
|
|
48
|
+
// Reset data
|
|
49
|
+
this.resetData();
|
|
50
|
+
if (this.isPointerSupported()) {
|
|
51
|
+
document.addEventListener('pointerdown', this.pointerDownHandler, { passive: true });
|
|
52
|
+
document.addEventListener('pointermove', this.pointerMoveHandler, { passive: true });
|
|
53
|
+
// Only listen for pointerup/cancel for swipe tracking on non-touch devices
|
|
54
|
+
if (!this.useTouchForSwipes) {
|
|
55
|
+
document.addEventListener('pointerup', this.pointerUpHandler, { passive: true });
|
|
56
|
+
document.addEventListener('pointercancel', this.pointerCancelHandler, { passive: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (this.useTouchForSwipes) {
|
|
60
|
+
document.addEventListener('touchstart', this.touchStartHandler, { passive: true });
|
|
61
|
+
document.addEventListener('touchmove', this.touchMoveHandler, { passive: true });
|
|
62
|
+
document.addEventListener('touchend', this.touchEndHandler, { passive: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Stops tracking touch behavior without clearing collected data.
|
|
67
|
+
*/
|
|
68
|
+
stopTracking() {
|
|
69
|
+
if (!this.isTracking)
|
|
70
|
+
return;
|
|
71
|
+
this.isTracking = false;
|
|
72
|
+
document.removeEventListener('pointerdown', this.pointerDownHandler);
|
|
73
|
+
document.removeEventListener('pointermove', this.pointerMoveHandler);
|
|
74
|
+
document.removeEventListener('pointerup', this.pointerUpHandler);
|
|
75
|
+
document.removeEventListener('pointercancel', this.pointerCancelHandler);
|
|
76
|
+
document.removeEventListener('touchstart', this.touchStartHandler);
|
|
77
|
+
document.removeEventListener('touchmove', this.touchMoveHandler);
|
|
78
|
+
document.removeEventListener('touchend', this.touchEndHandler);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns true if this tracker currently has its DOM listeners attached
|
|
82
|
+
* (i.e., it is actively collecting touch/pointer events).
|
|
83
|
+
*/
|
|
84
|
+
hasActiveListeners() {
|
|
85
|
+
return this.isTracking;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Snapshot of the raw event buffers this tracker has accumulated.
|
|
89
|
+
* Used to persist state across page reloads / SSR navigations.
|
|
90
|
+
*/
|
|
91
|
+
getState() {
|
|
92
|
+
return {
|
|
93
|
+
pressureData: this.pressureData,
|
|
94
|
+
touchSizes: this.touchSizes,
|
|
95
|
+
swipeGestures: this.swipeGestures,
|
|
96
|
+
swipeMovements: this.swipeMovements,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Restores raw event buffers from a previously captured snapshot. Transient
|
|
101
|
+
* mid-gesture state (swipe-start maps, last positions) is intentionally not
|
|
102
|
+
* restored — it is only meaningful within a single continuous interaction.
|
|
103
|
+
*/
|
|
104
|
+
restoreState(state) {
|
|
105
|
+
if (state.pressureData)
|
|
106
|
+
this.pressureData = state.pressureData;
|
|
107
|
+
if (state.touchSizes)
|
|
108
|
+
this.touchSizes = state.touchSizes;
|
|
109
|
+
if (state.swipeGestures)
|
|
110
|
+
this.swipeGestures = state.swipeGestures;
|
|
111
|
+
if (state.swipeMovements)
|
|
112
|
+
this.swipeMovements = state.swipeMovements;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resets all collected touch behavioral data.
|
|
116
|
+
*/
|
|
117
|
+
resetData() {
|
|
118
|
+
this.pressureData = [];
|
|
119
|
+
this.touchSizes = [];
|
|
120
|
+
this.swipeGestures = [];
|
|
121
|
+
this.swipeMovements = [];
|
|
122
|
+
this.touchSwipeStart.clear();
|
|
123
|
+
this.touchLastPosition.clear();
|
|
124
|
+
this.pointerSwipeStart.clear();
|
|
125
|
+
this.pointerLastPosition.clear();
|
|
126
|
+
}
|
|
127
|
+
handlePointerDown(event) {
|
|
128
|
+
if (!this.isTracking)
|
|
129
|
+
return;
|
|
130
|
+
if (!this.isTrackablePointer(event))
|
|
131
|
+
return;
|
|
132
|
+
// Collect pressure data
|
|
133
|
+
if (this.isPointerPressureSupported(event)) {
|
|
134
|
+
this.pressureData.push(event.pressure);
|
|
135
|
+
}
|
|
136
|
+
// Collect finger size data
|
|
137
|
+
if (this.isPointerSizeSupported(event)) {
|
|
138
|
+
this.touchSizes.push({
|
|
139
|
+
radiusX: (event.width || 0) / 2,
|
|
140
|
+
radiusY: (event.height || 0) / 2
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Track swipe start only on non-touch-pointer devices
|
|
144
|
+
if (!this.useTouchForSwipes) {
|
|
145
|
+
this.pointerSwipeStart.set(event.pointerId, {
|
|
146
|
+
x: event.clientX,
|
|
147
|
+
y: event.clientY,
|
|
148
|
+
time: Date.now()
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
handlePointerMove(event) {
|
|
153
|
+
if (!this.isTracking)
|
|
154
|
+
return;
|
|
155
|
+
if (!this.isTrackablePointer(event))
|
|
156
|
+
return;
|
|
157
|
+
// Collect pressure data
|
|
158
|
+
if (this.isPointerPressureSupported(event)) {
|
|
159
|
+
this.pressureData.push(event.pressure);
|
|
160
|
+
}
|
|
161
|
+
// Collect finger size data
|
|
162
|
+
if (this.isPointerSizeSupported(event)) {
|
|
163
|
+
this.touchSizes.push({
|
|
164
|
+
radiusX: (event.width || 0) / 2,
|
|
165
|
+
radiusY: (event.height || 0) / 2
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Track last position only on non-touch-pointer devices
|
|
169
|
+
if (!this.useTouchForSwipes) {
|
|
170
|
+
this.pointerLastPosition.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Handles pointer up events and calculates swipe metrics.
|
|
175
|
+
*/
|
|
176
|
+
handlePointerUp(event) {
|
|
177
|
+
if (!this.isTracking)
|
|
178
|
+
return;
|
|
179
|
+
if (!this.isTrackablePointer(event))
|
|
180
|
+
return;
|
|
181
|
+
this.finalizePointerSwipe(event.pointerId, event.clientX, event.clientY, false);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Handles pointer cancel events (non-touch devices only).
|
|
185
|
+
*/
|
|
186
|
+
handlePointerCancel(event) {
|
|
187
|
+
if (!this.isTracking)
|
|
188
|
+
return;
|
|
189
|
+
this.finalizePointerSwipe(event.pointerId, event.clientX, event.clientY, true);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Handles touch start events.
|
|
193
|
+
*/
|
|
194
|
+
handleTouchStart(event) {
|
|
195
|
+
if (!this.isTracking)
|
|
196
|
+
return;
|
|
197
|
+
for (let i = 0; i < event.changedTouches.length; i++) {
|
|
198
|
+
const touch = event.changedTouches[i];
|
|
199
|
+
this.touchSwipeStart.set(touch.identifier, {
|
|
200
|
+
x: touch.clientX,
|
|
201
|
+
y: touch.clientY,
|
|
202
|
+
time: Date.now()
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* touchmove - track the latest position so we know where the finger is.
|
|
208
|
+
*/
|
|
209
|
+
handleTouchMove(event) {
|
|
210
|
+
if (!this.isTracking)
|
|
211
|
+
return;
|
|
212
|
+
for (let i = 0; i < event.changedTouches.length; i++) {
|
|
213
|
+
const touch = event.changedTouches[i];
|
|
214
|
+
this.touchLastPosition.set(touch.identifier, {
|
|
215
|
+
x: touch.clientX,
|
|
216
|
+
y: touch.clientY
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* touchend - finalize the swipe using the real touchend coordinates.
|
|
222
|
+
* This always fires, even if the browser took over scrolling.
|
|
223
|
+
*/
|
|
224
|
+
handleTouchEnd(event) {
|
|
225
|
+
if (!this.isTracking)
|
|
226
|
+
return;
|
|
227
|
+
for (let i = 0; i < event.changedTouches.length; i++) {
|
|
228
|
+
const touch = event.changedTouches[i];
|
|
229
|
+
const touchId = touch.identifier;
|
|
230
|
+
const endX = touch.clientX;
|
|
231
|
+
const endY = touch.clientY;
|
|
232
|
+
const startData = this.touchSwipeStart.get(touchId);
|
|
233
|
+
if (startData) {
|
|
234
|
+
this.recordSwipe(startData, endX, endY);
|
|
235
|
+
this.touchSwipeStart.delete(touchId);
|
|
236
|
+
this.touchLastPosition.delete(touchId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Records a swipe from start data to the given end coordinates.
|
|
242
|
+
*/
|
|
243
|
+
recordSwipe(startData, endX, endY) {
|
|
244
|
+
const endTime = Date.now();
|
|
245
|
+
const deltaX = endX - startData.x;
|
|
246
|
+
const deltaY = endY - startData.y;
|
|
247
|
+
const duration = endTime - startData.time;
|
|
248
|
+
const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
249
|
+
if (length >= this.minSwipeLength && duration <= this.maxSwipeDuration) {
|
|
250
|
+
const speed = length / duration;
|
|
251
|
+
const angle = this.calculateAngle(deltaX, deltaY);
|
|
252
|
+
const swipe = {
|
|
253
|
+
speed,
|
|
254
|
+
angle,
|
|
255
|
+
length,
|
|
256
|
+
duration,
|
|
257
|
+
timestamp: startData.time
|
|
258
|
+
};
|
|
259
|
+
this.swipeGestures.push(swipe);
|
|
260
|
+
const movement = {
|
|
261
|
+
direction: this.calculateDirection(deltaX, deltaY),
|
|
262
|
+
start: [startData.x, startData.y],
|
|
263
|
+
end: [endX, endY]
|
|
264
|
+
};
|
|
265
|
+
this.swipeMovements.push(movement);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Finalizes a pointer-based swipe (non-touch devices only).
|
|
270
|
+
*/
|
|
271
|
+
finalizePointerSwipe(pointerId, endX, endY, useLastPosAsFallback) {
|
|
272
|
+
const startData = this.pointerSwipeStart.get(pointerId);
|
|
273
|
+
if (!startData)
|
|
274
|
+
return;
|
|
275
|
+
const lastPos = this.pointerLastPosition.get(pointerId);
|
|
276
|
+
const resolvedEndX = (useLastPosAsFallback && endX === 0 && endY === 0 && lastPos) ? lastPos.x : endX;
|
|
277
|
+
const resolvedEndY = (useLastPosAsFallback && endX === 0 && endY === 0 && lastPos) ? lastPos.y : endY;
|
|
278
|
+
this.recordSwipe(startData, resolvedEndX, resolvedEndY);
|
|
279
|
+
this.pointerSwipeStart.delete(pointerId);
|
|
280
|
+
this.pointerLastPosition.delete(pointerId);
|
|
281
|
+
}
|
|
282
|
+
// --- Biometrics output ---
|
|
283
|
+
/**
|
|
284
|
+
* Gets the complete touch behavioral biometrics.
|
|
285
|
+
*/
|
|
286
|
+
getTouchBehaviorBiometrics() {
|
|
287
|
+
return {
|
|
288
|
+
pressure: this.getPressureData(),
|
|
289
|
+
swipe: this.getSwipeMetrics(),
|
|
290
|
+
fingerSize: this.getFingerSizeData()
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Returns all recorded swipe movements with direction and coordinates.
|
|
295
|
+
*/
|
|
296
|
+
getSwipeMovements() {
|
|
297
|
+
return this.swipeMovements;
|
|
298
|
+
}
|
|
299
|
+
// --- Helper methods ---
|
|
300
|
+
calculateAngle(deltaX, deltaY) {
|
|
301
|
+
let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
|
302
|
+
if (angle < 0) {
|
|
303
|
+
angle += 360;
|
|
304
|
+
}
|
|
305
|
+
return angle;
|
|
306
|
+
}
|
|
307
|
+
calculateDirection(deltaX, deltaY) {
|
|
308
|
+
if (Math.abs(deltaX) >= Math.abs(deltaY)) {
|
|
309
|
+
return deltaX > 0 ? 'right' : 'left';
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
return deltaY > 0 ? 'down' : 'up';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
getPressureData() {
|
|
316
|
+
if (this.pressureData.length === 0) {
|
|
317
|
+
return {
|
|
318
|
+
averagePressure: null,
|
|
319
|
+
maxPressure: null,
|
|
320
|
+
minPressure: null,
|
|
321
|
+
pressureVariability: null,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const sum = this.pressureData.reduce((a, b) => a + b, 0);
|
|
325
|
+
const avg = sum / this.pressureData.length;
|
|
326
|
+
const max = Math.max(...this.pressureData);
|
|
327
|
+
const min = Math.min(...this.pressureData);
|
|
328
|
+
const variance = this.pressureData.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / this.pressureData.length;
|
|
329
|
+
const stdDev = Math.sqrt(variance);
|
|
330
|
+
return {
|
|
331
|
+
averagePressure: avg,
|
|
332
|
+
maxPressure: max,
|
|
333
|
+
minPressure: min,
|
|
334
|
+
pressureVariability: stdDev
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
getSwipeMetrics() {
|
|
338
|
+
if (this.swipeGestures.length === 0) {
|
|
339
|
+
return {
|
|
340
|
+
averageSpeed: null,
|
|
341
|
+
averageAngle: null,
|
|
342
|
+
averageLength: null,
|
|
343
|
+
averageDuration: null,
|
|
344
|
+
speedVariability: null,
|
|
345
|
+
angleVariability: null,
|
|
346
|
+
swipeCount: 0,
|
|
347
|
+
swipes: []
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const speeds = this.swipeGestures.map(s => s.speed);
|
|
351
|
+
const angles = this.swipeGestures.map(s => s.angle);
|
|
352
|
+
const lengths = this.swipeGestures.map(s => s.length);
|
|
353
|
+
const durations = this.swipeGestures.map(s => s.duration);
|
|
354
|
+
const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
|
|
355
|
+
const avgAngle = angles.reduce((a, b) => a + b, 0) / angles.length;
|
|
356
|
+
const avgLength = lengths.reduce((a, b) => a + b, 0) / lengths.length;
|
|
357
|
+
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
|
|
358
|
+
const speedVariance = speeds.reduce((acc, val) => acc + Math.pow(val - avgSpeed, 2), 0) / speeds.length;
|
|
359
|
+
const angleVariance = angles.reduce((acc, val) => acc + Math.pow(val - avgAngle, 2), 0) / angles.length;
|
|
360
|
+
return {
|
|
361
|
+
averageSpeed: avgSpeed,
|
|
362
|
+
averageAngle: avgAngle,
|
|
363
|
+
averageLength: avgLength,
|
|
364
|
+
averageDuration: avgDuration,
|
|
365
|
+
speedVariability: Math.sqrt(speedVariance),
|
|
366
|
+
angleVariability: Math.sqrt(angleVariance),
|
|
367
|
+
swipeCount: this.swipeGestures.length,
|
|
368
|
+
swipes: this.swipeMovements
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
getFingerSizeData() {
|
|
372
|
+
if (this.touchSizes.length === 0) {
|
|
373
|
+
return {
|
|
374
|
+
averageTouchRadius: null,
|
|
375
|
+
maxTouchRadius: null,
|
|
376
|
+
minTouchRadius: null,
|
|
377
|
+
sizeVariability: null,
|
|
378
|
+
averageTouchArea: null,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const radii = this.touchSizes.map(t => (t.radiusX + t.radiusY) / 2);
|
|
382
|
+
const areas = this.touchSizes.map(t => t.radiusX * t.radiusY);
|
|
383
|
+
const avgRadius = radii.reduce((a, b) => a + b, 0) / radii.length;
|
|
384
|
+
const maxRadius = Math.max(...radii);
|
|
385
|
+
const minRadius = Math.min(...radii);
|
|
386
|
+
const avgArea = areas.reduce((a, b) => a + b, 0) / areas.length;
|
|
387
|
+
const variance = radii.reduce((acc, val) => acc + Math.pow(val - avgRadius, 2), 0) / radii.length;
|
|
388
|
+
return {
|
|
389
|
+
averageTouchRadius: avgRadius,
|
|
390
|
+
maxTouchRadius: maxRadius,
|
|
391
|
+
minTouchRadius: minRadius,
|
|
392
|
+
sizeVariability: Math.sqrt(variance),
|
|
393
|
+
averageTouchArea: avgArea,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
isPointerSupported() {
|
|
397
|
+
return 'PointerEvent' in window && 'onpointerdown' in window;
|
|
398
|
+
}
|
|
399
|
+
isTouchSupported() {
|
|
400
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
401
|
+
}
|
|
402
|
+
isTrackablePointer(pointer) {
|
|
403
|
+
return pointer.pointerType === 'touch' || pointer.pointerType === 'pen';
|
|
404
|
+
}
|
|
405
|
+
isPointerPressureSupported(pointer) {
|
|
406
|
+
return typeof pointer.pressure === 'number' && pointer.pressure > 0;
|
|
407
|
+
}
|
|
408
|
+
isPointerSizeSupported(pointer) {
|
|
409
|
+
return typeof pointer.width === 'number' && typeof pointer.height === 'number' &&
|
|
410
|
+
(pointer.width > 0 || pointer.height > 0);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
exports.TouchBehavior = TouchBehavior;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const enum DefaultFingerprint {
|
|
2
|
+
/** The browser doesn't support audio context */
|
|
3
|
+
NotSupported = -1,
|
|
4
|
+
/** An unexpected timeout has happened */
|
|
5
|
+
Timeout = -2
|
|
6
|
+
}
|
|
7
|
+
export declare class AudioFingerprint {
|
|
8
|
+
private AudioContext;
|
|
9
|
+
getAudioFingerprint(): Promise<number>;
|
|
10
|
+
private setAudioParam;
|
|
11
|
+
private renderAudio;
|
|
12
|
+
private generateHash;
|
|
13
|
+
private makeErrorFormat;
|
|
14
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AudioFingerprint = void 0;
|
|
13
|
+
// Define the AudioFingerprint class
|
|
14
|
+
// Based on FingerprintJs(Ref: https://fingerprint.com/blog/audio-fingerprinting/)
|
|
15
|
+
class AudioFingerprint {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.AudioContext = window.OfflineAudioContext;
|
|
18
|
+
}
|
|
19
|
+
getAudioFingerprint() {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
if (!this.AudioContext) {
|
|
22
|
+
return -1 /* DefaultFingerprint.NotSupported */;
|
|
23
|
+
}
|
|
24
|
+
const hashFromIndex = 4500;
|
|
25
|
+
const hashToIndex = 5000;
|
|
26
|
+
const context = new this.AudioContext(1, hashToIndex, 44100);
|
|
27
|
+
const oscillator = context.createOscillator();
|
|
28
|
+
oscillator.type = 'triangle';
|
|
29
|
+
this.setAudioParam(context, oscillator.frequency, 10000);
|
|
30
|
+
const compressor = context.createDynamicsCompressor();
|
|
31
|
+
this.setAudioParam(context, compressor.threshold, -50);
|
|
32
|
+
this.setAudioParam(context, compressor.knee, 40);
|
|
33
|
+
this.setAudioParam(context, compressor.ratio, 12);
|
|
34
|
+
this.setAudioParam(context, compressor.reduction, -20);
|
|
35
|
+
this.setAudioParam(context, compressor.attack, 0);
|
|
36
|
+
this.setAudioParam(context, compressor.release, 0.25);
|
|
37
|
+
oscillator.connect(compressor);
|
|
38
|
+
compressor.connect(context.destination);
|
|
39
|
+
oscillator.start(0);
|
|
40
|
+
let buffer;
|
|
41
|
+
try {
|
|
42
|
+
buffer = yield this.renderAudio(context);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.name === "timeout" /* ErrorsName.Timeout */ || error.name === "suspended" /* ErrorsName.Suspended */) {
|
|
46
|
+
return -2 /* DefaultFingerprint.Timeout */;
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
oscillator.disconnect();
|
|
52
|
+
compressor.disconnect();
|
|
53
|
+
}
|
|
54
|
+
return this.generateHash(buffer.getChannelData(0).subarray(hashFromIndex, hashToIndex));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
setAudioParam(context, param, value) {
|
|
58
|
+
const isAudioParam = (value) => {
|
|
59
|
+
return (value !== null &&
|
|
60
|
+
typeof value === 'object' &&
|
|
61
|
+
'setValueAtTime' in value &&
|
|
62
|
+
typeof value.setValueAtTime === 'function');
|
|
63
|
+
};
|
|
64
|
+
if (isAudioParam(param)) {
|
|
65
|
+
param.setValueAtTime(value, context.currentTime);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
renderAudio(context) {
|
|
69
|
+
const resumeTriesMaxCount = 3;
|
|
70
|
+
const resumeRetryDelay = 250;
|
|
71
|
+
const runningTimeout = 500;
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
context.oncomplete = (event) => resolve(event.renderedBuffer);
|
|
74
|
+
let resumeTriesLeft = resumeTriesMaxCount;
|
|
75
|
+
const tryResume = () => {
|
|
76
|
+
context.startRendering();
|
|
77
|
+
switch (context.state) {
|
|
78
|
+
case 'running':
|
|
79
|
+
setTimeout(() => reject(this.makeErrorFormat("timeout" /* ErrorsName.Timeout */)), runningTimeout);
|
|
80
|
+
break;
|
|
81
|
+
case 'suspended':
|
|
82
|
+
// Retry if suspended
|
|
83
|
+
resumeTriesLeft--;
|
|
84
|
+
if (resumeTriesLeft > 0) {
|
|
85
|
+
setTimeout(tryResume, resumeRetryDelay);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
reject(this.makeErrorFormat("suspended" /* ErrorsName.Suspended */));
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
tryResume();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
generateHash(signal) {
|
|
97
|
+
let hash = 0;
|
|
98
|
+
for (let i = 0; i < signal.length; ++i) {
|
|
99
|
+
hash += Math.abs(signal[i]);
|
|
100
|
+
}
|
|
101
|
+
return hash;
|
|
102
|
+
}
|
|
103
|
+
makeErrorFormat(name) {
|
|
104
|
+
const error = new Error(name);
|
|
105
|
+
error.name = name;
|
|
106
|
+
return error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.AudioFingerprint = AudioFingerprint;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a canvas fingerprint, which is a technique used to identify browsers based on their unique rendering of a canvas element.
|
|
3
|
+
*/
|
|
4
|
+
export declare class Canvas {
|
|
5
|
+
getCanvasFingerprint(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if the browser supports the canvas element and successfully returns a 2D context.
|
|
8
|
+
*/
|
|
9
|
+
private _isCanvasSupported;
|
|
10
|
+
}
|