UNPKG

express-intercept

Version:

Build Express middleware to intercept / replace / inspect / transform response

199 lines (198 loc) 7.43 kB
"use strict"; // express-intercept.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.responseHandler = exports.requestHandler = void 0; const stream_1 = require("stream"); const _handler_js_1 = require("./_handler.js"); const _compression_js_1 = require("./_compression.js"); const async_request_handler_1 = require("async-request-handler"); const requestHandler = errorHandler => { return new RequestHandlerBuilder(errorHandler || defaultErrorHandler); }; exports.requestHandler = requestHandler; const responseHandler = errorHandler => { return new ResponseHandlerBuilder(errorHandler || defaultErrorHandler); }; exports.responseHandler = responseHandler; const defaultErrorHandler = (err, req, res, next) => { console.error(err); // use .send("") instead of .end(), since Node.js v13 res.status(500).send(""); }; class RequestHandlerBuilder { constructor(errorHandler) { this._error = errorHandler; } _error; /** * It appends a test condition to perform the RequestHandler. * Call this for multiple times to add multiple tests in AND condition. * Those tests could avoid unnecessary work later. */ for(condition) { this._for = AND(this._for, condition); return this; } _for; /** * It returns a RequestHandler which connects multiple RequestHandlers. * Use this after `requestHandler()` method but not after `responseHandler()`. */ use(handler, ...more) { let { _for, _error } = this; if (more.length) { handler = (0, async_request_handler_1.ASYNC)(handler, async_request_handler_1.ASYNC.apply(null, more)); } else { handler = (0, async_request_handler_1.ASYNC)(handler); } if (_for) handler = (0, async_request_handler_1.IF)(_for, handler); if (_error) handler = (0, async_request_handler_1.ASYNC)(handler, (0, async_request_handler_1.CATCH)(_error)); return handler; } /** * It returns a RequestHandler to inspect express Request object (aka `req`). * With `requestHandler()`, it works at request phase as normal RequestHandler works. */ getRequest(receiver) { return this.use(async (req, res, next) => { await receiver(req); next(); }); } } class ResponseHandlerBuilder extends RequestHandlerBuilder { use; /** * It appends a test condition to perform the RequestHandler. * Call this for multiple times to add multiple tests in AND condition. * Those tests could avoid unnecessary response interception work including additional buffering. */ if(condition) { this._if = AND(this._if, condition); return this; } _if; /** * It returns a RequestHandler to replace the response content body as a string. * It gives a single string even when the response stream is chunked and/or compressed. */ replaceString(replacer) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { const body = payload.getString(); const replaced = await replacer(body, req, res); if (body === replaced) return; // nothing changed payload.setString(replaced); })); } /** * It returns a RequestHandler to replace the response content body as a Buffer. * It gives a single Buffer even when the response stream is chunked and/or compressed. */ replaceBuffer(replacer) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { let body = payload.getBuffer(); body = await replacer(body, req, res); payload.setBuffer(body); })); } /** * It returns a RequestHandler to replace the response content body as a stream.Readable. * Interceptor may need to decompress the response stream when compressed. * Interceptor should return yet another stream.Readable to perform transform the stream. * Interceptor would use stream.Transform for most cases as it is a Readable. * Interceptor could return null or the upstream itself as given if transformation not happened. */ interceptStream(interceptor) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { return interceptor(payload, req, res); }, () => new ReadablePayload())); } /** * It returns a RequestHandler to retrieve the response content body as a string. * It gives a single string even when the response stream is chunked and/or compressed. */ getString(receiver) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { const body = payload.getString(); await receiver(body, req, res); })); } /** * It returns a RequestHandler to retrieve the response content body as a Buffer. * It gives a single Buffer even when the response stream is chunked and/or compressed. */ getBuffer(receiver) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { const body = payload.getBuffer(); await receiver(body, req, res); })); } /** * It returns a RequestHandler to inspect express Request object (aka `req`). * With `responseHandler()`, it works at response returning phase after `res.send()` fired. */ getRequest(receiver) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { await receiver(req); })); } /** * It returns a RequestHandler to inspect express Response object (aka `res`) on its response returning phase after res.send() fired. */ getResponse(receiver) { return super.use((0, _handler_js_1.buildResponseHandler)(this, async (payload, req, res) => { await receiver(res); })); } /** * It returns a RequestHandler to compress the response content. */ compressResponse() { return this.replaceBuffer((buf, req, res) => { const encoding = (0, _compression_js_1.findEncoding)(req.header("Accept-Encoding")); if (encoding) { res.setHeader("Content-Encoding", encoding); // signal to compress with the encoding } return buf; }); } /** * It returns a RequestHandler to decompress the response content. */ decompressResponse() { return this.replaceBuffer((buf, req, res) => { res.removeHeader("Content-Encoding"); // signal NOT to compress return buf; }); } } class ReadablePayload extends stream_1.Readable { _read() { // don't care } } /** * @private */ function AND(A, B) { if (!A) return B; if (!B) return A; return (arg) => { let result = A(arg); // result: false if (!result) return false; // result: true if (!isThenable(result)) return B(arg); // result: Promise<boolean> return result.then(result => (result && B(arg))); }; } const isThenable = (value) => ("function" === typeof (value.then));