@@ -6,14 +6,31 @@ import express from 'express';
6
6
import proxy from 'express-http-proxy' ;
7
7
import * as cheerio from 'cheerio' ;
8
8
9
- // Override the default `logger` functions to prepend the extension name
9
+ /**
10
+ * Logger override for logging in this extension.
11
+ */
10
12
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 } ` ) ;
12
17
} ;
13
18
logger . error = ( message ) => {
14
- console . error ( `[@ harperdb/ express] ${ message } ` ) ;
19
+ console . error ( `[harperdb- express] ${ message } ` ) ;
15
20
} ;
16
21
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
+
17
34
/**
18
35
* Define a list of allowed hosts to validate an incoming `x-forwarded-host`
19
36
* header that could be used to make this more dynamic in the future.
@@ -23,12 +40,26 @@ logger.error = (message) => {
23
40
*/
24
41
const allowedHosts = new Set ( [ '83c5-2600-1700-f2e0-b0f-74f7-c2c1-a4ad-e69d.ngrok-free.app' ] ) ;
25
42
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
+
26
55
/**
27
56
* @typedef {Object } ExtensionOptions - The configuration options for the extension.
28
57
* @property {number= } port - A port for the Express.js server. Defaults to 3000.
29
58
* @property {string= } subPath - A sub path for serving requests from. Defaults to `''`.
30
59
* @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.
31
61
* @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.
32
63
*/
33
64
34
65
/**
@@ -53,7 +84,9 @@ function resolveConfig(options) {
53
84
assertType ( 'port' , options . port , 'number' ) ;
54
85
assertType ( 'subPath' , options . subPath , 'string' ) ;
55
86
assertType ( 'middlewarePath' , options . middlewarePath , 'string' ) ;
87
+ assertType ( 'transformerPath' , options . transformerPath , 'string' ) ;
56
88
assertType ( 'staticPath' , options . staticPath , 'string' ) ;
89
+ assertType ( 'routes' , options . routes , 'object' ) ;
57
90
58
91
// Remove leading and trailing slashes from subPath
59
92
if ( options . subPath ?. [ 0 ] === '/' ) {
@@ -67,7 +100,9 @@ function resolveConfig(options) {
67
100
port : options . port ?? 3000 ,
68
101
subPath : options . subPath ?? '' ,
69
102
middlewarePath : options . middlewarePath ?? '' ,
103
+ transformerPath : options . transformerPath ?? '' ,
70
104
staticPath : options . staticPath ?? '' ,
105
+ routes : options . routes ?? [ ] ,
71
106
} ;
72
107
}
73
108
@@ -90,11 +125,53 @@ export function start(options = {}) {
90
125
return {
91
126
async handleDirectory ( _ , componentPath ) {
92
127
logger . info ( `Setting up Express.js app...` ) ;
128
+ let middlewareFn ;
129
+ let transformReqOptionsFn ;
130
+ let transformReqPathFn ;
131
+ let transformResFn ;
93
132
94
133
if ( ! fs . existsSync ( componentPath ) || ! fs . statSync ( componentPath ) . isDirectory ( ) ) {
95
134
throw new Error ( `Invalid component path: ${ componentPath } ` ) ;
96
135
}
97
136
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
+
98
175
const app = express ( ) ;
99
176
100
177
// Middleware for subPath handling
@@ -109,56 +186,65 @@ export function start(options = {}) {
109
186
next ( ) ;
110
187
} ) ;
111
188
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
122
190
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
+ }
124
196
next ( ) ;
125
197
} ) ;
126
198
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
+ }
133
203
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
+ } ,
137
217
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
+ } ,
141
221
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
+ ) ;
145
227
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
+ // });
162
248
163
249
// Middleware for static files
164
250
if ( ! ! config . staticPath ) {
0 commit comments