import * as plugins from '../plugins.js'; export type TCompressionMethod = 'gzip' | 'deflate' | 'br' | 'none'; export interface ICompressionResult { result: Buffer; compressionMethod: TCompressionMethod; } export class Compressor { private _cache: Map; private MAX_CACHE_SIZE: number = 100 * 1024 * 1024; // 100 MB constructor() { this._cache = new Map(); } private _addToCache(key: string, value: Buffer) { this._cache.set(key, value); this._manageCacheSize(); } private _manageCacheSize() { let currentSize = Array.from(this._cache.values()).reduce((acc, buffer) => acc + buffer.length, 0); while (currentSize > this.MAX_CACHE_SIZE) { const firstKey = this._cache.keys().next().value; const firstValue = this._cache.get(firstKey)!; currentSize -= firstValue.length; this._cache.delete(firstKey); } } public async compressContent( content: Buffer, method: 'gzip' | 'deflate' | 'br' | 'none' ): Promise { const cacheKey = content.toString('base64') + method; const cachedResult = this._cache.get(cacheKey); if (cachedResult) { return cachedResult; } return new Promise((resolve, reject) => { const callback = (err: Error | null, result: Buffer) => { if (err) reject(err); else { this._addToCache(cacheKey, result); resolve(result); } }; switch (method) { case 'gzip': plugins.zlib.gzip(content, { level: 1, },callback,); break; case 'br': plugins.zlib.brotliCompress(content, {}, callback); break; case 'deflate': plugins.zlib.deflate(content, callback); break; default: this._addToCache(cacheKey, content); resolve(content); } }); } public determineCompression(acceptEncoding: string | string[], preferredCompressionMethodsArg: TCompressionMethod[] = []) { // Ensure acceptEncoding is a single string const encodingString = Array.isArray(acceptEncoding) ? acceptEncoding.join(', ') : acceptEncoding; let compressionMethod: TCompressionMethod = 'none'; // Prioritize preferred compression methods if provided for (const preferredMethod of preferredCompressionMethodsArg) { if (new RegExp(`\\b${preferredMethod}\\b`).test(encodingString)) { return preferredMethod; } } // Fallback to default prioritization if no preferred method matches if (/\bbr\b/.test(encodingString)) { compressionMethod = 'br'; } else if (/\bgzip\b/.test(encodingString)) { compressionMethod = 'gzip'; } else if (/\bdeflate\b/.test(encodingString)) { compressionMethod = 'deflate'; } return compressionMethod; } public async maybeCompress(requestHeaders: plugins.http.IncomingHttpHeaders, content: Buffer, preferredCompressionMethodsArg?: TCompressionMethod[]): Promise { const acceptEncoding = requestHeaders['accept-encoding']; const compressionMethod = this.determineCompression(acceptEncoding, preferredCompressionMethodsArg); const result = await this.compressContent(content, compressionMethod); return { result, compressionMethod, }; } public createCompressionStream(method: 'gzip' | 'deflate' | 'br' | 'none') { let compressionStream: any; switch (method) { case 'gzip': compressionStream = plugins.zlib.createGzip(); case 'br': compressionStream = plugins.zlib.createBrotliCompress({ chunkSize: 16 * 1024, params: { }, }); case 'deflate': compressionStream = plugins.zlib.createDeflate(); default: compressionStream = plugins.smartstream.createPassThrough(); } } }