@ripple-ts/adapter-node 0.2.210 → 0.2.212

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,5 +1,26 @@
1
1
  # @ripple-ts/adapter-node
2
2
 
3
+ ## 0.2.212
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @ripple-ts/adapter@0.2.212
9
+
10
+ ## 0.2.211
11
+
12
+ ### Patch Changes
13
+
14
+ - [#694](https://github.com/Ripple-TS/ripple/pull/694)
15
+ [`fa285f4`](https://github.com/Ripple-TS/ripple/commit/fa285f441ab8d748c3dfea6adb463e3ca6d614b5)
16
+ Thanks [@trueadm](https://github.com/trueadm)! - Add a shared
17
+ `ServeStaticDirectoryOptions` type in `@ripple-ts/adapter` and update node/bun
18
+ adapters to consume it instead of redefining the same
19
+ `ServeStaticOptions & { dir?: string }` shape locally.
20
+ - Updated dependencies
21
+ [[`fa285f4`](https://github.com/Ripple-TS/ripple/commit/fa285f441ab8d748c3dfea6adb463e3ca6d614b5)]:
22
+ - @ripple-ts/adapter@0.2.211
23
+
3
24
  ## 0.2.210
4
25
 
5
26
  ### Patch Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominic Gannaway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @ripple-ts/adapter-node
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/%40ripple-ts%2Fadapter-node?logo=npm)](https://www.npmjs.com/package/@ripple-ts/adapter-node)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40ripple-ts%2Fadapter-node?logo=npm&label=downloads)](https://www.npmjs.com/package/@ripple-ts/adapter-node)
5
+
3
6
  Node.js adapter for Ripple metaframework apps.
4
7
 
5
8
  It bridges Node's `IncomingMessage`/`ServerResponse` API to Web
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Node.js adapter for Ripple metaframework (Web Request/Response bridge)",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.210",
6
+ "version": "0.2.212",
7
7
  "type": "module",
8
8
  "module": "src/index.js",
9
9
  "main": "src/index.js",
@@ -14,11 +14,8 @@
14
14
  "default": "./src/index.js"
15
15
  }
16
16
  },
17
- "scripts": {
18
- "test": "pnpm -w test --project adapter-node"
19
- },
20
17
  "dependencies": {
21
- "@ripple-ts/adapter": "workspace:*"
18
+ "@ripple-ts/adapter": "0.2.212"
22
19
  },
23
20
  "homepage": "https://ripplejs.com",
24
21
  "repository": {
@@ -28,5 +25,8 @@
28
25
  },
29
26
  "bugs": {
30
27
  "url": "https://github.com/Ripple-TS/ripple/issues"
28
+ },
29
+ "scripts": {
30
+ "test": "pnpm -w test --project adapter-node"
31
31
  }
32
- }
32
+ }
package/src/index.js CHANGED
@@ -30,18 +30,29 @@ function normalize_forwarded_value(value) {
30
30
  /**
31
31
  * @param {import('node:http').IncomingMessage} node_request
32
32
  * @param {AbortSignal} signal
33
+ * @param {boolean} [trust_proxy=false]
33
34
  * @returns {Request}
34
35
  */
35
- function node_request_to_web_request(node_request, signal) {
36
- const forwarded_proto = normalize_forwarded_value(
37
- first_header_value(node_request.headers['x-forwarded-proto']),
38
- );
39
- const forwarded_host = normalize_forwarded_value(
40
- first_header_value(node_request.headers['x-forwarded-host']),
41
- );
42
-
43
- const proto = forwarded_proto ?? 'http';
44
- const host = forwarded_host ?? first_header_value(node_request.headers.host) ?? 'localhost';
36
+ function node_request_to_web_request(node_request, signal, trust_proxy = false) {
37
+ let proto = 'http';
38
+ let host = first_header_value(node_request.headers.host) ?? 'localhost';
39
+
40
+ if (trust_proxy) {
41
+ const forwarded_proto = normalize_forwarded_value(
42
+ first_header_value(node_request.headers['x-forwarded-proto']),
43
+ );
44
+ const forwarded_host = normalize_forwarded_value(
45
+ first_header_value(node_request.headers['x-forwarded-host']),
46
+ );
47
+
48
+ if (forwarded_proto) proto = forwarded_proto;
49
+ if (forwarded_host) host = forwarded_host;
50
+ } else {
51
+ // Derive protocol from the socket when not trusting proxy headers
52
+ if (/** @type {import('node:tls').TLSSocket} */ (node_request.socket).encrypted) {
53
+ proto = 'https';
54
+ }
55
+ }
45
56
 
46
57
  const raw_url = node_request.url ?? '/';
47
58
  const base = `${proto}://${host}`;
@@ -114,21 +125,6 @@ function web_response_to_node_response(web_response, node_response, request_meth
114
125
  node_stream.pipe(node_response);
115
126
  }
116
127
 
117
- /** @typedef {import('@ripple-ts/adapter').ServeStaticDirectoryOptions} StaticServeOptions */
118
-
119
- /**
120
- * @typedef {{
121
- * port?: number,
122
- * hostname?: string,
123
- * middleware?: ((
124
- * req: import('node:http').IncomingMessage,
125
- * res: import('node:http').ServerResponse,
126
- * next: (error?: any) => void
127
- * ) => void) | null,
128
- * static?: StaticServeOptions | false,
129
- * }} ServeOptions
130
- */
131
-
132
128
  /**
133
129
  * @param {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, next: (error?: any) => void) => void} middleware
134
130
  * @param {import('node:http').IncomingMessage} node_request
@@ -160,9 +156,7 @@ function run_node_middleware(middleware, node_request, node_response) {
160
156
  }
161
157
 
162
158
  /**
163
- * @param {(request: Request, platform?: any) => Response | Promise<Response>} fetch_handler
164
- * @param {ServeOptions} [options]
165
- * @returns {{ listen: (port?: number) => import('node:http').Server, close: () => void }}
159
+ * @type {typeof import('@ripple-ts/adapter-node').serve}
166
160
  */
167
161
  export function serve(fetch_handler, options = {}) {
168
162
  const {
@@ -170,6 +164,7 @@ export function serve(fetch_handler, options = {}) {
170
164
  hostname = DEFAULT_HOSTNAME,
171
165
  middleware = null,
172
166
  static: static_options = {},
167
+ trustProxy: trust_proxy = false,
173
168
  } = options;
174
169
 
175
170
  /** @type {ReturnType<typeof serveStatic> | null} */
@@ -191,7 +186,11 @@ export function serve(fetch_handler, options = {}) {
191
186
 
192
187
  try {
193
188
  const run_fetch_handler = async () => {
194
- const request = node_request_to_web_request(node_request, abort_controller.signal);
189
+ const request = node_request_to_web_request(
190
+ node_request,
191
+ abort_controller.signal,
192
+ trust_proxy,
193
+ );
195
194
  return await fetch_handler(request, { node_request, node_response });
196
195
  };
197
196
 
@@ -130,6 +130,7 @@ describe('@ripple-ts/adapter-node serve()', () => {
130
130
  expect(response.headers.get('x-response')).toBe('ok');
131
131
  expect(await response.text()).toBe('created');
132
132
  },
133
+ { trustProxy: true },
133
134
  );
134
135
 
135
136
  expect(captured).toEqual({
@@ -140,6 +141,32 @@ describe('@ripple-ts/adapter-node serve()', () => {
140
141
  });
141
142
  });
142
143
 
144
+ it('ignores x-forwarded-* headers when trustProxy is not enabled', async () => {
145
+ /** @type {{ url: string } | null} */
146
+ let captured = null;
147
+
148
+ await with_server(
149
+ async (request) => {
150
+ captured = { url: request.url };
151
+ return new Response('ok');
152
+ },
153
+ async (base_url) => {
154
+ await fetch(`${base_url}/test`, {
155
+ headers: {
156
+ 'x-forwarded-proto': 'https',
157
+ 'x-forwarded-host': 'evil.com',
158
+ },
159
+ });
160
+ },
161
+ );
162
+
163
+ // Should NOT use forwarded headers; URL should use http and the real host
164
+ expect(captured).not.toBeNull();
165
+ const url = new URL((captured ?? { url: '' }).url);
166
+ expect(url.protocol).toBe('http:');
167
+ expect(url.hostname).not.toBe('evil.com');
168
+ });
169
+
143
170
  it('runs middleware before handler when middleware calls next()', async () => {
144
171
  /** @type {string[]} */
145
172
  const calls = [];
package/types/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  AdapterCoreOptions,
3
- FetchHandler,
4
- ServeResult,
3
+ ServeFunction,
5
4
  ServeStaticOptions as BaseServeStaticOptions,
6
5
  ServeStaticDirectoryOptions as BaseServeStaticDirectoryOptions,
7
6
  } from '@ripple-ts/adapter';
@@ -25,13 +24,14 @@ export type StaticMiddleware = (
25
24
  next: (error?: any) => void,
26
25
  ) => void;
27
26
 
28
- export function serve(
29
- fetch_handler: FetchHandler<{
27
+ export const serve: ServeFunction<
28
+ {
30
29
  node_request: import('node:http').IncomingMessage;
31
30
  node_response: import('node:http').ServerResponse;
32
- }>,
33
- options?: ServeOptions,
34
- ): ServeResult<import('node:http').Server>;
31
+ },
32
+ ServeOptions,
33
+ import('node:http').Server
34
+ >;
35
35
 
36
36
  /**
37
37
  * Create a middleware that serves static files from a directory