@quickcheck/device-intel-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,57 @@
1
+ QuickCheck Device Intelligence SDK — Proprietary License
2
+ Copyright (c) 2026 QuickCheck. All Rights Reserved.
3
+
4
+ 1. Grant of Use
5
+ Subject to a valid, active subscription to the QuickCheck Device
6
+ Intelligence service and the issuance of a per-organization SDK Key by
7
+ QuickCheck, Licensee is granted a non-exclusive, non-transferable,
8
+ revocable license to install this package and use it solely to submit
9
+ device intelligence assessments to the QuickCheck API on behalf of
10
+ Licensee's own end users.
11
+
12
+ 2. Restrictions
13
+ Without prior written authorization from QuickCheck, Licensee shall not:
14
+ (a) reverse-engineer, decompile, or disassemble the compiled artifacts
15
+ beyond what is strictly necessary for integration and debugging;
16
+ (b) redistribute, sublicense, sell, rent, lease, or otherwise transfer
17
+ this package or any derivative work to any third party;
18
+ (c) remove or alter any proprietary notices, SDK key validation, or
19
+ telemetry contained in the package;
20
+ (d) use the package to build a competing device intelligence, risk
21
+ scoring, or fraud prevention service;
22
+ (e) use the package to target, profile, or collect data from individuals
23
+ in violation of applicable data protection laws, including but not
24
+ limited to Ley 81 (Panama), GDPR (EU), or CCPA (California).
25
+
26
+ 3. SDK Keys & Subscription
27
+ Valid use requires a QuickCheck SDK Key tied to an active subscription.
28
+ QuickCheck reserves the right to revoke any SDK Key at any time in the
29
+ event of abuse, non-payment, or breach of this license. Revocation of
30
+ the SDK Key immediately terminates the grant of use in Section 1.
31
+
32
+ 4. Data & Privacy
33
+ The package transmits device fingerprint, IP, browser, and (when
34
+ authorized by the end user) geolocation signals to the QuickCheck API.
35
+ Licensee is solely responsible for obtaining any required end-user
36
+ consents and for complying with all data protection laws applicable to
37
+ its jurisdiction and industry.
38
+
39
+ 5. No Warranty
40
+ THIS PACKAGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
41
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY,
42
+ FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. QUICKCHECK DOES
43
+ NOT WARRANT THAT THE RISK SIGNALS EMITTED BY THIS PACKAGE WILL DETECT OR
44
+ PREVENT ALL FRAUDULENT ACTIVITY.
45
+
46
+ 6. Limitation of Liability
47
+ IN NO EVENT SHALL QUICKCHECK BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
48
+ SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES ARISING OUT OF OR RELATED
49
+ TO THE USE OR INABILITY TO USE THIS PACKAGE, EVEN IF ADVISED OF THE
50
+ POSSIBILITY OF SUCH DAMAGES.
51
+
52
+ 7. Governing Law
53
+ This License shall be governed by and construed in accordance with the
54
+ laws of the Republic of Panama. Any disputes arising hereunder shall be
55
+ resolved exclusively by the competent courts of Panama City, Panama.
56
+
57
+ Contact: legal@quickcheck.com.pa
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # @quickcheck/device-intel-sdk
2
+
3
+ **QuickCheck Device Intelligence SDK for Web (JavaScript / TypeScript)**
4
+
5
+ Device fingerprinting, IP intelligence, and real-time risk scoring for AML/KYC compliance. Cumple con el **Artículo 14** del Acuerdo SBP sobre vinculación remota de clientes por medios digitales.
6
+
7
+ ## Instalación
8
+
9
+ ### npm (recomendado para React / Vue / Angular / Next.js)
10
+
11
+ ```bash
12
+ npm install @quickcheck/device-intel-sdk
13
+ ```
14
+
15
+ ### CDN (vanilla JS / HTML)
16
+
17
+ ```html
18
+ <script src="https://qcassetsprod.blob.core.windows.net/sdk/web/v1/qcdi.min.js"></script>
19
+ ```
20
+
21
+ ## Uso rápido
22
+
23
+ ```js
24
+ import { QuickCheckDeviceIntel } from '@quickcheck/device-intel-sdk';
25
+
26
+ const qcdi = new QuickCheckDeviceIntel({
27
+ apiKey: 'qc_sdk_live_xxxxxxxxxxxx', // Obtenida en tu dashboard de QuickCheck
28
+ });
29
+
30
+ // Ejecutar assessment (típicamente en login, onboarding o transacciones)
31
+ const result = await qcdi.assess({
32
+ sessionType: 'onboarding',
33
+ externalUserId: 'usr_12345',
34
+ declaredCountry: 'PA',
35
+ });
36
+
37
+ // Actuar según el resultado
38
+ if (result.risk.action === 'block') {
39
+ alert('Acceso restringido. Código: ' + result.assessment_id);
40
+ return;
41
+ }
42
+ if (result.risk.action === 'review') {
43
+ showWarning('Se requiere verificación adicional');
44
+ }
45
+ // allow → continuar normalmente
46
+ ```
47
+
48
+ ## Configuración
49
+
50
+ ```typescript
51
+ interface QCConfig {
52
+ apiKey: string; // REQUERIDO
53
+ apiUrl?: string; // Default: https://api.quickcheck.com.pa
54
+ autoCollect?: boolean; // Default: true (pre-recopila señales)
55
+ requestGeolocation?: boolean; // Default: false (pide permiso GPS)
56
+ timeout?: number; // Default: 5000 ms
57
+ onError?: (error: Error) => void; // Callback para errores
58
+ debug?: boolean; // Default: false
59
+ }
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### `qcdi.assess(options)`
65
+
66
+ Ejecuta un assessment de dispositivo. Envía señales al endpoint QuickCheck y retorna el resultado de scoring.
67
+
68
+ ```typescript
69
+ interface AssessOptions {
70
+ sessionType: 'onboarding' | 'login' | 'transaction' | 'custom';
71
+ externalUserId?: string;
72
+ externalSessionId?: string;
73
+ declaredCountry?: string; // ISO 3166-1 alpha-2 (ej: 'PA', 'US')
74
+ personName?: string;
75
+ personDocumentId?: string;
76
+ customFields?: Record<string, unknown>;
77
+ }
78
+ ```
79
+
80
+ **Response:**
81
+ ```typescript
82
+ interface AssessmentResult {
83
+ assessment_id: string; // "dia_xxxxx"
84
+ session_id: number;
85
+ timestamp: string;
86
+ risk: {
87
+ score: number; // 0-100
88
+ level: 'low' | 'medium' | 'high' | 'critical';
89
+ action: 'allow' | 'review' | 'block';
90
+ recommendation: string | null;
91
+ };
92
+ flags: {
93
+ vpn_detected: boolean;
94
+ tor_detected: boolean;
95
+ proxy_detected: boolean;
96
+ datacenter_ip: boolean;
97
+ country_mismatch: boolean;
98
+ // ... ver tipos completos
99
+ };
100
+ ip_intelligence: { country_code, city, isp, is_vpn, is_tor, ... };
101
+ coherence: { country_match, timezone_match, language_match };
102
+ metadata: { processing_time_ms, rules_evaluated, rules_triggered };
103
+ }
104
+ ```
105
+
106
+ ### `qcdi.initialize()`
107
+
108
+ Pre-recopila señales del dispositivo. Se llama automáticamente si `autoCollect=true`.
109
+
110
+ ### `qcdi.refresh()`
111
+
112
+ Re-colecta las señales del dispositivo. Útil si el entorno cambió (ej: nueva red).
113
+
114
+ ### `qcdi.destroy()`
115
+
116
+ Libera recursos. Llamar en logout.
117
+
118
+ ## Señales recopiladas
119
+
120
+ | Categoría | Señales |
121
+ |-----------|---------|
122
+ | **Dispositivo** | OS, versión, resolución, zona horaria, idioma, hardware concurrency, memoria, touch support |
123
+ | **Red** | Tipo de conexión (wifi/cellular), effective type (4g/wifi), WebRTC IPs |
124
+ | **Privacidad** | Modo incógnito, WebGL renderer, canvas hash, WebRTC leak |
125
+ | **Fingerprint** | SHA-256 generado por ThumbmarkJS (MIT) |
126
+ | **Geolocalización** | GPS browser (opcional, requiere permiso del usuario) |
127
+
128
+ ## Cuándo ejecutar assessment
129
+
130
+ Recomendado en estos puntos del journey del usuario:
131
+
132
+ - **Onboarding / apertura de cuenta:** assessment obligatorio (Art. 14)
133
+ - **Login:** opcional, pero recomendado para detectar account takeover
134
+ - **Transacciones sensibles:** opcional, útil para transferencias de alto monto
135
+ - **Búsquedas de compliance:** si ofreces acceso a información sensible
136
+
137
+ ## Manejo de errores
138
+
139
+ El SDK lanza `QuickCheckError` con código específico:
140
+
141
+ ```js
142
+ try {
143
+ const result = await qcdi.assess({ sessionType: 'onboarding' });
144
+ } catch (err) {
145
+ if (err.code === 'UNAUTHORIZED') { /* API key inválida */ }
146
+ if (err.code === 'RATE_LIMITED') { /* cuota diaria excedida */ }
147
+ if (err.code === 'TIMEOUT') { /* timeout — fail-open recomendado */ }
148
+ if (err.code === 'NETWORK_ERROR') { /* sin conexión */ }
149
+ }
150
+ ```
151
+
152
+ **Recomendación:** fail-open en caso de error de red (permitir acceso) para no bloquear a usuarios legítimos por problemas de conectividad. Registrar el error para análisis posterior.
153
+
154
+ ## Privacidad y consentimiento
155
+
156
+ - Ninguna señal se envía sin que el usuario interactúe con tu aplicación
157
+ - El GPS solo se solicita si configuras `requestGeolocation: true` (requiere permiso explícito del usuario)
158
+ - El fingerprint hash NO contiene información identificable directamente
159
+
160
+ ## Ejemplos
161
+
162
+ Ver [`examples/vanilla/index.html`](./examples/vanilla/index.html) para una demo completa con UI.
163
+
164
+ ## License
165
+
166
+ Proprietary — QuickCheck Panamá (CORETECH SOLUTIONS, S.A.)
@@ -0,0 +1,257 @@
1
+ /**
2
+ * QuickCheck Device Intelligence SDK — TypeScript types.
3
+ *
4
+ * These types mirror the shared contract at shared/schemas/device-intel-payload.json
5
+ * and the backend schemas at agylequick-api/app/schemas/device_intel.py.
6
+ */
7
+ type Platform = 'web' | 'flutter_android' | 'flutter_ios' | 'voltmx_android' | 'voltmx_ios' | 'kony_android' | 'kony_ios';
8
+ type SessionType = 'onboarding' | 'login' | 'transaction' | 'custom' | 'search';
9
+ type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
10
+ type RiskAction = 'allow' | 'review' | 'block';
11
+ interface QCConfig {
12
+ /** API key from QuickCheck dashboard (format: qc_sdk_live_xxxxx). */
13
+ apiKey: string;
14
+ /** API base URL. Default: https://api.quickcheck.com.pa */
15
+ apiUrl?: string;
16
+ /** Pre-collect device signals on initialize(). Default: true */
17
+ autoCollect?: boolean;
18
+ /** Request geolocation permission from user (one-time snapshot). Default: false */
19
+ requestGeolocation?: boolean;
20
+ /**
21
+ * Continuous geolocation tracking. When true, the SDK uses watchPosition to track
22
+ * the user's location throughout the session. Abrupt changes (>500km) generate
23
+ * a geo_anomaly_in_session flag server-side.
24
+ * Default: false. Requires requestGeolocation=true.
25
+ */
26
+ continuousGeolocation?: boolean;
27
+ /** Request timeout in ms. Default: 5000 */
28
+ timeout?: number;
29
+ /** Error callback */
30
+ onError?: (error: Error) => void;
31
+ /** Debug mode (verbose logging). Default: false */
32
+ debug?: boolean;
33
+ }
34
+ interface AssessOptions {
35
+ /** Context of the assessment (required for Article 14 coherence checks) */
36
+ sessionType: SessionType;
37
+ /** ID of the user in the client's own system (opt) */
38
+ externalUserId?: string;
39
+ /** External session ID (opt) */
40
+ externalSessionId?: string;
41
+ /** Country declared by the user (ISO-3166 alpha-2, e.g. "PA"). Used for country_mismatch check. */
42
+ declaredCountry?: string;
43
+ /** Person name (opt) */
44
+ personName?: string;
45
+ /** Person document ID (opt) */
46
+ personDocumentId?: string;
47
+ /** Free-form custom fields (opt) */
48
+ customFields?: Record<string, unknown>;
49
+ /**
50
+ * Client-generated app-level session ID used to correlate multiple assess()
51
+ * calls within a single user session. Recommended pattern: generate once
52
+ * (e.g. `crypto.randomUUID()`) after login, store in memory/cookie, and pass
53
+ * on every assess() call until logout. Enables backend detection of device
54
+ * switching mid-session (hijacking / token sharing).
55
+ */
56
+ appSessionId?: string;
57
+ }
58
+ interface DeviceInfo {
59
+ fingerprint_hash: string;
60
+ visitor_id: string | null;
61
+ type: 'desktop' | 'mobile' | 'tablet' | null;
62
+ os_name: string;
63
+ os_version: string | null;
64
+ screen_resolution: string | null;
65
+ language: string | null;
66
+ languages: string[];
67
+ timezone_offset: number | null;
68
+ timezone_name: string | null;
69
+ hardware_concurrency: number | null;
70
+ device_memory_gb: number | null;
71
+ touch_support: boolean | null;
72
+ }
73
+ interface NetworkInfo {
74
+ connection_type: string | null;
75
+ effective_type: string | null;
76
+ }
77
+ interface PrivacyInfo {
78
+ is_incognito: boolean | null;
79
+ webgl_renderer: string | null;
80
+ canvas_hash: string | null;
81
+ local_ips: string[];
82
+ }
83
+ interface GeolocationInfo {
84
+ latitude: number | null;
85
+ longitude: number | null;
86
+ accuracy_m: number | null;
87
+ source: 'gps' | 'network' | 'ip' | 'manual';
88
+ }
89
+ interface GeolocationHistoryEntry {
90
+ latitude: number;
91
+ longitude: number;
92
+ accuracy_m?: number | null;
93
+ timestamp: string;
94
+ }
95
+ interface ContextInfo {
96
+ external_user_id: string | null;
97
+ external_session_id: string | null;
98
+ declared_country: string | null;
99
+ person_name: string | null;
100
+ person_document_id: string | null;
101
+ custom_fields: Record<string, unknown>;
102
+ previous_page_url?: string | null;
103
+ flow_from_url?: string | null;
104
+ session_uuid?: string | null;
105
+ geolocation_history?: GeolocationHistoryEntry[];
106
+ client_ipv4?: string | null;
107
+ client_ipv6?: string | null;
108
+ /**
109
+ * Optional client-generated ID that groups multiple assessments into one user
110
+ * session. Lets the backend detect device switching mid-session (hijacking).
111
+ * Generate once per app session (e.g. `crypto.randomUUID()` after login) and
112
+ * pass the same value on every assess() call.
113
+ */
114
+ app_session_id?: string | null;
115
+ }
116
+ interface DeviceIntelPayload {
117
+ sdk_version: string;
118
+ platform: Platform;
119
+ session_type: SessionType;
120
+ device: DeviceInfo;
121
+ network: NetworkInfo | null;
122
+ privacy: PrivacyInfo | null;
123
+ geolocation: GeolocationInfo | null;
124
+ context: ContextInfo | null;
125
+ }
126
+ interface RiskInfo {
127
+ score: number;
128
+ level: RiskLevel;
129
+ action: RiskAction;
130
+ recommendation: string | null;
131
+ }
132
+ interface FlagsSummary {
133
+ vpn_detected: boolean;
134
+ tor_detected: boolean;
135
+ proxy_detected: boolean;
136
+ datacenter_ip: boolean;
137
+ high_risk_jurisdiction: boolean;
138
+ country_mismatch: boolean;
139
+ timezone_mismatch: boolean;
140
+ language_mismatch: boolean;
141
+ impossible_travel: boolean;
142
+ suspicious_velocity: boolean;
143
+ rooted_device: boolean;
144
+ emulator_detected: boolean;
145
+ gps_spoofing: boolean;
146
+ bot_detected: boolean;
147
+ not_in_allowed_countries?: boolean;
148
+ country_blocked_by_org?: boolean;
149
+ geo_anomaly_in_session?: boolean;
150
+ }
151
+ interface IPIntelSummary {
152
+ country_code: string | null;
153
+ country_name: string | null;
154
+ city: string | null;
155
+ isp: string | null;
156
+ is_vpn: boolean;
157
+ is_tor: boolean;
158
+ is_proxy: boolean;
159
+ is_datacenter: boolean;
160
+ threat_score: number | null;
161
+ }
162
+ interface GeoVelocitySummary {
163
+ previous_country: string | null;
164
+ distance_km: number | null;
165
+ time_hours: number | null;
166
+ velocity_kmh: number | null;
167
+ flag: string | null;
168
+ }
169
+ interface CoherenceCheckResult {
170
+ country_match: boolean;
171
+ timezone_match: boolean;
172
+ language_match: boolean;
173
+ overall_coherent: boolean;
174
+ }
175
+ interface AssessmentMetadata {
176
+ processing_time_ms: number;
177
+ rules_evaluated: number;
178
+ rules_triggered: number;
179
+ }
180
+ interface AssessmentResult {
181
+ assessment_id: string;
182
+ session_id: number;
183
+ timestamp: string;
184
+ risk: RiskInfo;
185
+ flags: FlagsSummary;
186
+ ip_intelligence: IPIntelSummary;
187
+ geolocation?: Record<string, unknown> | null;
188
+ geo_velocity?: GeoVelocitySummary | null;
189
+ coherence: CoherenceCheckResult;
190
+ metadata: AssessmentMetadata;
191
+ }
192
+ declare class QuickCheckError extends Error {
193
+ readonly code: string;
194
+ readonly status?: number | undefined;
195
+ readonly details?: unknown | undefined;
196
+ constructor(message: string, code: string, status?: number | undefined, details?: unknown | undefined);
197
+ }
198
+
199
+ declare const SDK_VERSION = "1.0.0";
200
+ declare class QuickCheckDeviceIntel {
201
+ private readonly config;
202
+ private readonly client;
203
+ private deviceSignals;
204
+ private privacySignals;
205
+ private initialized;
206
+ private sessionUuid;
207
+ private geoWatchId;
208
+ private geoHistory;
209
+ private lastGeoLat;
210
+ private lastGeoLon;
211
+ private clientIpv4;
212
+ private clientIpv6;
213
+ constructor(config: QCConfig);
214
+ /**
215
+ * Resolve or create a session UUID (persists in sessionStorage — stable
216
+ * across reloads within same tab, gone when tab closes).
217
+ */
218
+ private resolveSessionUuid;
219
+ private generateUuid;
220
+ /**
221
+ * Pre-collect device signals. Called automatically if autoCollect=true.
222
+ */
223
+ initialize(): Promise<void>;
224
+ /**
225
+ * Start continuous geolocation tracking via watchPosition.
226
+ * Records a history entry every time the position moves significantly.
227
+ */
228
+ private startContinuousGeolocation;
229
+ private haversineMeters;
230
+ private getPreviousPageUrl;
231
+ private getFlowFromUrl;
232
+ /**
233
+ * Execute a device assessment. Sends collected signals + context to the QuickCheck API.
234
+ */
235
+ assess(options: AssessOptions): Promise<AssessmentResult>;
236
+ refresh(): Promise<void>;
237
+ destroy(): void;
238
+ }
239
+
240
+ /**
241
+ * HTTP client for QuickCheck Device Intelligence API.
242
+ */
243
+
244
+ interface ClientConfig {
245
+ apiKey: string;
246
+ apiUrl?: string;
247
+ timeout?: number;
248
+ }
249
+ declare class QCApiClient {
250
+ private readonly apiKey;
251
+ private readonly apiUrl;
252
+ private readonly timeout;
253
+ constructor(config: ClientConfig);
254
+ assess(payload: DeviceIntelPayload): Promise<AssessmentResult>;
255
+ }
256
+
257
+ export { type AssessOptions, type AssessmentMetadata, type AssessmentResult, type CoherenceCheckResult, type ContextInfo, type DeviceInfo, type DeviceIntelPayload, type FlagsSummary, type GeoVelocitySummary, type GeolocationInfo, type IPIntelSummary, type NetworkInfo, type Platform, type PrivacyInfo, QCApiClient, type QCConfig, QuickCheckDeviceIntel, QuickCheckError, type RiskAction, type RiskInfo, type RiskLevel, SDK_VERSION, type SessionType };
@@ -0,0 +1,257 @@
1
+ /**
2
+ * QuickCheck Device Intelligence SDK — TypeScript types.
3
+ *
4
+ * These types mirror the shared contract at shared/schemas/device-intel-payload.json
5
+ * and the backend schemas at agylequick-api/app/schemas/device_intel.py.
6
+ */
7
+ type Platform = 'web' | 'flutter_android' | 'flutter_ios' | 'voltmx_android' | 'voltmx_ios' | 'kony_android' | 'kony_ios';
8
+ type SessionType = 'onboarding' | 'login' | 'transaction' | 'custom' | 'search';
9
+ type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
10
+ type RiskAction = 'allow' | 'review' | 'block';
11
+ interface QCConfig {
12
+ /** API key from QuickCheck dashboard (format: qc_sdk_live_xxxxx). */
13
+ apiKey: string;
14
+ /** API base URL. Default: https://api.quickcheck.com.pa */
15
+ apiUrl?: string;
16
+ /** Pre-collect device signals on initialize(). Default: true */
17
+ autoCollect?: boolean;
18
+ /** Request geolocation permission from user (one-time snapshot). Default: false */
19
+ requestGeolocation?: boolean;
20
+ /**
21
+ * Continuous geolocation tracking. When true, the SDK uses watchPosition to track
22
+ * the user's location throughout the session. Abrupt changes (>500km) generate
23
+ * a geo_anomaly_in_session flag server-side.
24
+ * Default: false. Requires requestGeolocation=true.
25
+ */
26
+ continuousGeolocation?: boolean;
27
+ /** Request timeout in ms. Default: 5000 */
28
+ timeout?: number;
29
+ /** Error callback */
30
+ onError?: (error: Error) => void;
31
+ /** Debug mode (verbose logging). Default: false */
32
+ debug?: boolean;
33
+ }
34
+ interface AssessOptions {
35
+ /** Context of the assessment (required for Article 14 coherence checks) */
36
+ sessionType: SessionType;
37
+ /** ID of the user in the client's own system (opt) */
38
+ externalUserId?: string;
39
+ /** External session ID (opt) */
40
+ externalSessionId?: string;
41
+ /** Country declared by the user (ISO-3166 alpha-2, e.g. "PA"). Used for country_mismatch check. */
42
+ declaredCountry?: string;
43
+ /** Person name (opt) */
44
+ personName?: string;
45
+ /** Person document ID (opt) */
46
+ personDocumentId?: string;
47
+ /** Free-form custom fields (opt) */
48
+ customFields?: Record<string, unknown>;
49
+ /**
50
+ * Client-generated app-level session ID used to correlate multiple assess()
51
+ * calls within a single user session. Recommended pattern: generate once
52
+ * (e.g. `crypto.randomUUID()`) after login, store in memory/cookie, and pass
53
+ * on every assess() call until logout. Enables backend detection of device
54
+ * switching mid-session (hijacking / token sharing).
55
+ */
56
+ appSessionId?: string;
57
+ }
58
+ interface DeviceInfo {
59
+ fingerprint_hash: string;
60
+ visitor_id: string | null;
61
+ type: 'desktop' | 'mobile' | 'tablet' | null;
62
+ os_name: string;
63
+ os_version: string | null;
64
+ screen_resolution: string | null;
65
+ language: string | null;
66
+ languages: string[];
67
+ timezone_offset: number | null;
68
+ timezone_name: string | null;
69
+ hardware_concurrency: number | null;
70
+ device_memory_gb: number | null;
71
+ touch_support: boolean | null;
72
+ }
73
+ interface NetworkInfo {
74
+ connection_type: string | null;
75
+ effective_type: string | null;
76
+ }
77
+ interface PrivacyInfo {
78
+ is_incognito: boolean | null;
79
+ webgl_renderer: string | null;
80
+ canvas_hash: string | null;
81
+ local_ips: string[];
82
+ }
83
+ interface GeolocationInfo {
84
+ latitude: number | null;
85
+ longitude: number | null;
86
+ accuracy_m: number | null;
87
+ source: 'gps' | 'network' | 'ip' | 'manual';
88
+ }
89
+ interface GeolocationHistoryEntry {
90
+ latitude: number;
91
+ longitude: number;
92
+ accuracy_m?: number | null;
93
+ timestamp: string;
94
+ }
95
+ interface ContextInfo {
96
+ external_user_id: string | null;
97
+ external_session_id: string | null;
98
+ declared_country: string | null;
99
+ person_name: string | null;
100
+ person_document_id: string | null;
101
+ custom_fields: Record<string, unknown>;
102
+ previous_page_url?: string | null;
103
+ flow_from_url?: string | null;
104
+ session_uuid?: string | null;
105
+ geolocation_history?: GeolocationHistoryEntry[];
106
+ client_ipv4?: string | null;
107
+ client_ipv6?: string | null;
108
+ /**
109
+ * Optional client-generated ID that groups multiple assessments into one user
110
+ * session. Lets the backend detect device switching mid-session (hijacking).
111
+ * Generate once per app session (e.g. `crypto.randomUUID()` after login) and
112
+ * pass the same value on every assess() call.
113
+ */
114
+ app_session_id?: string | null;
115
+ }
116
+ interface DeviceIntelPayload {
117
+ sdk_version: string;
118
+ platform: Platform;
119
+ session_type: SessionType;
120
+ device: DeviceInfo;
121
+ network: NetworkInfo | null;
122
+ privacy: PrivacyInfo | null;
123
+ geolocation: GeolocationInfo | null;
124
+ context: ContextInfo | null;
125
+ }
126
+ interface RiskInfo {
127
+ score: number;
128
+ level: RiskLevel;
129
+ action: RiskAction;
130
+ recommendation: string | null;
131
+ }
132
+ interface FlagsSummary {
133
+ vpn_detected: boolean;
134
+ tor_detected: boolean;
135
+ proxy_detected: boolean;
136
+ datacenter_ip: boolean;
137
+ high_risk_jurisdiction: boolean;
138
+ country_mismatch: boolean;
139
+ timezone_mismatch: boolean;
140
+ language_mismatch: boolean;
141
+ impossible_travel: boolean;
142
+ suspicious_velocity: boolean;
143
+ rooted_device: boolean;
144
+ emulator_detected: boolean;
145
+ gps_spoofing: boolean;
146
+ bot_detected: boolean;
147
+ not_in_allowed_countries?: boolean;
148
+ country_blocked_by_org?: boolean;
149
+ geo_anomaly_in_session?: boolean;
150
+ }
151
+ interface IPIntelSummary {
152
+ country_code: string | null;
153
+ country_name: string | null;
154
+ city: string | null;
155
+ isp: string | null;
156
+ is_vpn: boolean;
157
+ is_tor: boolean;
158
+ is_proxy: boolean;
159
+ is_datacenter: boolean;
160
+ threat_score: number | null;
161
+ }
162
+ interface GeoVelocitySummary {
163
+ previous_country: string | null;
164
+ distance_km: number | null;
165
+ time_hours: number | null;
166
+ velocity_kmh: number | null;
167
+ flag: string | null;
168
+ }
169
+ interface CoherenceCheckResult {
170
+ country_match: boolean;
171
+ timezone_match: boolean;
172
+ language_match: boolean;
173
+ overall_coherent: boolean;
174
+ }
175
+ interface AssessmentMetadata {
176
+ processing_time_ms: number;
177
+ rules_evaluated: number;
178
+ rules_triggered: number;
179
+ }
180
+ interface AssessmentResult {
181
+ assessment_id: string;
182
+ session_id: number;
183
+ timestamp: string;
184
+ risk: RiskInfo;
185
+ flags: FlagsSummary;
186
+ ip_intelligence: IPIntelSummary;
187
+ geolocation?: Record<string, unknown> | null;
188
+ geo_velocity?: GeoVelocitySummary | null;
189
+ coherence: CoherenceCheckResult;
190
+ metadata: AssessmentMetadata;
191
+ }
192
+ declare class QuickCheckError extends Error {
193
+ readonly code: string;
194
+ readonly status?: number | undefined;
195
+ readonly details?: unknown | undefined;
196
+ constructor(message: string, code: string, status?: number | undefined, details?: unknown | undefined);
197
+ }
198
+
199
+ declare const SDK_VERSION = "1.0.0";
200
+ declare class QuickCheckDeviceIntel {
201
+ private readonly config;
202
+ private readonly client;
203
+ private deviceSignals;
204
+ private privacySignals;
205
+ private initialized;
206
+ private sessionUuid;
207
+ private geoWatchId;
208
+ private geoHistory;
209
+ private lastGeoLat;
210
+ private lastGeoLon;
211
+ private clientIpv4;
212
+ private clientIpv6;
213
+ constructor(config: QCConfig);
214
+ /**
215
+ * Resolve or create a session UUID (persists in sessionStorage — stable
216
+ * across reloads within same tab, gone when tab closes).
217
+ */
218
+ private resolveSessionUuid;
219
+ private generateUuid;
220
+ /**
221
+ * Pre-collect device signals. Called automatically if autoCollect=true.
222
+ */
223
+ initialize(): Promise<void>;
224
+ /**
225
+ * Start continuous geolocation tracking via watchPosition.
226
+ * Records a history entry every time the position moves significantly.
227
+ */
228
+ private startContinuousGeolocation;
229
+ private haversineMeters;
230
+ private getPreviousPageUrl;
231
+ private getFlowFromUrl;
232
+ /**
233
+ * Execute a device assessment. Sends collected signals + context to the QuickCheck API.
234
+ */
235
+ assess(options: AssessOptions): Promise<AssessmentResult>;
236
+ refresh(): Promise<void>;
237
+ destroy(): void;
238
+ }
239
+
240
+ /**
241
+ * HTTP client for QuickCheck Device Intelligence API.
242
+ */
243
+
244
+ interface ClientConfig {
245
+ apiKey: string;
246
+ apiUrl?: string;
247
+ timeout?: number;
248
+ }
249
+ declare class QCApiClient {
250
+ private readonly apiKey;
251
+ private readonly apiUrl;
252
+ private readonly timeout;
253
+ constructor(config: ClientConfig);
254
+ assess(payload: DeviceIntelPayload): Promise<AssessmentResult>;
255
+ }
256
+
257
+ export { type AssessOptions, type AssessmentMetadata, type AssessmentResult, type CoherenceCheckResult, type ContextInfo, type DeviceInfo, type DeviceIntelPayload, type FlagsSummary, type GeoVelocitySummary, type GeolocationInfo, type IPIntelSummary, type NetworkInfo, type Platform, type PrivacyInfo, QCApiClient, type QCConfig, QuickCheckDeviceIntel, QuickCheckError, type RiskAction, type RiskInfo, type RiskLevel, SDK_VERSION, type SessionType };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var k=Object.create;var g=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var G=(o,e)=>{for(var t in e)g(o,t,{get:e[t],enumerable:!0})},y=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of R(e))!D.call(o,i)&&i!==t&&g(o,i,{get:()=>e[i],enumerable:!(n=E(e,i))||n.enumerable});return o};var A=(o,e,t)=>(t=o!=null?k(P(o)):{},y(e||!o||!o.__esModule?g(t,"default",{value:o,enumerable:!0}):t,o)),U=o=>y(g({},"__esModule",{value:!0}),o);var M={};G(M,{QCApiClient:()=>d,QuickCheckDeviceIntel:()=>m,QuickCheckError:()=>c,SDK_VERSION:()=>h});module.exports=U(M);var c=class extends Error{constructor(t,n,i,r){super(t);this.code=n;this.status=i;this.details=r;this.name="QuickCheckError"}};var T="https://api.quickcheck.com.pa",L="/api/v1/sdk/device-intel/assess",d=class{constructor(e){if(!e.apiKey)throw new c("apiKey is required","MISSING_API_KEY");this.apiKey=e.apiKey,this.apiUrl=(e.apiUrl??T).replace(/\/$/,""),this.timeout=e.timeout??5e3}async assess(e){let t=`${this.apiUrl}${L}`,n=new AbortController,i=setTimeout(()=>n.abort(),this.timeout),r;try{r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","X-QC-SDK-Key":this.apiKey,"X-QC-SDK-Platform":e.platform},body:JSON.stringify(e),signal:n.signal})}catch(a){throw clearTimeout(i),a.name==="AbortError"?new c(`Request timed out after ${this.timeout}ms`,"TIMEOUT"):new c(`Network error: ${a.message}`,"NETWORK_ERROR",void 0,a)}clearTimeout(i);let s;try{s=await r.json()}catch{throw new c(`Invalid JSON response (status ${r.status})`,"INVALID_RESPONSE",r.status)}if(!r.ok){let a=typeof s=="object"&&s!==null&&"detail"in s?String(s.detail):`HTTP ${r.status}`,u=r.status===401?"UNAUTHORIZED":r.status===403?"FORBIDDEN":r.status===429?"RATE_LIMITED":"HTTP_ERROR";throw new c(a,u,r.status,s)}return s}};function _(){let o=typeof navigator<"u"?navigator:{},e=typeof window<"u"?window.screen:null,t=e?`${e.width}x${e.height}`:null,n=null,i=null;try{n=new Date().getTimezoneOffset(),i=Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}let r=(o.userAgent||"").toLowerCase(),s="desktop";/mobi|iphone|ipod|android/.test(r)?s="mobile":/ipad|tablet/.test(r)&&(s="tablet");let a="Unknown",u=null;if(/windows nt/.test(r)){a="Windows";let l=r.match(/windows nt ([\d.]+)/);u=l?l[1]:null}else if(/mac os x/.test(r)){a="macOS";let l=r.match(/mac os x ([\d_.]+)/);u=l?l[1].replace(/_/g,"."):null}else if(/android/.test(r)){a="Android";let l=r.match(/android ([\d.]+)/);u=l?l[1]:null}else if(/iphone|ipad|ipod/.test(r)){a="iOS";let l=r.match(/os ([\d_]+) like mac/);u=l?l[1].replace(/_/g,"."):null}else/linux/.test(r)&&(a="Linux");let f=o.deviceMemory??null;return{type:s,os_name:a,os_version:u,screen_resolution:t,language:o.language??null,languages:o.languages?Array.from(o.languages):[],timezone_offset:n,timezone_name:i,hardware_concurrency:o.hardwareConcurrency??null,device_memory_gb:f,touch_support:typeof window<"u"&&("ontouchstart"in window||(o.maxTouchPoints??0)>0)}}async function b(){try{let e=await(await import("@thumbmarkjs/thumbmarkjs")).getFingerprint();return typeof e=="string"?e:e&&typeof e.thumbmark=="string"?e.thumbmark:v()}catch{return v()}}function v(){let e=[typeof navigator<"u"?navigator.userAgent:"",typeof navigator<"u"?navigator.language:"",typeof screen<"u"?`${screen.width}x${screen.height}`:"",typeof screen<"u"?String(screen.colorDepth):"",String(new Date().getTimezoneOffset()),typeof navigator<"u"?String(navigator.hardwareConcurrency??""):""].join("|"),t=0;for(let n=0;n<e.length;n++)t=(t<<5)-t+e.charCodeAt(n),t|=0;return"qc_fb_"+Math.abs(t).toString(16).padStart(8,"0")}async function I(o=1e4){return typeof navigator>"u"||!navigator.geolocation?null:new Promise(e=>{let t=setTimeout(()=>e(null),o);navigator.geolocation.getCurrentPosition(n=>{clearTimeout(t),e({latitude:n.coords.latitude,longitude:n.coords.longitude,accuracy_m:Math.round(n.coords.accuracy),source:"gps"})},()=>{clearTimeout(t),e(null)},{enableHighAccuracy:!1,timeout:o,maximumAge:6e4})})}function w(){let e=navigator.connection;return{connection_type:e?.type??null,effective_type:e?.effectiveType??null}}async function p(o=2e3){return typeof RTCPeerConnection>"u"?[]:new Promise(e=>{let t=new Set;try{let n=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});n.createDataChannel(""),n.onicecandidate=i=>{if(!i.candidate||!i.candidate.candidate)return;let r=i.candidate.candidate.match(/(\d{1,3}(\.\d{1,3}){3})|([a-f0-9:]+:+[a-f0-9]+)/);r&&r[0]&&t.add(r[0])},n.createOffer().then(i=>n.setLocalDescription(i)).catch(()=>{}),setTimeout(()=>{try{n.close()}catch{}e(Array.from(t))},o)}catch{e([])}})}async function S(){let o=N(),e=O(),t=await K(),n=await p(1500);return{is_incognito:t,webgl_renderer:o,canvas_hash:e,local_ips:n}}function N(){try{let o=document.createElement("canvas"),e=o.getContext("webgl")||o.getContext("experimental-webgl");if(!e)return null;let t=e.getExtension("WEBGL_debug_renderer_info");return t?e.getParameter(t.UNMASKED_RENDERER_WEBGL):null}catch{return null}}function O(){try{let o=document.createElement("canvas");o.width=220,o.height=60;let e=o.getContext("2d");if(!e)return null;e.textBaseline="top",e.font="14px Arial",e.fillStyle="#f60",e.fillRect(125,1,62,20),e.fillStyle="#069",e.fillText("QuickCheck Device Intelligence",2,15),e.fillStyle="rgba(102, 204, 0, 0.7)",e.fillText("QuickCheck Device Intelligence",4,17);let t=o.toDataURL(),n=0;for(let i=0;i<t.length;i++){let r=t.charCodeAt(i);n=(n<<5)-n+r,n|=0}return n.toString(16)}catch{return null}}async function K(){try{let o=navigator;if(o.storage?.estimate){let{quota:e}=await o.storage.estimate();if(typeof e=="number")return e<120*1024*1024}}catch{}return null}var h="1.0.0",x="qcdi_session_uuid",C="qcdi_previous_page",m=class{constructor(e){this.deviceSignals=null;this.privacySignals=null;this.initialized=!1;this.sessionUuid="";this.geoWatchId=null;this.geoHistory=[];this.lastGeoLat=null;this.lastGeoLon=null;this.clientIpv4=null;this.clientIpv6=null;this.config={apiKey:e.apiKey,apiUrl:e.apiUrl??"https://api.quickcheck.com.pa",autoCollect:e.autoCollect??!0,requestGeolocation:e.requestGeolocation??!1,continuousGeolocation:e.continuousGeolocation??!1,timeout:e.timeout??5e3,onError:e.onError??(t=>console.error("[QCDI]",t)),debug:e.debug??!1},this.client=new d({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,timeout:this.config.timeout}),this.sessionUuid=this.resolveSessionUuid(),this.config.autoCollect&&this.initialize()}resolveSessionUuid(){try{let e=sessionStorage.getItem(x);if(e)return e;let t=this.generateUuid();return sessionStorage.setItem(x,t),t}catch{return this.generateUuid()}}generateUuid(){let e="0123456789abcdef",t="qcdis-";for(let n=0;n<32;n++)t+=e[Math.floor(Math.random()*16)],(n===7||n===11||n===15||n===19)&&(t+="-");return t}async initialize(){if(!this.initialized){this.config.debug&&console.log("[QCDI] Initializing...");try{let e=await b(),t=_();this.deviceSignals={fingerprint_hash:e,visitor_id:null,...t},this.privacySignals=await S();let n=await p(1500);for(let i of n)i.includes(":")?this.clientIpv6||(this.clientIpv6=i):this.clientIpv4||(this.clientIpv4=i);this.initialized=!0,this.config.continuousGeolocation&&this.config.requestGeolocation&&this.startContinuousGeolocation(),this.config.debug&&console.log("[QCDI] Initialized",{fingerprint:e,session_uuid:this.sessionUuid,ipv4:this.clientIpv4,ipv6:this.clientIpv6})}catch(e){this.config.onError(e)}}}startContinuousGeolocation(){typeof navigator>"u"||!navigator.geolocation||this.geoWatchId===null&&(this.geoWatchId=navigator.geolocation.watchPosition(e=>{let{latitude:t,longitude:n,accuracy:i}=e.coords;this.lastGeoLat!==null&&this.lastGeoLon!==null&&this.haversineMeters(this.lastGeoLat,this.lastGeoLon,t,n)<50||(this.lastGeoLat=t,this.lastGeoLon=n,this.geoHistory.push({latitude:t,longitude:n,accuracy_m:Math.round(i),timestamp:new Date().toISOString()}),this.geoHistory.length>100&&(this.geoHistory=this.geoHistory.slice(-100)))},e=>{this.config.debug&&console.warn("[QCDI] Geo watch error:",e)},{enableHighAccuracy:!1,timeout:1e4,maximumAge:3e4}))}haversineMeters(e,t,n,i){let s=l=>l*Math.PI/180,a=s(n-e),u=s(i-t),f=Math.sin(a/2)**2+Math.cos(s(e))*Math.cos(s(n))*Math.sin(u/2)**2;return 2*6371e3*Math.asin(Math.sqrt(f))}getPreviousPageUrl(){try{if(typeof document<"u"&&document.referrer)return document.referrer.slice(0,500)}catch{}return null}getFlowFromUrl(){try{let e=sessionStorage.getItem(C);return typeof window<"u"&&sessionStorage.setItem(C,window.location.href.slice(0,500)),e}catch{return null}}async assess(e){if(await this.initialize(),!this.deviceSignals)throw new Error("Device signals not collected. Call initialize() first.");let t=null;this.config.continuousGeolocation&&this.lastGeoLat!==null?t={latitude:this.lastGeoLat,longitude:this.lastGeoLon,accuracy_m:null,source:"gps"}:this.config.requestGeolocation&&(t=await I());let n={sdk_version:h,platform:"web",session_type:e.sessionType,device:this.deviceSignals,network:w(),privacy:this.privacySignals,geolocation:t,context:{external_user_id:e.externalUserId??null,external_session_id:e.externalSessionId??null,declared_country:e.declaredCountry??null,person_name:e.personName??null,person_document_id:e.personDocumentId??null,custom_fields:e.customFields??{},previous_page_url:this.getPreviousPageUrl(),flow_from_url:this.getFlowFromUrl(),session_uuid:this.sessionUuid,geolocation_history:this.geoHistory.slice(),client_ipv4:this.clientIpv4,client_ipv6:this.clientIpv6,app_session_id:e.appSessionId??null}};this.config.debug&&console.log("[QCDI] Assessing",n);try{let i=await this.client.assess(n);return this.config.debug&&console.log("[QCDI] Result",i),i}catch(i){throw this.config.onError(i),i}}async refresh(){this.initialized=!1,await this.initialize()}destroy(){this.geoWatchId!==null&&typeof navigator<"u"&&navigator.geolocation.clearWatch(this.geoWatchId),this.geoWatchId=null,this.geoHistory=[],this.deviceSignals=null,this.privacySignals=null,this.initialized=!1}};0&&(module.exports={QCApiClient,QuickCheckDeviceIntel,QuickCheckError,SDK_VERSION});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var u=class extends Error{constructor(n,t,r,i){super(n);this.code=t;this.status=r;this.details=i;this.name="QuickCheckError"}};var x="https://api.quickcheck.com.pa",C="/api/v1/sdk/device-intel/assess",d=class{constructor(e){if(!e.apiKey)throw new u("apiKey is required","MISSING_API_KEY");this.apiKey=e.apiKey,this.apiUrl=(e.apiUrl??x).replace(/\/$/,""),this.timeout=e.timeout??5e3}async assess(e){let n=`${this.apiUrl}${C}`,t=new AbortController,r=setTimeout(()=>t.abort(),this.timeout),i;try{i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-QC-SDK-Key":this.apiKey,"X-QC-SDK-Platform":e.platform},body:JSON.stringify(e),signal:t.signal})}catch(a){throw clearTimeout(r),a.name==="AbortError"?new u(`Request timed out after ${this.timeout}ms`,"TIMEOUT"):new u(`Network error: ${a.message}`,"NETWORK_ERROR",void 0,a)}clearTimeout(r);let s;try{s=await i.json()}catch{throw new u(`Invalid JSON response (status ${i.status})`,"INVALID_RESPONSE",i.status)}if(!i.ok){let a=typeof s=="object"&&s!==null&&"detail"in s?String(s.detail):`HTTP ${i.status}`,c=i.status===401?"UNAUTHORIZED":i.status===403?"FORBIDDEN":i.status===429?"RATE_LIMITED":"HTTP_ERROR";throw new u(a,c,i.status,s)}return s}};function f(){let o=typeof navigator<"u"?navigator:{},e=typeof window<"u"?window.screen:null,n=e?`${e.width}x${e.height}`:null,t=null,r=null;try{t=new Date().getTimezoneOffset(),r=Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}let i=(o.userAgent||"").toLowerCase(),s="desktop";/mobi|iphone|ipod|android/.test(i)?s="mobile":/ipad|tablet/.test(i)&&(s="tablet");let a="Unknown",c=null;if(/windows nt/.test(i)){a="Windows";let l=i.match(/windows nt ([\d.]+)/);c=l?l[1]:null}else if(/mac os x/.test(i)){a="macOS";let l=i.match(/mac os x ([\d_.]+)/);c=l?l[1].replace(/_/g,"."):null}else if(/android/.test(i)){a="Android";let l=i.match(/android ([\d.]+)/);c=l?l[1]:null}else if(/iphone|ipad|ipod/.test(i)){a="iOS";let l=i.match(/os ([\d_]+) like mac/);c=l?l[1].replace(/_/g,"."):null}else/linux/.test(i)&&(a="Linux");let p=o.deviceMemory??null;return{type:s,os_name:a,os_version:c,screen_resolution:n,language:o.language??null,languages:o.languages?Array.from(o.languages):[],timezone_offset:t,timezone_name:r,hardware_concurrency:o.hardwareConcurrency??null,device_memory_gb:p,touch_support:typeof window<"u"&&("ontouchstart"in window||(o.maxTouchPoints??0)>0)}}async function y(){try{let e=await(await import("@thumbmarkjs/thumbmarkjs")).getFingerprint();return typeof e=="string"?e:e&&typeof e.thumbmark=="string"?e.thumbmark:h()}catch{return h()}}function h(){let e=[typeof navigator<"u"?navigator.userAgent:"",typeof navigator<"u"?navigator.language:"",typeof screen<"u"?`${screen.width}x${screen.height}`:"",typeof screen<"u"?String(screen.colorDepth):"",String(new Date().getTimezoneOffset()),typeof navigator<"u"?String(navigator.hardwareConcurrency??""):""].join("|"),n=0;for(let t=0;t<e.length;t++)n=(n<<5)-n+e.charCodeAt(t),n|=0;return"qc_fb_"+Math.abs(n).toString(16).padStart(8,"0")}async function _(o=1e4){return typeof navigator>"u"||!navigator.geolocation?null:new Promise(e=>{let n=setTimeout(()=>e(null),o);navigator.geolocation.getCurrentPosition(t=>{clearTimeout(n),e({latitude:t.coords.latitude,longitude:t.coords.longitude,accuracy_m:Math.round(t.coords.accuracy),source:"gps"})},()=>{clearTimeout(n),e(null)},{enableHighAccuracy:!1,timeout:o,maximumAge:6e4})})}function v(){let e=navigator.connection;return{connection_type:e?.type??null,effective_type:e?.effectiveType??null}}async function g(o=2e3){return typeof RTCPeerConnection>"u"?[]:new Promise(e=>{let n=new Set;try{let t=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});t.createDataChannel(""),t.onicecandidate=r=>{if(!r.candidate||!r.candidate.candidate)return;let i=r.candidate.candidate.match(/(\d{1,3}(\.\d{1,3}){3})|([a-f0-9:]+:+[a-f0-9]+)/);i&&i[0]&&n.add(i[0])},t.createOffer().then(r=>t.setLocalDescription(r)).catch(()=>{}),setTimeout(()=>{try{t.close()}catch{}e(Array.from(n))},o)}catch{e([])}})}async function b(){let o=k(),e=E(),n=await R(),t=await g(1500);return{is_incognito:n,webgl_renderer:o,canvas_hash:e,local_ips:t}}function k(){try{let o=document.createElement("canvas"),e=o.getContext("webgl")||o.getContext("experimental-webgl");if(!e)return null;let n=e.getExtension("WEBGL_debug_renderer_info");return n?e.getParameter(n.UNMASKED_RENDERER_WEBGL):null}catch{return null}}function E(){try{let o=document.createElement("canvas");o.width=220,o.height=60;let e=o.getContext("2d");if(!e)return null;e.textBaseline="top",e.font="14px Arial",e.fillStyle="#f60",e.fillRect(125,1,62,20),e.fillStyle="#069",e.fillText("QuickCheck Device Intelligence",2,15),e.fillStyle="rgba(102, 204, 0, 0.7)",e.fillText("QuickCheck Device Intelligence",4,17);let n=o.toDataURL(),t=0;for(let r=0;r<n.length;r++){let i=n.charCodeAt(r);t=(t<<5)-t+i,t|=0}return t.toString(16)}catch{return null}}async function R(){try{let o=navigator;if(o.storage?.estimate){let{quota:e}=await o.storage.estimate();if(typeof e=="number")return e<120*1024*1024}}catch{}return null}var S="1.0.0",I="qcdi_session_uuid",w="qcdi_previous_page",m=class{constructor(e){this.deviceSignals=null;this.privacySignals=null;this.initialized=!1;this.sessionUuid="";this.geoWatchId=null;this.geoHistory=[];this.lastGeoLat=null;this.lastGeoLon=null;this.clientIpv4=null;this.clientIpv6=null;this.config={apiKey:e.apiKey,apiUrl:e.apiUrl??"https://api.quickcheck.com.pa",autoCollect:e.autoCollect??!0,requestGeolocation:e.requestGeolocation??!1,continuousGeolocation:e.continuousGeolocation??!1,timeout:e.timeout??5e3,onError:e.onError??(n=>console.error("[QCDI]",n)),debug:e.debug??!1},this.client=new d({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,timeout:this.config.timeout}),this.sessionUuid=this.resolveSessionUuid(),this.config.autoCollect&&this.initialize()}resolveSessionUuid(){try{let e=sessionStorage.getItem(I);if(e)return e;let n=this.generateUuid();return sessionStorage.setItem(I,n),n}catch{return this.generateUuid()}}generateUuid(){let e="0123456789abcdef",n="qcdis-";for(let t=0;t<32;t++)n+=e[Math.floor(Math.random()*16)],(t===7||t===11||t===15||t===19)&&(n+="-");return n}async initialize(){if(!this.initialized){this.config.debug&&console.log("[QCDI] Initializing...");try{let e=await y(),n=f();this.deviceSignals={fingerprint_hash:e,visitor_id:null,...n},this.privacySignals=await b();let t=await g(1500);for(let r of t)r.includes(":")?this.clientIpv6||(this.clientIpv6=r):this.clientIpv4||(this.clientIpv4=r);this.initialized=!0,this.config.continuousGeolocation&&this.config.requestGeolocation&&this.startContinuousGeolocation(),this.config.debug&&console.log("[QCDI] Initialized",{fingerprint:e,session_uuid:this.sessionUuid,ipv4:this.clientIpv4,ipv6:this.clientIpv6})}catch(e){this.config.onError(e)}}}startContinuousGeolocation(){typeof navigator>"u"||!navigator.geolocation||this.geoWatchId===null&&(this.geoWatchId=navigator.geolocation.watchPosition(e=>{let{latitude:n,longitude:t,accuracy:r}=e.coords;this.lastGeoLat!==null&&this.lastGeoLon!==null&&this.haversineMeters(this.lastGeoLat,this.lastGeoLon,n,t)<50||(this.lastGeoLat=n,this.lastGeoLon=t,this.geoHistory.push({latitude:n,longitude:t,accuracy_m:Math.round(r),timestamp:new Date().toISOString()}),this.geoHistory.length>100&&(this.geoHistory=this.geoHistory.slice(-100)))},e=>{this.config.debug&&console.warn("[QCDI] Geo watch error:",e)},{enableHighAccuracy:!1,timeout:1e4,maximumAge:3e4}))}haversineMeters(e,n,t,r){let s=l=>l*Math.PI/180,a=s(t-e),c=s(r-n),p=Math.sin(a/2)**2+Math.cos(s(e))*Math.cos(s(t))*Math.sin(c/2)**2;return 2*6371e3*Math.asin(Math.sqrt(p))}getPreviousPageUrl(){try{if(typeof document<"u"&&document.referrer)return document.referrer.slice(0,500)}catch{}return null}getFlowFromUrl(){try{let e=sessionStorage.getItem(w);return typeof window<"u"&&sessionStorage.setItem(w,window.location.href.slice(0,500)),e}catch{return null}}async assess(e){if(await this.initialize(),!this.deviceSignals)throw new Error("Device signals not collected. Call initialize() first.");let n=null;this.config.continuousGeolocation&&this.lastGeoLat!==null?n={latitude:this.lastGeoLat,longitude:this.lastGeoLon,accuracy_m:null,source:"gps"}:this.config.requestGeolocation&&(n=await _());let t={sdk_version:S,platform:"web",session_type:e.sessionType,device:this.deviceSignals,network:v(),privacy:this.privacySignals,geolocation:n,context:{external_user_id:e.externalUserId??null,external_session_id:e.externalSessionId??null,declared_country:e.declaredCountry??null,person_name:e.personName??null,person_document_id:e.personDocumentId??null,custom_fields:e.customFields??{},previous_page_url:this.getPreviousPageUrl(),flow_from_url:this.getFlowFromUrl(),session_uuid:this.sessionUuid,geolocation_history:this.geoHistory.slice(),client_ipv4:this.clientIpv4,client_ipv6:this.clientIpv6,app_session_id:e.appSessionId??null}};this.config.debug&&console.log("[QCDI] Assessing",t);try{let r=await this.client.assess(t);return this.config.debug&&console.log("[QCDI] Result",r),r}catch(r){throw this.config.onError(r),r}}async refresh(){this.initialized=!1,await this.initialize()}destroy(){this.geoWatchId!==null&&typeof navigator<"u"&&navigator.geolocation.clearWatch(this.geoWatchId),this.geoWatchId=null,this.geoHistory=[],this.deviceSignals=null,this.privacySignals=null,this.initialized=!1}};export{d as QCApiClient,m as QuickCheckDeviceIntel,u as QuickCheckError,S as SDK_VERSION};
@@ -0,0 +1,11 @@
1
+ "use strict";var QuickCheck=(()=>{var R=Object.defineProperty;var te=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var oe=(n,e)=>()=>(n&&(e=n(n=0)),e);var L=(n,e)=>{for(var r in e)R(n,r,{get:e[r],enumerable:!0})},ie=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ne(e))!re.call(n,i)&&i!==r&&R(n,i,{get:()=>e[i],enumerable:!(o=te(e,i))||o.enumerable});return n};var ae=n=>ie(R({},"__esModule",{value:!0}),n);var q={};L(q,{getFingerprint:()=>me,getFingerprintData:()=>H,getFingerprintPerformance:()=>ge,getVersion:()=>fe,includeComponent:()=>v,setOption:()=>ce});function y(n,e,r,o){return new(r||(r=Promise))((function(i,a){function t(l){try{u(o.next(l))}catch(s){a(s)}}function c(l){try{u(o.throw(l))}catch(s){a(s)}}function u(l){var s;l.done?i(l.value):(s=l.value,s instanceof r?s:new r((function(g){g(s)}))).then(t,c)}u((o=o.apply(n,e||[])).next())}))}function w(n,e){var r,o,i,a,t={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:c(0),throw:c(1),return:c(2)},typeof Symbol=="function"&&(a[Symbol.iterator]=function(){return this}),a;function c(u){return function(l){return(function(s){if(r)throw new TypeError("Generator is already executing.");for(;a&&(a=0,s[0]&&(t=0)),t;)try{if(r=1,o&&(i=2&s[0]?o.return:s[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,s[1])).done)return i;switch(o=0,i&&(s=[2&s[0],i.value]),s[0]){case 0:case 1:i=s;break;case 4:return t.label++,{value:s[1],done:!1};case 5:t.label++,o=s[1],s=[0];continue;case 7:s=t.ops.pop(),t.trys.pop();continue;default:if(i=t.trys,!((i=i.length>0&&i[i.length-1])||s[0]!==6&&s[0]!==2)){t=0;continue}if(s[0]===3&&(!i||s[1]>i[0]&&s[1]<i[3])){t.label=s[1];break}if(s[0]===6&&t.label<i[1]){t.label=i[1],i=s;break}if(i&&t.label<i[2]){t.label=i[2],t.ops.push(s);break}i[2]&&t.ops.pop(),t.trys.pop();continue}s=e.call(n,t)}catch(g){s=[6,g],o=0}finally{r=i=0}if(5&s[0])throw s[1];return{value:s[0]?s[1]:void 0,done:!0}})([u,l])}}}function ce(n,e){if(!["include","exclude","permissions_to_check","retries","timeout","logging"].includes(n))throw new Error("Unknown option "+n);if(["include","exclude","permissions_to_check"].includes(n)&&(!Array.isArray(e)||!e.every((function(r){return typeof r=="string"}))))throw new Error("The value of the include, exclude and permissions_to_check must be an array of strings");if(["retries","timeout"].includes(n)&&typeof e!="number")throw new Error("The value of retries must be a number");f[n]=e}function A(n){return n^=n>>>16,n=Math.imul(n,2246822507),n^=n>>>13,n=Math.imul(n,3266489909),(n^=n>>>16)>>>0}function p(n,e){return n<<e|n>>>32-e}function D(n,e){var r;if(e===void 0&&(e=0),e=e?0|e:0,typeof n=="string"&&(r=n,n=new TextEncoder().encode(r).buffer),!(n instanceof ArrayBuffer))throw new TypeError("Expected key to be ArrayBuffer or string");var o=new Uint32Array([e,e,e,e]);(function(a,t){for(var c=a.byteLength/16|0,u=new Uint32Array(a,0,4*c),l=0;l<c;l++){var s=u.subarray(4*l,4*(l+1));s[0]=Math.imul(s[0],m[0]),s[0]=p(s[0],15),s[0]=Math.imul(s[0],m[1]),t[0]=t[0]^s[0],t[0]=p(t[0],19),t[0]=t[0]+t[1],t[0]=Math.imul(t[0],5)+1444728091,s[1]=Math.imul(s[1],m[1]),s[1]=p(s[1],16),s[1]=Math.imul(s[1],m[2]),t[1]=t[1]^s[1],t[1]=p(t[1],17),t[1]=t[1]+t[2],t[1]=Math.imul(t[1],5)+197830471,s[2]=Math.imul(s[2],m[2]),s[2]=p(s[2],17),s[2]=Math.imul(s[2],m[3]),t[2]=t[2]^s[2],t[2]=p(t[2],15),t[2]=t[2]+t[3],t[2]=Math.imul(t[2],5)+2530024501,s[3]=Math.imul(s[3],m[3]),s[3]=p(s[3],18),s[3]=Math.imul(s[3],m[0]),t[3]=t[3]^s[3],t[3]=p(t[3],13),t[3]=t[3]+t[0],t[3]=Math.imul(t[3],5)+850148119}})(n,o),(function(a,t){var c=a.byteLength/16|0,u=a.byteLength%16,l=new Uint32Array(4),s=new Uint8Array(a,16*c,u);switch(u){case 15:l[3]=l[3]^s[14]<<16;case 14:l[3]=l[3]^s[13]<<8;case 13:l[3]=l[3]^s[12],l[3]=Math.imul(l[3],m[3]),l[3]=p(l[3],18),l[3]=Math.imul(l[3],m[0]),t[3]=t[3]^l[3];case 12:l[2]=l[2]^s[11]<<24;case 11:l[2]=l[2]^s[10]<<16;case 10:l[2]=l[2]^s[9]<<8;case 9:l[2]=l[2]^s[8],l[2]=Math.imul(l[2],m[2]),l[2]=p(l[2],17),l[2]=Math.imul(l[2],m[3]),t[2]=t[2]^l[2];case 8:l[1]=l[1]^s[7]<<24;case 7:l[1]=l[1]^s[6]<<16;case 6:l[1]=l[1]^s[5]<<8;case 5:l[1]=l[1]^s[4],l[1]=Math.imul(l[1],m[1]),l[1]=p(l[1],16),l[1]=Math.imul(l[1],m[2]),t[1]=t[1]^l[1];case 4:l[0]=l[0]^s[3]<<24;case 3:l[0]=l[0]^s[2]<<16;case 2:l[0]=l[0]^s[1]<<8;case 1:l[0]=l[0]^s[0],l[0]=Math.imul(l[0],m[0]),l[0]=p(l[0],15),l[0]=Math.imul(l[0],m[1]),t[0]=t[0]^l[0]}})(n,o),(function(a,t){t[0]=t[0]^a.byteLength,t[1]=t[1]^a.byteLength,t[2]=t[2]^a.byteLength,t[3]=t[3]^a.byteLength,t[0]=t[0]+t[1]|0,t[0]=t[0]+t[2]|0,t[0]=t[0]+t[3]|0,t[1]=t[1]+t[0]|0,t[2]=t[2]+t[0]|0,t[3]=t[3]+t[0]|0,t[0]=A(t[0]),t[1]=A(t[1]),t[2]=A(t[2]),t[3]=A(t[3]),t[0]=t[0]+t[1]|0,t[0]=t[0]+t[2]|0,t[0]=t[0]+t[3]|0,t[1]=t[1]+t[0]|0,t[2]=t[2]+t[0]|0,t[3]=t[3]+t[0]|0})(n,o);var i=new Uint8Array(o.buffer);return Array.from(i).map((function(a){return a.toString(16).padStart(2,"0")})).join("")}function W(n,e){return new Promise((function(r){setTimeout((function(){return r(e)}),n)}))}function ue(n,e,r){return Promise.all(n.map((function(o){var i=performance.now();return Promise.race([o.then((function(a){return{value:a,elapsed:performance.now()-i}})),W(e,r).then((function(a){return{value:a,elapsed:performance.now()-i}}))])})))}function de(n,e,r){return Promise.all(n.map((function(o){return Promise.race([o,W(e,r)])})))}function fe(){return"0.20.5"}function H(){return y(this,void 0,void 0,(function(){var n,e,r,o,i;return w(this,(function(a){switch(a.label){case 0:return a.trys.push([0,2,,3]),n=F(),e=Object.keys(n),[4,de(Object.values(n),f?.timeout||1e3,B)];case 1:return r=a.sent(),o=r.filter((function(t){return t!==void 0})),i={},o.forEach((function(t,c){i[e[c]]=t})),[2,j(i,f.exclude||[],f.include||[],"")];case 2:throw a.sent();case 3:return[2]}}))}))}function j(n,e,r,o){o===void 0&&(o="");for(var i={},a=function(l,s){var g=o+l+".";if(typeof s!="object"||Array.isArray(s)){var k=e.some((function(P){return g.startsWith(P)})),x=r.some((function(P){return g.startsWith(P)}));k&&!x||(i[l]=s)}else{var S=j(s,e,r,g);Object.keys(S).length>0&&(i[l]=S)}},t=0,c=Object.entries(n);t<c.length;t++){var u=c[t];a(u[0],u[1])}return i}function me(n){return y(this,void 0,void 0,(function(){var e,r;return w(this,(function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,H()];case 1:return e=o.sent(),r=D(JSON.stringify(e)),Math.random()<1e-5&&f.logging&&(function(i,a){y(this,void 0,void 0,(function(){var t,c;return w(this,(function(u){switch(u.label){case 0:if(t="https://logging.thumbmarkjs.com/v1/log",c={thumbmark:i,components:a,version:"0.20.5"},sessionStorage.getItem("_tmjs_l"))return[3,4];sessionStorage.setItem("_tmjs_l","1"),u.label=1;case 1:return u.trys.push([1,3,,4]),[4,fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})];case 2:case 3:return u.sent(),[3,4];case 4:return[2]}}))}))})(r,e),n?[2,{hash:r.toString(),data:e}]:[2,r.toString()];case 2:throw o.sent();case 3:return[2]}}))}))}function ge(){return y(this,void 0,void 0,(function(){var n,e,r,o;return w(this,(function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),n=F(),e=Object.keys(n),[4,ue(Object.values(n),f?.timeout||1e3,B)];case 1:return r=i.sent(),o={elapsed:{}},r.forEach((function(a,t){o[e[t]]=a.value,o.elapsed[e[t]]=a.elapsed})),[2,o];case 2:throw i.sent();case 3:return[2]}}))}))}function I(){if(typeof navigator>"u")return{name:"unknown",version:"unknown"};for(var n=navigator.userAgent,e={edg:"Edge",opr:"Opera",samsung:"SamsungBrowser"},r=0,o=[/(?<name>SamsungBrowser)\/(?<version>\d+(?:\.\d+)?)/,/(?<name>Edge|Edg)\/(?<version>\d+(?:\.\d+)?)/,/(?<name>(?:Chrome|Chromium|OPR|Opera|Vivaldi|Brave))\/(?<version>\d+(?:\.\d+)?)/,/(?<name>(?:Firefox|Waterfox|Iceweasel|IceCat))\/(?<version>\d+(?:\.\d+)?)/,/(?<name>Safari)\/(?<version>\d+(?:\.\d+)?)/,/(?<name>MSIE|Trident|IEMobile).+?(?<version>\d+(?:\.\d+)?)/,/(?<name>samsung).*Version\/(?<version>\d+(?:\.\d+)?)/i,/(?<name>[A-Za-z]+)\/(?<version>\d+(?:\.\d+)?)/];r<o.length;r++){var i=o[r],a=n.match(i);if(a&&a.groups)return{name:e[a.groups.name.toLowerCase()]||a.groups.name,version:a.groups.version}}return{name:"unknown",version:"unknown"}}function he(n){for(var e=0,r=0;r<n.length;++r)e+=Math.abs(n[r]);return e}function K(n,e,r){for(var o=[],i=0;i<n[0].data.length;i++){for(var a=[],t=0;t<n.length;t++)a.push(n[t].data[i]);o.push(ve(a))}var c=new Uint8ClampedArray(o);return new ImageData(c,e,r)}function ve(n){if(n.length===0)return 0;for(var e={},r=0,o=n;r<o.length;r++)e[a=o[r]]=(e[a]||0)+1;var i=n[0];for(var a in e)e[a]>e[i]&&(i=parseInt(a,10));return i}function O(n,e){if(!n)throw new Error("Canvas context not supported");return n.font,n.font="72px ".concat(e),n.measureText("WwMmLli0Oo").width}function Se(){var n,e=document.createElement("canvas"),r=(n=e.getContext("webgl"))!==null&&n!==void 0?n:e.getContext("experimental-webgl");if(r&&"getParameter"in r)try{var o=(r.getParameter(r.VENDOR)||"").toString(),i=(r.getParameter(r.RENDERER)||"").toString(),a={vendor:o,renderer:i,version:(r.getParameter(r.VERSION)||"").toString(),shadingLanguageVersion:(r.getParameter(r.SHADING_LANGUAGE_VERSION)||"").toString()};if(!i.length||!o.length){var t=r.getExtension("WEBGL_debug_renderer_info");if(t){var c=(r.getParameter(t.UNMASKED_VENDOR_WEBGL)||"").toString(),u=(r.getParameter(t.UNMASKED_RENDERER_WEBGL)||"").toString();c&&(a.vendorUnmasked=c),u&&(a.rendererUnmasked=u)}}return a}catch{}return"undefined"}function Ie(){var n=new Float32Array(1),e=new Uint8Array(n.buffer);return n[0]=1/0,n[0]=n[0]-n[0],e[3]}function Ee(n,e){var r={};return e.forEach((function(o){var i=(function(a){if(a.length===0)return null;var t={};a.forEach((function(l){var s=String(l);t[s]=(t[s]||0)+1}));var c=a[0],u=1;return Object.keys(t).forEach((function(l){t[l]>u&&(c=l,u=t[l])})),c})(n.map((function(a){return o in a?a[o]:void 0})).filter((function(a){return a!==void 0})));i&&(r[o]=i)})),r}function xe(){var n=[],e={"prefers-contrast":["high","more","low","less","forced","no-preference"],"any-hover":["hover","none"],"any-pointer":["none","coarse","fine"],pointer:["none","coarse","fine"],hover:["hover","none"],update:["fast","slow"],"inverted-colors":["inverted","none"],"prefers-reduced-motion":["reduce","no-preference"],"prefers-reduced-transparency":["reduce","no-preference"],scripting:["none","initial-only","enabled"],"forced-colors":["active","none"]};return Object.keys(e).forEach((function(r){e[r].forEach((function(o){matchMedia("(".concat(r,": ").concat(o,")")).matches&&n.push("".concat(r,": ").concat(o))}))})),n}function Pe(){if(window.location.protocol==="https:"&&typeof window.ApplePaySession=="function")try{for(var n=window.ApplePaySession.supportsVersion,e=15;e>0;e--)if(n(e))return e}catch{return 0}return 0}var f,N,B,v,F,m,pe,z,U,ye,we,T,be,_e,h,Ce,d,_,V=oe(()=>{"use strict";f={exclude:[],include:[],logging:!0,timeout:1e3};N={},B={timeout:"true"},v=function(n,e){typeof window<"u"&&(N[n]=e)},F=function(){return Object.fromEntries(Object.entries(N).filter((function(n){var e,r=n[0];return!(!((e=f?.exclude)===null||e===void 0)&&e.includes(r))})).filter((function(n){var e,r,o,i,a=n[0];return!((e=f?.include)===null||e===void 0)&&e.some((function(t){return t.includes(".")}))?(r=f?.include)===null||r===void 0?void 0:r.some((function(t){return t.startsWith(a)})):((o=f?.include)===null||o===void 0?void 0:o.length)===0||((i=f?.include)===null||i===void 0?void 0:i.includes(a))})).map((function(n){return[n[0],(0,n[1])()]})))};m=new Uint32Array([597399067,2869860233,951274213,2716044179]);pe=I();["SamsungBrowser","Safari"].includes(pe.name)||v("audio",(function(){return y(this,void 0,void 0,(function(){return w(this,(function(n){return[2,new Promise((function(e,r){try{var o=new(window.OfflineAudioContext||window.webkitOfflineAudioContext)(1,5e3,44100),i=o.createBufferSource(),a=o.createOscillator();a.frequency.value=1e3;var t,c=o.createDynamicsCompressor();c.threshold.value=-50,c.knee.value=40,c.ratio.value=12,c.attack.value=0,c.release.value=.2,a.connect(c),c.connect(o.destination),a.start(),o.oncomplete=function(u){t=u.renderedBuffer.getChannelData(0),e({sampleHash:he(t),oscillator:a.type,maxChannels:o.destination.maxChannelCount,channelCountMode:i.channelCountMode})},o.startRendering()}catch(u){console.error("Error creating audio fingerprint:",u),r(u)}}))]}))}))}));z=I(),U=z.name.toLowerCase(),ye=z.version.split(".")[0]||"0",we=parseInt(ye,10);U==="firefox"||U==="safari"&&we===17||v("canvas",(function(){return document.createElement("canvas").getContext("2d"),new Promise((function(n){var e=Array.from({length:3},(function(){return(function(){var r=document.createElement("canvas"),o=r.getContext("2d");if(!o)return new ImageData(1,1);r.width=280,r.height=20;var i=o.createLinearGradient(0,0,r.width,r.height);i.addColorStop(0,"red"),i.addColorStop(.16666666666666666,"orange"),i.addColorStop(.3333333333333333,"yellow"),i.addColorStop(.5,"green"),i.addColorStop(.6666666666666666,"blue"),i.addColorStop(.8333333333333334,"indigo"),i.addColorStop(1,"violet"),o.fillStyle=i,o.fillRect(0,0,r.width,r.height);var a="Random Text WMwmil10Oo";return o.font="23.123px Arial",o.fillStyle="black",o.fillText(a,-5,15),o.fillStyle="rgba(0, 0, 255, 0.5)",o.fillText(a,-3.3,17.7),o.beginPath(),o.moveTo(0,0),o.lineTo(2*r.width/7,r.height),o.strokeStyle="white",o.lineWidth=2,o.stroke(),o.getImageData(0,0,r.width,r.height)})()}));n({commonImageDataHash:D(K(e,280,20).data.toString()).toString()})}))}));be=["Arial","Arial Black","Arial Narrow","Arial Rounded MT","Arimo","Archivo","Barlow","Bebas Neue","Bitter","Bookman","Calibri","Cabin","Candara","Century","Century Gothic","Comic Sans MS","Constantia","Courier","Courier New","Crimson Text","DM Mono","DM Sans","DM Serif Display","DM Serif Text","Dosis","Droid Sans","Exo","Fira Code","Fira Sans","Franklin Gothic Medium","Garamond","Geneva","Georgia","Gill Sans","Helvetica","Impact","Inconsolata","Indie Flower","Inter","Josefin Sans","Karla","Lato","Lexend","Lucida Bright","Lucida Console","Lucida Sans Unicode","Manrope","Merriweather","Merriweather Sans","Montserrat","Myriad","Noto Sans","Nunito","Nunito Sans","Open Sans","Optima","Orbitron","Oswald","Pacifico","Palatino","Perpetua","PT Sans","PT Serif","Poppins","Prompt","Public Sans","Quicksand","Rajdhani","Recursive","Roboto","Roboto Condensed","Rockwell","Rubik","Segoe Print","Segoe Script","Segoe UI","Sora","Source Sans Pro","Space Mono","Tahoma","Taviraj","Times","Times New Roman","Titillium Web","Trebuchet MS","Ubuntu","Varela Round","Verdana","Work Sans"],_e=["monospace","sans-serif","serif"];I().name!="Firefox"&&v("fonts",(function(){var n=this;return new Promise((function(e,r){try{(function(o){var i;y(this,void 0,void 0,(function(){var a,t,c;return w(this,(function(u){switch(u.label){case 0:return document.body?[3,2]:[4,(l=50,new Promise((function(g){return setTimeout(g,l,s)})))];case 1:return u.sent(),[3,0];case 2:if((a=document.createElement("iframe")).setAttribute("frameBorder","0"),(t=a.style).setProperty("position","fixed"),t.setProperty("display","block","important"),t.setProperty("visibility","visible"),t.setProperty("border","0"),t.setProperty("opacity","0"),a.src="about:blank",document.body.appendChild(a),!(c=a.contentDocument||((i=a.contentWindow)===null||i===void 0?void 0:i.document)))throw new Error("Iframe document is not accessible");return o({iframe:c}),setTimeout((function(){document.body.removeChild(a)}),0),[2]}var l,s}))}))})((function(o){var i=o.iframe;return y(n,void 0,void 0,(function(){var a,t,c,u;return w(this,(function(l){return a=i.createElement("canvas"),t=a.getContext("2d"),c=_e.map((function(s){return O(t,s)})),u={},be.forEach((function(s){var g=O(t,s);c.includes(g)||(u[s]=g)})),e(u),[2]}))}))}))}catch{r({error:"unsupported"})}}))})),v("hardware",(function(){return new Promise((function(n,e){var r=navigator.deviceMemory!==void 0?navigator.deviceMemory:0,o=window.performance&&window.performance.memory?window.performance.memory:0;n({videocard:Se(),architecture:Ie(),deviceMemory:r.toString()||"undefined",jsHeapSizeLimit:o.jsHeapSizeLimit||0})}))})),v("locales",(function(){return new Promise((function(n){n({languages:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone})}))})),v("permissions",(function(){return y(this,void 0,void 0,(function(){var n;return w(this,(function(e){return T=f?.permissions_to_check||["accelerometer","accessibility","accessibility-events","ambient-light-sensor","background-fetch","background-sync","bluetooth","camera","clipboard-read","clipboard-write","device-info","display-capture","gyroscope","geolocation","local-fonts","magnetometer","microphone","midi","nfc","notifications","payment-handler","persistent-storage","push","speaker","storage-access","top-level-storage-access","window-management","query"],n=Array.from({length:f?.retries||3},(function(){return(function(){return y(this,void 0,void 0,(function(){var r,o,i,a,t;return w(this,(function(c){switch(c.label){case 0:r={},o=0,i=T,c.label=1;case 1:if(!(o<i.length))return[3,6];a=i[o],c.label=2;case 2:return c.trys.push([2,4,,5]),[4,navigator.permissions.query({name:a})];case 3:return t=c.sent(),r[a]=t.state.toString(),[3,5];case 4:return c.sent(),[3,5];case 5:return o++,[3,1];case 6:return[2,r]}}))}))})()})),[2,Promise.all(n).then((function(r){return Ee(r,T)}))]}))}))})),v("plugins",(function(){var n=[];if(navigator.plugins)for(var e=0;e<navigator.plugins.length;e++){var r=navigator.plugins[e];n.push([r.name,r.filename,r.description].join("|"))}return new Promise((function(o){o({plugins:n})}))})),v("screen",(function(){return new Promise((function(n){n({is_touchscreen:navigator.maxTouchPoints>0,maxTouchPoints:navigator.maxTouchPoints,colorDepth:screen.colorDepth,mediaMatches:xe()})}))})),v("system",(function(){return new Promise((function(n){var e=I();n({platform:window.navigator.platform,cookieEnabled:window.navigator.cookieEnabled,productSub:navigator.productSub,product:navigator.product,useragent:navigator.userAgent,hardwareConcurrency:navigator.hardwareConcurrency,browser:{name:e.name,version:e.version},applePayVersion:Pe()})}))}));Ce=I().name!=="SamsungBrowser"?1:3,d=null;v("webgl",(function(){return y(this,void 0,void 0,(function(){var n;return w(this,(function(e){typeof document<"u"&&((h=document.createElement("canvas")).width=200,h.height=100,d=h.getContext("webgl"));try{if(!d)throw new Error("WebGL not supported");return n=Array.from({length:Ce},(function(){return(function(){try{if(!d)throw new Error("WebGL not supported");var r=`
2
+ attribute vec2 position;
3
+ void main() {
4
+ gl_Position = vec4(position, 0.0, 1.0);
5
+ }
6
+ `,o=`
7
+ precision mediump float;
8
+ void main() {
9
+ gl_FragColor = vec4(0.812, 0.195, 0.553, 0.921); // Set line color
10
+ }
11
+ `,i=d.createShader(d.VERTEX_SHADER),a=d.createShader(d.FRAGMENT_SHADER);if(!i||!a)throw new Error("Failed to create shaders");if(d.shaderSource(i,r),d.shaderSource(a,o),d.compileShader(i),!d.getShaderParameter(i,d.COMPILE_STATUS))throw new Error("Vertex shader compilation failed: "+d.getShaderInfoLog(i));if(d.compileShader(a),!d.getShaderParameter(a,d.COMPILE_STATUS))throw new Error("Fragment shader compilation failed: "+d.getShaderInfoLog(a));var t=d.createProgram();if(!t)throw new Error("Failed to create shader program");if(d.attachShader(t,i),d.attachShader(t,a),d.linkProgram(t),!d.getProgramParameter(t,d.LINK_STATUS))throw new Error("Shader program linking failed: "+d.getProgramInfoLog(t));d.useProgram(t);for(var c=137,u=new Float32Array(4*c),l=2*Math.PI/c,s=0;s<c;s++){var g=s*l;u[4*s]=0,u[4*s+1]=0,u[4*s+2]=Math.cos(g)*(h.width/2),u[4*s+3]=Math.sin(g)*(h.height/2)}var k=d.createBuffer();d.bindBuffer(d.ARRAY_BUFFER,k),d.bufferData(d.ARRAY_BUFFER,u,d.STATIC_DRAW);var x=d.getAttribLocation(t,"position");d.enableVertexAttribArray(x),d.vertexAttribPointer(x,2,d.FLOAT,!1,0,0),d.viewport(0,0,h.width,h.height),d.clearColor(0,0,0,1),d.clear(d.COLOR_BUFFER_BIT),d.drawArrays(d.LINES,0,2*c);var S=new Uint8ClampedArray(h.width*h.height*4);return d.readPixels(0,0,h.width,h.height,d.RGBA,d.UNSIGNED_BYTE,S),new ImageData(S,h.width,h.height)}catch{return new ImageData(1,1)}finally{d&&(d.bindBuffer(d.ARRAY_BUFFER,null),d.useProgram(null),d.viewport(0,0,d.drawingBufferWidth,d.drawingBufferHeight),d.clearColor(0,0,0,0))}})()})),[2,{commonImageHash:D(K(n,h.width,h.height).data.toString()).toString()}]}catch{return[2,{webgl:"unsupported"}]}return[2]}))}))}));_=function(n,e,r,o){for(var i=(r-e)/o,a=0,t=0;t<o;t++)a+=n(e+(t+.5)*i);return a*i};v("math",(function(){return y(void 0,void 0,void 0,(function(){return w(this,(function(n){return[2,{acos:Math.acos(.5),asin:_(Math.asin,-1,1,97),atan:_(Math.atan,-1,1,97),cos:_(Math.cos,0,Math.PI,97),cosh:Math.cosh(9/7),e:Math.E,largeCos:Math.cos(1e20),largeSin:Math.sin(1e20),largeTan:Math.tan(1e20),log:Math.log(1e3),pi:Math.PI,sin:_(Math.sin,-Math.PI,Math.PI,97),sinh:_(Math.sinh,-9/7,7/9,97),sqrt:Math.sqrt(2),tan:_(Math.tan,0,2*Math.PI,97),tanh:_(Math.tanh,-9/7,7/9,97)}]}))}))}))});var De={};L(De,{QuickCheckDeviceIntel:()=>E,default:()=>Te});var b=class extends Error{constructor(r,o,i,a){super(r);this.code=o;this.status=i;this.details=a;this.name="QuickCheckError"}};var se="https://api.quickcheck.com.pa",le="/api/v1/sdk/device-intel/assess",C=class{constructor(e){if(!e.apiKey)throw new b("apiKey is required","MISSING_API_KEY");this.apiKey=e.apiKey,this.apiUrl=(e.apiUrl??se).replace(/\/$/,""),this.timeout=e.timeout??5e3}async assess(e){let r=`${this.apiUrl}${le}`,o=new AbortController,i=setTimeout(()=>o.abort(),this.timeout),a;try{a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json","X-QC-SDK-Key":this.apiKey,"X-QC-SDK-Platform":e.platform},body:JSON.stringify(e),signal:o.signal})}catch(c){throw clearTimeout(i),c.name==="AbortError"?new b(`Request timed out after ${this.timeout}ms`,"TIMEOUT"):new b(`Network error: ${c.message}`,"NETWORK_ERROR",void 0,c)}clearTimeout(i);let t;try{t=await a.json()}catch{throw new b(`Invalid JSON response (status ${a.status})`,"INVALID_RESPONSE",a.status)}if(!a.ok){let c=typeof t=="object"&&t!==null&&"detail"in t?String(t.detail):`HTTP ${a.status}`,u=a.status===401?"UNAUTHORIZED":a.status===403?"FORBIDDEN":a.status===429?"RATE_LIMITED":"HTTP_ERROR";throw new b(c,u,a.status,t)}return t}};function G(){let n=typeof navigator<"u"?navigator:{},e=typeof window<"u"?window.screen:null,r=e?`${e.width}x${e.height}`:null,o=null,i=null;try{o=new Date().getTimezoneOffset(),i=Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}let a=(n.userAgent||"").toLowerCase(),t="desktop";/mobi|iphone|ipod|android/.test(a)?t="mobile":/ipad|tablet/.test(a)&&(t="tablet");let c="Unknown",u=null;if(/windows nt/.test(a)){c="Windows";let s=a.match(/windows nt ([\d.]+)/);u=s?s[1]:null}else if(/mac os x/.test(a)){c="macOS";let s=a.match(/mac os x ([\d_.]+)/);u=s?s[1].replace(/_/g,"."):null}else if(/android/.test(a)){c="Android";let s=a.match(/android ([\d.]+)/);u=s?s[1]:null}else if(/iphone|ipad|ipod/.test(a)){c="iOS";let s=a.match(/os ([\d_]+) like mac/);u=s?s[1].replace(/_/g,"."):null}else/linux/.test(a)&&(c="Linux");let l=n.deviceMemory??null;return{type:t,os_name:c,os_version:u,screen_resolution:r,language:n.language??null,languages:n.languages?Array.from(n.languages):[],timezone_offset:o,timezone_name:i,hardware_concurrency:n.hardwareConcurrency??null,device_memory_gb:l,touch_support:typeof window<"u"&&("ontouchstart"in window||(n.maxTouchPoints??0)>0)}}async function $(){try{let e=await(await Promise.resolve().then(()=>(V(),q))).getFingerprint();return typeof e=="string"?e:e&&typeof e.thumbmark=="string"?e.thumbmark:Q()}catch{return Q()}}function Q(){let e=[typeof navigator<"u"?navigator.userAgent:"",typeof navigator<"u"?navigator.language:"",typeof screen<"u"?`${screen.width}x${screen.height}`:"",typeof screen<"u"?String(screen.colorDepth):"",String(new Date().getTimezoneOffset()),typeof navigator<"u"?String(navigator.hardwareConcurrency??""):""].join("|"),r=0;for(let o=0;o<e.length;o++)r=(r<<5)-r+e.charCodeAt(o),r|=0;return"qc_fb_"+Math.abs(r).toString(16).padStart(8,"0")}async function Y(n=1e4){return typeof navigator>"u"||!navigator.geolocation?null:new Promise(e=>{let r=setTimeout(()=>e(null),n);navigator.geolocation.getCurrentPosition(o=>{clearTimeout(r),e({latitude:o.coords.latitude,longitude:o.coords.longitude,accuracy_m:Math.round(o.coords.accuracy),source:"gps"})},()=>{clearTimeout(r),e(null)},{enableHighAccuracy:!1,timeout:n,maximumAge:6e4})})}function J(){let e=navigator.connection;return{connection_type:e?.type??null,effective_type:e?.effectiveType??null}}async function M(n=2e3){return typeof RTCPeerConnection>"u"?[]:new Promise(e=>{let r=new Set;try{let o=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});o.createDataChannel(""),o.onicecandidate=i=>{if(!i.candidate||!i.candidate.candidate)return;let a=i.candidate.candidate.match(/(\d{1,3}(\.\d{1,3}){3})|([a-f0-9:]+:+[a-f0-9]+)/);a&&a[0]&&r.add(a[0])},o.createOffer().then(i=>o.setLocalDescription(i)).catch(()=>{}),setTimeout(()=>{try{o.close()}catch{}e(Array.from(r))},n)}catch{e([])}})}async function Z(){let n=Ae(),e=Me(),r=await ke(),o=await M(1500);return{is_incognito:r,webgl_renderer:n,canvas_hash:e,local_ips:o}}function Ae(){try{let n=document.createElement("canvas"),e=n.getContext("webgl")||n.getContext("experimental-webgl");if(!e)return null;let r=e.getExtension("WEBGL_debug_renderer_info");return r?e.getParameter(r.UNMASKED_RENDERER_WEBGL):null}catch{return null}}function Me(){try{let n=document.createElement("canvas");n.width=220,n.height=60;let e=n.getContext("2d");if(!e)return null;e.textBaseline="top",e.font="14px Arial",e.fillStyle="#f60",e.fillRect(125,1,62,20),e.fillStyle="#069",e.fillText("QuickCheck Device Intelligence",2,15),e.fillStyle="rgba(102, 204, 0, 0.7)",e.fillText("QuickCheck Device Intelligence",4,17);let r=n.toDataURL(),o=0;for(let i=0;i<r.length;i++){let a=r.charCodeAt(i);o=(o<<5)-o+a,o|=0}return o.toString(16)}catch{return null}}async function ke(){try{let n=navigator;if(n.storage?.estimate){let{quota:e}=await n.storage.estimate();if(typeof e=="number")return e<120*1024*1024}}catch{}return null}var Re="1.0.0",X="qcdi_session_uuid",ee="qcdi_previous_page",E=class{constructor(e){this.deviceSignals=null;this.privacySignals=null;this.initialized=!1;this.sessionUuid="";this.geoWatchId=null;this.geoHistory=[];this.lastGeoLat=null;this.lastGeoLon=null;this.clientIpv4=null;this.clientIpv6=null;this.config={apiKey:e.apiKey,apiUrl:e.apiUrl??"https://api.quickcheck.com.pa",autoCollect:e.autoCollect??!0,requestGeolocation:e.requestGeolocation??!1,continuousGeolocation:e.continuousGeolocation??!1,timeout:e.timeout??5e3,onError:e.onError??(r=>console.error("[QCDI]",r)),debug:e.debug??!1},this.client=new C({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,timeout:this.config.timeout}),this.sessionUuid=this.resolveSessionUuid(),this.config.autoCollect&&this.initialize()}resolveSessionUuid(){try{let e=sessionStorage.getItem(X);if(e)return e;let r=this.generateUuid();return sessionStorage.setItem(X,r),r}catch{return this.generateUuid()}}generateUuid(){let e="0123456789abcdef",r="qcdis-";for(let o=0;o<32;o++)r+=e[Math.floor(Math.random()*16)],(o===7||o===11||o===15||o===19)&&(r+="-");return r}async initialize(){if(!this.initialized){this.config.debug&&console.log("[QCDI] Initializing...");try{let e=await $(),r=G();this.deviceSignals={fingerprint_hash:e,visitor_id:null,...r},this.privacySignals=await Z();let o=await M(1500);for(let i of o)i.includes(":")?this.clientIpv6||(this.clientIpv6=i):this.clientIpv4||(this.clientIpv4=i);this.initialized=!0,this.config.continuousGeolocation&&this.config.requestGeolocation&&this.startContinuousGeolocation(),this.config.debug&&console.log("[QCDI] Initialized",{fingerprint:e,session_uuid:this.sessionUuid,ipv4:this.clientIpv4,ipv6:this.clientIpv6})}catch(e){this.config.onError(e)}}}startContinuousGeolocation(){typeof navigator>"u"||!navigator.geolocation||this.geoWatchId===null&&(this.geoWatchId=navigator.geolocation.watchPosition(e=>{let{latitude:r,longitude:o,accuracy:i}=e.coords;this.lastGeoLat!==null&&this.lastGeoLon!==null&&this.haversineMeters(this.lastGeoLat,this.lastGeoLon,r,o)<50||(this.lastGeoLat=r,this.lastGeoLon=o,this.geoHistory.push({latitude:r,longitude:o,accuracy_m:Math.round(i),timestamp:new Date().toISOString()}),this.geoHistory.length>100&&(this.geoHistory=this.geoHistory.slice(-100)))},e=>{this.config.debug&&console.warn("[QCDI] Geo watch error:",e)},{enableHighAccuracy:!1,timeout:1e4,maximumAge:3e4}))}haversineMeters(e,r,o,i){let t=s=>s*Math.PI/180,c=t(o-e),u=t(i-r),l=Math.sin(c/2)**2+Math.cos(t(e))*Math.cos(t(o))*Math.sin(u/2)**2;return 2*6371e3*Math.asin(Math.sqrt(l))}getPreviousPageUrl(){try{if(typeof document<"u"&&document.referrer)return document.referrer.slice(0,500)}catch{}return null}getFlowFromUrl(){try{let e=sessionStorage.getItem(ee);return typeof window<"u"&&sessionStorage.setItem(ee,window.location.href.slice(0,500)),e}catch{return null}}async assess(e){if(await this.initialize(),!this.deviceSignals)throw new Error("Device signals not collected. Call initialize() first.");let r=null;this.config.continuousGeolocation&&this.lastGeoLat!==null?r={latitude:this.lastGeoLat,longitude:this.lastGeoLon,accuracy_m:null,source:"gps"}:this.config.requestGeolocation&&(r=await Y());let o={sdk_version:Re,platform:"web",session_type:e.sessionType,device:this.deviceSignals,network:J(),privacy:this.privacySignals,geolocation:r,context:{external_user_id:e.externalUserId??null,external_session_id:e.externalSessionId??null,declared_country:e.declaredCountry??null,person_name:e.personName??null,person_document_id:e.personDocumentId??null,custom_fields:e.customFields??{},previous_page_url:this.getPreviousPageUrl(),flow_from_url:this.getFlowFromUrl(),session_uuid:this.sessionUuid,geolocation_history:this.geoHistory.slice(),client_ipv4:this.clientIpv4,client_ipv6:this.clientIpv6,app_session_id:e.appSessionId??null}};this.config.debug&&console.log("[QCDI] Assessing",o);try{let i=await this.client.assess(o);return this.config.debug&&console.log("[QCDI] Result",i),i}catch(i){throw this.config.onError(i),i}}async refresh(){this.initialized=!1,await this.initialize()}destroy(){this.geoWatchId!==null&&typeof navigator<"u"&&navigator.geolocation.clearWatch(this.geoWatchId),this.geoWatchId=null,this.geoHistory=[],this.deviceSignals=null,this.privacySignals=null,this.initialized=!1}};var Te=E;return ae(De);})();
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@quickcheck/device-intel-sdk",
3
+ "version": "1.0.0",
4
+ "description": "QuickCheck Device Intelligence SDK — Device fingerprinting, IP intelligence, and real-time risk scoring for AML/KYC compliance (Article 14).",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "QuickCheck",
7
+ "homepage": "https://quickcheck.com.pa/sdks/docs/web",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts --format cjs,esm --dts --minify --clean",
25
+ "build:umd": "tsup src/umd.ts --format iife --global-name QuickCheck --minify --out-dir dist/umd",
26
+ "build:all": "npm run build && npm run build:umd",
27
+ "dev": "tsup src/index.ts --format esm --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "npm run build:all"
30
+ },
31
+ "dependencies": {
32
+ "@thumbmarkjs/thumbmarkjs": "^0.20.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.3.0"
38
+ },
39
+ "keywords": [
40
+ "device-intelligence",
41
+ "fingerprint",
42
+ "aml",
43
+ "kyc",
44
+ "compliance",
45
+ "risk-scoring",
46
+ "fraud-prevention",
47
+ "geolocation",
48
+ "quickcheck",
49
+ "panama"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }