@tracetail/angular 2.3.3
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 +201 -0
- package/dist/README.md +201 -0
- package/dist/esm2022/lib/directives/fraud-detection.directive.mjs +77 -0
- package/dist/esm2022/lib/guards/fraud-protection.guard.mjs +51 -0
- package/dist/esm2022/lib/interceptors/tracetail.interceptor.mjs +32 -0
- package/dist/esm2022/lib/tracetail.config.mjs +11 -0
- package/dist/esm2022/lib/tracetail.module.mjs +56 -0
- package/dist/esm2022/lib/tracetail.service.mjs +179 -0
- package/dist/esm2022/lib/tracetail.types.mjs +5 -0
- package/dist/esm2022/public-api.mjs +11 -0
- package/dist/esm2022/tracetail-angular.mjs +5 -0
- package/dist/fesm2022/tracetail-angular.mjs +403 -0
- package/dist/fesm2022/tracetail-angular.mjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/lib/directives/fraud-detection.directive.d.ts +17 -0
- package/dist/lib/guards/fraud-protection.guard.d.ts +12 -0
- package/dist/lib/interceptors/tracetail.interceptor.d.ts +11 -0
- package/dist/lib/tracetail.config.d.ts +4 -0
- package/dist/lib/tracetail.module.d.ts +12 -0
- package/dist/lib/tracetail.service.d.ts +44 -0
- package/dist/lib/tracetail.types.d.ts +55 -0
- package/dist/public-api.d.ts +10 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @tracetail/angular
|
|
2
|
+
|
|
3
|
+
Official Angular SDK for TraceTail - Enterprise Browser Fingerprinting with over 99.5% accuracy.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Over 99.5% Accuracy** - Industry-leading browser fingerprinting
|
|
8
|
+
- ⚡ **<25ms Performance** - Lightning-fast fingerprint generation
|
|
9
|
+
- 🛡️ **Fraud Detection** - Built-in risk scoring and fraud prevention
|
|
10
|
+
- 🔄 **Real-time Updates** - Live visitor tracking and analytics
|
|
11
|
+
- 📦 **27KB Bundle** - Lightweight and optimized
|
|
12
|
+
- 🏗️ **Angular 14+** - Full support for modern Angular versions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @tracetail/angular
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Import TraceTailModule
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { TraceTailModule } from '@tracetail/angular';
|
|
26
|
+
|
|
27
|
+
@NgModule({
|
|
28
|
+
imports: [
|
|
29
|
+
TraceTailModule.forRoot({
|
|
30
|
+
apiKey: 'your-api-key-here',
|
|
31
|
+
config: {
|
|
32
|
+
enhanced: true
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
export class AppModule { }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Use in Components
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { TraceTailService } from '@tracetail/angular';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
template: `
|
|
47
|
+
<div *ngIf="fingerprint$ | async as fp">
|
|
48
|
+
Visitor: {{ fp.visitorId }}
|
|
49
|
+
Risk: {{ fp.riskScore }}
|
|
50
|
+
</div>
|
|
51
|
+
`
|
|
52
|
+
})
|
|
53
|
+
export class MyComponent {
|
|
54
|
+
fingerprint$ = this.traceTail.fingerprint$;
|
|
55
|
+
|
|
56
|
+
constructor(private traceTail: TraceTailService) {}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### 🔐 Fraud Detection Guard
|
|
63
|
+
|
|
64
|
+
Protect routes from high-risk visitors:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const routes: Routes = [
|
|
68
|
+
{
|
|
69
|
+
path: 'checkout',
|
|
70
|
+
component: CheckoutComponent,
|
|
71
|
+
canActivate: [FraudProtectionGuard],
|
|
72
|
+
data: { maxRiskScore: 0.5 }
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 🎨 Fraud Detection Directive
|
|
78
|
+
|
|
79
|
+
Apply fraud detection to any element:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<button
|
|
83
|
+
appFraudDetection="high"
|
|
84
|
+
[disableOnRisk]="true"
|
|
85
|
+
class="purchase-btn"
|
|
86
|
+
>
|
|
87
|
+
Complete Purchase
|
|
88
|
+
</button>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 🔄 HTTP Interceptor
|
|
92
|
+
|
|
93
|
+
Automatically add visitor ID to all API requests:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Automatically included when using forRoot()
|
|
97
|
+
// Adds X-TraceTail-Visitor-ID header to requests
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 📊 Event Tracking
|
|
101
|
+
|
|
102
|
+
Track user actions with fraud detection:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
async onLogin(credentials: any) {
|
|
106
|
+
const result = await this.traceTail.trackEvent('login', {
|
|
107
|
+
username: credentials.username
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result.fraudulent) {
|
|
111
|
+
// Handle fraud
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## API Reference
|
|
117
|
+
|
|
118
|
+
### TraceTailService
|
|
119
|
+
|
|
120
|
+
- `fingerprint$: Observable<Fingerprint>` - Current fingerprint data
|
|
121
|
+
- `loading$: Observable<boolean>` - Loading state
|
|
122
|
+
- `error$: Observable<Error>` - Error state
|
|
123
|
+
- `getFingerprint(): Promise<Fingerprint>` - Get fingerprint as promise
|
|
124
|
+
- `trackEvent(event, data?): Promise<TrackingResult>` - Track events
|
|
125
|
+
- `checkFraud(data): Promise<FraudResult>` - Check fraud risk
|
|
126
|
+
- `retry(): void` - Retry fingerprinting
|
|
127
|
+
- `refresh(): Promise<void>` - Force refresh
|
|
128
|
+
|
|
129
|
+
### Types
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
interface Fingerprint {
|
|
133
|
+
visitorId: string;
|
|
134
|
+
confidence: number; // 0-1
|
|
135
|
+
riskScore: number; // 0-1
|
|
136
|
+
fraudulent: boolean;
|
|
137
|
+
signals: SignalData;
|
|
138
|
+
timestamp: Date;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
### Authentication Protection
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@Component({
|
|
148
|
+
template: `
|
|
149
|
+
<form (ngSubmit)="login()">
|
|
150
|
+
<div *ngIf="(riskScore$ | async) > 0.7" class="warning">
|
|
151
|
+
High risk detected - additional verification required
|
|
152
|
+
</div>
|
|
153
|
+
<!-- form fields -->
|
|
154
|
+
</form>
|
|
155
|
+
`
|
|
156
|
+
})
|
|
157
|
+
export class LoginComponent {
|
|
158
|
+
riskScore$ = this.traceTail.fingerprint$.pipe(
|
|
159
|
+
map(fp => fp?.riskScore || 0)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
constructor(private traceTail: TraceTailService) {}
|
|
163
|
+
|
|
164
|
+
async login() {
|
|
165
|
+
const fp = await this.traceTail.getFingerprint();
|
|
166
|
+
if (fp.riskScore > 0.7) {
|
|
167
|
+
// Require 2FA
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Personalization
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
@Injectable()
|
|
177
|
+
export class PersonalizationService {
|
|
178
|
+
preferences$ = this.traceTail.fingerprint$.pipe(
|
|
179
|
+
switchMap(fp => fp ?
|
|
180
|
+
this.http.get(`/api/preferences/${fp.visitorId}`) :
|
|
181
|
+
of(null)
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
constructor(
|
|
186
|
+
private traceTail: TraceTailService,
|
|
187
|
+
private http: HttpClient
|
|
188
|
+
) {}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Support
|
|
193
|
+
|
|
194
|
+
- 📚 [Documentation](https://docs.tracetail.com/angular)
|
|
195
|
+
- 💬 [Discord Community](https://discord.gg/tracetail)
|
|
196
|
+
- 📧 [Email Support](mailto:support@tracetail.com)
|
|
197
|
+
- 🐛 [Issue Tracker](https://github.com/tracetail/tracetail/issues)
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT - see [LICENSE](LICENSE) for details.
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @tracetail/angular
|
|
2
|
+
|
|
3
|
+
Official Angular SDK for TraceTail - Enterprise Browser Fingerprinting with over 99.5% accuracy.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Over 99.5% Accuracy** - Industry-leading browser fingerprinting
|
|
8
|
+
- ⚡ **<25ms Performance** - Lightning-fast fingerprint generation
|
|
9
|
+
- 🛡️ **Fraud Detection** - Built-in risk scoring and fraud prevention
|
|
10
|
+
- 🔄 **Real-time Updates** - Live visitor tracking and analytics
|
|
11
|
+
- 📦 **27KB Bundle** - Lightweight and optimized
|
|
12
|
+
- 🏗️ **Angular 14+** - Full support for modern Angular versions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @tracetail/angular
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Import TraceTailModule
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { TraceTailModule } from '@tracetail/angular';
|
|
26
|
+
|
|
27
|
+
@NgModule({
|
|
28
|
+
imports: [
|
|
29
|
+
TraceTailModule.forRoot({
|
|
30
|
+
apiKey: 'your-api-key-here',
|
|
31
|
+
config: {
|
|
32
|
+
enhanced: true
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
export class AppModule { }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Use in Components
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { TraceTailService } from '@tracetail/angular';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
template: `
|
|
47
|
+
<div *ngIf="fingerprint$ | async as fp">
|
|
48
|
+
Visitor: {{ fp.visitorId }}
|
|
49
|
+
Risk: {{ fp.riskScore }}
|
|
50
|
+
</div>
|
|
51
|
+
`
|
|
52
|
+
})
|
|
53
|
+
export class MyComponent {
|
|
54
|
+
fingerprint$ = this.traceTail.fingerprint$;
|
|
55
|
+
|
|
56
|
+
constructor(private traceTail: TraceTailService) {}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### 🔐 Fraud Detection Guard
|
|
63
|
+
|
|
64
|
+
Protect routes from high-risk visitors:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const routes: Routes = [
|
|
68
|
+
{
|
|
69
|
+
path: 'checkout',
|
|
70
|
+
component: CheckoutComponent,
|
|
71
|
+
canActivate: [FraudProtectionGuard],
|
|
72
|
+
data: { maxRiskScore: 0.5 }
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 🎨 Fraud Detection Directive
|
|
78
|
+
|
|
79
|
+
Apply fraud detection to any element:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<button
|
|
83
|
+
appFraudDetection="high"
|
|
84
|
+
[disableOnRisk]="true"
|
|
85
|
+
class="purchase-btn"
|
|
86
|
+
>
|
|
87
|
+
Complete Purchase
|
|
88
|
+
</button>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 🔄 HTTP Interceptor
|
|
92
|
+
|
|
93
|
+
Automatically add visitor ID to all API requests:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Automatically included when using forRoot()
|
|
97
|
+
// Adds X-TraceTail-Visitor-ID header to requests
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 📊 Event Tracking
|
|
101
|
+
|
|
102
|
+
Track user actions with fraud detection:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
async onLogin(credentials: any) {
|
|
106
|
+
const result = await this.traceTail.trackEvent('login', {
|
|
107
|
+
username: credentials.username
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result.fraudulent) {
|
|
111
|
+
// Handle fraud
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## API Reference
|
|
117
|
+
|
|
118
|
+
### TraceTailService
|
|
119
|
+
|
|
120
|
+
- `fingerprint$: Observable<Fingerprint>` - Current fingerprint data
|
|
121
|
+
- `loading$: Observable<boolean>` - Loading state
|
|
122
|
+
- `error$: Observable<Error>` - Error state
|
|
123
|
+
- `getFingerprint(): Promise<Fingerprint>` - Get fingerprint as promise
|
|
124
|
+
- `trackEvent(event, data?): Promise<TrackingResult>` - Track events
|
|
125
|
+
- `checkFraud(data): Promise<FraudResult>` - Check fraud risk
|
|
126
|
+
- `retry(): void` - Retry fingerprinting
|
|
127
|
+
- `refresh(): Promise<void>` - Force refresh
|
|
128
|
+
|
|
129
|
+
### Types
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
interface Fingerprint {
|
|
133
|
+
visitorId: string;
|
|
134
|
+
confidence: number; // 0-1
|
|
135
|
+
riskScore: number; // 0-1
|
|
136
|
+
fraudulent: boolean;
|
|
137
|
+
signals: SignalData;
|
|
138
|
+
timestamp: Date;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
### Authentication Protection
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@Component({
|
|
148
|
+
template: `
|
|
149
|
+
<form (ngSubmit)="login()">
|
|
150
|
+
<div *ngIf="(riskScore$ | async) > 0.7" class="warning">
|
|
151
|
+
High risk detected - additional verification required
|
|
152
|
+
</div>
|
|
153
|
+
<!-- form fields -->
|
|
154
|
+
</form>
|
|
155
|
+
`
|
|
156
|
+
})
|
|
157
|
+
export class LoginComponent {
|
|
158
|
+
riskScore$ = this.traceTail.fingerprint$.pipe(
|
|
159
|
+
map(fp => fp?.riskScore || 0)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
constructor(private traceTail: TraceTailService) {}
|
|
163
|
+
|
|
164
|
+
async login() {
|
|
165
|
+
const fp = await this.traceTail.getFingerprint();
|
|
166
|
+
if (fp.riskScore > 0.7) {
|
|
167
|
+
// Require 2FA
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Personalization
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
@Injectable()
|
|
177
|
+
export class PersonalizationService {
|
|
178
|
+
preferences$ = this.traceTail.fingerprint$.pipe(
|
|
179
|
+
switchMap(fp => fp ?
|
|
180
|
+
this.http.get(`/api/preferences/${fp.visitorId}`) :
|
|
181
|
+
of(null)
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
constructor(
|
|
186
|
+
private traceTail: TraceTailService,
|
|
187
|
+
private http: HttpClient
|
|
188
|
+
) {}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Support
|
|
193
|
+
|
|
194
|
+
- 📚 [Documentation](https://docs.tracetail.com/angular)
|
|
195
|
+
- 💬 [Discord Community](https://discord.gg/tracetail)
|
|
196
|
+
- 📧 [Email Support](mailto:support@tracetail.com)
|
|
197
|
+
- 🐛 [Issue Tracker](https://github.com/tracetail/tracetail/issues)
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Directive, Input } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import * as i1 from "../tracetail.service";
|
|
4
|
+
export class FraudDetectionDirective {
|
|
5
|
+
el;
|
|
6
|
+
traceTail;
|
|
7
|
+
appFraudDetection = 'medium';
|
|
8
|
+
disableOnRisk = true;
|
|
9
|
+
cssClass = 'high-risk';
|
|
10
|
+
subscription = null;
|
|
11
|
+
constructor(el, traceTail) {
|
|
12
|
+
this.el = el;
|
|
13
|
+
this.traceTail = traceTail;
|
|
14
|
+
}
|
|
15
|
+
ngOnInit() {
|
|
16
|
+
this.subscription = this.traceTail.fingerprint$.subscribe(fingerprint => {
|
|
17
|
+
if (!fingerprint)
|
|
18
|
+
return;
|
|
19
|
+
const thresholds = {
|
|
20
|
+
low: 0.3,
|
|
21
|
+
medium: 0.5,
|
|
22
|
+
high: 0.7
|
|
23
|
+
};
|
|
24
|
+
const threshold = thresholds[this.appFraudDetection];
|
|
25
|
+
const isHighRisk = fingerprint.riskScore > threshold;
|
|
26
|
+
// Apply or remove CSS class
|
|
27
|
+
if (isHighRisk) {
|
|
28
|
+
this.el.nativeElement.classList.add(this.cssClass);
|
|
29
|
+
// Optionally disable the element
|
|
30
|
+
if (this.disableOnRisk && this.isFormElement()) {
|
|
31
|
+
this.el.nativeElement.setAttribute('disabled', 'true');
|
|
32
|
+
this.el.nativeElement.setAttribute('title', 'High fraud risk detected');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.el.nativeElement.classList.remove(this.cssClass);
|
|
37
|
+
if (this.disableOnRisk && this.isFormElement()) {
|
|
38
|
+
this.el.nativeElement.removeAttribute('disabled');
|
|
39
|
+
this.el.nativeElement.removeAttribute('title');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Emit custom event
|
|
43
|
+
this.el.nativeElement.dispatchEvent(new CustomEvent('fraudStatusChange', {
|
|
44
|
+
detail: {
|
|
45
|
+
isHighRisk,
|
|
46
|
+
riskScore: fingerprint.riskScore,
|
|
47
|
+
threshold
|
|
48
|
+
},
|
|
49
|
+
bubbles: true
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
ngOnDestroy() {
|
|
54
|
+
if (this.subscription) {
|
|
55
|
+
this.subscription.unsubscribe();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
isFormElement() {
|
|
59
|
+
const tagName = this.el.nativeElement.tagName.toLowerCase();
|
|
60
|
+
return ['input', 'button', 'select', 'textarea', 'form'].includes(tagName);
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudDetectionDirective, deps: [{ token: i0.ElementRef }, { token: i1.TraceTailService }], target: i0.ɵɵFactoryTarget.Directive });
|
|
63
|
+
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 });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudDetectionDirective, decorators: [{
|
|
66
|
+
type: Directive,
|
|
67
|
+
args: [{
|
|
68
|
+
selector: '[appFraudDetection]'
|
|
69
|
+
}]
|
|
70
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.TraceTailService }], propDecorators: { appFraudDetection: [{
|
|
71
|
+
type: Input
|
|
72
|
+
}], disableOnRisk: [{
|
|
73
|
+
type: Input
|
|
74
|
+
}], cssClass: [{
|
|
75
|
+
type: Input
|
|
76
|
+
}] } });
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnJhdWQtZGV0ZWN0aW9uLmRpcmVjdGl2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZGlyZWN0aXZlcy9mcmF1ZC1kZXRlY3Rpb24uZGlyZWN0aXZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxTQUFTLEVBQ1QsS0FBSyxFQUlOLE1BQU0sZUFBZSxDQUFDOzs7QUFPdkIsTUFBTSxPQUFPLHVCQUF1QjtJQVF4QjtJQUNBO0lBUkQsaUJBQWlCLEdBQThCLFFBQVEsQ0FBQztJQUN4RCxhQUFhLEdBQUcsSUFBSSxDQUFDO0lBQ3JCLFFBQVEsR0FBRyxXQUFXLENBQUM7SUFFeEIsWUFBWSxHQUF3QixJQUFJLENBQUM7SUFFakQsWUFDVSxFQUEyQixFQUMzQixTQUEyQjtRQUQzQixPQUFFLEdBQUYsRUFBRSxDQUF5QjtRQUMzQixjQUFTLEdBQVQsU0FBUyxDQUFrQjtJQUNsQyxDQUFDO0lBRUosUUFBUTtRQUNOLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxFQUFFO1lBQ3RFLElBQUksQ0FBQyxXQUFXO2dCQUFFLE9BQU87WUFFekIsTUFBTSxVQUFVLEdBQUc7Z0JBQ2pCLEdBQUcsRUFBRSxHQUFHO2dCQUNSLE1BQU0sRUFBRSxHQUFHO2dCQUNYLElBQUksRUFBRSxHQUFHO2FBQ1YsQ0FBQztZQUVGLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQztZQUNyRCxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQztZQUVyRCw0QkFBNEI7WUFDNUIsSUFBSSxVQUFVLEVBQUU7Z0JBQ2QsSUFBSSxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBRW5ELGlDQUFpQztnQkFDakMsSUFBSSxJQUFJLENBQUMsYUFBYSxJQUFJLElBQUksQ0FBQyxhQUFhLEVBQUUsRUFBRTtvQkFDOUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQztvQkFDdkQsSUFBSSxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLE9BQU8sRUFBRSwwQkFBMEIsQ0FBQyxDQUFDO2lCQUN6RTthQUNGO2lCQUFNO2dCQUNMLElBQUksQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUV0RCxJQUFJLElBQUksQ0FBQyxhQUFhLElBQUksSUFBSSxDQUFDLGFBQWEsRUFBRSxFQUFFO29CQUM5QyxJQUFJLENBQUMsRUFBRSxDQUFDLGFBQWEsQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7b0JBQ2xELElBQUksQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsQ0FBQztpQkFDaEQ7YUFDRjtZQUVELG9CQUFvQjtZQUNwQixJQUFJLENBQUMsRUFBRSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsSUFBSSxXQUFXLENBQUMsbUJBQW1CLEVBQUU7Z0JBQ3ZFLE1BQU0sRUFBRTtvQkFDTixVQUFVO29CQUNWLFNBQVMsRUFBRSxXQUFXLENBQUMsU0FBUztvQkFDaEMsU0FBUztpQkFDVjtnQkFDRCxPQUFPLEVBQUUsSUFBSTthQUNkLENBQUMsQ0FBQyxDQUFDO1FBQ04sQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsV0FBVztRQUNULElBQUksSUFBSSxDQUFDLFlBQVksRUFBRTtZQUNyQixJQUFJLENBQUMsWUFBWSxDQUFDLFdBQVcsRUFBRSxDQUFDO1NBQ2pDO0lBQ0gsQ0FBQztJQUVPLGFBQWE7UUFDbkIsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQzVELE9BQU8sQ0FBQyxPQUFPLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzdFLENBQUM7d0dBaEVVLHVCQUF1Qjs0RkFBdkIsdUJBQXVCOzs0RkFBdkIsdUJBQXVCO2tCQUhuQyxTQUFTO21CQUFDO29CQUNULFFBQVEsRUFBRSxxQkFBcUI7aUJBQ2hDOzhHQUVVLGlCQUFpQjtzQkFBekIsS0FBSztnQkFDRyxhQUFhO3NCQUFyQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBcbiAgRGlyZWN0aXZlLCBcbiAgSW5wdXQsIFxuICBFbGVtZW50UmVmLCBcbiAgT25Jbml0LCBcbiAgT25EZXN0cm95IFxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFN1YnNjcmlwdGlvbiB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgVHJhY2VUYWlsU2VydmljZSB9IGZyb20gJy4uL3RyYWNldGFpbC5zZXJ2aWNlJztcblxuQERpcmVjdGl2ZSh7XG4gIHNlbGVjdG9yOiAnW2FwcEZyYXVkRGV0ZWN0aW9uXSdcbn0pXG5leHBvcnQgY2xhc3MgRnJhdWREZXRlY3Rpb25EaXJlY3RpdmUgaW1wbGVtZW50cyBPbkluaXQsIE9uRGVzdHJveSB7XG4gIEBJbnB1dCgpIGFwcEZyYXVkRGV0ZWN0aW9uOiAnbG93JyB8ICdtZWRpdW0nIHwgJ2hpZ2gnID0gJ21lZGl1bSc7XG4gIEBJbnB1dCgpIGRpc2FibGVPblJpc2sgPSB0cnVlO1xuICBASW5wdXQoKSBjc3NDbGFzcyA9ICdoaWdoLXJpc2snO1xuXG4gIHByaXZhdGUgc3Vic2NyaXB0aW9uOiBTdWJzY3JpcHRpb24gfCBudWxsID0gbnVsbDtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIGVsOiBFbGVtZW50UmVmPEhUTUxFbGVtZW50PixcbiAgICBwcml2YXRlIHRyYWNlVGFpbDogVHJhY2VUYWlsU2VydmljZVxuICApIHt9XG5cbiAgbmdPbkluaXQoKTogdm9pZCB7XG4gICAgdGhpcy5zdWJzY3JpcHRpb24gPSB0aGlzLnRyYWNlVGFpbC5maW5nZXJwcmludCQuc3Vic2NyaWJlKGZpbmdlcnByaW50ID0+IHtcbiAgICAgIGlmICghZmluZ2VycHJpbnQpIHJldHVybjtcblxuICAgICAgY29uc3QgdGhyZXNob2xkcyA9IHtcbiAgICAgICAgbG93OiAwLjMsXG4gICAgICAgIG1lZGl1bTogMC41LFxuICAgICAgICBoaWdoOiAwLjdcbiAgICAgIH07XG5cbiAgICAgIGNvbnN0IHRocmVzaG9sZCA9IHRocmVzaG9sZHNbdGhpcy5hcHBGcmF1ZERldGVjdGlvbl07XG4gICAgICBjb25zdCBpc0hpZ2hSaXNrID0gZmluZ2VycHJpbnQucmlza1Njb3JlID4gdGhyZXNob2xkO1xuXG4gICAgICAvLyBBcHBseSBvciByZW1vdmUgQ1NTIGNsYXNzXG4gICAgICBpZiAoaXNIaWdoUmlzaykge1xuICAgICAgICB0aGlzLmVsLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LmFkZCh0aGlzLmNzc0NsYXNzKTtcbiAgICAgICAgXG4gICAgICAgIC8vIE9wdGlvbmFsbHkgZGlzYWJsZSB0aGUgZWxlbWVudFxuICAgICAgICBpZiAodGhpcy5kaXNhYmxlT25SaXNrICYmIHRoaXMuaXNGb3JtRWxlbWVudCgpKSB7XG4gICAgICAgICAgdGhpcy5lbC5uYXRpdmVFbGVtZW50LnNldEF0dHJpYnV0ZSgnZGlzYWJsZWQnLCAndHJ1ZScpO1xuICAgICAgICAgIHRoaXMuZWwubmF0aXZlRWxlbWVudC5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgJ0hpZ2ggZnJhdWQgcmlzayBkZXRlY3RlZCcpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLmVsLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LnJlbW92ZSh0aGlzLmNzc0NsYXNzKTtcbiAgICAgICAgXG4gICAgICAgIGlmICh0aGlzLmRpc2FibGVPblJpc2sgJiYgdGhpcy5pc0Zvcm1FbGVtZW50KCkpIHtcbiAgICAgICAgICB0aGlzLmVsLm5hdGl2ZUVsZW1lbnQucmVtb3ZlQXR0cmlidXRlKCdkaXNhYmxlZCcpO1xuICAgICAgICAgIHRoaXMuZWwubmF0aXZlRWxlbWVudC5yZW1vdmVBdHRyaWJ1dGUoJ3RpdGxlJyk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gRW1pdCBjdXN0b20gZXZlbnRcbiAgICAgIHRoaXMuZWwubmF0aXZlRWxlbWVudC5kaXNwYXRjaEV2ZW50KG5ldyBDdXN0b21FdmVudCgnZnJhdWRTdGF0dXNDaGFuZ2UnLCB7XG4gICAgICAgIGRldGFpbDoge1xuICAgICAgICAgIGlzSGlnaFJpc2ssXG4gICAgICAgICAgcmlza1Njb3JlOiBmaW5nZXJwcmludC5yaXNrU2NvcmUsXG4gICAgICAgICAgdGhyZXNob2xkXG4gICAgICAgIH0sXG4gICAgICAgIGJ1YmJsZXM6IHRydWVcbiAgICAgIH0pKTtcbiAgICB9KTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnN1YnNjcmlwdGlvbikge1xuICAgICAgdGhpcy5zdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGlzRm9ybUVsZW1lbnQoKTogYm9vbGVhbiB7XG4gICAgY29uc3QgdGFnTmFtZSA9IHRoaXMuZWwubmF0aXZlRWxlbWVudC50YWdOYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgcmV0dXJuIFsnaW5wdXQnLCAnYnV0dG9uJywgJ3NlbGVjdCcsICd0ZXh0YXJlYScsICdmb3JtJ10uaW5jbHVkZXModGFnTmFtZSk7XG4gIH1cbn0iXX0=
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { map, take } from 'rxjs/operators';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import * as i1 from "../tracetail.service";
|
|
5
|
+
import * as i2 from "@angular/router";
|
|
6
|
+
export class FraudProtectionGuard {
|
|
7
|
+
traceTail;
|
|
8
|
+
router;
|
|
9
|
+
constructor(traceTail, router) {
|
|
10
|
+
this.traceTail = traceTail;
|
|
11
|
+
this.router = router;
|
|
12
|
+
}
|
|
13
|
+
canActivate(route, state) {
|
|
14
|
+
// Get risk threshold from route data or use default
|
|
15
|
+
const maxRiskScore = route.data['maxRiskScore'] ?? 0.7;
|
|
16
|
+
const verificationUrl = route.data['verificationUrl'] ?? '/verify';
|
|
17
|
+
return this.traceTail.fingerprint$.pipe(take(1), map(fingerprint => {
|
|
18
|
+
if (!fingerprint) {
|
|
19
|
+
// No fingerprint yet, allow but monitor
|
|
20
|
+
console.warn('No fingerprint available for fraud check');
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// Check if user is fraudulent or high risk
|
|
24
|
+
if (fingerprint.fraudulent || fingerprint.riskScore > maxRiskScore) {
|
|
25
|
+
console.warn('High risk user blocked:', {
|
|
26
|
+
visitorId: fingerprint.visitorId,
|
|
27
|
+
riskScore: fingerprint.riskScore,
|
|
28
|
+
fraudulent: fingerprint.fraudulent
|
|
29
|
+
});
|
|
30
|
+
// Redirect to verification page
|
|
31
|
+
return this.router.createUrlTree([verificationUrl], {
|
|
32
|
+
queryParams: {
|
|
33
|
+
reason: fingerprint.fraudulent ? 'fraud' : 'risk',
|
|
34
|
+
returnUrl: state.url
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// User is safe, allow access
|
|
39
|
+
return true;
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, deps: [{ token: i1.TraceTailService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
43
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, providedIn: 'root' });
|
|
44
|
+
}
|
|
45
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FraudProtectionGuard, decorators: [{
|
|
46
|
+
type: Injectable,
|
|
47
|
+
args: [{
|
|
48
|
+
providedIn: 'root'
|
|
49
|
+
}]
|
|
50
|
+
}], ctorParameters: () => [{ type: i1.TraceTailService }, { type: i2.Router }] });
|
|
51
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnJhdWQtcHJvdGVjdGlvbi5ndWFyZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZ3VhcmRzL2ZyYXVkLXByb3RlY3Rpb24uZ3VhcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQVMzQyxPQUFPLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxNQUFNLGdCQUFnQixDQUFDOzs7O0FBTTNDLE1BQU0sT0FBTyxvQkFBb0I7SUFFckI7SUFDQTtJQUZWLFlBQ1UsU0FBMkIsRUFDM0IsTUFBYztRQURkLGNBQVMsR0FBVCxTQUFTLENBQWtCO1FBQzNCLFdBQU0sR0FBTixNQUFNLENBQVE7SUFDckIsQ0FBQztJQUVKLFdBQVcsQ0FDVCxLQUE2QixFQUM3QixLQUEwQjtRQUUxQixvREFBb0Q7UUFDcEQsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsSUFBSSxHQUFHLENBQUM7UUFDdkQsTUFBTSxlQUFlLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLFNBQVMsQ0FBQztRQUVuRSxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLElBQUksQ0FDckMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUNQLEdBQUcsQ0FBQyxXQUFXLENBQUMsRUFBRTtZQUNoQixJQUFJLENBQUMsV0FBVyxFQUFFO2dCQUNoQix3Q0FBd0M7Z0JBQ3hDLE9BQU8sQ0FBQyxJQUFJLENBQUMsMENBQTBDLENBQUMsQ0FBQztnQkFDekQsT0FBTyxJQUFJLENBQUM7YUFDYjtZQUVELDJDQUEyQztZQUMzQyxJQUFJLFdBQVcsQ0FBQyxVQUFVLElBQUksV0FBVyxDQUFDLFNBQVMsR0FBRyxZQUFZLEVBQUU7Z0JBQ2xFLE9BQU8sQ0FBQyxJQUFJLENBQUMseUJBQXlCLEVBQUU7b0JBQ3RDLFNBQVMsRUFBRSxXQUFXLENBQUMsU0FBUztvQkFDaEMsU0FBUyxFQUFFLFdBQVcsQ0FBQyxTQUFTO29CQUNoQyxVQUFVLEVBQUUsV0FBVyxDQUFDLFVBQVU7aUJBQ25DLENBQUMsQ0FBQztnQkFFSCxnQ0FBZ0M7Z0JBQ2hDLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQyxlQUFlLENBQUMsRUFBRTtvQkFDbEQsV0FBVyxFQUFFO3dCQUNYLE1BQU0sRUFBRSxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU07d0JBQ2pELFNBQVMsRUFBRSxLQUFLLENBQUMsR0FBRztxQkFDckI7aUJBQ0YsQ0FBQyxDQUFDO2FBQ0o7WUFFRCw2QkFBNkI7WUFDN0IsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDLENBQUMsQ0FDSCxDQUFDO0lBQ0osQ0FBQzt3R0E1Q1Usb0JBQW9COzRHQUFwQixvQkFBb0IsY0FGbkIsTUFBTTs7NEZBRVAsb0JBQW9CO2tCQUhoQyxVQUFVO21CQUFDO29CQUNWLFVBQVUsRUFBRSxNQUFNO2lCQUNuQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7XG4gIENhbkFjdGl2YXRlLFxuICBBY3RpdmF0ZWRSb3V0ZVNuYXBzaG90LFxuICBSb3V0ZXJTdGF0ZVNuYXBzaG90LFxuICBSb3V0ZXIsXG4gIFVybFRyZWVcbn0gZnJvbSAnQGFuZ3VsYXIvcm91dGVyJztcbmltcG9ydCB7IE9ic2VydmFibGUgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IG1hcCwgdGFrZSB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7IFRyYWNlVGFpbFNlcnZpY2UgfSBmcm9tICcuLi90cmFjZXRhaWwuc2VydmljZSc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIEZyYXVkUHJvdGVjdGlvbkd1YXJkIGltcGxlbWVudHMgQ2FuQWN0aXZhdGUge1xuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHRyYWNlVGFpbDogVHJhY2VUYWlsU2VydmljZSxcbiAgICBwcml2YXRlIHJvdXRlcjogUm91dGVyXG4gICkge31cblxuICBjYW5BY3RpdmF0ZShcbiAgICByb3V0ZTogQWN0aXZhdGVkUm91dGVTbmFwc2hvdCxcbiAgICBzdGF0ZTogUm91dGVyU3RhdGVTbmFwc2hvdFxuICApOiBPYnNlcnZhYmxlPGJvb2xlYW4gfCBVcmxUcmVlPiB7XG4gICAgLy8gR2V0IHJpc2sgdGhyZXNob2xkIGZyb20gcm91dGUgZGF0YSBvciB1c2UgZGVmYXVsdFxuICAgIGNvbnN0IG1heFJpc2tTY29yZSA9IHJvdXRlLmRhdGFbJ21heFJpc2tTY29yZSddID8/IDAuNztcbiAgICBjb25zdCB2ZXJpZmljYXRpb25VcmwgPSByb3V0ZS5kYXRhWyd2ZXJpZmljYXRpb25VcmwnXSA/PyAnL3ZlcmlmeSc7XG5cbiAgICByZXR1cm4gdGhpcy50cmFjZVRhaWwuZmluZ2VycHJpbnQkLnBpcGUoXG4gICAgICB0YWtlKDEpLFxuICAgICAgbWFwKGZpbmdlcnByaW50ID0+IHtcbiAgICAgICAgaWYgKCFmaW5nZXJwcmludCkge1xuICAgICAgICAgIC8vIE5vIGZpbmdlcnByaW50IHlldCwgYWxsb3cgYnV0IG1vbml0b3JcbiAgICAgICAgICBjb25zb2xlLndhcm4oJ05vIGZpbmdlcnByaW50IGF2YWlsYWJsZSBmb3IgZnJhdWQgY2hlY2snKTtcbiAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENoZWNrIGlmIHVzZXIgaXMgZnJhdWR1bGVudCBvciBoaWdoIHJpc2tcbiAgICAgICAgaWYgKGZpbmdlcnByaW50LmZyYXVkdWxlbnQgfHwgZmluZ2VycHJpbnQucmlza1Njb3JlID4gbWF4Umlza1Njb3JlKSB7XG4gICAgICAgICAgY29uc29sZS53YXJuKCdIaWdoIHJpc2sgdXNlciBibG9ja2VkOicsIHtcbiAgICAgICAgICAgIHZpc2l0b3JJZDogZmluZ2VycHJpbnQudmlzaXRvcklkLFxuICAgICAgICAgICAgcmlza1Njb3JlOiBmaW5nZXJwcmludC5yaXNrU2NvcmUsXG4gICAgICAgICAgICBmcmF1ZHVsZW50OiBmaW5nZXJwcmludC5mcmF1ZHVsZW50XG4gICAgICAgICAgfSk7XG5cbiAgICAgICAgICAvLyBSZWRpcmVjdCB0byB2ZXJpZmljYXRpb24gcGFnZVxuICAgICAgICAgIHJldHVybiB0aGlzLnJvdXRlci5jcmVhdGVVcmxUcmVlKFt2ZXJpZmljYXRpb25VcmxdLCB7XG4gICAgICAgICAgICBxdWVyeVBhcmFtczoge1xuICAgICAgICAgICAgICByZWFzb246IGZpbmdlcnByaW50LmZyYXVkdWxlbnQgPyAnZnJhdWQnIDogJ3Jpc2snLFxuICAgICAgICAgICAgICByZXR1cm5Vcmw6IHN0YXRlLnVybFxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gVXNlciBpcyBzYWZlLCBhbGxvdyBhY2Nlc3NcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9KVxuICAgICk7XG4gIH1cbn0iXX0=
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { switchMap, take } from 'rxjs/operators';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import * as i1 from "../tracetail.service";
|
|
5
|
+
export class TraceTailInterceptor {
|
|
6
|
+
traceTail;
|
|
7
|
+
constructor(traceTail) {
|
|
8
|
+
this.traceTail = traceTail;
|
|
9
|
+
}
|
|
10
|
+
intercept(req, next) {
|
|
11
|
+
// Skip TraceTail API requests to prevent circular dependency
|
|
12
|
+
if (req.url.includes('api.tracetail.com')) {
|
|
13
|
+
return next.handle(req);
|
|
14
|
+
}
|
|
15
|
+
return this.traceTail.fingerprint$.pipe(take(1), switchMap(fingerprint => {
|
|
16
|
+
// Clone the request and add TraceTail headers
|
|
17
|
+
const modifiedReq = req.clone({
|
|
18
|
+
headers: req.headers
|
|
19
|
+
.set('X-TraceTail-Visitor-ID', fingerprint?.visitorId || 'unknown')
|
|
20
|
+
.set('X-TraceTail-Risk-Score', fingerprint?.riskScore?.toString() || '0')
|
|
21
|
+
.set('X-TraceTail-Confidence', fingerprint?.confidence?.toString() || '0')
|
|
22
|
+
});
|
|
23
|
+
return next.handle(modifiedReq);
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor, deps: [{ token: i1.TraceTailService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
27
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor });
|
|
28
|
+
}
|
|
29
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailInterceptor, decorators: [{
|
|
30
|
+
type: Injectable
|
|
31
|
+
}], ctorParameters: () => [{ type: i1.TraceTailService }] });
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2V0YWlsLmludGVyY2VwdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9pbnRlcmNlcHRvcnMvdHJhY2V0YWlsLmludGVyY2VwdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFRM0MsT0FBTyxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQzs7O0FBSWpELE1BQU0sT0FBTyxvQkFBb0I7SUFDWDtJQUFwQixZQUFvQixTQUEyQjtRQUEzQixjQUFTLEdBQVQsU0FBUyxDQUFrQjtJQUFHLENBQUM7SUFFbkQsU0FBUyxDQUNQLEdBQXFCLEVBQ3JCLElBQWlCO1FBRWpCLDZEQUE2RDtRQUM3RCxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFDLEVBQUU7WUFDekMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQ3pCO1FBRUQsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQ3JDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFDUCxTQUFTLENBQUMsV0FBVyxDQUFDLEVBQUU7WUFDdEIsOENBQThDO1lBQzlDLE1BQU0sV0FBVyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUM7Z0JBQzVCLE9BQU8sRUFBRSxHQUFHLENBQUMsT0FBTztxQkFDakIsR0FBRyxDQUFDLHdCQUF3QixFQUFFLFdBQVcsRUFBRSxTQUFTLElBQUksU0FBUyxDQUFDO3FCQUNsRSxHQUFHLENBQUMsd0JBQXdCLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxHQUFHLENBQUM7cUJBQ3hFLEdBQUcsQ0FBQyx3QkFBd0IsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxJQUFJLEdBQUcsQ0FBQzthQUM3RSxDQUFDLENBQUM7WUFFSCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDLENBQ0gsQ0FBQztJQUNKLENBQUM7d0dBMUJVLG9CQUFvQjs0R0FBcEIsb0JBQW9COzs0RkFBcEIsb0JBQW9CO2tCQURoQyxVQUFVIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgSHR0cEludGVyY2VwdG9yLFxuICBIdHRwUmVxdWVzdCxcbiAgSHR0cEhhbmRsZXIsXG4gIEh0dHBFdmVudFxufSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XG5pbXBvcnQgeyBPYnNlcnZhYmxlIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBzd2l0Y2hNYXAsIHRha2UgfSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5pbXBvcnQgeyBUcmFjZVRhaWxTZXJ2aWNlIH0gZnJvbSAnLi4vdHJhY2V0YWlsLnNlcnZpY2UnO1xuXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgVHJhY2VUYWlsSW50ZXJjZXB0b3IgaW1wbGVtZW50cyBIdHRwSW50ZXJjZXB0b3Ige1xuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHRyYWNlVGFpbDogVHJhY2VUYWlsU2VydmljZSkge31cblxuICBpbnRlcmNlcHQoXG4gICAgcmVxOiBIdHRwUmVxdWVzdDxhbnk+LCBcbiAgICBuZXh0OiBIdHRwSGFuZGxlclxuICApOiBPYnNlcnZhYmxlPEh0dHBFdmVudDxhbnk+PiB7XG4gICAgLy8gU2tpcCBUcmFjZVRhaWwgQVBJIHJlcXVlc3RzIHRvIHByZXZlbnQgY2lyY3VsYXIgZGVwZW5kZW5jeVxuICAgIGlmIChyZXEudXJsLmluY2x1ZGVzKCdhcGkudHJhY2V0YWlsLmNvbScpKSB7XG4gICAgICByZXR1cm4gbmV4dC5oYW5kbGUocmVxKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy50cmFjZVRhaWwuZmluZ2VycHJpbnQkLnBpcGUoXG4gICAgICB0YWtlKDEpLFxuICAgICAgc3dpdGNoTWFwKGZpbmdlcnByaW50ID0+IHtcbiAgICAgICAgLy8gQ2xvbmUgdGhlIHJlcXVlc3QgYW5kIGFkZCBUcmFjZVRhaWwgaGVhZGVyc1xuICAgICAgICBjb25zdCBtb2RpZmllZFJlcSA9IHJlcS5jbG9uZSh7XG4gICAgICAgICAgaGVhZGVyczogcmVxLmhlYWRlcnNcbiAgICAgICAgICAgIC5zZXQoJ1gtVHJhY2VUYWlsLVZpc2l0b3ItSUQnLCBmaW5nZXJwcmludD8udmlzaXRvcklkIHx8ICd1bmtub3duJylcbiAgICAgICAgICAgIC5zZXQoJ1gtVHJhY2VUYWlsLVJpc2stU2NvcmUnLCBmaW5nZXJwcmludD8ucmlza1Njb3JlPy50b1N0cmluZygpIHx8ICcwJylcbiAgICAgICAgICAgIC5zZXQoJ1gtVHJhY2VUYWlsLUNvbmZpZGVuY2UnLCBmaW5nZXJwcmludD8uY29uZmlkZW5jZT8udG9TdHJpbmcoKSB8fCAnMCcpXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJldHVybiBuZXh0LmhhbmRsZShtb2RpZmllZFJlcSk7XG4gICAgICB9KVxuICAgICk7XG4gIH1cbn0iXX0=
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
export const TRACETAIL_CONFIG = new InjectionToken('TRACETAIL_CONFIG');
|
|
3
|
+
export const DEFAULT_CONFIG = {
|
|
4
|
+
config: {
|
|
5
|
+
enhanced: true,
|
|
6
|
+
timeout: 5000,
|
|
7
|
+
endpoint: 'https://api.tracetail.com',
|
|
8
|
+
testMode: false
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2V0YWlsLmNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvdHJhY2V0YWlsLmNvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRy9DLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLElBQUksY0FBYyxDQUFrQixrQkFBa0IsQ0FBQyxDQUFDO0FBRXhGLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBNkI7SUFDdEQsTUFBTSxFQUFFO1FBQ04sUUFBUSxFQUFFLElBQUk7UUFDZCxPQUFPLEVBQUUsSUFBSTtRQUNiLFFBQVEsRUFBRSwyQkFBMkI7UUFDckMsUUFBUSxFQUFFLEtBQUs7S0FDaEI7Q0FDRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0aW9uVG9rZW4gfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFRyYWNlVGFpbENvbmZpZyB9IGZyb20gJy4vdHJhY2V0YWlsLnR5cGVzJztcblxuZXhwb3J0IGNvbnN0IFRSQUNFVEFJTF9DT05GSUcgPSBuZXcgSW5qZWN0aW9uVG9rZW48VHJhY2VUYWlsQ29uZmlnPignVFJBQ0VUQUlMX0NPTkZJRycpO1xuXG5leHBvcnQgY29uc3QgREVGQVVMVF9DT05GSUc6IFBhcnRpYWw8VHJhY2VUYWlsQ29uZmlnPiA9IHtcbiAgY29uZmlnOiB7XG4gICAgZW5oYW5jZWQ6IHRydWUsXG4gICAgdGltZW91dDogNTAwMCxcbiAgICBlbmRwb2ludDogJ2h0dHBzOi8vYXBpLnRyYWNldGFpbC5jb20nLFxuICAgIHRlc3RNb2RlOiBmYWxzZVxuICB9XG59OyJdfQ==
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
4
|
+
import { TraceTailService } from './tracetail.service';
|
|
5
|
+
import { TRACETAIL_CONFIG, DEFAULT_CONFIG } from './tracetail.config';
|
|
6
|
+
import { FraudDetectionDirective } from './directives/fraud-detection.directive';
|
|
7
|
+
import { TraceTailInterceptor } from './interceptors/tracetail.interceptor';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
export class TraceTailModule {
|
|
10
|
+
static forRoot(config) {
|
|
11
|
+
return {
|
|
12
|
+
ngModule: TraceTailModule,
|
|
13
|
+
providers: [
|
|
14
|
+
{
|
|
15
|
+
provide: TRACETAIL_CONFIG,
|
|
16
|
+
useValue: { ...DEFAULT_CONFIG, ...config }
|
|
17
|
+
},
|
|
18
|
+
TraceTailService,
|
|
19
|
+
{
|
|
20
|
+
provide: HTTP_INTERCEPTORS,
|
|
21
|
+
useClass: TraceTailInterceptor,
|
|
22
|
+
multi: true
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
static forFeature(config) {
|
|
28
|
+
return {
|
|
29
|
+
ngModule: TraceTailModule,
|
|
30
|
+
providers: config ? [
|
|
31
|
+
{
|
|
32
|
+
provide: TRACETAIL_CONFIG,
|
|
33
|
+
useValue: { ...DEFAULT_CONFIG, ...config }
|
|
34
|
+
}
|
|
35
|
+
] : []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
39
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, declarations: [FraudDetectionDirective], imports: [CommonModule], exports: [FraudDetectionDirective] });
|
|
40
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, imports: [CommonModule] });
|
|
41
|
+
}
|
|
42
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TraceTailModule, decorators: [{
|
|
43
|
+
type: NgModule,
|
|
44
|
+
args: [{
|
|
45
|
+
declarations: [
|
|
46
|
+
FraudDetectionDirective
|
|
47
|
+
],
|
|
48
|
+
imports: [
|
|
49
|
+
CommonModule
|
|
50
|
+
],
|
|
51
|
+
exports: [
|
|
52
|
+
FraudDetectionDirective
|
|
53
|
+
]
|
|
54
|
+
}]
|
|
55
|
+
}] });
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2V0YWlsLm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvdHJhY2V0YWlsLm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUF1QixNQUFNLGVBQWUsQ0FBQztBQUM5RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDekQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFdkQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGNBQWMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3RFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLHdDQUF3QyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLHNDQUFzQyxDQUFDOztBQWE1RSxNQUFNLE9BQU8sZUFBZTtJQUMxQixNQUFNLENBQUMsT0FBTyxDQUFDLE1BQXVCO1FBQ3BDLE9BQU87WUFDTCxRQUFRLEVBQUUsZUFBZTtZQUN6QixTQUFTLEVBQUU7Z0JBQ1Q7b0JBQ0UsT0FBTyxFQUFFLGdCQUFnQjtvQkFDekIsUUFBUSxFQUFFLEVBQUUsR0FBRyxjQUFjLEVBQUUsR0FBRyxNQUFNLEVBQUU7aUJBQzNDO2dCQUNELGdCQUFnQjtnQkFDaEI7b0JBQ0UsT0FBTyxFQUFFLGlCQUFpQjtvQkFDMUIsUUFBUSxFQUFFLG9CQUFvQjtvQkFDOUIsS0FBSyxFQUFFLElBQUk7aUJBQ1o7YUFDRjtTQUNGLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxDQUFDLFVBQVUsQ0FBQyxNQUFpQztRQUNqRCxPQUFPO1lBQ0wsUUFBUSxFQUFFLGVBQWU7WUFDekIsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7Z0JBQ2xCO29CQUNFLE9BQU8sRUFBRSxnQkFBZ0I7b0JBQ3pCLFFBQVEsRUFBRSxFQUFFLEdBQUcsY0FBYyxFQUFFLEdBQUcsTUFBTSxFQUFFO2lCQUMzQzthQUNGLENBQUMsQ0FBQyxDQUFDLEVBQUU7U0FDUCxDQUFDO0lBQ0osQ0FBQzt3R0E3QlUsZUFBZTt5R0FBZixlQUFlLGlCQVR4Qix1QkFBdUIsYUFHdkIsWUFBWSxhQUdaLHVCQUF1Qjt5R0FHZCxlQUFlLFlBTnhCLFlBQVk7OzRGQU1ILGVBQWU7a0JBWDNCLFFBQVE7bUJBQUM7b0JBQ1IsWUFBWSxFQUFFO3dCQUNaLHVCQUF1QjtxQkFDeEI7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLFlBQVk7cUJBQ2I7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLHVCQUF1QjtxQkFDeEI7aUJBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOZ01vZHVsZSwgTW9kdWxlV2l0aFByb3ZpZGVycyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IEhUVFBfSU5URVJDRVBUT1JTIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uL2h0dHAnO1xuaW1wb3J0IHsgVHJhY2VUYWlsU2VydmljZSB9IGZyb20gJy4vdHJhY2V0YWlsLnNlcnZpY2UnO1xuaW1wb3J0IHsgVHJhY2VUYWlsQ29uZmlnIH0gZnJvbSAnLi90cmFjZXRhaWwudHlwZXMnO1xuaW1wb3J0IHsgVFJBQ0VUQUlMX0NPTkZJRywgREVGQVVMVF9DT05GSUcgfSBmcm9tICcuL3RyYWNldGFpbC5jb25maWcnO1xuaW1wb3J0IHsgRnJhdWREZXRlY3Rpb25EaXJlY3RpdmUgfSBmcm9tICcuL2RpcmVjdGl2ZXMvZnJhdWQtZGV0ZWN0aW9uLmRpcmVjdGl2ZSc7XG5pbXBvcnQgeyBUcmFjZVRhaWxJbnRlcmNlcHRvciB9IGZyb20gJy4vaW50ZXJjZXB0b3JzL3RyYWNldGFpbC5pbnRlcmNlcHRvcic7XG5cbkBOZ01vZHVsZSh7XG4gIGRlY2xhcmF0aW9uczogW1xuICAgIEZyYXVkRGV0ZWN0aW9uRGlyZWN0aXZlXG4gIF0sXG4gIGltcG9ydHM6IFtcbiAgICBDb21tb25Nb2R1bGVcbiAgXSxcbiAgZXhwb3J0czogW1xuICAgIEZyYXVkRGV0ZWN0aW9uRGlyZWN0aXZlXG4gIF1cbn0pXG5leHBvcnQgY2xhc3MgVHJhY2VUYWlsTW9kdWxlIHtcbiAgc3RhdGljIGZvclJvb3QoY29uZmlnOiBUcmFjZVRhaWxDb25maWcpOiBNb2R1bGVXaXRoUHJvdmlkZXJzPFRyYWNlVGFpbE1vZHVsZT4ge1xuICAgIHJldHVybiB7XG4gICAgICBuZ01vZHVsZTogVHJhY2VUYWlsTW9kdWxlLFxuICAgICAgcHJvdmlkZXJzOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBwcm92aWRlOiBUUkFDRVRBSUxfQ09ORklHLFxuICAgICAgICAgIHVzZVZhbHVlOiB7IC4uLkRFRkFVTFRfQ09ORklHLCAuLi5jb25maWcgfVxuICAgICAgICB9LFxuICAgICAgICBUcmFjZVRhaWxTZXJ2aWNlLFxuICAgICAgICB7XG4gICAgICAgICAgcHJvdmlkZTogSFRUUF9JTlRFUkNFUFRPUlMsXG4gICAgICAgICAgdXNlQ2xhc3M6IFRyYWNlVGFpbEludGVyY2VwdG9yLFxuICAgICAgICAgIG11bHRpOiB0cnVlXG4gICAgICAgIH1cbiAgICAgIF1cbiAgICB9O1xuICB9XG5cbiAgc3RhdGljIGZvckZlYXR1cmUoY29uZmlnPzogUGFydGlhbDxUcmFjZVRhaWxDb25maWc+KTogTW9kdWxlV2l0aFByb3ZpZGVyczxUcmFjZVRhaWxNb2R1bGU+IHtcbiAgICByZXR1cm4ge1xuICAgICAgbmdNb2R1bGU6IFRyYWNlVGFpbE1vZHVsZSxcbiAgICAgIHByb3ZpZGVyczogY29uZmlnID8gW1xuICAgICAgICB7XG4gICAgICAgICAgcHJvdmlkZTogVFJBQ0VUQUlMX0NPTkZJRyxcbiAgICAgICAgICB1c2VWYWx1ZTogeyAuLi5ERUZBVUxUX0NPTkZJRywgLi4uY29uZmlnIH1cbiAgICAgICAgfVxuICAgICAgXSA6IFtdXG4gICAgfTtcbiAgfVxufSJdfQ==
|