@firela/billclaw-core 0.1.5 → 0.3.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 +21 -0
- package/dist/billclaw.d.ts +8 -0
- package/dist/billclaw.d.ts.map +1 -1
- package/dist/billclaw.js +85 -3
- package/dist/billclaw.js.map +1 -1
- package/dist/config/config-manager.d.ts +127 -0
- package/dist/config/config-manager.d.ts.map +1 -0
- package/dist/config/config-manager.js +304 -0
- package/dist/config/config-manager.js.map +1 -0
- package/dist/config/env-loader.d.ts +33 -0
- package/dist/config/env-loader.d.ts.map +1 -0
- package/dist/config/env-loader.js +115 -0
- package/dist/config/env-loader.js.map +1 -0
- package/dist/config/index.d.ts +14 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +14 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/errors.d.ts +110 -11
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +421 -122
- package/dist/errors/errors.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models/config.d.ts +147 -0
- package/dist/models/config.d.ts.map +1 -1
- package/dist/models/config.js +36 -0
- package/dist/models/config.js.map +1 -1
- package/dist/oauth/index.d.ts +12 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +13 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/providers/gmail.d.ts +63 -0
- package/dist/oauth/providers/gmail.d.ts.map +1 -0
- package/dist/oauth/providers/gmail.js +213 -0
- package/dist/oauth/providers/gmail.js.map +1 -0
- package/dist/oauth/providers/plaid.d.ts +40 -0
- package/dist/oauth/providers/plaid.d.ts.map +1 -0
- package/dist/oauth/providers/plaid.js +90 -0
- package/dist/oauth/providers/plaid.js.map +1 -0
- package/dist/oauth/types.d.ts +102 -0
- package/dist/oauth/types.d.ts.map +1 -0
- package/dist/oauth/types.js +10 -0
- package/dist/oauth/types.js.map +1 -0
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sources/gmail/gmail-fetch.d.ts +2 -2
- package/dist/sources/gmail/gmail-fetch.d.ts.map +1 -1
- package/dist/sources/gmail/gmail-fetch.js +34 -4
- package/dist/sources/gmail/gmail-fetch.js.map +1 -1
- package/dist/sources/plaid/plaid-sync.d.ts +2 -2
- package/dist/sources/plaid/plaid-sync.d.ts.map +1 -1
- package/dist/sources/plaid/plaid-sync.js +78 -18
- package/dist/sources/plaid/plaid-sync.js.map +1 -1
- package/dist/storage/locking.d.ts +4 -0
- package/dist/storage/locking.d.ts.map +1 -1
- package/dist/storage/locking.js +4 -0
- package/dist/storage/locking.js.map +1 -1
- package/dist/sync/sync-service.d.ts +2 -2
- package/dist/sync/sync-service.d.ts.map +1 -1
- package/dist/sync/sync-service.js +6 -1
- package/dist/sync/sync-service.js.map +1 -1
- package/dist/test-fixtures.d.ts.map +1 -1
- package/dist/test-fixtures.js +5 -0
- package/dist/test-fixtures.js.map +1 -1
- package/dist/webhooks/deduplication.d.ts +117 -0
- package/dist/webhooks/deduplication.d.ts.map +1 -0
- package/dist/webhooks/deduplication.js +258 -0
- package/dist/webhooks/deduplication.js.map +1 -0
- package/dist/webhooks/handlers/gmail.d.ts +39 -0
- package/dist/webhooks/handlers/gmail.d.ts.map +1 -0
- package/dist/webhooks/handlers/gmail.js +56 -0
- package/dist/webhooks/handlers/gmail.js.map +1 -0
- package/dist/webhooks/handlers/gocardless.d.ts +39 -0
- package/dist/webhooks/handlers/gocardless.d.ts.map +1 -0
- package/dist/webhooks/handlers/gocardless.js +73 -0
- package/dist/webhooks/handlers/gocardless.js.map +1 -0
- package/dist/webhooks/handlers/index.d.ts +10 -0
- package/dist/webhooks/handlers/index.d.ts.map +1 -0
- package/dist/webhooks/handlers/index.js +10 -0
- package/dist/webhooks/handlers/index.js.map +1 -0
- package/dist/webhooks/handlers/plaid.d.ts +73 -0
- package/dist/webhooks/handlers/plaid.d.ts.map +1 -0
- package/dist/webhooks/handlers/plaid.js +169 -0
- package/dist/webhooks/handlers/plaid.js.map +1 -0
- package/dist/webhooks/index.d.ts +15 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +17 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/processor.d.ts +76 -0
- package/dist/webhooks/processor.d.ts.map +1 -0
- package/dist/webhooks/processor.js +116 -0
- package/dist/webhooks/processor.js.map +1 -0
- package/dist/webhooks/router.d.ts +80 -0
- package/dist/webhooks/router.d.ts.map +1 -0
- package/dist/webhooks/router.js +107 -0
- package/dist/webhooks/router.js.map +1 -0
- package/dist/webhooks/security.d.ts +90 -0
- package/dist/webhooks/security.d.ts.map +1 -0
- package/dist/webhooks/security.js +138 -0
- package/dist/webhooks/security.js.map +1 -0
- package/dist/webhooks/sync-rate-limiter.d.ts +138 -0
- package/dist/webhooks/sync-rate-limiter.d.ts.map +1 -0
- package/dist/webhooks/sync-rate-limiter.js +228 -0
- package/dist/webhooks/sync-rate-limiter.js.map +1 -0
- package/dist/webhooks/types.d.ts +140 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +18 -0
- package/dist/webhooks/types.js.map +1 -0
- package/package.json +12 -12
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook security layer (P0)
|
|
3
|
+
*
|
|
4
|
+
* Provides replay attack protection, signature verification, and
|
|
5
|
+
* rate limiting coordination for inbound webhooks.
|
|
6
|
+
*
|
|
7
|
+
* Security features:
|
|
8
|
+
* - Replay attack protection using timestamp + nonce validation
|
|
9
|
+
* - HMAC-SHA256 signature verification with timing-safe comparison
|
|
10
|
+
* - Rate limiting coordination
|
|
11
|
+
*/
|
|
12
|
+
import type { Logger } from "../errors/errors.js";
|
|
13
|
+
import type { WebhookDeduplication } from "./deduplication.js";
|
|
14
|
+
/**
|
|
15
|
+
* Configuration for webhook security
|
|
16
|
+
*/
|
|
17
|
+
export interface WebhookSecurityConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Maximum allowed timestamp age in milliseconds
|
|
20
|
+
* Default: 15 minutes
|
|
21
|
+
*/
|
|
22
|
+
maxTimestampAge?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Future timestamp tolerance in milliseconds
|
|
25
|
+
* Default: 5 minutes (to handle clock skew)
|
|
26
|
+
*/
|
|
27
|
+
futureTimestampTolerance?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Deduplication cache for nonce tracking
|
|
30
|
+
*/
|
|
31
|
+
deduplication: WebhookDeduplication;
|
|
32
|
+
/**
|
|
33
|
+
* Logger instance
|
|
34
|
+
*/
|
|
35
|
+
logger: Logger;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Webhook security layer
|
|
39
|
+
*
|
|
40
|
+
* Provides P0 security features for webhook processing.
|
|
41
|
+
*/
|
|
42
|
+
export declare class WebhookSecurity {
|
|
43
|
+
private readonly maxTimestampAge;
|
|
44
|
+
private readonly futureTolerance;
|
|
45
|
+
private readonly deduplication;
|
|
46
|
+
private readonly logger;
|
|
47
|
+
constructor(config: WebhookSecurityConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Validate replay protection (timestamp + nonce)
|
|
50
|
+
*
|
|
51
|
+
* Checks that:
|
|
52
|
+
* 1. Timestamp is within valid range (not too old, not too far in future)
|
|
53
|
+
* 2. Nonce has not been used before
|
|
54
|
+
*
|
|
55
|
+
* @param timestamp - Webhook timestamp (Unix milliseconds)
|
|
56
|
+
* @param nonce - Unique webhook identifier
|
|
57
|
+
* @returns True if valid, false if replay detected
|
|
58
|
+
*/
|
|
59
|
+
validateReplayProtection(timestamp: number | undefined, nonce: string | undefined): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Verify webhook signature
|
|
62
|
+
*
|
|
63
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
64
|
+
*
|
|
65
|
+
* @param payload - Request payload (stringified JSON)
|
|
66
|
+
* @param signature - Signature to verify (format: "sha256=<hex>")
|
|
67
|
+
* @param secret - Webhook secret key
|
|
68
|
+
* @returns True if signature is valid
|
|
69
|
+
*/
|
|
70
|
+
verifySignature(payload: string, signature: string | undefined, secret: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Generate HMAC-SHA256 signature for testing
|
|
73
|
+
*
|
|
74
|
+
* @param payload - Payload to sign
|
|
75
|
+
* @param secret - Secret key for HMAC
|
|
76
|
+
* @returns Signature in format "sha256=<hex>"
|
|
77
|
+
*/
|
|
78
|
+
generateSignature(payload: string, secret: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Create a nonce for testing
|
|
81
|
+
*
|
|
82
|
+
* @returns Unique nonce string
|
|
83
|
+
*/
|
|
84
|
+
generateNonce(): string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a webhook security instance with default configuration
|
|
88
|
+
*/
|
|
89
|
+
export declare function createWebhookSecurity(deduplication: WebhookDeduplication, logger: Logger): WebhookSecurity;
|
|
90
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/webhooks/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAEjC;;OAEG;IACH,aAAa,EAAE,oBAAoB,CAAA;IAEnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAQD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,MAAM,EAAE,qBAAqB;IAOzC;;;;;;;;;;OAUG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,OAAO,CAAC;IA4CnB;;;;;;;;;OASG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,MAAM,GACb,OAAO;IA2BV;;;;;;OAMG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAM1D;;;;OAIG;IACH,aAAa,IAAI,MAAM;CAGxB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,oBAAoB,EACnC,MAAM,EAAE,MAAM,GACb,eAAe,CAKjB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook security layer (P0)
|
|
3
|
+
*
|
|
4
|
+
* Provides replay attack protection, signature verification, and
|
|
5
|
+
* rate limiting coordination for inbound webhooks.
|
|
6
|
+
*
|
|
7
|
+
* Security features:
|
|
8
|
+
* - Replay attack protection using timestamp + nonce validation
|
|
9
|
+
* - HMAC-SHA256 signature verification with timing-safe comparison
|
|
10
|
+
* - Rate limiting coordination
|
|
11
|
+
*/
|
|
12
|
+
import * as crypto from "node:crypto";
|
|
13
|
+
/**
|
|
14
|
+
* Default configuration values
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_MAX_TIMESTAMP_AGE = 15 * 60 * 1000; // 15 minutes
|
|
17
|
+
const DEFAULT_FUTURE_TOLERANCE = 5 * 60 * 1000; // 5 minutes
|
|
18
|
+
/**
|
|
19
|
+
* Webhook security layer
|
|
20
|
+
*
|
|
21
|
+
* Provides P0 security features for webhook processing.
|
|
22
|
+
*/
|
|
23
|
+
export class WebhookSecurity {
|
|
24
|
+
maxTimestampAge;
|
|
25
|
+
futureTolerance;
|
|
26
|
+
deduplication;
|
|
27
|
+
logger;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.maxTimestampAge = config.maxTimestampAge ?? DEFAULT_MAX_TIMESTAMP_AGE;
|
|
30
|
+
this.futureTolerance = config.futureTimestampTolerance ?? DEFAULT_FUTURE_TOLERANCE;
|
|
31
|
+
this.deduplication = config.deduplication;
|
|
32
|
+
this.logger = config.logger;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate replay protection (timestamp + nonce)
|
|
36
|
+
*
|
|
37
|
+
* Checks that:
|
|
38
|
+
* 1. Timestamp is within valid range (not too old, not too far in future)
|
|
39
|
+
* 2. Nonce has not been used before
|
|
40
|
+
*
|
|
41
|
+
* @param timestamp - Webhook timestamp (Unix milliseconds)
|
|
42
|
+
* @param nonce - Unique webhook identifier
|
|
43
|
+
* @returns True if valid, false if replay detected
|
|
44
|
+
*/
|
|
45
|
+
async validateReplayProtection(timestamp, nonce) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
// Check if timestamp is provided
|
|
48
|
+
if (timestamp === undefined) {
|
|
49
|
+
this.logger.debug?.("Webhook missing timestamp");
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Check if nonce is provided
|
|
53
|
+
if (nonce === undefined) {
|
|
54
|
+
this.logger.debug?.("Webhook missing nonce");
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// Check timestamp is not too old
|
|
58
|
+
if (now - timestamp > this.maxTimestampAge) {
|
|
59
|
+
this.logger.warn?.(`Webhook rejected: timestamp too old (${now - timestamp}ms ago)`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// Check timestamp is not too far in future (allow clock skew)
|
|
63
|
+
if (timestamp - now > this.futureTolerance) {
|
|
64
|
+
this.logger.warn?.(`Webhook rejected: timestamp too far in future (${timestamp - now}ms)`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// Check nonce has not been used before
|
|
68
|
+
const isProcessed = await this.deduplication.isProcessed(nonce);
|
|
69
|
+
if (isProcessed) {
|
|
70
|
+
this.logger.warn?.(`Webhook rejected: nonce already used (${nonce})`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// Mark nonce as processed
|
|
74
|
+
await this.deduplication.markProcessed(nonce, 60_000); // 60 second TTL
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Verify webhook signature
|
|
79
|
+
*
|
|
80
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
81
|
+
*
|
|
82
|
+
* @param payload - Request payload (stringified JSON)
|
|
83
|
+
* @param signature - Signature to verify (format: "sha256=<hex>")
|
|
84
|
+
* @param secret - Webhook secret key
|
|
85
|
+
* @returns True if signature is valid
|
|
86
|
+
*/
|
|
87
|
+
verifySignature(payload, signature, secret) {
|
|
88
|
+
if (!signature) {
|
|
89
|
+
this.logger.debug?.("Webhook missing signature");
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
// Compute expected signature
|
|
94
|
+
const expectedSignature = crypto
|
|
95
|
+
.createHmac("sha256", secret)
|
|
96
|
+
.update(payload)
|
|
97
|
+
.digest("hex");
|
|
98
|
+
// Extract provided signature (remove "sha256=" prefix if present)
|
|
99
|
+
const providedSignature = signature.replace(/^sha256=/i, "");
|
|
100
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
101
|
+
return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(providedSignature));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.logger.error?.("Signature verification failed:", error);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate HMAC-SHA256 signature for testing
|
|
110
|
+
*
|
|
111
|
+
* @param payload - Payload to sign
|
|
112
|
+
* @param secret - Secret key for HMAC
|
|
113
|
+
* @returns Signature in format "sha256=<hex>"
|
|
114
|
+
*/
|
|
115
|
+
generateSignature(payload, secret) {
|
|
116
|
+
const hmac = crypto.createHmac("sha256", secret);
|
|
117
|
+
hmac.update(payload);
|
|
118
|
+
return `sha256=${hmac.digest("hex")}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a nonce for testing
|
|
122
|
+
*
|
|
123
|
+
* @returns Unique nonce string
|
|
124
|
+
*/
|
|
125
|
+
generateNonce() {
|
|
126
|
+
return `nonce_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a webhook security instance with default configuration
|
|
131
|
+
*/
|
|
132
|
+
export function createWebhookSecurity(deduplication, logger) {
|
|
133
|
+
return new WebhookSecurity({
|
|
134
|
+
deduplication,
|
|
135
|
+
logger,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/webhooks/security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync rate limiter (P0)
|
|
3
|
+
*
|
|
4
|
+
* Prevents Plaid API bans from webhook-triggered sync floods.
|
|
5
|
+
* Implements separate rate limit buckets for manual vs webhook-triggered syncs.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Separate rate limit buckets: manual vs webhook-triggered
|
|
9
|
+
* - Circuit breaker to disable webhook syncs when rate limit near
|
|
10
|
+
* - Sliding window for accurate rate limiting
|
|
11
|
+
*/
|
|
12
|
+
import type { Logger } from "../errors/errors.js";
|
|
13
|
+
/**
|
|
14
|
+
* Rate limit configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface RateLimitConfig {
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of requests allowed
|
|
19
|
+
*/
|
|
20
|
+
requests: number;
|
|
21
|
+
/**
|
|
22
|
+
* Time window in milliseconds
|
|
23
|
+
*/
|
|
24
|
+
window: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sync rate limiter configuration
|
|
28
|
+
*/
|
|
29
|
+
export interface SyncRateLimiterConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Rate limit for manual syncs
|
|
32
|
+
*/
|
|
33
|
+
manual: RateLimitConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Rate limit for webhook-triggered syncs
|
|
36
|
+
*/
|
|
37
|
+
webhook: RateLimitConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Circuit breaker threshold (0-1)
|
|
40
|
+
* Disable webhook syncs when usage exceeds this ratio
|
|
41
|
+
*/
|
|
42
|
+
circuitThreshold?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Logger instance
|
|
45
|
+
*/
|
|
46
|
+
logger: Logger;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sync rate limiter
|
|
50
|
+
*
|
|
51
|
+
* Tracks sync requests and enforces rate limits separately for
|
|
52
|
+
* manual and webhook-triggered syncs.
|
|
53
|
+
*/
|
|
54
|
+
export declare class SyncRateLimiter {
|
|
55
|
+
private readonly config;
|
|
56
|
+
private readonly logger;
|
|
57
|
+
private readonly requests;
|
|
58
|
+
private circuitOpen;
|
|
59
|
+
private circuitOpenUntil;
|
|
60
|
+
constructor(config: SyncRateLimiterConfig);
|
|
61
|
+
/**
|
|
62
|
+
* Record a manual sync request
|
|
63
|
+
*
|
|
64
|
+
* @param accountId - Account ID for the sync
|
|
65
|
+
*/
|
|
66
|
+
recordManualSync(accountId: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Record a webhook-triggered sync request
|
|
69
|
+
*
|
|
70
|
+
* @param accountId - Account ID for the sync
|
|
71
|
+
*/
|
|
72
|
+
recordWebhookSync(accountId: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Check if webhook sync is allowed
|
|
75
|
+
*
|
|
76
|
+
* @param accountId - Account ID for the sync
|
|
77
|
+
* @returns True if sync is allowed
|
|
78
|
+
*/
|
|
79
|
+
isWebhookSyncAllowed(accountId: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Check if manual sync is allowed
|
|
82
|
+
*
|
|
83
|
+
* @param accountId - Account ID for the sync
|
|
84
|
+
* @returns True if sync is allowed
|
|
85
|
+
*/
|
|
86
|
+
isManualSyncAllowed(accountId: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Check if circuit breaker is open
|
|
89
|
+
*/
|
|
90
|
+
isCircuitOpen(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Open circuit breaker
|
|
93
|
+
*
|
|
94
|
+
* Disables webhook syncs for a cooldown period.
|
|
95
|
+
*/
|
|
96
|
+
openCircuit(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Close circuit breaker
|
|
99
|
+
*/
|
|
100
|
+
closeCircuit(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get rate limiter statistics
|
|
103
|
+
*/
|
|
104
|
+
getStats(): {
|
|
105
|
+
manualCount: number;
|
|
106
|
+
webhookCount: number;
|
|
107
|
+
circuitOpen: boolean;
|
|
108
|
+
usageRatio: number;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Reset rate limiter (for testing)
|
|
112
|
+
*/
|
|
113
|
+
reset(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Clean up old requests
|
|
116
|
+
*/
|
|
117
|
+
cleanup(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Record a request
|
|
120
|
+
*/
|
|
121
|
+
private recordRequest;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a sync rate limiter with default configuration
|
|
125
|
+
*/
|
|
126
|
+
export declare function createSyncRateLimiter(logger: Logger, config?: Partial<SyncRateLimiterConfig>): SyncRateLimiter;
|
|
127
|
+
/**
|
|
128
|
+
* In-memory rate limiter for testing
|
|
129
|
+
*/
|
|
130
|
+
export declare class InMemoryRateLimiter {
|
|
131
|
+
private readonly config;
|
|
132
|
+
private readonly counters;
|
|
133
|
+
constructor(config: RateLimitConfig, _logger: Logger);
|
|
134
|
+
recordRequest(identifier: string): Promise<void>;
|
|
135
|
+
isRateLimited(identifier: string): Promise<boolean>;
|
|
136
|
+
reset(): void;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=sync-rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/webhooks/sync-rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,eAAe,CAAA;IAEvB;;OAEG;IACH,OAAO,EAAE,eAAe,CAAA;IAExB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAyBD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiD;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAAI;gBAEhB,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzC;;;;OAIG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI1C;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAyChD;;;;;OAKG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAmB/C;;OAEG;IACH,aAAa,IAAI,OAAO;IAcxB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAKnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAMpB;;OAEG;IACH,QAAQ,IAAI;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB;IAsBD;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,OAAO,IAAI,IAAI;IAmBf;;OAEG;IACH,OAAO,CAAC,aAAa;CAWtB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,eAAe,CAQjB;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAGlC,MAAM,EAAE,eAAe,EACxC,OAAO,EAAE,MAAM;IAGX,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKzD,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync rate limiter (P0)
|
|
3
|
+
*
|
|
4
|
+
* Prevents Plaid API bans from webhook-triggered sync floods.
|
|
5
|
+
* Implements separate rate limit buckets for manual vs webhook-triggered syncs.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Separate rate limit buckets: manual vs webhook-triggered
|
|
9
|
+
* - Circuit breaker to disable webhook syncs when rate limit near
|
|
10
|
+
* - Sliding window for accurate rate limiting
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Default configuration values
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
manual: {
|
|
17
|
+
requests: 10,
|
|
18
|
+
window: 60_000, // 1 minute
|
|
19
|
+
},
|
|
20
|
+
webhook: {
|
|
21
|
+
requests: 3,
|
|
22
|
+
window: 60_000, // 1 minute
|
|
23
|
+
},
|
|
24
|
+
circuitThreshold: 0.8, // 80%
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Sync rate limiter
|
|
28
|
+
*
|
|
29
|
+
* Tracks sync requests and enforces rate limits separately for
|
|
30
|
+
* manual and webhook-triggered syncs.
|
|
31
|
+
*/
|
|
32
|
+
export class SyncRateLimiter {
|
|
33
|
+
config;
|
|
34
|
+
logger;
|
|
35
|
+
requests = [];
|
|
36
|
+
circuitOpen = false;
|
|
37
|
+
circuitOpenUntil = 0;
|
|
38
|
+
constructor(config) {
|
|
39
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
40
|
+
this.logger = config.logger;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Record a manual sync request
|
|
44
|
+
*
|
|
45
|
+
* @param accountId - Account ID for the sync
|
|
46
|
+
*/
|
|
47
|
+
recordManualSync(accountId) {
|
|
48
|
+
this.recordRequest("manual", accountId);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Record a webhook-triggered sync request
|
|
52
|
+
*
|
|
53
|
+
* @param accountId - Account ID for the sync
|
|
54
|
+
*/
|
|
55
|
+
recordWebhookSync(accountId) {
|
|
56
|
+
this.recordRequest("webhook", accountId);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if webhook sync is allowed
|
|
60
|
+
*
|
|
61
|
+
* @param accountId - Account ID for the sync
|
|
62
|
+
* @returns True if sync is allowed
|
|
63
|
+
*/
|
|
64
|
+
isWebhookSyncAllowed(accountId) {
|
|
65
|
+
// Check circuit breaker
|
|
66
|
+
if (this.isCircuitOpen()) {
|
|
67
|
+
this.logger.warn?.(`Webhook sync blocked for ${accountId}: circuit breaker open`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Check rate limit
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const windowStart = now - this.config.webhook.window;
|
|
73
|
+
// Count webhook requests in window
|
|
74
|
+
const webhookCount = this.requests.filter((r) => r.type === "webhook" && r.timestamp >= windowStart).length;
|
|
75
|
+
if (webhookCount >= this.config.webhook.requests) {
|
|
76
|
+
this.logger.warn?.(`Webhook sync blocked for ${accountId}: rate limit exceeded (${webhookCount}/${this.config.webhook.requests})`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Check if we should open circuit breaker
|
|
80
|
+
const totalCount = this.requests.filter((r) => r.timestamp >= windowStart).length;
|
|
81
|
+
const totalLimit = this.config.manual.requests + this.config.webhook.requests;
|
|
82
|
+
const usageRatio = totalCount / totalLimit;
|
|
83
|
+
if (usageRatio >= this.config.circuitThreshold) {
|
|
84
|
+
this.openCircuit();
|
|
85
|
+
this.logger.warn?.(`Circuit breaker opened: usage at ${Math.round(usageRatio * 100)}%`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if manual sync is allowed
|
|
92
|
+
*
|
|
93
|
+
* @param accountId - Account ID for the sync
|
|
94
|
+
* @returns True if sync is allowed
|
|
95
|
+
*/
|
|
96
|
+
isManualSyncAllowed(accountId) {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
const windowStart = now - this.config.manual.window;
|
|
99
|
+
// Count manual requests in window
|
|
100
|
+
const manualCount = this.requests.filter((r) => r.type === "manual" && r.timestamp >= windowStart).length;
|
|
101
|
+
if (manualCount >= this.config.manual.requests) {
|
|
102
|
+
this.logger.warn?.(`Manual sync blocked for ${accountId}: rate limit exceeded (${manualCount}/${this.config.manual.requests})`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if circuit breaker is open
|
|
109
|
+
*/
|
|
110
|
+
isCircuitOpen() {
|
|
111
|
+
if (!this.circuitOpen) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Check if circuit should close
|
|
115
|
+
if (Date.now() > this.circuitOpenUntil) {
|
|
116
|
+
this.closeCircuit();
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Open circuit breaker
|
|
123
|
+
*
|
|
124
|
+
* Disables webhook syncs for a cooldown period.
|
|
125
|
+
*/
|
|
126
|
+
openCircuit() {
|
|
127
|
+
this.circuitOpen = true;
|
|
128
|
+
this.circuitOpenUntil = Date.now() + this.config.manual.window; // Open for 1 window period
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Close circuit breaker
|
|
132
|
+
*/
|
|
133
|
+
closeCircuit() {
|
|
134
|
+
this.circuitOpen = false;
|
|
135
|
+
this.circuitOpenUntil = 0;
|
|
136
|
+
this.logger.info?.("Circuit breaker closed");
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get rate limiter statistics
|
|
140
|
+
*/
|
|
141
|
+
getStats() {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const windowStart = now - this.config.manual.window;
|
|
144
|
+
const manualCount = this.requests.filter((r) => r.type === "manual" && r.timestamp >= windowStart).length;
|
|
145
|
+
const webhookCount = this.requests.filter((r) => r.type === "webhook" && r.timestamp >= windowStart).length;
|
|
146
|
+
const totalLimit = this.config.manual.requests + this.config.webhook.requests;
|
|
147
|
+
const usageRatio = (manualCount + webhookCount) / totalLimit;
|
|
148
|
+
return {
|
|
149
|
+
manualCount,
|
|
150
|
+
webhookCount,
|
|
151
|
+
circuitOpen: this.isCircuitOpen(),
|
|
152
|
+
usageRatio,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Reset rate limiter (for testing)
|
|
157
|
+
*/
|
|
158
|
+
reset() {
|
|
159
|
+
this.requests.splice(0, this.requests.length);
|
|
160
|
+
this.closeCircuit();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Clean up old requests
|
|
164
|
+
*/
|
|
165
|
+
cleanup() {
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const windowStart = now - this.config.manual.window;
|
|
168
|
+
const initialLength = this.requests.length;
|
|
169
|
+
const toKeep = [];
|
|
170
|
+
for (const req of this.requests) {
|
|
171
|
+
if (req.timestamp >= windowStart) {
|
|
172
|
+
toKeep.push(req);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this.requests.splice(0, this.requests.length, ...toKeep);
|
|
176
|
+
const removed = initialLength - this.requests.length;
|
|
177
|
+
if (removed > 0) {
|
|
178
|
+
this.logger.debug?.(`Cleaned up ${removed} old request entries`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Record a request
|
|
183
|
+
*/
|
|
184
|
+
recordRequest(type, _accountId) {
|
|
185
|
+
this.requests.push({
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
type,
|
|
188
|
+
});
|
|
189
|
+
// Cleanup old entries periodically
|
|
190
|
+
if (this.requests.length > 1000) {
|
|
191
|
+
this.cleanup();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create a sync rate limiter with default configuration
|
|
197
|
+
*/
|
|
198
|
+
export function createSyncRateLimiter(logger, config) {
|
|
199
|
+
return new SyncRateLimiter({
|
|
200
|
+
manual: { requests: 10, window: 60_000 },
|
|
201
|
+
webhook: { requests: 3, window: 60_000 },
|
|
202
|
+
circuitThreshold: 0.8,
|
|
203
|
+
logger,
|
|
204
|
+
...config,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* In-memory rate limiter for testing
|
|
209
|
+
*/
|
|
210
|
+
export class InMemoryRateLimiter {
|
|
211
|
+
config;
|
|
212
|
+
counters = new Map();
|
|
213
|
+
constructor(config, _logger) {
|
|
214
|
+
this.config = config;
|
|
215
|
+
}
|
|
216
|
+
async recordRequest(identifier) {
|
|
217
|
+
const count = (this.counters.get(identifier) || 0) + 1;
|
|
218
|
+
this.counters.set(identifier, count);
|
|
219
|
+
}
|
|
220
|
+
async isRateLimited(identifier) {
|
|
221
|
+
const count = this.counters.get(identifier) || 0;
|
|
222
|
+
return count >= this.config.requests;
|
|
223
|
+
}
|
|
224
|
+
reset() {
|
|
225
|
+
this.counters.clear();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=sync-rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-rate-limiter.js","sourceRoot":"","sources":["../../src/webhooks/sync-rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA6CH;;GAEG;AACH,MAAM,cAAc,GAAoD;IACtE,MAAM,EAAE;QACN,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,MAAM,EAAE,WAAW;KAC5B;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,MAAM,EAAE,WAAW;KAC5B;IACD,gBAAgB,EAAE,GAAG,EAAE,MAAM;CAC9B,CAAA;AAUD;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IACT,MAAM,CAAiD;IACvD,MAAM,CAAQ;IACd,QAAQ,GAAmB,EAAE,CAAA;IACtC,WAAW,GAAG,KAAK,CAAA;IACnB,gBAAgB,GAAG,CAAC,CAAA;IAE5B,YAAY,MAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAA;QAC9C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;IAC7B,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,SAAiB;QAChC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;IACzC,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,SAAiB;QACjC,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAC1C,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,SAAiB;QACpC,wBAAwB;QACxB,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,4BAA4B,SAAS,wBAAwB,CAC9D,CAAA;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,mBAAmB;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAA;QAEpD,mCAAmC;QACnC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,IAAI,WAAW,CAC1D,CAAC,MAAM,CAAA;QAER,IAAI,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACjD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,4BAA4B,SAAS,0BAA0B,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,GAAG,CAC/G,CAAA;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,0CAA0C;QAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,WAAW,CAAC,CAAC,MAAM,CAAA;QACjF,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAA;QAC7E,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAA;QAE1C,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC/C,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,oCAAoC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,CACpE,CAAA;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,SAAiB;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QAEnD,kCAAkC;QAClC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,WAAW,CACzD,CAAC,MAAM,CAAA;QAER,IAAI,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAChB,2BAA2B,SAAS,0BAA0B,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAG,CAC5G,CAAA;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;OAEG;IACH,aAAa;QACX,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,gCAAgC;QAChC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACvC,IAAI,CAAC,YAAY,EAAE,CAAA;YACnB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA,CAAC,2BAA2B;IAC5F,CAAC;IAED;;OAEG;IACH,YAAY;QACV,IAAI,CAAC,WAAW,GAAG,KAAK,CAAA;QACxB,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAA;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,wBAAwB,CAAC,CAAA;IAC9C,CAAC;IAED;;OAEG;IACH,QAAQ;QAMN,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QAEnD,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,WAAW,CACzD,CAAC,MAAM,CAAA;QACR,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,IAAI,WAAW,CAC1D,CAAC,MAAM,CAAA;QAER,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAA;QAC7E,MAAM,UAAU,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,GAAG,UAAU,CAAA;QAE5D,OAAO;YACL,WAAW;YACX,YAAY;YACZ,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE;YACjC,UAAU;SACX,CAAA;IACH,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAC7C,IAAI,CAAC,YAAY,EAAE,CAAA;IACrB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QAEnD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAA;QAC1C,MAAM,MAAM,GAAmB,EAAE,CAAA;QACjC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,IAAI,GAAG,CAAC,SAAS,IAAI,WAAW,EAAE,CAAC;gBACjC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAA;QAExD,MAAM,OAAO,GAAG,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAA;QACpD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,cAAc,OAAO,sBAAsB,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,IAA0B,EAAE,UAAkB;QAClE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACjB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI;SACL,CAAC,CAAA;QAEF,mCAAmC;QACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,EAAE,CAAA;QAChB,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,MAAuC;IAEvC,OAAO,IAAI,eAAe,CAAC;QACzB,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QACxC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE;QACxC,gBAAgB,EAAE,GAAG;QACrB,MAAM;QACN,GAAG,MAAM;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAIX;IAHF,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAErD,YACmB,MAAuB,EACxC,OAAe;QADE,WAAM,GAAN,MAAM,CAAiB;IAEvC,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAC,UAAkB;QACpC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;QACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;IACtC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,UAAkB;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAChD,OAAO,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAA;IACtC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;CACF"}
|