UNPKG

@iota-big3/sdk-gateway

Version:

Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching

231 lines 8.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChaosMiddleware = exports.ChaosError = void 0; const tslib_1 = require("tslib"); const events_1 = require("events"); const crypto = tslib_1.__importStar(require("crypto")); class ChaosError extends Error { constructor(message, type, injectedAt = Date.now()) { super(message); this.type = type; this.injectedAt = injectedAt; this.name = 'ChaosError'; } } exports.ChaosError = ChaosError; class ChaosMiddleware extends events_1.EventEmitter { constructor(options) { super(); this.injectionCount = new Map(); this.options = options; // Use seeded random for reproducible chaos if (options.seed !== undefined) { let seed = options.seed; this.random = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }; } else { this.random = Math.random; } } async injectChaosAsync(request) { if (!this.options.enabled) return; // Network latency injection if (this.shouldInject('networkLatency')) { const delay = await this.injectNetworkLatencyAsync(); this.emit('chaos:injected', { type: 'networkLatency', delay, request: request.id }); } // Service failure injection if (this.shouldInject('serviceFailure')) { const statusCode = this.selectRandomStatusCode(); this.emit('chaos:injected', { type: 'serviceFailure', statusCode, request: request.id }); throw new ChaosError(`Service failure injected: HTTP ${statusCode}`, 'serviceFailure'); } // Timeout injection if (this.shouldInject('timeouts')) { const duration = this.options.failures.timeouts.duration; this.emit('chaos:injected', { type: 'timeout', duration, request: request.id }); await this.sleepAsync(duration); throw new ChaosError('Request timeout injected', 'timeout'); } // Resource exhaustion simulation if (this.shouldInject('resourceExhaustion')) { await this.simulateResourceExhaustionAsync(request); } } transformResponse(response) { if (!this.options.enabled) return response; // Malformed response injection if (this.shouldInject('malformedResponse')) { const type = this.selectRandomType(this.options.failures.malformedResponse.types); const malformed = this.createMalformedResponse(response, type); this.emit('chaos:injected', { type: 'malformedResponse', subtype: type, originalSize: JSON.stringify(response.body).length, malformedSize: JSON.stringify(malformed.body).length }); return malformed; } return response; } shouldInject(type) { const config = this.options.failures[type]; if (!config || config.probability === 0) return false; const roll = this.random(); const shouldInject = roll < config.probability; if (shouldInject) { const count = (this.injectionCount.get(type) || 0) + 1; this.injectionCount.set(type, count); } return shouldInject; } async injectNetworkLatencyAsync() { const { minMs, maxMs } = this.options.failures.networkLatency; const delay = minMs + (maxMs - minMs) * this.random(); await this.sleepAsync(delay); return delay; } selectRandomStatusCode() { const codes = this.options.failures.serviceFailure.statusCodes; return codes[Math.floor(this.random() * codes.length)] || 500; } selectRandomType(types) { const index = Math.floor(this.random() * types.length); return types[index]; // We know types array is not empty from usage } async simulateResourceExhaustionAsync(request) { const type = this.options.failures.resourceExhaustion.type; this.emit('chaos:injected', { type: 'resourceExhaustion', subtype: type, request: request.id }); switch (type) { case 'cpu': await this.simulateCPUSpikeAsync(); break; case 'memory': await this.simulateMemoryPressureAsync(); break; case 'connections': throw new ChaosError('Connection pool exhausted', 'resourceExhaustion'); } } async simulateCPUSpikeAsync() { const start = Date.now(); const duration = 100 + this.random() * 400; // 100-500ms // CPU-intensive operation while (Date.now() - start < duration) { // Perform meaningless calculations to consume CPU for (let i = 0; i < 10000; i++) { Math.sqrt(crypto.randomInt(0, Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER); } } } async simulateMemoryPressureAsync() { // Allocate temporary memory const arrays = []; const size = 1024 * 1024; // 1MB per array const count = 10 + Math.floor(this.random() * 40); // 10-50MB for (let i = 0; i < count; i++) { arrays.push(new Array(size).fill(0)); } // Hold for a bit then release await this.sleepAsync(100); arrays.length = 0; // Release memory } createMalformedResponse(response, type) { switch (type) { case 'truncated': { const body = JSON.stringify(response.body); const truncated = body.substring(0, Math.floor(body.length / 2)); return { ...response, body: truncated, headers: { ...response.headers, 'x-chaos': 'truncated-response' } }; } case 'invalid-json': return { ...response, body: '{"invalid": json"syntax}', headers: { ...response.headers, 'content-type': 'application/json', 'x-chaos': 'invalid-json' } }; case 'empty': return { ...response, body: '', headers: { ...response.headers, 'content-length': '0', 'x-chaos': 'empty-response' } }; case 'huge': { // Generate a huge response const hugeArray = new Array(10000).fill({ data: 'x'.repeat(1000), timestamp: Date.now(), random: crypto.randomInt(0, Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER }); return { ...response, body: { huge: hugeArray }, headers: { ...response.headers, 'x-chaos': 'huge-response' } }; } } } sleepAsync(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } getStatistics() { return { enabled: this.options.enabled, injections: Object.fromEntries(this.injectionCount), uptime: process.uptime() }; } reset() { this.injectionCount.clear(); this.emit('chaos:reset'); } enable() { this.options.enabled = true; this.emit('chaos:enabled'); } disable() { this.options.enabled = false; this.emit('chaos:disabled'); } } exports.ChaosMiddleware = ChaosMiddleware; //# sourceMappingURL=chaos-middleware.js.map