@flowerforce/flowerbase 1.8.1-beta.1 → 1.8.1-beta.3

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 1.8.1 (2026-03-27)
2
+
3
+
4
+ ### 🩹 Fixes
5
+
6
+ - add config FUNCTION_CALL_BODY_LIMIT ([5f6ec70](https://github.com/flowerforce/flowerbase/commit/5f6ec70))
7
+
1
8
  ## 1.8.0 (2026-03-20)
2
9
 
3
10
 
package/README.md CHANGED
@@ -484,6 +484,7 @@ Ensure the following environment variables are set in your .env file or deployme
484
484
  | `PORT` | The port on which the server will run. | `3000` |
485
485
  | `MONGODB_URL` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
486
486
  | `JWT_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
487
+ | `FUNCTION_CALL_BODY_LIMIT_BYTES` | Max request body size in bytes for `POST /api/client/<version>/app/:appId/functions/call`. | `52428800` |
487
488
  | `HOST` | The host address the server binds to (usually `0.0.0.0` for public access). | `0.0.0.0` |
488
489
  | `API_VERSION` | API version used in client base path. | `v2.0` |
489
490
  | `HTTPS_SCHEMA` | The schema for your server requests (usually `https` or `http`). | `http` |
@@ -518,6 +519,7 @@ PROJECT_ID=your-project-id
518
519
  PORT=3000
519
520
  MONGODB_URL=mongodb+srv://username:password@cluster.mongodb.net/dbname
520
521
  JWT_SECRET=your-jwt-secret
522
+ FUNCTION_CALL_BODY_LIMIT_BYTES=52428800
521
523
  HOST=0.0.0.0
522
524
  API_VERSION=v2.0
523
525
  HTTPS_SCHEMA=http
@@ -555,6 +557,45 @@ MONIT_ALLOW_EDIT=true
555
557
  - If `MONIT_ALLOWED_IPS` is set, only those IPs can reach `/monit` (ensure `req.ip` reflects your proxy setup / `trustProxy`).
556
558
  - You can disable **invoke** or **edit** with `MONIT_ALLOW_INVOKE=false` and/or `MONIT_ALLOW_EDIT=false`.
557
559
 
560
+ ### 🔐 Reverse Proxy and SSL Termination
561
+
562
+ When Flowerbase runs behind Nginx or HAProxy with SSL termination, make sure the proxy forwards the public scheme/host/port headers.
563
+
564
+ Flowerbase uses these headers for `GET /api/client/<version>/app/:appId/location`, and Realm-style clients use that response to build auth URLs (including `/login`).
565
+
566
+ - Header priority for host and scheme is: `Forwarded` first, then `X-Forwarded-*`, then `Host`.
567
+ - If `X-Forwarded-Port` is set and host has no explicit port, Flowerbase appends it.
568
+
569
+ #### Nginx
570
+
571
+ ```nginx
572
+ location / {
573
+ proxy_pass http://flowerbase_upstream;
574
+ proxy_set_header Host $http_host;
575
+ proxy_set_header X-Forwarded-Proto $scheme;
576
+ proxy_set_header X-Forwarded-Host $http_host;
577
+ proxy_set_header X-Forwarded-Port $server_port;
578
+ }
579
+ ```
580
+
581
+ #### HAProxy
582
+
583
+ ```haproxy
584
+ frontend fe_https
585
+ bind *:443 ssl crt /etc/haproxy/certs/site.pem
586
+ mode http
587
+ default_backend be_flowerbase
588
+
589
+ backend be_flowerbase
590
+ mode http
591
+ server app1 127.0.0.1:3000 check
592
+ http-request set-header Host %[req.hdr(host)]
593
+ http-request set-header X-Forwarded-Proto https if { ssl_fc }
594
+ http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
595
+ http-request set-header X-Forwarded-Host %[req.hdr(host)]
596
+ http-request set-header X-Forwarded-Port %[dst_port]
597
+ ```
598
+
558
599
 
559
600
  <a id="build"></a>
560
601
  ## 🚀 Build & Deploy the Server
@@ -3,6 +3,7 @@ export declare const DEFAULT_CONFIG: {
3
3
  PORT: number;
4
4
  MONGODB_URL: string;
5
5
  JWT_SECRET: string;
6
+ FUNCTION_CALL_BODY_LIMIT_BYTES: number;
6
7
  API_VERSION: string;
7
8
  HTTPS_SCHEMA: string;
8
9
  HOST: string;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAsBpC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCsB,eAAe,EAAE;;;;;;CAMjE,CAAA;AACD,eAAO,MAAM,WAAW,QAA8C,CAAA;AACtE,eAAO,MAAM,YAAY,QAA8B,CAAA;AACvD,eAAO,MAAM,OAAO,QAAgB,CAAA;AACpC,eAAO,MAAM,YAAY,QAAiC,CAAA;AAE1D,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7E,eAAO,MAAM,WAAW;;;;;;;mBAOqB,aAAa;;;;;;;;;CAOzD,CAAA;AAED,eAAO,MAAM,SAAS;;;CAGrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAsBpC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCsB,eAAe,EAAE;;;;;;CAMjE,CAAA;AACD,eAAO,MAAM,WAAW,QAA8C,CAAA;AACtE,eAAO,MAAM,YAAY,QAA8B,CAAA;AACvD,eAAO,MAAM,OAAO,QAAgB,CAAA;AACpC,eAAO,MAAM,YAAY,QAAiC,CAAA;AAE1D,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7E,eAAO,MAAM,WAAW;;;;;;;mBAOqB,aAAa;;;;;;;;;CAOzD,CAAA;AAED,eAAO,MAAM,SAAS;;;CAGrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAA"}
package/dist/constants.js CHANGED
@@ -31,6 +31,7 @@ exports.DEFAULT_CONFIG = {
31
31
  PORT: Number(process.env.PORT) || 3000,
32
32
  MONGODB_URL: process.env.MONGODB_URL || '',
33
33
  JWT_SECRET: process.env.JWT_SECRET || '',
34
+ FUNCTION_CALL_BODY_LIMIT_BYTES: Number(process.env.FUNCTION_CALL_BODY_LIMIT_BYTES) || 50 * 1024 * 1024,
34
35
  API_VERSION: process.env.API_VERSION || 'v2.0',
35
36
  HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
36
37
  HOST: process.env.HOST || '0.0.0.0',
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAIvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAmHhD,eAAO,MAAM,iCAAiC,GAAI,OAAO,OAAO,KAAG,OA0BlE,CAAA;AAID,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,OAgD9D,CAAA;AAqHD,eAAO,MAAM,oCAAoC,GAAI,QAAQ,QAAQ,YAClC,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,kBAyRjC,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/features/functions/controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAKvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAmHhD,eAAO,MAAM,iCAAiC,GAAI,OAAO,OAAO,KAAG,OA0BlE,CAAA;AAID,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,OAgD9D,CAAA;AAqHD,eAAO,MAAM,oCAAoC,GAAI,QAAQ,QAAQ,YAClC,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,kBA0RjC,CAAA"}
@@ -22,6 +22,7 @@ var __rest = (this && this.__rest) || function (s, e) {
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.functionsController = exports.shouldSkipReadabilityLookupForChange = exports.mapWatchFilterToDocumentQuery = exports.mapWatchFilterToChangeStreamMatch = void 0;
24
24
  const bson_1 = require("bson");
25
+ const constants_1 = require("../../constants");
25
26
  const services_1 = require("../../services");
26
27
  const context_1 = require("../../utils/context");
27
28
  const utils_1 = require("./utils");
@@ -273,6 +274,7 @@ exports.shouldSkipReadabilityLookupForChange = shouldSkipReadabilityLookupForCha
273
274
  const functionsController = (app_1, _a) => __awaiter(void 0, [app_1, _a], void 0, function* (app, { functionsList, rules }) {
274
275
  app.addHook('preHandler', app.jwtAuthentication);
275
276
  app.post('/call', {
277
+ bodyLimit: constants_1.DEFAULT_CONFIG.FUNCTION_CALL_BODY_LIMIT_BYTES,
276
278
  schema: {
277
279
  tags: ['Functions']
278
280
  }
@@ -1 +1 @@
1
- {"version":3,"file":"exposeRoutes.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/exposeRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,eAAe,kBAqF1D,CAAA"}
1
+ {"version":3,"file":"exposeRoutes.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/exposeRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAmCzC;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,SAAS,eAAe,kBA2F1D,CAAA"}
@@ -15,6 +15,36 @@ const utils_1 = require("../../auth/utils");
15
15
  const constants_1 = require("../../constants");
16
16
  const handleUserRegistration_model_1 = require("../../shared/models/handleUserRegistration.model");
17
17
  const crypto_1 = require("../crypto");
18
+ const parseFirstHeaderValue = (header) => {
19
+ var _a;
20
+ if (!header)
21
+ return undefined;
22
+ const raw = Array.isArray(header) ? header[0] : header;
23
+ return ((_a = raw === null || raw === void 0 ? void 0 : raw.split(',')[0]) === null || _a === void 0 ? void 0 : _a.trim()) || undefined;
24
+ };
25
+ const parseForwardedHeader = (header) => {
26
+ var _a;
27
+ if (!header)
28
+ return {};
29
+ const segment = (_a = header.split(',')[0]) === null || _a === void 0 ? void 0 : _a.trim();
30
+ if (!segment)
31
+ return {};
32
+ const tokens = segment.split(';').map((item) => item.trim());
33
+ const protoToken = tokens.find((item) => item.toLowerCase().startsWith('proto='));
34
+ const hostToken = tokens.find((item) => item.toLowerCase().startsWith('host='));
35
+ const clean = (value) => value === null || value === void 0 ? void 0 : value.replace(/^"|"$/g, '');
36
+ return {
37
+ proto: clean(protoToken === null || protoToken === void 0 ? void 0 : protoToken.split('=')[1]),
38
+ host: clean(hostToken === null || hostToken === void 0 ? void 0 : hostToken.split('=')[1])
39
+ };
40
+ };
41
+ const hasExplicitPort = (host) => {
42
+ if (/^\[[^\]]+]\:\d+$/.test(host))
43
+ return true;
44
+ if (/^[^:]+\:\d+$/.test(host))
45
+ return true;
46
+ return false;
47
+ };
18
48
  /**
19
49
  * > Used to expose all app routes
20
50
  * @param fastify -> the fastify instance
@@ -27,12 +57,16 @@ const exposeRoutes = (fastify) => __awaiter(void 0, void 0, void 0, function* ()
27
57
  tags: ['System']
28
58
  }
29
59
  }, (req) => __awaiter(void 0, void 0, void 0, function* () {
30
- var _a, _b, _c;
31
- const schema = (_a = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.HTTPS_SCHEMA) !== null && _a !== void 0 ? _a : 'http';
32
- const headerHost = (_b = req.headers.host) !== null && _b !== void 0 ? _b : 'localhost:3000';
33
- const hostname = headerHost.split(':')[0];
34
- const port = (_c = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.PORT) !== null && _c !== void 0 ? _c : 3000;
35
- const host = process.env.NODE_ENV === "production" ? hostname : `${hostname}:${port}`;
60
+ var _a, _b, _c, _d, _e, _f, _g;
61
+ const forwarded = parseForwardedHeader(parseFirstHeaderValue(req.headers.forwarded));
62
+ const forwardedProto = parseFirstHeaderValue(req.headers['x-forwarded-proto']);
63
+ const forwardedHost = parseFirstHeaderValue(req.headers['x-forwarded-host']);
64
+ const forwardedPort = parseFirstHeaderValue(req.headers['x-forwarded-port']);
65
+ const schema = (_c = (_b = (_a = forwarded.proto) !== null && _a !== void 0 ? _a : forwardedProto) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.HTTPS_SCHEMA) !== null && _c !== void 0 ? _c : 'http';
66
+ let host = (_f = (_e = (_d = forwarded.host) !== null && _d !== void 0 ? _d : forwardedHost) !== null && _e !== void 0 ? _e : req.headers.host) !== null && _f !== void 0 ? _f : `localhost:${(_g = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.PORT) !== null && _g !== void 0 ? _g : 3000}`;
67
+ if (forwardedPort && !hasExplicitPort(host)) {
68
+ host = `${host}:${forwardedPort}`;
69
+ }
36
70
  const wsSchema = 'wss';
37
71
  return {
38
72
  deployment_model: 'LOCAL',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.8.1-beta.1",
3
+ "version": "1.8.1-beta.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/constants.ts CHANGED
@@ -25,6 +25,7 @@ export const DEFAULT_CONFIG = {
25
25
  PORT: Number(process.env.PORT) || 3000,
26
26
  MONGODB_URL: process.env.MONGODB_URL || '',
27
27
  JWT_SECRET: process.env.JWT_SECRET || '',
28
+ FUNCTION_CALL_BODY_LIMIT_BYTES: Number(process.env.FUNCTION_CALL_BODY_LIMIT_BYTES) || 50 * 1024 * 1024,
28
29
  API_VERSION: process.env.API_VERSION || 'v2.0',
29
30
  HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
30
31
  HOST: process.env.HOST || '0.0.0.0',
@@ -0,0 +1,60 @@
1
+ import Fastify, { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
2
+ import { GenerateContext } from '../../../utils/context'
3
+ import { functionsController } from '../controller'
4
+
5
+ jest.mock('../../../utils/context', () => ({
6
+ GenerateContext: jest.fn()
7
+ }))
8
+
9
+ describe('functionsController', () => {
10
+ let app: FastifyInstance
11
+
12
+ beforeEach(async () => {
13
+ app = Fastify()
14
+
15
+ app.decorate('jwtAuthentication', async (request: FastifyRequest, _reply: FastifyReply) => {
16
+ ;(request as any).user = {
17
+ id: '507f191e810c19729de860ea',
18
+ typ: 'access'
19
+ }
20
+ })
21
+
22
+ ;(GenerateContext as jest.Mock).mockResolvedValue({ ok: true })
23
+
24
+ await app.register(functionsController, {
25
+ functionsList: {
26
+ largePayloadEcho: {
27
+ code: 'exports = () => ({ ok: true })'
28
+ }
29
+ },
30
+ rules: {}
31
+ })
32
+ await app.ready()
33
+ })
34
+
35
+ afterEach(async () => {
36
+ await app.close()
37
+ jest.clearAllMocks()
38
+ })
39
+
40
+ it('accepts payloads larger than Fastify default body limit on POST /call', async () => {
41
+ const largeValue = 'x'.repeat(2 * 1024 * 1024)
42
+
43
+ const response = await app.inject({
44
+ method: 'POST',
45
+ url: '/call',
46
+ payload: {
47
+ name: 'largePayloadEcho',
48
+ arguments: [{ largeValue }]
49
+ }
50
+ })
51
+
52
+ expect(response.statusCode).toBe(200)
53
+ expect(JSON.parse(response.body)).toEqual({ ok: true })
54
+ expect(GenerateContext).toHaveBeenCalledWith(
55
+ expect.objectContaining({
56
+ args: [{ largeValue }]
57
+ })
58
+ )
59
+ })
60
+ })
@@ -2,6 +2,7 @@ import type { ServerResponse } from 'http'
2
2
  import { EJSON, ObjectId } from 'bson'
3
3
  import type { FastifyRequest } from 'fastify'
4
4
  import type { Document } from 'mongodb'
5
+ import { DEFAULT_CONFIG } from '../../constants'
5
6
  import { services } from '../../services'
6
7
  import { GenerateContext } from '../../utils/context'
7
8
  import { Base64Function, FunctionCallBase64Dto, FunctionCallDto } from './dtos'
@@ -331,6 +332,7 @@ export const functionsController: FunctionController = async (
331
332
  app.addHook('preHandler', app.jwtAuthentication)
332
333
 
333
334
  app.post<{ Body: FunctionCallDto }>('/call', {
335
+ bodyLimit: DEFAULT_CONFIG.FUNCTION_CALL_BODY_LIMIT_BYTES,
334
336
  schema: {
335
337
  tags: ['Functions']
336
338
  }
@@ -38,7 +38,10 @@ describe('exposeRoutes', () => {
38
38
  const appId = 'it'
39
39
  const response = await config.app!.inject({
40
40
  method: 'GET',
41
- url: `${API_VERSION}/app/${appId}/location`
41
+ url: `${API_VERSION}/app/${appId}/location`,
42
+ headers: {
43
+ host: 'localhost:3000'
44
+ }
42
45
  })
43
46
 
44
47
  expect(response.statusCode).toBe(200)
@@ -50,6 +53,28 @@ describe('exposeRoutes', () => {
50
53
  })
51
54
  })
52
55
 
56
+ it('GET location should prioritize forwarded host and port', async () => {
57
+ const appId = 'it'
58
+ const response = await config.app!.inject({
59
+ method: 'GET',
60
+ url: `${API_VERSION}/app/${appId}/location`,
61
+ headers: {
62
+ host: 'internal-flowerbase:3000',
63
+ 'x-forwarded-proto': 'https',
64
+ 'x-forwarded-host': 'api.example.com',
65
+ 'x-forwarded-port': '8443'
66
+ }
67
+ })
68
+
69
+ expect(response.statusCode).toBe(200)
70
+ expect(JSON.parse(response.body)).toEqual({
71
+ deployment_model: 'LOCAL',
72
+ location: 'IE',
73
+ hostname: 'https://api.example.com:8443',
74
+ ws_hostname: 'wss://api.example.com:8443'
75
+ })
76
+ })
77
+
53
78
  it('exposeRoutes should handle errors in the catch block', async () => {
54
79
  const mockedApp = Fastify()
55
80
  // Forced fail on get method
@@ -6,6 +6,34 @@ import { API_VERSION, AUTH_CONFIG, AUTH_DB_NAME, DEFAULT_CONFIG } from '../../co
6
6
  import { PROVIDER } from '../../shared/models/handleUserRegistration.model'
7
7
  import { hashPassword } from '../crypto'
8
8
 
9
+ const parseFirstHeaderValue = (header: string | string[] | undefined): string | undefined => {
10
+ if (!header) return undefined
11
+ const raw = Array.isArray(header) ? header[0] : header
12
+ return raw?.split(',')[0]?.trim() || undefined
13
+ }
14
+
15
+ const parseForwardedHeader = (header: string | undefined): { proto?: string; host?: string } => {
16
+ if (!header) return {}
17
+ const segment = header.split(',')[0]?.trim()
18
+ if (!segment) return {}
19
+
20
+ const tokens = segment.split(';').map((item) => item.trim())
21
+ const protoToken = tokens.find((item) => item.toLowerCase().startsWith('proto='))
22
+ const hostToken = tokens.find((item) => item.toLowerCase().startsWith('host='))
23
+ const clean = (value?: string) => value?.replace(/^"|"$/g, '')
24
+
25
+ return {
26
+ proto: clean(protoToken?.split('=')[1]),
27
+ host: clean(hostToken?.split('=')[1])
28
+ }
29
+ }
30
+
31
+ const hasExplicitPort = (host: string): boolean => {
32
+ if (/^\[[^\]]+]\:\d+$/.test(host)) return true
33
+ if (/^[^:]+\:\d+$/.test(host)) return true
34
+ return false
35
+ }
36
+
9
37
  /**
10
38
  * > Used to expose all app routes
11
39
  * @param fastify -> the fastify instance
@@ -18,11 +46,17 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
18
46
  tags: ['System']
19
47
  }
20
48
  }, async (req) => {
21
- const schema = DEFAULT_CONFIG?.HTTPS_SCHEMA ?? 'http'
22
- const headerHost = req.headers.host ?? 'localhost:3000'
23
- const hostname = headerHost.split(':')[0]
24
- const port = DEFAULT_CONFIG?.PORT ?? 3000
25
- const host = process.env.NODE_ENV === "production" ? hostname : `${hostname}:${port}`
49
+ const forwarded = parseForwardedHeader(parseFirstHeaderValue(req.headers.forwarded))
50
+ const forwardedProto = parseFirstHeaderValue(req.headers['x-forwarded-proto'])
51
+ const forwardedHost = parseFirstHeaderValue(req.headers['x-forwarded-host'])
52
+ const forwardedPort = parseFirstHeaderValue(req.headers['x-forwarded-port'])
53
+ const schema = forwarded.proto ?? forwardedProto ?? DEFAULT_CONFIG?.HTTPS_SCHEMA ?? 'http'
54
+ let host = forwarded.host ?? forwardedHost ?? req.headers.host ?? `localhost:${DEFAULT_CONFIG?.PORT ?? 3000}`
55
+
56
+ if (forwardedPort && !hasExplicitPort(host)) {
57
+ host = `${host}:${forwardedPort}`
58
+ }
59
+
26
60
  const wsSchema = 'wss'
27
61
 
28
62
  return {