@fougere/transport-http 0.11.0-alpha.0 → 0.12.0-alpha.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 +1 @@
1
- {"version":3,"file":"RunningReceiver.d.ts","sourceRoot":"","sources":["../../src/serve/RunningReceiver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qDAAqD;AACrD,eAAO,MAAM,cAAc,UAAoC,CAAC;AAEhE,wBAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqE7F"}
1
+ {"version":3,"file":"RunningReceiver.d.ts","sourceRoot":"","sources":["../../src/serve/RunningReceiver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qDAAqD;AACrD,eAAO,MAAM,cAAc,UAAoC,CAAC;AAEhE,wBAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAM7F"}
@@ -4,28 +4,37 @@ import { MAX_BODY_BYTES, CALL_PATH, parseError, tooLarge } from '../policy.js';
4
4
  /** What a frond binds when nobody says otherwise. */
5
5
  export const LOOPBACK_HOSTS = ['127.0.0.1', 'localhost', '::1'];
6
6
  export function serve(runner, options = {}) {
7
+ const host = options.host ?? (options.hosts ?? LOOPBACK_HOSTS)[0];
8
+ const refusal = whyNotHere(host, options);
9
+ if (refusal)
10
+ return Promise.reject(refusal);
11
+ return listening(createServer(answering(runner, options)), host, options);
12
+ }
13
+ /**
14
+ * Where a receiver binds and what it admits are two questions. `verify` answers the second —
15
+ * without it this receiver takes the `state` it is handed, and `hosts` is all that stands.
16
+ * Loopback or signed: there is no third way to serve.
17
+ */
18
+ function whyNotHere(host, options) {
7
19
  const allowed = options.hosts ?? LOOPBACK_HOSTS;
8
20
  if (allowed.length === 0) {
9
- return Promise.reject(new Error('A Fougere receiver needs at least one host to bind, `hosts` is empty'));
10
- }
11
- const host = options.host ?? allowed[0];
12
- // Where a receiver binds, and what it admits, are two questions now. `verify` answers
13
- // the second — without it this receiver still takes the `state` it is handed, and
14
- // `hosts` is all that stands. The default keeps it on the machine; widening it is
15
- // written down rather than inferred, and a widened receiver wants `requireIdentity`.
16
- if (!allowed.includes(host)) {
17
- return Promise.reject(new Error(`A Fougere receiver binds one of [${allowed.join(', ')}], got '${host}' — add it to \`hosts\` to allow it`));
21
+ return new Error('A Fougere receiver needs at least one host to bind, `hosts` is empty');
18
22
  }
19
- /** Loopback or signed there is no third way to serve. */
20
- if (!LOOPBACK_HOSTS.includes(host) && !options.verify && !options.allowUnsigned) {
21
- return Promise.reject(new Error(`A Fougere receiver on '${host}' is reachable from outside this machine and would believe whatever `
22
- + 'state it is handed.\n'
23
- + ' - `fougere keys` once, then inject FOUGERE_ROOT_KEY here (and `fougere grant <frond>` for each caller), or\n'
24
- + ' - keep it on loopback, or\n'
25
- + ' - pass `allowUnsigned: true` if a mesh or an ingress already authenticated the caller.'));
23
+ if (host === undefined || !allowed.includes(host)) {
24
+ return new Error(`A Fougere receiver binds one of [${allowed.join(', ')}], got '${host}' add it to \`hosts\` to allow it`);
26
25
  }
26
+ if (LOOPBACK_HOSTS.includes(host) || options.verify || options.allowUnsigned)
27
+ return undefined;
28
+ return new Error(`A Fougere receiver on '${host}' is reachable from outside this machine and would believe whatever `
29
+ + 'state it is handed.\n'
30
+ + ' - `fougere keys` once, then inject FOUGERE_ROOT_KEY here (and `fougere grant <frond>` for each caller), or\n'
31
+ + ' - keep it on loopback, or\n'
32
+ + ' - pass `allowUnsigned: true` if a mesh or an ingress already authenticated the caller.');
33
+ }
34
+ /** One path, one method, and a body that is refused before it is read whole. */
35
+ function answering(runner, options) {
27
36
  const maxBodyBytes = options.maxBodyBytes ?? MAX_BODY_BYTES;
28
- const server = createServer(async (req, res) => {
37
+ return async (req, res) => {
29
38
  if (req.method !== 'POST' || req.url !== CALL_PATH) {
30
39
  res.writeHead(404).end();
31
40
  return;
@@ -43,22 +52,23 @@ export function serve(runner, options = {}) {
43
52
  }
44
53
  let response;
45
54
  try {
46
- const raw = JSON.parse(Buffer.concat(chunks).toString('utf8'));
47
- response = await handleRpc(runner, raw, options);
55
+ response = await handleRpc(runner, JSON.parse(Buffer.concat(chunks).toString('utf8')), options);
48
56
  }
49
57
  catch {
50
58
  response = parseError();
51
59
  }
52
60
  res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(response));
53
- });
61
+ };
62
+ }
63
+ /** The port a receiver ended up on — asked of the server, since 0 means "whatever is free". */
64
+ function listening(server, host, options) {
54
65
  return new Promise((resolve, reject) => {
55
66
  server.once('error', reject);
56
67
  server.requestTimeout = options.requestTimeoutMs ?? 15_000;
57
68
  server.listen(options.port ?? 0, host, () => {
58
69
  const address = server.address();
59
- const port = typeof address === 'object' && address !== null ? address.port : 0;
60
70
  resolve({
61
- port,
71
+ port: typeof address === 'object' && address !== null ? address.port : 0,
62
72
  close: () => new Promise((done, fail) => server.close((err) => (err ? fail(err) : done()))),
63
73
  });
64
74
  });
@@ -1 +1 @@
1
- {"version":3,"file":"RunningReceiver.js","sourceRoot":"","sources":["../../src/serve/RunningReceiver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RunningReceiver.js","sourceRoot":"","sources":["../../src/serve/RunningReceiver.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/transport-http",
3
- "version": "0.11.0-alpha.0",
3
+ "version": "0.12.0-alpha.0",
4
4
  "description": "The call contract on the wire, in JSON-RPC 2.0 (server + client).",
5
5
  "keywords": [
6
6
  "fougere",
@@ -38,13 +38,13 @@
38
38
  "src"
39
39
  ],
40
40
  "dependencies": {
41
- "@fougere/core": "0.11.0-alpha.0"
41
+ "@fougere/core": "0.12.0-alpha.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "jiti": "^2.6.1",
45
- "@fougere/container": "0.11.0-alpha.0",
46
- "@fougere/compiler": "0.11.0-alpha.0",
47
- "@fougere/schema": "0.11.0-alpha.0"
45
+ "@fougere/container": "0.12.0-alpha.0",
46
+ "@fougere/schema": "0.12.0-alpha.0",
47
+ "@fougere/compiler": "0.12.0-alpha.0"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
@@ -1,4 +1,4 @@
1
- import { createServer } from 'node:http';
1
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
2
2
  import type { Transport } from '@fougere/core';
3
3
  import type { RpcResponse } from '../jsonrpc/RpcResponse.js';
4
4
  import { handleRpc } from '../server.js';
@@ -14,37 +14,49 @@ export interface RunningReceiver {
14
14
  export const LOOPBACK_HOSTS = ['127.0.0.1', 'localhost', '::1'];
15
15
 
16
16
  export function serve(runner: Transport, options: ServeOptions = {}): Promise<RunningReceiver> {
17
+ const host = options.host ?? (options.hosts ?? LOOPBACK_HOSTS)[0];
18
+ const refusal = whyNotHere(host, options);
19
+ if (refusal) return Promise.reject(refusal);
20
+
21
+ return listening(createServer(answering(runner, options)), host!, options);
22
+ }
23
+
24
+ /**
25
+ * Where a receiver binds and what it admits are two questions. `verify` answers the second —
26
+ * without it this receiver takes the `state` it is handed, and `hosts` is all that stands.
27
+ * Loopback or signed: there is no third way to serve.
28
+ */
29
+ function whyNotHere(host: string | undefined, options: ServeOptions): Error | undefined {
17
30
  const allowed = options.hosts ?? LOOPBACK_HOSTS;
18
31
  if (allowed.length === 0) {
19
- return Promise.reject(new Error('A Fougere receiver needs at least one host to bind, `hosts` is empty'));
20
- }
21
- const host = options.host ?? allowed[0];
22
- // Where a receiver binds, and what it admits, are two questions now. `verify` answers
23
- // the second — without it this receiver still takes the `state` it is handed, and
24
- // `hosts` is all that stands. The default keeps it on the machine; widening it is
25
- // written down rather than inferred, and a widened receiver wants `requireIdentity`.
26
- if (!allowed.includes(host)) {
27
- return Promise.reject(
28
- new Error(`A Fougere receiver binds one of [${allowed.join(', ')}], got '${host}' — add it to \`hosts\` to allow it`),
29
- );
32
+ return new Error('A Fougere receiver needs at least one host to bind, `hosts` is empty');
30
33
  }
31
- /** Loopback or signed — there is no third way to serve. */
32
- if (!LOOPBACK_HOSTS.includes(host) && !options.verify && !options.allowUnsigned) {
33
- return Promise.reject(
34
- new Error(
35
- `A Fougere receiver on '${host}' is reachable from outside this machine and would believe whatever `
36
- + 'state it is handed.\n'
37
- + ' - `fougere keys` once, then inject FOUGERE_ROOT_KEY here (and `fougere grant <frond>` for each caller), or\n'
38
- + ' - keep it on loopback, or\n'
39
- + ' - pass `allowUnsigned: true` if a mesh or an ingress already authenticated the caller.',
40
- ),
34
+
35
+ if (host === undefined || !allowed.includes(host)) {
36
+ return new Error(
37
+ `A Fougere receiver binds one of [${allowed.join(', ')}], got '${host}' — add it to \`hosts\` to allow it`,
41
38
  );
42
39
  }
43
40
 
41
+ if (LOOPBACK_HOSTS.includes(host) || options.verify || options.allowUnsigned) return undefined;
42
+
43
+ return new Error(
44
+ `A Fougere receiver on '${host}' is reachable from outside this machine and would believe whatever `
45
+ + 'state it is handed.\n'
46
+ + ' - `fougere keys` once, then inject FOUGERE_ROOT_KEY here (and `fougere grant <frond>` for each caller), or\n'
47
+ + ' - keep it on loopback, or\n'
48
+ + ' - pass `allowUnsigned: true` if a mesh or an ingress already authenticated the caller.',
49
+ );
50
+ }
51
+
52
+ /** One path, one method, and a body that is refused before it is read whole. */
53
+ function answering(runner: Transport, options: ServeOptions) {
44
54
  const maxBodyBytes = options.maxBodyBytes ?? MAX_BODY_BYTES;
45
- const server = createServer(async (req, res) => {
55
+
56
+ return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
46
57
  if (req.method !== 'POST' || req.url !== CALL_PATH) {
47
58
  res.writeHead(404).end();
59
+
48
60
  return;
49
61
  }
50
62
 
@@ -55,6 +67,7 @@ export function serve(runner: Transport, options: ServeOptions = {}): Promise<Ru
55
67
  size += chunk.length;
56
68
  if (size > maxBodyBytes) {
57
69
  res.writeHead(413, { 'content-type': 'application/json' }).end(JSON.stringify(tooLarge()));
70
+
58
71
  return;
59
72
  }
60
73
  chunks.push(chunk);
@@ -62,22 +75,25 @@ export function serve(runner: Transport, options: ServeOptions = {}): Promise<Ru
62
75
 
63
76
  let response: RpcResponse;
64
77
  try {
65
- const raw = JSON.parse(Buffer.concat(chunks).toString('utf8'));
66
- response = await handleRpc(runner, raw, options);
78
+ response = await handleRpc(runner, JSON.parse(Buffer.concat(chunks).toString('utf8')), options);
67
79
  } catch {
68
80
  response = parseError();
69
81
  }
82
+
70
83
  res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(response));
71
- });
84
+ };
85
+ }
72
86
 
87
+ /** The port a receiver ended up on — asked of the server, since 0 means "whatever is free". */
88
+ function listening(server: Server, host: string, options: ServeOptions): Promise<RunningReceiver> {
73
89
  return new Promise((resolve, reject) => {
74
90
  server.once('error', reject);
75
91
  server.requestTimeout = options.requestTimeoutMs ?? 15_000;
76
92
  server.listen(options.port ?? 0, host, () => {
77
93
  const address = server.address();
78
- const port = typeof address === 'object' && address !== null ? address.port : 0;
94
+
79
95
  resolve({
80
- port,
96
+ port: typeof address === 'object' && address !== null ? address.port : 0,
81
97
  close: () => new Promise<void>((done, fail) => server.close((err) => (err ? fail(err) : done()))),
82
98
  });
83
99
  });