@sap-ux/backend-proxy-middleware 0.9.18 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,14 @@
1
1
  import type { ServerOptions } from 'http-proxy';
2
2
  import type { RequestHandler, Options } from 'http-proxy-middleware';
3
3
  import type { ClientRequest, IncomingMessage, ServerResponse } from 'http';
4
- import type { Logger } from '@sap-ux/logger';
5
- import { ToolsLogger } from '@sap-ux/logger';
4
+ import { ToolsLogger, type Logger } from '@sap-ux/logger';
6
5
  import type { BackendConfig, DestinationBackendConfig } from './types';
7
6
  import type { BackendSystem } from '@sap-ux/store';
8
7
  import type { Url } from 'url';
8
+ import type { Socket } from 'node:net';
9
+ import type { Request } from 'express';
10
+ import type connect from 'connect';
11
+ export type EnhancedIncomingMessage = (IncomingMessage & Pick<Request, 'originalUrl'>) | connect.IncomingMessage;
9
12
  /**
10
13
  * Collection of custom event handler for the proxy.
11
14
  */
@@ -18,7 +21,7 @@ export declare const ProxyEventHandlers: {
18
21
  * @param _res (not used)
19
22
  * @param _options (not used)
20
23
  */
21
- onProxyReq(proxyReq: ClientRequest, _req?: IncomingMessage, _res?: ServerResponse, _options?: ServerOptions): void;
24
+ proxyReq(proxyReq: ClientRequest, _req?: IncomingMessage, _res?: ServerResponse, _options?: ServerOptions): void;
22
25
  /**
23
26
  * Retrieve the set-cookie headers from the response and transform secure cookies to insecure ones.
24
27
  *
@@ -26,7 +29,7 @@ export declare const ProxyEventHandlers: {
26
29
  * @param _req (not used) original request
27
30
  * @param _res (not used)
28
31
  */
29
- onProxyRes(proxyRes: IncomingMessage, _req?: IncomingMessage, _res?: ServerResponse): void;
32
+ proxyRes(proxyRes: IncomingMessage, _req?: IncomingMessage, _res?: ServerResponse): void;
30
33
  };
31
34
  /**
32
35
  * Specifically handling errors due to unsigned certificates and empty errors.
@@ -42,13 +45,13 @@ export declare function proxyErrorHandler(err: Error & {
42
45
  }, req: IncomingMessage & {
43
46
  next?: Function;
44
47
  originalUrl?: string;
45
- }, logger: ToolsLogger, _res?: ServerResponse, _target?: string | Partial<Url>): void;
48
+ }, logger: ToolsLogger, _res?: ServerResponse | Socket, _target?: string | Partial<Url>): void;
46
49
  /**
47
50
  * Collection of path rewrite functions.
48
51
  */
49
52
  export declare const PathRewriters: {
50
53
  /**
51
- * Generates a rewrite funtion that replace the match string with the prefix in the given string.
54
+ * Generates a rewrite function that replaces the matched string with the prefix in the given string.
52
55
  *
53
56
  * @param match part of the path that is to be replaced
54
57
  * @param prefix new path that is used as replacement
@@ -69,7 +72,7 @@ export declare const PathRewriters: {
69
72
  * @param log logger instance
70
73
  * @returns a path rewrite function
71
74
  */
72
- getPathRewrite(config: BackendConfig, log: Logger): ((path: string) => string) | undefined;
75
+ getPathRewrite(config: BackendConfig, log: Logger): (path: string, req: IncomingMessage) => string;
73
76
  };
74
77
  /**
75
78
  * Initialize i18next with the translations for this module.
@@ -33,7 +33,7 @@ exports.ProxyEventHandlers = {
33
33
  * @param _res (not used)
34
34
  * @param _options (not used)
35
35
  */
