@agirails/sdk 2.5.3 → 2.5.5
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/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
package/src/utils/RateLimiter.ts
DELETED
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RateLimiter - Prevents excessive API/RPC calls
|
|
3
|
-
*
|
|
4
|
-
* SECURITY FIX (M-4): Rate limiting to prevent:
|
|
5
|
-
* - API rate limit exhaustion
|
|
6
|
-
* - Self-inflicted DoS
|
|
7
|
-
* - Excessive RPC costs
|
|
8
|
-
*
|
|
9
|
-
* @module utils/RateLimiter
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Rate limiter configuration
|
|
14
|
-
*/
|
|
15
|
-
export interface RateLimiterConfig {
|
|
16
|
-
/** Maximum requests per window */
|
|
17
|
-
maxRequests: number;
|
|
18
|
-
/** Time window in milliseconds */
|
|
19
|
-
windowMs: number;
|
|
20
|
-
/** Optional: Burst allowance (extra requests allowed briefly) */
|
|
21
|
-
burstAllowance?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Rate limiter result
|
|
26
|
-
*/
|
|
27
|
-
export interface RateLimitResult {
|
|
28
|
-
/** Whether the request is allowed */
|
|
29
|
-
allowed: boolean;
|
|
30
|
-
/** Remaining requests in current window */
|
|
31
|
-
remaining: number;
|
|
32
|
-
/** Time until window resets (ms) */
|
|
33
|
-
resetIn: number;
|
|
34
|
-
/** Suggested retry delay if not allowed */
|
|
35
|
-
retryAfter?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Simple sliding window rate limiter
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```typescript
|
|
43
|
-
* const limiter = new RateLimiter({ maxRequests: 10, windowMs: 1000 });
|
|
44
|
-
*
|
|
45
|
-
* const result = limiter.tryAcquire();
|
|
46
|
-
* if (!result.allowed) {
|
|
47
|
-
* console.log(`Rate limited. Retry after ${result.retryAfter}ms`);
|
|
48
|
-
* }
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
export class RateLimiter {
|
|
52
|
-
private timestamps: number[] = [];
|
|
53
|
-
private readonly config: Required<RateLimiterConfig>;
|
|
54
|
-
|
|
55
|
-
constructor(config: RateLimiterConfig) {
|
|
56
|
-
this.config = {
|
|
57
|
-
maxRequests: config.maxRequests,
|
|
58
|
-
windowMs: config.windowMs,
|
|
59
|
-
burstAllowance: config.burstAllowance ?? 0,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Try to acquire a rate limit slot
|
|
65
|
-
*
|
|
66
|
-
* @returns Rate limit result
|
|
67
|
-
*/
|
|
68
|
-
tryAcquire(): RateLimitResult {
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
const windowStart = now - this.config.windowMs;
|
|
71
|
-
|
|
72
|
-
// Remove expired timestamps
|
|
73
|
-
this.timestamps = this.timestamps.filter((ts) => ts > windowStart);
|
|
74
|
-
|
|
75
|
-
const effectiveLimit = this.config.maxRequests + this.config.burstAllowance;
|
|
76
|
-
|
|
77
|
-
if (this.timestamps.length < effectiveLimit) {
|
|
78
|
-
this.timestamps.push(now);
|
|
79
|
-
return {
|
|
80
|
-
allowed: true,
|
|
81
|
-
remaining: effectiveLimit - this.timestamps.length,
|
|
82
|
-
resetIn: this.timestamps.length > 0
|
|
83
|
-
? this.timestamps[0] + this.config.windowMs - now
|
|
84
|
-
: this.config.windowMs,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Rate limited
|
|
89
|
-
const oldestTimestamp = this.timestamps[0];
|
|
90
|
-
const retryAfter = oldestTimestamp + this.config.windowMs - now;
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
allowed: false,
|
|
94
|
-
remaining: 0,
|
|
95
|
-
resetIn: retryAfter,
|
|
96
|
-
retryAfter: Math.max(0, retryAfter),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Wait until rate limit allows request
|
|
102
|
-
*
|
|
103
|
-
* @returns Promise that resolves when request is allowed
|
|
104
|
-
*/
|
|
105
|
-
async acquire(): Promise<void> {
|
|
106
|
-
const result = this.tryAcquire();
|
|
107
|
-
|
|
108
|
-
if (result.allowed) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Wait and retry
|
|
113
|
-
await new Promise((resolve) => setTimeout(resolve, result.retryAfter));
|
|
114
|
-
return this.acquire();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get current rate limit status
|
|
119
|
-
*/
|
|
120
|
-
getStatus(): { used: number; remaining: number; resetIn: number } {
|
|
121
|
-
const now = Date.now();
|
|
122
|
-
const windowStart = now - this.config.windowMs;
|
|
123
|
-
|
|
124
|
-
// Clean up expired timestamps
|
|
125
|
-
this.timestamps = this.timestamps.filter((ts) => ts > windowStart);
|
|
126
|
-
|
|
127
|
-
const effectiveLimit = this.config.maxRequests + this.config.burstAllowance;
|
|
128
|
-
const used = this.timestamps.length;
|
|
129
|
-
const remaining = Math.max(0, effectiveLimit - used);
|
|
130
|
-
const resetIn = this.timestamps.length > 0
|
|
131
|
-
? this.timestamps[0] + this.config.windowMs - now
|
|
132
|
-
: this.config.windowMs;
|
|
133
|
-
|
|
134
|
-
return { used, remaining, resetIn };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Reset the rate limiter
|
|
139
|
-
*/
|
|
140
|
-
reset(): void {
|
|
141
|
-
this.timestamps = [];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Circuit breaker states
|
|
147
|
-
*/
|
|
148
|
-
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Circuit breaker configuration
|
|
152
|
-
*/
|
|
153
|
-
export interface CircuitBreakerConfig {
|
|
154
|
-
/** Number of failures before opening circuit */
|
|
155
|
-
failureThreshold: number;
|
|
156
|
-
/** Time to wait before trying again (ms) */
|
|
157
|
-
recoveryTimeout: number;
|
|
158
|
-
/** Number of successes in half-open needed to close circuit */
|
|
159
|
-
successThreshold?: number;
|
|
160
|
-
/** SECURITY FIX (MEDIUM-5): Timeout for half-open test (ms). If no result reported, auto-reset. Default: 30000 */
|
|
161
|
-
halfOpenTestTimeout?: number;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Circuit breaker result
|
|
166
|
-
*/
|
|
167
|
-
export interface CircuitBreakerResult {
|
|
168
|
-
/** Whether the operation is allowed */
|
|
169
|
-
allowed: boolean;
|
|
170
|
-
/** Current circuit state */
|
|
171
|
-
state: CircuitState;
|
|
172
|
-
/** Number of consecutive failures */
|
|
173
|
-
failures: number;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Circuit Breaker - Prevents cascading failures
|
|
178
|
-
*
|
|
179
|
-
* SECURITY FIX (M-5): Circuit breaker to:
|
|
180
|
-
* - Prevent repeated calls to failing services
|
|
181
|
-
* - Allow systems to recover
|
|
182
|
-
* - Provide graceful degradation
|
|
183
|
-
*
|
|
184
|
-
* States:
|
|
185
|
-
* - CLOSED: Normal operation, requests pass through
|
|
186
|
-
* - OPEN: Circuit tripped, requests fail fast
|
|
187
|
-
* - HALF-OPEN: Testing if service recovered
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```typescript
|
|
191
|
-
* const breaker = new CircuitBreaker({
|
|
192
|
-
* failureThreshold: 5,
|
|
193
|
-
* recoveryTimeout: 30000
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* async function callService() {
|
|
197
|
-
* const result = breaker.canExecute();
|
|
198
|
-
* if (!result.allowed) {
|
|
199
|
-
* throw new Error(`Circuit open. Try again later.`);
|
|
200
|
-
* }
|
|
201
|
-
*
|
|
202
|
-
* try {
|
|
203
|
-
* const response = await riskyOperation();
|
|
204
|
-
* breaker.recordSuccess();
|
|
205
|
-
* return response;
|
|
206
|
-
* } catch (error) {
|
|
207
|
-
* breaker.recordFailure();
|
|
208
|
-
* throw error;
|
|
209
|
-
* }
|
|
210
|
-
* }
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
export class CircuitBreaker {
|
|
214
|
-
private state: CircuitState = 'closed';
|
|
215
|
-
private failures = 0;
|
|
216
|
-
private successes = 0;
|
|
217
|
-
private lastFailureTime = 0;
|
|
218
|
-
private readonly config: Required<CircuitBreakerConfig>;
|
|
219
|
-
|
|
220
|
-
// SECURITY FIX (CIRCUIT-HALFOPEN): Track if a test request is in progress
|
|
221
|
-
// Half-open state should only allow ONE request at a time to test service recovery
|
|
222
|
-
private halfOpenTestInProgress = false;
|
|
223
|
-
|
|
224
|
-
// SECURITY FIX (MEDIUM-5): Track when half-open test started for timeout detection
|
|
225
|
-
private halfOpenTestStartTime = 0;
|
|
226
|
-
|
|
227
|
-
constructor(config: CircuitBreakerConfig) {
|
|
228
|
-
this.config = {
|
|
229
|
-
failureThreshold: config.failureThreshold,
|
|
230
|
-
recoveryTimeout: config.recoveryTimeout,
|
|
231
|
-
successThreshold: config.successThreshold ?? 1,
|
|
232
|
-
// SECURITY FIX (MEDIUM-5): Default 30 second timeout for half-open tests
|
|
233
|
-
halfOpenTestTimeout: config.halfOpenTestTimeout ?? 30000,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Check if operation can be executed
|
|
239
|
-
*
|
|
240
|
-
* SECURITY FIX (CIRCUIT-HALFOPEN): In half-open state, only allow ONE request
|
|
241
|
-
* at a time to prevent overwhelming a recovering service.
|
|
242
|
-
*/
|
|
243
|
-
canExecute(): CircuitBreakerResult {
|
|
244
|
-
const now = Date.now();
|
|
245
|
-
|
|
246
|
-
switch (this.state) {
|
|
247
|
-
case 'closed':
|
|
248
|
-
return {
|
|
249
|
-
allowed: true,
|
|
250
|
-
state: this.state,
|
|
251
|
-
failures: this.failures,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
case 'open':
|
|
255
|
-
// Check if recovery timeout has passed
|
|
256
|
-
if (now - this.lastFailureTime >= this.config.recoveryTimeout) {
|
|
257
|
-
this.state = 'half-open';
|
|
258
|
-
this.successes = 0;
|
|
259
|
-
// SECURITY FIX (HIGH-7): Removed duplicate assignment (was setting false then true)
|
|
260
|
-
// SECURITY FIX (CIRCUIT-HALFOPEN): Mark test as in progress for first allowed request
|
|
261
|
-
this.halfOpenTestInProgress = true;
|
|
262
|
-
// SECURITY FIX (MEDIUM-5): Record when test started for timeout detection
|
|
263
|
-
this.halfOpenTestStartTime = now;
|
|
264
|
-
return {
|
|
265
|
-
allowed: true,
|
|
266
|
-
state: this.state,
|
|
267
|
-
failures: this.failures,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
return {
|
|
271
|
-
allowed: false,
|
|
272
|
-
state: this.state,
|
|
273
|
-
failures: this.failures,
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
case 'half-open':
|
|
277
|
-
// SECURITY FIX (MEDIUM-5): Check if test has timed out (caller never reported result)
|
|
278
|
-
// This prevents the circuit breaker from getting stuck if caller crashes/forgets to report
|
|
279
|
-
if (this.halfOpenTestInProgress && this.halfOpenTestStartTime > 0) {
|
|
280
|
-
const testDuration = now - this.halfOpenTestStartTime;
|
|
281
|
-
if (testDuration >= this.config.halfOpenTestTimeout) {
|
|
282
|
-
// Timeout: reset the test flag and allow a new test
|
|
283
|
-
this.halfOpenTestInProgress = false;
|
|
284
|
-
this.halfOpenTestStartTime = 0;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// SECURITY FIX (CIRCUIT-HALFOPEN): Only allow if no test is in progress
|
|
289
|
-
// This prevents multiple concurrent requests from overwhelming a recovering service
|
|
290
|
-
if (this.halfOpenTestInProgress) {
|
|
291
|
-
return {
|
|
292
|
-
allowed: false,
|
|
293
|
-
state: this.state,
|
|
294
|
-
failures: this.failures,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
// Mark test as in progress
|
|
298
|
-
this.halfOpenTestInProgress = true;
|
|
299
|
-
// SECURITY FIX (MEDIUM-5): Record when test started
|
|
300
|
-
this.halfOpenTestStartTime = now;
|
|
301
|
-
return {
|
|
302
|
-
allowed: true,
|
|
303
|
-
state: this.state,
|
|
304
|
-
failures: this.failures,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Record a successful operation
|
|
311
|
-
*
|
|
312
|
-
* SECURITY FIX (CIRCUIT-HALFOPEN): Clears test-in-progress flag
|
|
313
|
-
* SECURITY FIX (MEDIUM-5): Clears test start time
|
|
314
|
-
*/
|
|
315
|
-
recordSuccess(): void {
|
|
316
|
-
if (this.state === 'half-open') {
|
|
317
|
-
// SECURITY FIX (CIRCUIT-HALFOPEN): Clear test flag
|
|
318
|
-
this.halfOpenTestInProgress = false;
|
|
319
|
-
// SECURITY FIX (MEDIUM-5): Clear test start time
|
|
320
|
-
this.halfOpenTestStartTime = 0;
|
|
321
|
-
this.successes++;
|
|
322
|
-
if (this.successes >= this.config.successThreshold) {
|
|
323
|
-
this.state = 'closed';
|
|
324
|
-
this.failures = 0;
|
|
325
|
-
this.successes = 0;
|
|
326
|
-
}
|
|
327
|
-
} else if (this.state === 'closed') {
|
|
328
|
-
// Reset failure count on success
|
|
329
|
-
this.failures = 0;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Record a failed operation
|
|
335
|
-
*
|
|
336
|
-
* SECURITY FIX (CIRCUIT-HALFOPEN): Clears test-in-progress flag
|
|
337
|
-
* SECURITY FIX (MEDIUM-5): Clears test start time
|
|
338
|
-
*/
|
|
339
|
-
recordFailure(): void {
|
|
340
|
-
this.failures++;
|
|
341
|
-
this.lastFailureTime = Date.now();
|
|
342
|
-
|
|
343
|
-
if (this.state === 'half-open') {
|
|
344
|
-
// SECURITY FIX (CIRCUIT-HALFOPEN): Clear test flag before opening
|
|
345
|
-
this.halfOpenTestInProgress = false;
|
|
346
|
-
// SECURITY FIX (MEDIUM-5): Clear test start time
|
|
347
|
-
this.halfOpenTestStartTime = 0;
|
|
348
|
-
// Immediately open on failure in half-open state
|
|
349
|
-
this.state = 'open';
|
|
350
|
-
} else if (this.state === 'closed' && this.failures >= this.config.failureThreshold) {
|
|
351
|
-
this.state = 'open';
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Get current circuit state
|
|
357
|
-
*/
|
|
358
|
-
getState(): CircuitState {
|
|
359
|
-
// Check for automatic transition from open to half-open
|
|
360
|
-
if (this.state === 'open') {
|
|
361
|
-
const now = Date.now();
|
|
362
|
-
if (now - this.lastFailureTime >= this.config.recoveryTimeout) {
|
|
363
|
-
this.state = 'half-open';
|
|
364
|
-
this.successes = 0;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return this.state;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get detailed status
|
|
372
|
-
*/
|
|
373
|
-
getStatus(): {
|
|
374
|
-
state: CircuitState;
|
|
375
|
-
failures: number;
|
|
376
|
-
timeSinceLastFailure: number;
|
|
377
|
-
recoveryTimeRemaining: number;
|
|
378
|
-
} {
|
|
379
|
-
const now = Date.now();
|
|
380
|
-
const timeSinceLastFailure = this.lastFailureTime > 0
|
|
381
|
-
? now - this.lastFailureTime
|
|
382
|
-
: 0;
|
|
383
|
-
const recoveryTimeRemaining = this.state === 'open'
|
|
384
|
-
? Math.max(0, this.config.recoveryTimeout - timeSinceLastFailure)
|
|
385
|
-
: 0;
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
state: this.getState(),
|
|
389
|
-
failures: this.failures,
|
|
390
|
-
timeSinceLastFailure,
|
|
391
|
-
recoveryTimeRemaining,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Manually reset the circuit breaker
|
|
397
|
-
*
|
|
398
|
-
* SECURITY FIX (CIRCUIT-HALFOPEN): Also clears test-in-progress flag
|
|
399
|
-
* SECURITY FIX (MEDIUM-5): Also clears test start time
|
|
400
|
-
*/
|
|
401
|
-
reset(): void {
|
|
402
|
-
this.state = 'closed';
|
|
403
|
-
this.failures = 0;
|
|
404
|
-
this.successes = 0;
|
|
405
|
-
this.lastFailureTime = 0;
|
|
406
|
-
this.halfOpenTestInProgress = false;
|
|
407
|
-
this.halfOpenTestStartTime = 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Execute a function with circuit breaker protection
|
|
412
|
-
*
|
|
413
|
-
* @param fn - Async function to execute
|
|
414
|
-
* @returns Function result or throws if circuit is open
|
|
415
|
-
*/
|
|
416
|
-
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
417
|
-
const result = this.canExecute();
|
|
418
|
-
|
|
419
|
-
if (!result.allowed) {
|
|
420
|
-
throw new Error(
|
|
421
|
-
`Circuit breaker is open (${this.failures} failures). ` +
|
|
422
|
-
`Recovery in ${this.getStatus().recoveryTimeRemaining}ms.`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
const response = await fn();
|
|
428
|
-
this.recordSuccess();
|
|
429
|
-
return response;
|
|
430
|
-
} catch (error) {
|
|
431
|
-
this.recordFailure();
|
|
432
|
-
throw error;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Combined rate limiter and circuit breaker for API protection
|
|
439
|
-
*
|
|
440
|
-
* @example
|
|
441
|
-
* ```typescript
|
|
442
|
-
* const protector = new APIProtector({
|
|
443
|
-
* rateLimiter: { maxRequests: 100, windowMs: 60000 },
|
|
444
|
-
* circuitBreaker: { failureThreshold: 5, recoveryTimeout: 30000 }
|
|
445
|
-
* });
|
|
446
|
-
*
|
|
447
|
-
* const result = await protector.execute(async () => {
|
|
448
|
-
* return await apiCall();
|
|
449
|
-
* });
|
|
450
|
-
* ```
|
|
451
|
-
*/
|
|
452
|
-
export class APIProtector {
|
|
453
|
-
private readonly rateLimiter: RateLimiter;
|
|
454
|
-
private readonly circuitBreaker: CircuitBreaker;
|
|
455
|
-
|
|
456
|
-
constructor(config: {
|
|
457
|
-
rateLimiter: RateLimiterConfig;
|
|
458
|
-
circuitBreaker: CircuitBreakerConfig;
|
|
459
|
-
}) {
|
|
460
|
-
this.rateLimiter = new RateLimiter(config.rateLimiter);
|
|
461
|
-
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Check if operation is allowed (both rate limit and circuit)
|
|
466
|
-
*/
|
|
467
|
-
canExecute(): { allowed: boolean; reason?: string } {
|
|
468
|
-
// Check circuit breaker first
|
|
469
|
-
const circuitResult = this.circuitBreaker.canExecute();
|
|
470
|
-
if (!circuitResult.allowed) {
|
|
471
|
-
return {
|
|
472
|
-
allowed: false,
|
|
473
|
-
reason: `Circuit breaker open (${circuitResult.failures} failures)`,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Check rate limiter
|
|
478
|
-
const rateResult = this.rateLimiter.tryAcquire();
|
|
479
|
-
if (!rateResult.allowed) {
|
|
480
|
-
return {
|
|
481
|
-
allowed: false,
|
|
482
|
-
reason: `Rate limited. Retry after ${rateResult.retryAfter}ms`,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
return { allowed: true };
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Execute with both rate limiting and circuit breaker protection
|
|
491
|
-
*/
|
|
492
|
-
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
493
|
-
// Wait for rate limit
|
|
494
|
-
await this.rateLimiter.acquire();
|
|
495
|
-
|
|
496
|
-
// Execute with circuit breaker
|
|
497
|
-
return this.circuitBreaker.execute(fn);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Get combined status
|
|
502
|
-
*/
|
|
503
|
-
getStatus(): {
|
|
504
|
-
rateLimiter: ReturnType<RateLimiter['getStatus']>;
|
|
505
|
-
circuitBreaker: ReturnType<CircuitBreaker['getStatus']>;
|
|
506
|
-
} {
|
|
507
|
-
return {
|
|
508
|
-
rateLimiter: this.rateLimiter.getStatus(),
|
|
509
|
-
circuitBreaker: this.circuitBreaker.getStatus(),
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Record success (for circuit breaker)
|
|
515
|
-
*/
|
|
516
|
-
recordSuccess(): void {
|
|
517
|
-
this.circuitBreaker.recordSuccess();
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Record failure (for circuit breaker)
|
|
522
|
-
*/
|
|
523
|
-
recordFailure(): void {
|
|
524
|
-
this.circuitBreaker.recordFailure();
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Reset both protections
|
|
529
|
-
*/
|
|
530
|
-
reset(): void {
|
|
531
|
-
this.rateLimiter.reset();
|
|
532
|
-
this.circuitBreaker.reset();
|
|
533
|
-
}
|
|
534
|
-
}
|