UNPKG

@tracetail/angular

Version:

Angular SDK for TraceTail browser fingerprinting - over 99.5% accuracy

404 lines (393 loc) 15.6 kB
import * as i0 from '@angular/core'; import { InjectionToken, Inject, Injectable, Input, Directive, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { BehaviorSubject } from 'rxjs'; import TraceTail from '@tracetail/js'; import { take, switchMap, map } from 'rxjs/operators'; import * as i2 from '@angular/router'; const TRACETAIL_CONFIG = new InjectionToken('TRACETAIL_CONFIG'); const DEFAULT_CONFIG = { config: { enhanced: true, timeout: 5000, endpoint: 'https://api.tracetail.com', testMode: false } }; class TraceTailService { config; client; fingerprintSubject = new BehaviorSubject(null); loadingSubject = new BehaviorSubject(true); errorSubject = new BehaviorSubject(null); fingerprint$ = this.fingerprintSubject.asObservable(); loading$ = this.loadingSubject.asObservable(); error$ = this.errorSubject.asObservable(); constructor(config) { this.config = config; this.initializeClient(); } initializeClient() { try { // Handle test mode if (this.config.config?.testMode && this.config.config.mockData) { this.fingerprintSubject.next({ visitorId: 'mock-visitor-123', confidence: 0.99, riskScore: 0.1, fraudulent: false, signals: {}, timestamp: new Date(), ...this.config.config.mockData }); this.loadingSubject.next(false); return; } // Initialize real client this.client = new TraceTail({ apiKey: this.config.apiKey, timeout: this.config.config?.timeout, debug: this.config.config?.debug || false }); // Get initial fingerprint this.loadFingerprint(); } catch (error) { this.handleError(error); } } async loadFingerprint() { try { this.loadingSubject.next(true); this.errorSubject.next(null); const result = await this.client.generateFingerprint(); const fingerprint = { visitorId: result.visitorId, confidence: result.confidence, riskScore: 0, fraudulent: false, signals: result.components || {}, timestamp: new Date() }; this.fingerprintSubject.next(fingerprint); this.loadingSubject.next(false); } catch (error) { this.handleError(error); } } handleError(error) { console.error('TraceTail error:', error); this.errorSubject.next(error); this.loadingSubject.next(false); } /** * Get current fingerprint as a promise */ async getFingerprint() { const current = this.fingerprintSubject.value; if (current) { return current; } // Wait for fingerprint to be loaded return new Promise((resolve, reject) => { const subscription = this.fingerprint$.subscribe({ next: (fingerprint) => { if (fingerprint) { subscription.unsubscribe(); resolve(fingerprint); } }, error: (error) => { subscription.unsubscribe(); reject(error); } }); }); } /** * Track an event */ async trackEvent(event, data) { try { // TraceTail doesn't have a track method - this is custom app functionality // For now, just return mock data with a generated event ID const eventId = `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; return { success: true, fraudulent: false, riskScore: 0, eventId: eventId }; } catch (error) { console.error('Track event error:', error); throw error; } } /** * Check for fraud */ async checkFraud(data) { try { const fingerprint = await this.getFingerprint(); // Simple fraud detection logic based on risk score const block = fingerprint.riskScore > 0.8; const challenge = fingerprint.riskScore > 0.5 && fingerprint.riskScore <= 0.8; const allow = fingerprint.riskScore <= 0.5; const reasons = []; if (fingerprint.signals.vpn) reasons.push('VPN detected'); if (fingerprint.signals.tor) reasons.push('Tor browser detected'); if (fingerprint.signals.proxy) reasons.push('Proxy detected'); if (fingerprint.riskScore > 0.7) reasons.push('High risk score'); return { block, challenge, allow, riskScore: fingerprint.riskScore, reasons }; } catch (error) { console.error('Fraud check error:', error); throw error; } } /** * Retry fingerprinting */ retry() { this.loadFingerprint(); } /** * Force refresh fingerprint */ async refresh() { await this.loadFingerprint(); } /** * Get raw TraceTail client instance */ getClient() { return this.client; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailService, deps: [{ token: TRACETAIL_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [TRACETAIL_CONFIG] }] }] }); class FraudDetectionDirective { el; traceTail; appFraudDetection = 'medium'; disableOnRisk = true; cssClass = 'high-risk'; subscription = null; constructor(el, traceTail) { this.el = el; this.traceTail = traceTail; } ngOnInit() { this.subscription = this.traceTail.fingerprint$.subscribe(fingerprint => { if (!fingerprint) return; const thresholds = { low: 0.3, medium: 0.5, high: 0.7 }; const threshold = thresholds[this.appFraudDetection]; const isHighRisk = fingerprint.riskScore > threshold; // Apply or remove CSS class if (isHighRisk) { this.el.nativeElement.classList.add(this.cssClass); // Optionally disable the element if (this.disableOnRisk && this.isFormElement()) { this.el.nativeElement.setAttribute('disabled', 'true'); this.el.nativeElement.setAttribute('title', 'High fraud risk detected'); } } else { this.el.nativeElement.classList.remove(this.cssClass); if (this.disableOnRisk && this.isFormElement()) { this.el.nativeElement.removeAttribute('disabled'); this.el.nativeElement.removeAttribute('title'); } } // Emit custom event this.el.nativeElement.dispatchEvent(new CustomEvent('fraudStatusChange', { detail: { isHighRisk, riskScore: fingerprint.riskScore, threshold }, bubbles: true })); }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } isFormElement() { const tagName = this.el.nativeElement.tagName.toLowerCase(); return ['input', 'button', 'select', 'textarea', 'form'].includes(tagName); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudDetectionDirective, deps: [{ token: i0.ElementRef }, { token: TraceTailService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: FraudDetectionDirective, selector: "[appFraudDetection]", inputs: { appFraudDetection: "appFraudDetection", disableOnRisk: "disableOnRisk", cssClass: "cssClass" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudDetectionDirective, decorators: [{ type: Directive, args: [{ selector: '[appFraudDetection]' }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: TraceTailService }], propDecorators: { appFraudDetection: [{ type: Input }], disableOnRisk: [{ type: Input }], cssClass: [{ type: Input }] } }); class TraceTailInterceptor { traceTail; constructor(traceTail) { this.traceTail = traceTail; } intercept(req, next) { // Skip TraceTail API requests to prevent circular dependency if (req.url.includes('api.tracetail.com')) { return next.handle(req); } return this.traceTail.fingerprint$.pipe(take(1), switchMap(fingerprint => { // Clone the request and add TraceTail headers const modifiedReq = req.clone({ headers: req.headers .set('X-TraceTail-Visitor-ID', fingerprint?.visitorId || 'unknown') .set('X-TraceTail-Risk-Score', fingerprint?.riskScore?.toString() || '0') .set('X-TraceTail-Confidence', fingerprint?.confidence?.toString() || '0') }); return next.handle(modifiedReq); })); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor, deps: [{ token: TraceTailService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: TraceTailService }] }); class TraceTailModule { static forRoot(config) { return { ngModule: TraceTailModule, providers: [ { provide: TRACETAIL_CONFIG, useValue: { ...DEFAULT_CONFIG, ...config } }, TraceTailService, { provide: HTTP_INTERCEPTORS, useClass: TraceTailInterceptor, multi: true } ] }; } static forFeature(config) { return { ngModule: TraceTailModule, providers: config ? [ { provide: TRACETAIL_CONFIG, useValue: { ...DEFAULT_CONFIG, ...config } } ] : [] }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, declarations: [FraudDetectionDirective], imports: [CommonModule], exports: [FraudDetectionDirective] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, decorators: [{ type: NgModule, args: [{ declarations: [ FraudDetectionDirective ], imports: [ CommonModule ], exports: [ FraudDetectionDirective ] }] }] }); /** * TraceTail types for Angular */ class FraudProtectionGuard { traceTail; router; constructor(traceTail, router) { this.traceTail = traceTail; this.router = router; } canActivate(route, state) { // Get risk threshold from route data or use default const maxRiskScore = route.data['maxRiskScore'] ?? 0.7; const verificationUrl = route.data['verificationUrl'] ?? '/verify'; return this.traceTail.fingerprint$.pipe(take(1), map(fingerprint => { if (!fingerprint) { // No fingerprint yet, allow but monitor console.warn('No fingerprint available for fraud check'); return true; } // Check if user is fraudulent or high risk if (fingerprint.fraudulent || fingerprint.riskScore > maxRiskScore) { console.warn('High risk user blocked:', { visitorId: fingerprint.visitorId, riskScore: fingerprint.riskScore, fraudulent: fingerprint.fraudulent }); // Redirect to verification page return this.router.createUrlTree([verificationUrl], { queryParams: { reason: fingerprint.fraudulent ? 'fraud' : 'risk', returnUrl: state.url } }); } // User is safe, allow access return true; })); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, deps: [{ token: TraceTailService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: TraceTailService }, { type: i2.Router }] }); /** * Public API Surface of @tracetail/angular */ /** * Generated bundle index. Do not edit. */ export { DEFAULT_CONFIG, FraudDetectionDirective, FraudProtectionGuard, TRACETAIL_CONFIG, TraceTailInterceptor, TraceTailModule, TraceTailService }; //# sourceMappingURL=tracetail-angular.mjs.map