36
- onProxyReq(proxyReq, _req, _res, _options) {
36
+ proxyReq(proxyReq, _req, _res, _options) {
37
37
  if (proxyReq.path?.includes('Fiorilaunchpad.html') && !proxyReq.headersSent) {
38
38
  proxyReq.setHeader('accept-encoding', '*');
39
39
  }
@@ -45,7 +45,7 @@ exports.ProxyEventHandlers = {
45
45
  * @param _req (not used) original request
46
46
  * @param _res (not used)
47
47
  */
48
- onProxyRes(proxyRes, _req, _res) {
48
+ proxyRes(proxyRes, _req, _res) {
49
49
  const header = proxyRes?.headers?.['set-cookie'];
50
50
  if (header?.length) {
51
51
  for (let i = header.length - 1; i >= 0; i--) {
@@ -108,7 +108,7 @@ async function getApiHubKey(logger) {
108
108
  */
109
109
  exports.PathRewriters = {
110
110
  /**
111
- * Generates a rewrite funtion that replace the match string with the prefix in the given string.
111
+ * Generates a rewrite function that replaces the matched string with the prefix in the given string.
112
112
  *
113
113
  * @param match part of the path that is to be replaced
114
114
  * @param prefix new path that is used as replacement
@@ -143,6 +143,11 @@ exports.PathRewriters = {
143
143
  */
144
144
  getPathRewrite(config, log) {
145
145
  const functions = [];
146
+ functions.push((path, req) => {
147
+ // ensure that the path starts with the configured path
148
+ // it might get lost when you nest router instances
149
+ return req.originalUrl?.includes(path) ? req.originalUrl : path;
150
+ });
146
151
  if (config.pathReplace) {
147
152
  functions.push(exports.PathRewriters.replacePrefix(config.path, config.pathReplace));
148
153
  }
@@ -150,9 +155,11 @@ exports.PathRewriters = {
150
155
  functions.push(exports.PathRewriters.replaceClient(config.client));
151
156
  }
152
157
  if (functions.length > 0) {
153
- return (path) => {
158
+ return (path, req) => {
154
159
  let newPath = path;
155
- functions.forEach((func) => (newPath = func(newPath)));
160
+ for (const func of functions) {
161
+ newPath = func(newPath, req);
162
+ }
156
163
  if (newPath !== path) {
157
164
  log.info(`Rewrite path ${path} > ${newPath}`);
158
165
  }
@@ -210,7 +217,7 @@ async function enhanceConfigsForDestination(proxyOptions, backend) {
210
217
  }
211
218
  }
212
219
  else {
213
- throw new Error();
220
+ throw new Error('Destination not found. Please check your configuration or user role assignment.');
214
221
  }
215
222
  }
216
223
  }
@@ -262,20 +269,23 @@ async function enhanceConfigForSystem(proxyOptions, system, oAuthRequired, token
262
269
  * @param logger optional logger instance
263
270
  * @returns options for the http-proxy-middleware
264
271
  */
265
- async function generateProxyMiddlewareOptions(backend, options = {}, logger = new logger_1.ToolsLogger()) {
272
+ async function generateProxyMiddlewareOptions(backend, options = {}, logger = new logger_1.ToolsLogger({
273
+ transports: [new logger_1.UI5ToolingTransport({ moduleName: 'backend-proxy-middleware' })]
274
+ })) {
266
275
  // add required options
267
276
  const proxyOptions = {
268
277
  headers: {},
269
- ...exports.ProxyEventHandlers,
270
- onError: (err, req, res, target) => {
271
- proxyErrorHandler(err, req, logger, res, target);
278
+ on: {
279
+ error: (err, req, res, target) => {
280
+ proxyErrorHandler(err, req, logger, res, target);
281
+ },
282
+ ...exports.ProxyEventHandlers
272
283
  },
273
- ...options
284
+ ...options,
285
+ changeOrigin: true,
286
+ target: backend.url,
287
+ pathRewrite: exports.PathRewriters.getPathRewrite(backend, logger)
274
288
  };
275
- proxyOptions.changeOrigin = true;
276
- proxyOptions.logProvider = () => logger;
277
- // always set the target to the url provided in yaml
278
- proxyOptions.target = backend.url;
279
289
  // overwrite url if running in AppStudio
280
290
  if ((0, btp_utils_1.isAppStudio)()) {
281
291
  const destBackend = backend;
@@ -317,7 +327,6 @@ async function generateProxyMiddlewareOptions(backend, options = {}, logger = ne
317
327
  if (!proxyOptions.auth && process.env.FIORI_TOOLS_USER && process.env.FIORI_TOOLS_PASSWORD) {
318
328
  proxyOptions.auth = `${process.env.FIORI_TOOLS_USER}:${process.env.FIORI_TOOLS_PASSWORD}`;
319
329
  }
320
- proxyOptions.pathRewrite = exports.PathRewriters.getPathRewrite(backend, logger);
321
330
  if (backend.bsp) {
322
331
  await (0, bsp_1.addOptionsForEmbeddedBSP)(backend.bsp, proxyOptions, logger);
323
332
  }
@@ -336,7 +345,7 @@ async function generateProxyMiddlewareOptions(backend, options = {}, logger = ne
336
345
  if (backend.proxy) {
337
346
  proxyOptions.agent = new https_proxy_agent_1.HttpsProxyAgent(backend.proxy);
338
347
  }
339
- logger.info(`Backend proxy created for ${proxyOptions.target} ${backend.path ? backend.path : ''}`);
348
+ logger.info(`Backend proxy created for ${proxyOptions.target}`);
340
349
  return proxyOptions;
341
350
  }
342
351
  /**
@@ -59,6 +59,7 @@ export type BackendConfig = LocalBackendConfig | DestinationBackendConfig;
59
59
  export interface BackendMiddlewareConfig {
60
60
  backend: BackendConfig;
61
61
  options?: Partial<Options>;
62
+ debug?: boolean;
62
63
  }
63
64
  export interface MiddlewareParameters<T> {
64
65
  resources: object;
package/dist/ext/bsp.js CHANGED
@@ -28,13 +28,13 @@ function convertAppDescriptorToManifest(bsp) {
28
28
  */
29
29
  async function promptUserPass(log) {
30
30
  if ((0, btp_utils_1.isAppStudio)()) {
31
- const { authNeeded } = await (0, prompts_1.default)([
31
+ const { authNeeded } = (await (0, prompts_1.default)([
32
32
  {
33
33
  type: 'confirm',
34
34
  name: 'authNeeded',
35
35
  message: `${(0, chalk_1.cyan)(i18next_1.default.t('info.authNeeded'))}\n\n`
36
36
  }
37
- ]);
37
+ ]));
38
38
  if (!authNeeded) {
39
39
  return undefined;
40
40
  }
@@ -42,7 +42,7 @@ async function promptUserPass(log) {
42
42
  else {
43
43
  log.info((0, chalk_1.yellow)(i18next_1.default.t('info.credentialsRequiredForFLP')));
44
44
  }
45
- const { username, password } = await (0, prompts_1.default)([
45
+ const { username, password } = (await (0, prompts_1.default)([
46
46
  {
47
47
  type: 'text',
48
48
  name: 'username',
@@ -74,7 +74,7 @@ async function promptUserPass(log) {
74
74
  log.info((0, chalk_1.yellow)(i18next_1.default.t('info.operationAborted')));
75
75
  return process.exit(1);
76
76
  }
77
- });
77
+ }));
78
78
  return `${username}:${password}`;
79
79
  }
80
80
  /**
@@ -88,8 +88,11 @@ async function addOptionsForEmbeddedBSP(bspPath, proxyOptions, logger) {
88
88
  const regex = new RegExp('(' + bspPath + '/manifest\\.appdescr\\b)', 'i');
89
89
  proxyOptions.router = (req) => {
90
90
  // redirects the request for manifest.appdescr to localhost
91
- if (req.path.match(regex)) {
92
- return req.protocol + '://' + req.headers.host;
91
+ if (req.url?.match(regex)) {
92
+ const protocol = 'protocol' in req
93
+ ? req.protocol
94
+ : req.headers.referer?.substring(0, req.headers.referer.indexOf(':')) ?? 'http';
95
+ return protocol + '://' + req.headers.host;
93
96
  }
94
97
  else {
95
98
  return undefined;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const dotenv_1 = __importDefault(require("dotenv"));
7
7
  const logger_1 = require("@sap-ux/logger");
8
+ const express_1 = __importDefault(require("express"));
8
9
  const http_proxy_middleware_1 = require("http-proxy-middleware");
9
10
  const proxy_1 = require("./base/proxy");
10
11
  /**
@@ -21,10 +22,11 @@ function formatProxyForLogging(proxy) {
21
22
  proxy = proxy.replace(proxy.slice(forwardSlashIndex + 2, atIndex), '***:***');
22
23
  }
23
24
  }
25
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
24
26
  return proxy || 'none';
25
27
  }
26
28
  /**
27
- * UI5 middleware allowing to to proxy backends.
29
+ * UI5 middleware allowing to proxy backends.
28
30
  *
29
31
  * @param params input parameters for UI5 middleware
30
32
  * @param params.options configuration options
@@ -32,28 +34,25 @@ function formatProxyForLogging(proxy) {
32
34
  */
33
35
  module.exports = async ({ options }) => {
34
36
  const logger = new logger_1.ToolsLogger({
37
+ logLevel: options.configuration?.debug ? logger_1.LogLevel.Debug : logger_1.LogLevel.Info,
35
38
  transports: [new logger_1.UI5ToolingTransport({ moduleName: 'backend-proxy-middleware' })]
36
39
  });
37
40
  await (0, proxy_1.initI18n)();
38
41
  dotenv_1.default.config();
42
+ const router = express_1.default.Router();
39
43
  const backend = options.configuration.backend;
40
44
  const configOptions = options.configuration.options ?? {};
41
45
  configOptions.secure = configOptions.secure !== undefined ? !!configOptions.secure : true;
46
+ configOptions.logger = options.configuration?.debug ? logger : undefined;
42
47
  try {
43
48
  const proxyOptions = await (0, proxy_1.generateProxyMiddlewareOptions)(options.configuration.backend, configOptions, logger);
44
49
  const proxyFn = (0, http_proxy_middleware_1.createProxyMiddleware)(proxyOptions);
45
50
  logger.info(`Starting backend-proxy-middleware using following configuration:\nbackend: ${JSON.stringify({
46
51
  ...backend,
47
52
  proxy: formatProxyForLogging(backend.proxy)
48
- })}\noptions: ${JSON.stringify(configOptions)}'`);
49
- return (req, res, next) => {
50
- if (req.path.startsWith(backend.path)) {
51
- proxyFn(req, res, next);
52
- }
53
- else {
54
- next();
55
- }
56
- };
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
+ })}\noptions: ${JSON.stringify(({ logger, ...rest } = configOptions) => rest)}'\nlog: '${options.configuration?.debug ? 'debug' : 'info'}'`);
55
+ return router.use(backend.path, proxyFn);
57
56
  }
58
57
  catch (e) {
59
58
  const message = `Failed to register backend for ${backend.path}. Check configuration in yaml file. \n\t${e}`;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Abackend-proxy-middleware"
11
11
  },
12
- "version": "0.9.18",
12
+ "version": "0.10.0",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -23,13 +23,13 @@
23
23
  "dependencies": {
24
24
  "chalk": "4.1.2",
25
25
  "dotenv": "16.3.1",
26
- "http-proxy-middleware": "2.0.9",
26
+ "http-proxy-middleware": "3.0.5",
27
27
  "https-proxy-agent": "5.0.1",
28
28
  "i18next": "25.3.0",
29
29
  "prompts": "2.4.2",
30
30
  "proxy-from-env": "1.1.0",
31
- "@sap-ux/axios-extension": "1.22.8",
32
- "@sap-ux/btp-utils": "1.1.2",
31
+ "@sap-ux/axios-extension": "1.22.9",
32
+ "@sap-ux/btp-utils": "1.1.3",
33
33
  "@sap-ux/logger": "0.7.0",
34
34
  "@sap-ux/store": "1.1.4"
35
35
  },
@@ -42,7 +42,9 @@
42
42
  "express": "4.21.2",
43
43
  "nock": "13.4.0",
44
44
  "supertest": "7.1.4",
45
- "yaml": "2.2.2"
45
+ "yaml": "2.2.2",
46
+ "connect": "^3.7.0",
47
+ "@types/connect": "^3.4.38"
46
48
  },
47
49
  "ui5": {
48
50
  "dependencies": []