Skip to content

Commit 10021f6

Browse files
committed
Add transform support
1 parent a29ebcc commit 10021f6

File tree

2 files changed

+133
-47
lines changed

2 files changed

+133
-47
lines changed

extension.js

Lines changed: 131 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,31 @@ import express from 'express';
66
import proxy from 'express-http-proxy';
77
import * as cheerio from 'cheerio';
88

9-
// Override the default `logger` functions to prepend the extension name
9+
/**
10+
* Logger override for logging in this extension.
11+
*/
1012
logger.info = (message) => {
11-
console.log(`[@harperdb/express] ${message}`);
13+
console.log(`[harperdb-express] ${message}`);
14+
};
15+
logger.debug = (message) => {
16+
console.log(`[harperdb-express] ${message}`);
1217
};
1318
logger.error = (message) => {
14-
console.error(`[@harperdb/express] ${message}`);
19+
console.error(`[harperdb-express] ${message}`);
1520
};
1621

22+
/**
23+
* @typedef {Object} Transformer
24+
* @property {function(RequestOptions): RequestOptions} transformRequestOptions - Function to transform request options.
25+
* @property {function(Request): string} transformRequestPath - Function to transform request path.
26+
* @property {function(ProxyRes, ProxyResData, UserReq, UserRes): string} transformResponse - Function to transform response.
27+
*/
28+
29+
/**
30+
* @typedef {Object} Middleware
31+
* @property {function(Request, Response, NextFunction): void} middleware - Function to transform request options.
32+
*/
33+
1734
/**
1835
* Define a list of allowed hosts to validate an incoming `x-forwarded-host`
1936
* header that could be used to make this more dynamic in the future.
@@ -23,12 +40,26 @@ logger.error = (message) => {
2340
*/
2441
const allowedHosts = new Set(['83c5-2600-1700-f2e0-b0f-74f7-c2c1-a4ad-e69d.ngrok-free.app']);
2542

43+
/**
44+
*
45+
* The 'proxy' library accepts a function to determine the proxy host.
46+
*
47+
* @param {IncomingMessage} param0
48+
* @returns {string} Hostname
49+
*/
50+
const determineProxyHost = ({ headers }) => {
51+
// Return the hostname (TODO: make this more dynamic)
52+
return 'https://www.google.com';
53+
};
54+
2655
/**
2756
* @typedef {Object} ExtensionOptions - The configuration options for the extension.
2857
* @property {number=} port - A port for the Express.js server. Defaults to 3000.
2958
* @property {string=} subPath - A sub path for serving requests from. Defaults to `''`.
3059
* @property {string=} middlewarePath - A path to a middleware file to be used by the Express.js server.
60+
* @property {string=} transformerPath - A path to a transformer file to be used by the Express.js server.
3161
* @property {string=} staticPath - A path to a static files directory to be served by the Express.js server.
62+
* @property {Array<{pattern: string, host: string}>} routes - Configurable routes for proxying requests.
3263
*/
3364

