@iota-big3/sdk-gateway
Version:
Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching
231 lines • 8.29 kB
JavaScript
"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