3465
/**
@@ -53,7 +84,9 @@ function resolveConfig(options) {
5384
assertType('port', options.port, 'number');
5485
assertType('subPath', options.subPath, 'string');
5586
assertType('middlewarePath', options.middlewarePath, 'string');
87+
assertType('transformerPath', options.transformerPath, 'string');
5688
assertType('staticPath', options.staticPath, 'string');
89+
assertType('routes', options.routes, 'object');
5790

5891
// Remove leading and trailing slashes from subPath
5992
if (options.subPath?.[0] === '/') {
@@ -67,7 +100,9 @@ function resolveConfig(options) {
67100
port: options.port ?? 3000,
68101
subPath: options.subPath ?? '',
69102
middlewarePath: options.middlewarePath ?? '',
103+
transformerPath: options.transformerPath ?? '',
70104
staticPath: options.staticPath ?? '',
105+
routes: options.routes ?? [],
71106
};
72107
}
73108

@@ -90,11 +125,53 @@ export function start(options = {}) {
90125
return {
91126
async handleDirectory(_, componentPath) {
92127
logger.info(`Setting up Express.js app...`);
128+
let middlewareFn;
129+
let transformReqOptionsFn;
130+
let transformReqPathFn;
131+
let transformResFn;
93132

94133
if (!fs.existsSync(componentPath) || !fs.statSync(componentPath).isDirectory()) {
95134
throw new Error(`Invalid component path: ${componentPath}`);
96135
}
97136

137+
// User-defined middleware
138+
if (!!config.middlewarePath) {
139+
// Check to ensure the middleware path is a valid file
140+
if (!fs.existsSync(config.middlewarePath) || !fs.statSync(config.middlewarePath).isFile()) {
141+
throw new Error(`Invalid middleware path: ${config.middlewarePath}`);
142+
}
143+
144+
// Middleware must be be a module with a default export
145+
const importPath = path.resolve(componentPath, config.middlewarePath);
146+
const middleware = (await import(importPath)).default;
147+
148+
if (typeof middleware !== 'function') {
149+
throw new Error(`Middleware must be a function. Received: ${typeof middleware}`);
150+
}
151+
152+
middlewareFn = middleware;
153+
}
154+
155+
// User-defined transformer
156+
if (!!config.transformerPath) {
157+
// Check to ensure the transformer path is a valid file
158+
if (!fs.existsSync(config.transformerPath) || !fs.statSync(config.transformerPath).isFile()) {
159+
throw new Error(`Invalid transformer path: ${config.transformerPath}`);
160+
}
161+
162+
// Transformer must be be a module with a default export
163+
const importPath = path.resolve(componentPath, config.transformerPath);
164+
const { transformRequestOptions, transformRequestPath, transformResponse } = await import(importPath);
165+
166+
console.log('transformRequestOptions', transformRequestOptions);
167+
console.log('transformRequestPath', transformRequestPath);
168+
console.log('transformResponse', transformResponse);
169+
170+
transformReqOptionsFn = transformRequestOptions;
171+
transformReqPathFn = transformRequestPath;
172+
transformResFn = transformResponse;
173+
}
174+
98175
const app = express();
99176

100177
// Middleware for subPath handling
@@ -109,56 +186,65 @@ export function start(options = {}) {
109186
next();
110187
});
111188

112-
// // Middleware to validate host
113-
// app.use((req, res, next) => {
114-
// const host = req.headers['x-forwarded-host'] || req.hostname;
115-
// if (!allowedHosts.has(host)) {
116-
// console.error(`Rejected request from unauthorized host: ${host}`);
117-
// return res.status(403).send('Forbidden');
118-
// }
119-
// next();
120-
// });
121-
189+
// Middleware to validate host
122190
app.use((req, res, next) => {
123-
res.body = `Hello World from ${req.url}`;
191+
const host = req.headers['x-forwarded-host'] || req.hostname;
192+
if (!allowedHosts.has(host)) {
193+
console.error(`Rejected request from unauthorized host: ${host}`);
194+
//return res.status(403).send('Forbidden');
195+
}
124196
next();
125197
});
126198

127-
// User-defined middleware
128-
if (!!config.middlewarePath) {
129-
// Check to ensure the middleware path is a valid file
130-
if (!fs.existsSync(config.middlewarePath) || !fs.statSync(config.middlewarePath).isFile()) {
131-
throw new Error(`Invalid middleware path: ${config.middlewarePath}`);
132-
}
199+
if (middlewareFn) {
200+
logger.info(`Using middleware: ${config.middlewarePath}`);
201+
app.use(middlewareFn);
202+
}
133203

134-
// Middleware must be be a module with a default export
135-
const importPath = path.resolve(componentPath, config.middlewarePath);
136-
const middleware = (await import(importPath)).default;
204+
app.use(
205+
proxy(determineProxyHost, {
206+
/**
207+
*
208+
* Set the 'accept-encoding' header so that the origin hopefully
209+
* responds with gzip data so the 'proxy' library can decompress the
210+
* stream as part of the PoC.
211+
*
212+
*/
213+
proxyReqOptDecorator: (proxyReqOpts) => {
214+
proxyReqOpts.headers['accept-encoding'] = 'gzip';
215+
return transformReqOptionsFn ? transformReqOptionsFn(proxyReqOpts) : proxyReqOpts;
216+
},
137217

138-
if (typeof middleware !== 'function') {
139-
throw new Error(`Middleware must be a function. Received: ${typeof middleware}`);
140-
}
218+
proxyReqPathResolver: (req) => {
219+
return transformReqPathFn ? transformReqPathFn(req) : req.url;
220+
},
141221

142-
logger.info(`Using middleware: ${config.middlewarePath}`);
143-
app.use(middleware);
144-
}
222+
userResDecorator: (proxyRes, proxyResData, userReq, userRes) => {
223+
return transformResFn ? transformResFn(proxyRes, proxyResData, userReq, userRes) : proxyResData;
224+
},
225+
})
226+
);
145227

146-
// // Middleware for proxying and DOM manipulation
147-
// app.use(
148-
// proxy('https://example.com', {
149-
// proxyReqPathResolver: (req) => req.url,
150-
// userResDecorator: async (proxyRes, proxyResData, req, res) => {
151-
// const contentType = proxyRes.headers['content-type'] || '';
152-
// if (contentType.includes('text/html')) {
153-
// const $ = cheerio.load(proxyResData.toString('utf-8'));
154-
// // Example DOM manipulation
155-
// $('title').text('Modified Title');
156-
// return $.html();
157-
// }
158-
// return proxyResData;
159-
// },
160-
// })
161-
// );
228+
// Configure route patterns with proxying and response handling
229+
// config.routes.forEach(({ pattern, host }) => {
230+
// logger.info(`Setting up route: ${pattern} -> ${host}`);
231+
// app.use(
232+
// pattern,
233+
// proxy(host, {
234+
// proxyReqPathResolver: (req) => req.url,
235+
// // userResDecorator: async (proxyRes, proxyResData, req, res) => {
236+
// // const contentType = proxyRes.headers['content-type'] || '';
237+
// // if (contentType.includes('text/html')) {
238+
// // const $ = cheerio.load(proxyResData.toString('utf-8'));
239+
// // // Example: Append a custom footer to the page
240+
// // $('body').append('<footer>Custom Footer</footer>');
241+
// // return $.html();
242+
// // }
243+
// // return proxyResData;
244+
// // },
245+
// })
246+
// );
247+
// });
162248

163249
// Middleware for static files
164250
if (!!config.staticPath) {

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
"config.yaml",
1919
"extension.js"
2020
],
21-
"name": "@harperdb/express",
21+
"name": "harperdb-proxy-transform",
2222
"prettier": "@harperdb/code-guidelines/prettier",
2323
"repository": {
2424
"type": "git",
25-
"url": "git+https://github.com/tristanlee85/harperdb-express.git"
25+
"url": "git+https://github.com/tristanlee85/harperdb-proxy-transform.git"
2626
},
2727
"scripts": {
2828
"format": "prettier .",

0 commit comments

Comments
 (0)