@openclaw/crabline 0.1.11 → 0.1.12

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.
Files changed (197) hide show
  1. package/README.md +99 -12
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +264 -50
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/matrix-ids.d.ts +5 -0
  31. package/dist/src/matrix-ids.js +83 -0
  32. package/dist/src/matrix-ids.js.map +1 -0
  33. package/dist/src/openclaw/artifact-generation.d.ts +7 -2
  34. package/dist/src/openclaw/artifact-generation.js +399 -27
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  36. package/dist/src/openclaw/bridges/matrix.js +21 -4
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +13 -8
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  41. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  42. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  43. package/dist/src/openclaw/bridges/signal.js +15 -6
  44. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  45. package/dist/src/openclaw/bridges/slack.js +116 -23
  46. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  47. package/dist/src/openclaw/bridges/telegram.js +45 -19
  48. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  49. package/dist/src/openclaw/bridges/whatsapp.js +32 -16
  50. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  51. package/dist/src/openclaw/bridges/zalo.js +8 -3
  52. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  53. package/dist/src/openclaw/private-file.d.ts +38 -3
  54. package/dist/src/openclaw/private-file.js +2008 -87
  55. package/dist/src/openclaw/private-file.js.map +1 -1
  56. package/dist/src/openclaw/shared.d.ts +1 -0
  57. package/dist/src/openclaw/shared.js +48 -13
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +9 -0
  60. package/dist/src/openclaw/smoke-lock.js +626 -172
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -1
  62. package/dist/src/openclaw.js +225 -42
  63. package/dist/src/openclaw.js.map +1 -1
  64. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  65. package/dist/src/platform/process-owned-lock.js +1698 -0
  66. package/dist/src/platform/process-owned-lock.js.map +1 -0
  67. package/dist/src/platform/recorder-directory.d.ts +1 -0
  68. package/dist/src/platform/recorder-directory.js +14 -0
  69. package/dist/src/platform/recorder-directory.js.map +1 -0
  70. package/dist/src/platform/windows-acl.d.ts +18 -0
  71. package/dist/src/platform/windows-acl.js +1367 -0
  72. package/dist/src/platform/windows-acl.js.map +1 -0
  73. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  74. package/dist/src/platform/windows-lock-root.js +98 -0
  75. package/dist/src/platform/windows-lock-root.js.map +1 -0
  76. package/dist/src/providers/builtin/discord.d.ts +4 -0
  77. package/dist/src/providers/builtin/discord.js +52 -9
  78. package/dist/src/providers/builtin/discord.js.map +1 -1
  79. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  80. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  81. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  82. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  83. package/dist/src/providers/builtin/feishu.js +96 -26
  84. package/dist/src/providers/builtin/feishu.js.map +1 -1
  85. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  86. package/dist/src/providers/builtin/googlechat.js +95 -26
  87. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  88. package/dist/src/providers/builtin/imessage.js +60 -17
  89. package/dist/src/providers/builtin/imessage.js.map +1 -1
  90. package/dist/src/providers/builtin/loopback.js +88 -35
  91. package/dist/src/providers/builtin/loopback.js.map +1 -1
  92. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  93. package/dist/src/providers/builtin/matrix.js +78 -21
  94. package/dist/src/providers/builtin/matrix.js.map +1 -1
  95. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  96. package/dist/src/providers/builtin/mattermost.js +138 -24
  97. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  98. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  99. package/dist/src/providers/builtin/msteams.js +118 -38
  100. package/dist/src/providers/builtin/msteams.js.map +1 -1
  101. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  102. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  103. package/dist/src/providers/builtin/script.js +1057 -146
  104. package/dist/src/providers/builtin/script.js.map +1 -1
  105. package/dist/src/providers/builtin/slack.d.ts +8 -1
  106. package/dist/src/providers/builtin/slack.js +327 -24
  107. package/dist/src/providers/builtin/slack.js.map +1 -1
  108. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  109. package/dist/src/providers/builtin/telegram.js +55 -14
  110. package/dist/src/providers/builtin/telegram.js.map +1 -1
  111. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  112. package/dist/src/providers/builtin/whatsapp.js +157 -29
  113. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  114. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  115. package/dist/src/providers/builtin/zalo.js +31 -7
  116. package/dist/src/providers/builtin/zalo.js.map +1 -1
  117. package/dist/src/providers/catalog.d.ts +1 -1
  118. package/dist/src/providers/catalog.js +1 -1
  119. package/dist/src/providers/catalog.js.map +1 -1
  120. package/dist/src/providers/local-mock.d.ts +4 -0
  121. package/dist/src/providers/local-mock.js +162 -35
  122. package/dist/src/providers/local-mock.js.map +1 -1
  123. package/dist/src/providers/native-ids.d.ts +2 -0
  124. package/dist/src/providers/native-ids.js +3 -0
  125. package/dist/src/providers/native-ids.js.map +1 -1
  126. package/dist/src/providers/recorder.d.ts +14 -3
  127. package/dist/src/providers/recorder.js +521 -41
  128. package/dist/src/providers/recorder.js.map +1 -1
  129. package/dist/src/providers/registry.d.ts +1 -0
  130. package/dist/src/providers/registry.js +65 -20
  131. package/dist/src/providers/registry.js.map +1 -1
  132. package/dist/src/providers/signed-jwt.d.ts +6 -1
  133. package/dist/src/providers/signed-jwt.js +241 -50
  134. package/dist/src/providers/signed-jwt.js.map +1 -1
  135. package/dist/src/providers/slack-ids.d.ts +1 -0
  136. package/dist/src/providers/slack-ids.js +5 -0
  137. package/dist/src/providers/slack-ids.js.map +1 -1
  138. package/dist/src/providers/target-normalizers.d.ts +9 -1
  139. package/dist/src/providers/target-normalizers.js +177 -58
  140. package/dist/src/providers/target-normalizers.js.map +1 -1
  141. package/dist/src/providers/webhook-server.d.ts +3 -0
  142. package/dist/src/providers/webhook-server.js +268 -47
  143. package/dist/src/providers/webhook-server.js.map +1 -1
  144. package/dist/src/servers/http.d.ts +14 -2
  145. package/dist/src/servers/http.js +229 -58
  146. package/dist/src/servers/http.js.map +1 -1
  147. package/dist/src/servers/matrix.d.ts +3 -0
  148. package/dist/src/servers/matrix.js +268 -110
  149. package/dist/src/servers/matrix.js.map +1 -1
  150. package/dist/src/servers/mattermost.d.ts +5 -0
  151. package/dist/src/servers/mattermost.js +389 -91
  152. package/dist/src/servers/mattermost.js.map +1 -1
  153. package/dist/src/servers/recorder.d.ts +11 -0
  154. package/dist/src/servers/recorder.js +1072 -69
  155. package/dist/src/servers/recorder.js.map +1 -1
  156. package/dist/src/servers/signal.d.ts +1 -0
  157. package/dist/src/servers/signal.js +279 -44
  158. package/dist/src/servers/signal.js.map +1 -1
  159. package/dist/src/servers/slack.d.ts +15 -0
  160. package/dist/src/servers/slack.js +259 -51
  161. package/dist/src/servers/slack.js.map +1 -1
  162. package/dist/src/servers/telegram-identity.d.ts +6 -0
  163. package/dist/src/servers/telegram-identity.js +27 -0
  164. package/dist/src/servers/telegram-identity.js.map +1 -0
  165. package/dist/src/servers/telegram.d.ts +17 -6
  166. package/dist/src/servers/telegram.js +1169 -107
  167. package/dist/src/servers/telegram.js.map +1 -1
  168. package/dist/src/servers/webhook-target.d.ts +12 -4
  169. package/dist/src/servers/webhook-target.js +226 -28
  170. package/dist/src/servers/webhook-target.js.map +1 -1
  171. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  172. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  173. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  174. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  175. package/dist/src/servers/whatsapp-jid.js +9 -5
  176. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  177. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  178. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  179. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  181. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  182. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  183. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  184. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  185. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  186. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  187. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  188. package/dist/src/servers/whatsapp.d.ts +7 -0
  189. package/dist/src/servers/whatsapp.js +149 -24
  190. package/dist/src/servers/whatsapp.js.map +1 -1
  191. package/dist/src/servers/zalo.d.ts +1 -0
  192. package/dist/src/servers/zalo.js +193 -82
  193. package/dist/src/servers/zalo.js.map +1 -1
  194. package/docs/channel-setup.md +185 -43
  195. package/fixtures/examples/crabline.example.yaml +3 -2
  196. package/package.json +18 -10
  197. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -14,29 +14,40 @@ export type HttpJsonHandlerResult = Response | {
14
14
  };
15
15
  export declare const ADMIN_TOKEN_HEADER = "x-crabline-admin-token";
16
16
  export declare const DEFAULT_MAX_REQUEST_BODY_BYTES: number;
17
+ export declare const DEFAULT_MAX_RESPONSE_BODY_BYTES: number;
17
18
  export declare const DEFAULT_SERVER_SHUTDOWN_GRACE_MS = 250;
18
19
  export declare class InvalidJsonBodyError extends Error {
19
- constructor(cause: unknown);
20
+ constructor(cause: unknown, message?: string);
20
21
  }
21
22
  export declare class RequestBodyTooLargeError extends Error {
22
23
  readonly maxBytes: number;
23
24
  constructor(maxBytes: number);
24
25
  }
26
+ export declare class ResponseBodyTooLargeError extends Error {
27
+ readonly maxBytes: number;
28
+ constructor(maxBytes: number);
29
+ }
25
30
  export declare function jsonResponse(value: unknown, status?: number): Response;
26
31
  export declare function drainRequestBody(request: IncomingMessage): void;
27
32
  export declare function readBody(request: IncomingMessage, maxBytes?: number): Promise<Buffer>;
28
33
  export declare function parseUnknownRequestBody(request: IncomingMessage, maxBytes?: number): Promise<unknown>;
34
+ export declare function isJsonMediaType(value: string): boolean;
29
35
  export declare function parseRequestBody(request: IncomingMessage): Promise<Record<string, unknown>>;
30
36
  export declare function isJsonObject(value: unknown): value is Record<string, unknown>;
31
37
  export declare function queryRecord(url: URL): Record<string, string>;
32
38
  export declare function formatUrlHost(host: string): string;
39
+ export declare function isLoopbackAddress(address: string): boolean;
33
40
  export declare function isLoopbackHost(host: string): boolean;
34
- export declare function writeResponse(response: ServerResponse, fetchResponse: Response): Promise<void>;
41
+ export declare function advertisedHostForBindAddress(host: string, boundAddress: string): string;
42
+ export declare function assertLoopbackBindAddress(configuredHost: string, boundAddress: string, serverName: string): void;
43
+ export declare function writeFetchResponseHeaders(response: ServerResponse, fetchResponse: Response): void;
44
+ export declare function writeResponse(response: ServerResponse, fetchResponse: Response, maxBytes?: number): Promise<void>;
35
45
  export declare function closeServer(server: Server, graceMs?: number): Promise<void>;
36
46
  export declare function startHttpJsonServer(params: {
37
47
  handle: (request: IncomingMessage, response: ServerResponse) => Promise<HttpJsonHandlerResult>;
38
48
  handleError?: (error: unknown, request: IncomingMessage) => Response | undefined;
39
49
  host: string;
50
+ maxResponseBodyBytes?: number | undefined;
40
51
  port: number;
41
52
  serverName: string;
42
53
  }): Promise<{
@@ -47,5 +58,6 @@ export declare function startHttpJsonServer(params: {
47
58
  export declare function readString(value: unknown): string | undefined;
48
59
  export declare function readTrimmedString(value: unknown): string | undefined;
49
60
  export declare function readInteger(value: unknown): number | undefined;
61
+ export declare function constantTimeTokenEqual(providedToken: string, expectedToken: string): boolean;
50
62
  export declare function hasAdminToken(request: IncomingMessage, expectedToken: string): boolean;
51
63
  export declare function adminAuthError(): Response;
@@ -1,12 +1,33 @@
1
1
  import { createServer } from "node:http";
2
- import { timingSafeEqual } from "node:crypto";
2
+ import { createHash, timingSafeEqual } from "node:crypto";
3
+ import { BlockList, isIP } from "node:net";
3
4
  import { CrablineError } from "../core/errors.js";
4
5
  export const ADMIN_TOKEN_HEADER = "x-crabline-admin-token";
5
6
  export const DEFAULT_MAX_REQUEST_BODY_BYTES = 1024 * 1024;
7
+ export const DEFAULT_MAX_RESPONSE_BODY_BYTES = 8 * 1024 * 1024;
6
8
  export const DEFAULT_SERVER_SHUTDOWN_GRACE_MS = 250;
9
+ const DRAINING_REQUESTS = new WeakSet();
10
+ const LOOPBACK_ADDRESSES = new BlockList();
11
+ LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
12
+ LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
13
+ LOOPBACK_ADDRESSES.addSubnet("::ffff:127.0.0.0", 104, "ipv6");
14
+ const UNSPECIFIED_ADDRESSES = new BlockList();
15
+ UNSPECIFIED_ADDRESSES.addAddress("0.0.0.0", "ipv4");
16
+ UNSPECIFIED_ADDRESSES.addAddress("::", "ipv6");
17
+ const HOP_BY_HOP_RESPONSE_HEADERS = new Set([
18
+ "connection",
19
+ "keep-alive",
20
+ "proxy-authenticate",
21
+ "proxy-authorization",
22
+ "proxy-connection",
23
+ "te",
24
+ "trailer",
25
+ "transfer-encoding",
26
+ "upgrade",
27
+ ]);
7
28
  export class InvalidJsonBodyError extends Error {
8
- constructor(cause) {
9
- super("Request body is not valid JSON.", { cause });
29
+ constructor(cause, message = "Request body is not valid JSON.") {
30
+ super(message, { cause });
10
31
  this.name = "InvalidJsonBodyError";
11
32
  }
12
33
  }
@@ -18,16 +39,30 @@ export class RequestBodyTooLargeError extends Error {
18
39
  this.name = "RequestBodyTooLargeError";
19
40
  }
20
41
  }
42
+ export class ResponseBodyTooLargeError extends Error {
43
+ maxBytes;
44
+ constructor(maxBytes) {
45
+ super(`Response body exceeds the ${maxBytes} byte limit.`);
46
+ this.maxBytes = maxBytes;
47
+ this.name = "ResponseBodyTooLargeError";
48
+ }
49
+ }
21
50
  export function jsonResponse(value, status = 200) {
22
51
  return Response.json(value, { status });
23
52
  }
24
53
  export function drainRequestBody(request) {
54
+ if (request.readableEnded || DRAINING_REQUESTS.has(request)) {
55
+ return;
56
+ }
57
+ DRAINING_REQUESTS.add(request);
25
58
  const ignoreError = () => { };
26
59
  request.on("error", ignoreError);
27
- if (request.destroyed || request.readableEnded) {
60
+ if (request.destroyed) {
61
+ // IncomingMessage may emit its terminal error after destroyed becomes true.
28
62
  return;
29
63
  }
30
64
  const cleanup = () => {
65
+ DRAINING_REQUESTS.delete(request);
31
66
  request.off("close", cleanup);
32
67
  request.off("end", cleanup);
33
68
  request.off("error", ignoreError);
@@ -37,6 +72,10 @@ export function drainRequestBody(request) {
37
72
  request.resume();
38
73
  }
39
74
  export async function readBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES) {
75
+ requirePositiveSafeInteger(maxBytes, "maxBytes");
76
+ if (request.aborted) {
77
+ throw new Error("Request body stream was aborted.");
78
+ }
40
79
  const contentLengthHeader = request.headers["content-length"];
41
80
  const contentLengthValue = Array.isArray(contentLengthHeader)
42
81
  ? contentLengthHeader[0]
@@ -90,6 +129,9 @@ export async function readBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTE
90
129
  request.on("data", onData);
91
130
  request.on("end", onEnd);
92
131
  request.on("error", onError);
132
+ if (request.aborted) {
133
+ onAborted();
134
+ }
93
135
  });
94
136
  }
95
137
  export async function parseUnknownRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES) {
@@ -98,10 +140,10 @@ export async function parseUnknownRequestBody(request, maxBytes = DEFAULT_MAX_RE
98
140
  return {};
99
141
  }
100
142
  const contentType = request.headers["content-type"] ?? "";
101
- const includesJson = Array.isArray(contentType)
102
- ? contentType.some((entry) => entry.toLowerCase().includes("json"))
103
- : contentType.toLowerCase().includes("json");
104
- if (includesJson) {
143
+ const isJson = Array.isArray(contentType)
144
+ ? contentType.some(isJsonMediaType)
145
+ : isJsonMediaType(contentType);
146
+ if (isJson) {
105
147
  try {
106
148
  return JSON.parse(body.toString("utf8"));
107
149
  }
@@ -112,8 +154,17 @@ export async function parseUnknownRequestBody(request, maxBytes = DEFAULT_MAX_RE
112
154
  const params = new URLSearchParams(body.toString("utf8"));
113
155
  return Object.fromEntries(params.entries());
114
156
  }
157
+ export function isJsonMediaType(value) {
158
+ const mediaType = value.split(";", 1)[0]?.trim().toLowerCase();
159
+ return (mediaType === "application/json" ||
160
+ /^[a-z0-9!#$%&'*+.^_`|~-]+\/[a-z0-9!#$%&'*+.^_`|~-]+\+json$/u.test(mediaType ?? ""));
161
+ }
115
162
  export async function parseRequestBody(request) {
116
- return (await parseUnknownRequestBody(request, Number.MAX_SAFE_INTEGER));
163
+ const body = await parseUnknownRequestBody(request);
164
+ if (!isJsonObject(body)) {
165
+ throw new InvalidJsonBodyError(undefined, "Request body must be a JSON object.");
166
+ }
167
+ return body;
117
168
  }
118
169
  export function isJsonObject(value) {
119
170
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -124,71 +175,171 @@ export function queryRecord(url) {
124
175
  export function formatUrlHost(host) {
125
176
  return host.includes(":") ? `[${host}]` : host;
126
177
  }
127
- export function isLoopbackHost(host) {
128
- const normalized = host
178
+ function normalizeHost(host) {
179
+ return host
129
180
  .trim()
130
181
  .replace(/^\[(.*)\]$/u, "$1")
131
- .toLowerCase();
132
- return (normalized === "localhost" ||
133
- normalized.endsWith(".localhost") ||
134
- normalized === "::1" ||
135
- normalized.startsWith("::ffff:127.") ||
136
- /^127(?:\.\d{1,3}){3}$/u.test(normalized));
182
+ .toLowerCase()
183
+ .replace(/\.$/u, "");
184
+ }
185
+ export function isLoopbackAddress(address) {
186
+ const normalized = normalizeHost(address);
187
+ const family = isIP(normalized);
188
+ return family === 4
189
+ ? LOOPBACK_ADDRESSES.check(normalized, "ipv4")
190
+ : family === 6
191
+ ? LOOPBACK_ADDRESSES.check(normalized, "ipv6")
192
+ : false;
193
+ }
194
+ export function isLoopbackHost(host) {
195
+ const normalized = normalizeHost(host);
196
+ if (normalized === "localhost" || normalized.endsWith(".localhost")) {
197
+ return true;
198
+ }
199
+ return isLoopbackAddress(normalized);
200
+ }
201
+ export function advertisedHostForBindAddress(host, boundAddress) {
202
+ const normalizedBoundAddress = normalizeHost(boundAddress);
203
+ const boundFamily = isIP(normalizedBoundAddress);
204
+ if (boundFamily === 4 && UNSPECIFIED_ADDRESSES.check(normalizedBoundAddress, "ipv4")) {
205
+ return "127.0.0.1";
206
+ }
207
+ if (boundFamily === 6 && UNSPECIFIED_ADDRESSES.check(normalizedBoundAddress, "ipv6")) {
208
+ return "::1";
209
+ }
210
+ return isLoopbackHost(host) && isLoopbackAddress(boundAddress) ? boundAddress : host;
137
211
  }
138
- export async function writeResponse(response, fetchResponse) {
139
- response.statusCode = fetchResponse.status;
212
+ export function assertLoopbackBindAddress(configuredHost, boundAddress, serverName) {
213
+ if (isLoopbackHost(configuredHost) && !isLoopbackAddress(boundAddress)) {
214
+ throw new CrablineError(`${serverName} resolved a loopback hostname to non-loopback address ${boundAddress}.`, { kind: "connectivity" });
215
+ }
216
+ }
217
+ export function writeFetchResponseHeaders(response, fetchResponse) {
218
+ const preserveRepresentationLength = response.req?.method === "HEAD" || fetchResponse.status === 304;
219
+ const connectionHeaders = new Set((fetchResponse.headers.get("connection") ?? "")
220
+ .split(",")
221
+ .map((name) => name.trim().toLowerCase())
222
+ .filter(Boolean));
223
+ if (connectionHeaders.has("close")) {
224
+ response.shouldKeepAlive = false;
225
+ }
140
226
  for (const [name, value] of fetchResponse.headers) {
141
- if (name.toLowerCase() === "set-cookie") {
227
+ const normalizedName = name.toLowerCase();
228
+ if (normalizedName === "set-cookie" ||
229
+ HOP_BY_HOP_RESPONSE_HEADERS.has(normalizedName) ||
230
+ connectionHeaders.has(normalizedName) ||
231
+ (normalizedName === "content-length" && !preserveRepresentationLength)) {
142
232
  continue;
143
233
  }
144
234
  response.setHeader(name, value);
145
235
  }
146
236
  const setCookies = fetchResponse.headers.getSetCookie();
147
- if (setCookies.length > 0) {
237
+ if (setCookies.length > 0 && !connectionHeaders.has("set-cookie")) {
148
238
  response.setHeader("set-cookie", setCookies);
149
239
  }
150
- const body = Buffer.from(await fetchResponse.arrayBuffer());
151
- await new Promise((resolve, reject) => {
240
+ }
241
+ export async function writeResponse(response, fetchResponse, maxBytes = DEFAULT_MAX_RESPONSE_BODY_BYTES) {
242
+ requirePositiveSafeInteger(maxBytes, "maxResponseBodyBytes");
243
+ const reader = fetchResponse.body?.getReader();
244
+ let cancellation;
245
+ let rejectStopped;
246
+ let stoppedError;
247
+ const stopped = new Promise((_, reject) => {
248
+ rejectStopped = reject;
249
+ });
250
+ void stopped.catch(() => { });
251
+ const cancelBody = (reason) => {
252
+ if (!reader || cancellation) {
253
+ return;
254
+ }
255
+ cancellation = reader.cancel(reason).catch(() => { });
256
+ };
257
+ const stop = (error) => {
258
+ if (stoppedError) {
259
+ return;
260
+ }
261
+ stoppedError = error;
262
+ cancelBody(error);
263
+ rejectStopped(error);
264
+ };
265
+ const onClose = () => {
266
+ if (!response.writableFinished) {
267
+ stop(new Error("HTTP response closed before delivery completed."));
268
+ }
269
+ };
270
+ const onError = (error) => stop(error);
271
+ const onRequestAborted = () => stop(new Error("HTTP response request was aborted."));
272
+ response.once("close", onClose);
273
+ response.once("error", onError);
274
+ response.req?.once("aborted", onRequestAborted);
275
+ try {
152
276
  if (response.destroyed ||
153
277
  response.req?.aborted ||
154
278
  response.req?.socket.destroyed ||
155
279
  response.socket?.destroyed) {
156
- reject(new Error("HTTP response closed before delivery completed."));
280
+ throw new Error("HTTP response closed before delivery completed.");
281
+ }
282
+ if (response.req?.method === "HEAD") {
283
+ cancelBody(new Error("HEAD response body is not transferred."));
284
+ response.statusCode = fetchResponse.status;
285
+ writeFetchResponseHeaders(response, fetchResponse);
286
+ let onFinish;
287
+ const finished = new Promise((resolve) => {
288
+ onFinish = resolve;
289
+ response.once("finish", onFinish);
290
+ });
291
+ try {
292
+ response.end();
293
+ await Promise.race([finished, stopped]);
294
+ }
295
+ finally {
296
+ response.off("finish", onFinish);
297
+ }
157
298
  return;
158
299
  }
159
- const cleanup = () => {
160
- response.off("close", onClose);
161
- response.off("error", onError);
162
- response.off("finish", onFinish);
163
- };
164
- const onClose = () => {
165
- if (response.writableFinished) {
166
- cleanup();
167
- resolve();
168
- return;
300
+ const chunks = [];
301
+ let bodyLength = 0;
302
+ if (reader) {
303
+ while (true) {
304
+ const chunk = await Promise.race([reader.read(), stopped]);
305
+ if (chunk.done) {
306
+ break;
307
+ }
308
+ if (chunk.value.byteLength > 0) {
309
+ if (bodyLength + chunk.value.byteLength > maxBytes) {
310
+ throw new ResponseBodyTooLargeError(maxBytes);
311
+ }
312
+ const buffer = Buffer.from(chunk.value);
313
+ chunks.push(buffer);
314
+ bodyLength += buffer.length;
315
+ }
169
316
  }
170
- cleanup();
171
- reject(new Error("HTTP response closed before delivery completed."));
172
- };
173
- const onError = (error) => {
174
- cleanup();
175
- reject(error);
176
- };
177
- const onFinish = () => {
178
- cleanup();
179
- resolve();
180
- };
181
- response.once("close", onClose);
182
- response.once("error", onError);
183
- response.once("finish", onFinish);
317
+ }
318
+ const body = Buffer.concat(chunks, bodyLength);
319
+ response.statusCode = fetchResponse.status;
320
+ writeFetchResponseHeaders(response, fetchResponse);
321
+ let onFinish;
322
+ const finished = new Promise((resolve) => {
323
+ onFinish = resolve;
324
+ response.once("finish", onFinish);
325
+ });
184
326
  try {
185
327
  response.end(body);
328
+ await Promise.race([finished, stopped]);
186
329
  }
187
- catch (error) {
188
- cleanup();
189
- reject(error);
330
+ finally {
331
+ response.off("finish", onFinish);
190
332
  }
191
- });
333
+ }
334
+ catch (error) {
335
+ cancelBody(error instanceof Error ? error : new Error(String(error)));
336
+ throw error;
337
+ }
338
+ finally {
339
+ response.off("close", onClose);
340
+ response.off("error", onError);
341
+ response.req?.off("aborted", onRequestAborted);
342
+ }
192
343
  }
193
344
  export function closeServer(server, graceMs = DEFAULT_SERVER_SHUTDOWN_GRACE_MS) {
194
345
  return new Promise((resolve, reject) => {
@@ -211,12 +362,14 @@ export function closeServer(server, graceMs = DEFAULT_SERVER_SHUTDOWN_GRACE_MS)
211
362
  });
212
363
  }
213
364
  export async function startHttpJsonServer(params) {
365
+ const maxResponseBodyBytes = requirePositiveSafeInteger(params.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES, "maxResponseBodyBytes");
214
366
  const handleRequest = async (request, response) => {
215
367
  try {
216
368
  const result = await params.handle(request, response);
369
+ drainRequestBody(request);
217
370
  const handled = result instanceof Response ? { response: result } : result;
218
371
  try {
219
- await writeResponse(response, handled.response);
372
+ await writeResponse(response, handled.response, maxResponseBodyBytes);
220
373
  }
221
374
  catch (error) {
222
375
  await handled.onWriteFailure?.();
@@ -230,6 +383,7 @@ export async function startHttpJsonServer(params) {
230
383
  }
231
384
  }
232
385
  catch (error) {
386
+ drainRequestBody(request);
233
387
  let handled;
234
388
  try {
235
389
  handled = params.handleError?.(error, request);
@@ -242,7 +396,7 @@ export async function startHttpJsonServer(params) {
242
396
  jsonResponse({
243
397
  error: "internal server error",
244
398
  ok: false,
245
- }, 500));
399
+ }, 500), maxResponseBodyBytes);
246
400
  }
247
401
  catch {
248
402
  response.destroy();
@@ -265,14 +419,28 @@ export async function startHttpJsonServer(params) {
265
419
  kind: "connectivity",
266
420
  });
267
421
  }
422
+ try {
423
+ assertLoopbackBindAddress(params.host, address.address, params.serverName);
424
+ }
425
+ catch (error) {
426
+ await closeServer(server);
427
+ throw error;
428
+ }
429
+ const advertisedHost = advertisedHostForBindAddress(params.host, address.address);
268
430
  return {
269
- baseUrl: `http://${formatUrlHost(params.host)}:${address.port}`,
431
+ baseUrl: `http://${formatUrlHost(advertisedHost)}:${address.port}`,
270
432
  async close() {
271
433
  await closeServer(server);
272
434
  },
273
435
  server,
274
436
  };
275
437
  }
438
+ function requirePositiveSafeInteger(value, name) {
439
+ if (!Number.isSafeInteger(value) || value <= 0) {
440
+ throw new RangeError(`${name} must be a positive safe integer.`);
441
+ }
442
+ return value;
443
+ }
276
444
  export function readString(value) {
277
445
  if (typeof value === "string" && value.length > 0) {
278
446
  return value;
@@ -304,6 +472,11 @@ function readBearerToken(authorization) {
304
472
  }
305
473
  return trimmed.slice(7);
306
474
  }
475
+ export function constantTimeTokenEqual(providedToken, expectedToken) {
476
+ const provided = createHash("sha256").update(providedToken).digest();
477
+ const expected = createHash("sha256").update(expectedToken).digest();
478
+ return timingSafeEqual(provided, expected);
479
+ }
307
480
  export function hasAdminToken(request, expectedToken) {
308
481
  const header = request.headers[ADMIN_TOKEN_HEADER];
309
482
  const directToken = Array.isArray(header) ? header[0] : header;
@@ -311,9 +484,7 @@ export function hasAdminToken(request, expectedToken) {
311
484
  if (!providedToken) {
312
485
  return false;
313
486
  }
314
- const provided = Buffer.from(providedToken);
315
- const expected = Buffer.from(expectedToken);
316
- return provided.length === expected.length && timingSafeEqual(provided, expected);
487
+ return constantTimeTokenEqual(providedToken, expectedToken);
317
488
  }
318
489
  export function adminAuthError() {
319
490
  return new Response("unauthorized", {
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/servers/http.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { timingSafeEqual } from \"node:crypto\";\nimport { CrablineError } from \"../core/errors.js\";\n\nexport type ServerRequestEvent = {\n at: string;\n body?: unknown;\n method: string;\n path: string;\n query: Record<string, string>;\n type: \"admin\" | \"api\";\n};\n\nexport type HttpJsonHandlerResult =\n | Response\n | {\n onWriteFailure?(): Promise<void> | void;\n onWriteSuccess?(): Promise<void> | void;\n response: Response;\n };\n\nexport const ADMIN_TOKEN_HEADER = \"x-crabline-admin-token\";\nexport const DEFAULT_MAX_REQUEST_BODY_BYTES = 1024 * 1024;\nexport const DEFAULT_SERVER_SHUTDOWN_GRACE_MS = 250;\n\nexport class InvalidJsonBodyError extends Error {\n constructor(cause: unknown) {\n super(\"Request body is not valid JSON.\", { cause });\n this.name = \"InvalidJsonBodyError\";\n }\n}\n\nexport class RequestBodyTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`Request body exceeds the ${maxBytes} byte limit.`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\nexport function jsonResponse(value: unknown, status = 200): Response {\n return Response.json(value, { status });\n}\n\nexport function drainRequestBody(request: IncomingMessage): void {\n const ignoreError = () => {};\n request.on(\"error\", ignoreError);\n if (request.destroyed || request.readableEnded) {\n return;\n }\n const cleanup = () => {\n request.off(\"close\", cleanup);\n request.off(\"end\", cleanup);\n request.off(\"error\", ignoreError);\n };\n\n request.once(\"close\", cleanup);\n request.once(\"end\", cleanup);\n request.resume();\n}\n\nexport async function readBody(\n request: IncomingMessage,\n maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES,\n): Promise<Buffer> {\n const contentLengthHeader = request.headers[\"content-length\"];\n const contentLengthValue = Array.isArray(contentLengthHeader)\n ? contentLengthHeader[0]\n : contentLengthHeader;\n if (contentLengthValue && /^\\d+$/u.test(contentLengthValue)) {\n const contentLength = Number(contentLengthValue);\n if (!Number.isSafeInteger(contentLength) || contentLength > maxBytes) {\n drainRequestBody(request);\n throw new RequestBodyTooLargeError(maxBytes);\n }\n }\n\n return await new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let length = 0;\n let settled = false;\n\n const cleanup = () => {\n request.off(\"aborted\", onAborted);\n request.off(\"data\", onData);\n request.off(\"end\", onEnd);\n request.off(\"error\", onError);\n };\n const fail = (error: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n drainRequestBody(request);\n cleanup();\n reject(error);\n };\n const onAborted = () => fail(new Error(\"Request body stream was aborted.\"));\n const onData = (chunk: Buffer | string) => {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n length += buffer.length;\n if (length > maxBytes) {\n fail(new RequestBodyTooLargeError(maxBytes));\n return;\n }\n chunks.push(buffer);\n };\n const onEnd = () => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(Buffer.concat(chunks, length));\n };\n const onError = (error: Error) => fail(error);\n\n request.on(\"aborted\", onAborted);\n request.on(\"data\", onData);\n request.on(\"end\", onEnd);\n request.on(\"error\", onError);\n });\n}\n\nexport async function parseUnknownRequestBody(\n request: IncomingMessage,\n maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES,\n): Promise<unknown> {\n const body = await readBody(request, maxBytes);\n if (body.length === 0) {\n return {};\n }\n const contentType = request.headers[\"content-type\"] ?? \"\";\n const includesJson = Array.isArray(contentType)\n ? contentType.some((entry) => entry.toLowerCase().includes(\"json\"))\n : contentType.toLowerCase().includes(\"json\");\n if (includesJson) {\n try {\n return JSON.parse(body.toString(\"utf8\")) as unknown;\n } catch (error) {\n throw new InvalidJsonBodyError(error);\n }\n }\n const params = new URLSearchParams(body.toString(\"utf8\"));\n return Object.fromEntries(params.entries());\n}\n\nexport async function parseRequestBody(request: IncomingMessage): Promise<Record<string, unknown>> {\n return (await parseUnknownRequestBody(request, Number.MAX_SAFE_INTEGER)) as Record<\n string,\n unknown\n >;\n}\n\nexport function isJsonObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function queryRecord(url: URL): Record<string, string> {\n return Object.fromEntries(url.searchParams.entries());\n}\n\nexport function formatUrlHost(host: string): string {\n return host.includes(\":\") ? `[${host}]` : host;\n}\n\nexport function isLoopbackHost(host: string): boolean {\n const normalized = host\n .trim()\n .replace(/^\\[(.*)\\]$/u, \"$1\")\n .toLowerCase();\n return (\n normalized === \"localhost\" ||\n normalized.endsWith(\".localhost\") ||\n normalized === \"::1\" ||\n normalized.startsWith(\"::ffff:127.\") ||\n /^127(?:\\.\\d{1,3}){3}$/u.test(normalized)\n );\n}\n\nexport async function writeResponse(\n response: ServerResponse,\n fetchResponse: Response,\n): Promise<void> {\n response.statusCode = fetchResponse.status;\n for (const [name, value] of fetchResponse.headers) {\n if (name.toLowerCase() === \"set-cookie\") {\n continue;\n }\n response.setHeader(name, value);\n }\n const setCookies = fetchResponse.headers.getSetCookie();\n if (setCookies.length > 0) {\n response.setHeader(\"set-cookie\", setCookies);\n }\n const body = Buffer.from(await fetchResponse.arrayBuffer());\n await new Promise<void>((resolve, reject) => {\n if (\n response.destroyed ||\n response.req?.aborted ||\n response.req?.socket.destroyed ||\n response.socket?.destroyed\n ) {\n reject(new Error(\"HTTP response closed before delivery completed.\"));\n return;\n }\n const cleanup = () => {\n response.off(\"close\", onClose);\n response.off(\"error\", onError);\n response.off(\"finish\", onFinish);\n };\n const onClose = () => {\n if (response.writableFinished) {\n cleanup();\n resolve();\n return;\n }\n cleanup();\n reject(new Error(\"HTTP response closed before delivery completed.\"));\n };\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onFinish = () => {\n cleanup();\n resolve();\n };\n response.once(\"close\", onClose);\n response.once(\"error\", onError);\n response.once(\"finish\", onFinish);\n try {\n response.end(body);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n\nexport function closeServer(\n server: Server,\n graceMs = DEFAULT_SERVER_SHUTDOWN_GRACE_MS,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const closeIdleInterval = setInterval(() => server.closeIdleConnections(), 25);\n closeIdleInterval.unref();\n const forceCloseTimer = setTimeout(() => {\n server.closeAllConnections();\n }, graceMs);\n forceCloseTimer.unref();\n server.close((error) => {\n clearInterval(closeIdleInterval);\n clearTimeout(forceCloseTimer);\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections();\n });\n}\n\nexport async function startHttpJsonServer(params: {\n handle: (request: IncomingMessage, response: ServerResponse) => Promise<HttpJsonHandlerResult>;\n handleError?: (error: unknown, request: IncomingMessage) => Response | undefined;\n host: string;\n port: number;\n serverName: string;\n}): Promise<{ baseUrl: string; close(): Promise<void>; server: Server }> {\n const handleRequest = async (request: IncomingMessage, response: ServerResponse) => {\n try {\n const result = await params.handle(request, response);\n const handled = result instanceof Response ? { response: result } : result;\n try {\n await writeResponse(response, handled.response);\n } catch (error) {\n await handled.onWriteFailure?.();\n throw error;\n }\n try {\n await handled.onWriteSuccess?.();\n } catch {\n // Delivery is already committed; lifecycle failures must not trigger another response.\n }\n } catch (error) {\n let handled: Response | undefined;\n try {\n handled = params.handleError?.(error, request);\n } catch {\n handled = undefined;\n }\n try {\n await writeResponse(\n response,\n handled ??\n jsonResponse(\n {\n error: \"internal server error\",\n ok: false,\n },\n 500,\n ),\n );\n } catch {\n response.destroy();\n }\n }\n };\n const server = createServer((request, response) => {\n void handleRequest(request, response);\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(params.port, params.host, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new CrablineError(`Unable to resolve ${params.serverName} local server address.`, {\n kind: \"connectivity\",\n });\n }\n return {\n baseUrl: `http://${formatUrlHost(params.host)}:${address.port}`,\n async close() {\n await closeServer(server);\n },\n server,\n };\n}\n\nexport function readString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n return value.toString();\n }\n return undefined;\n}\n\nexport function readTrimmedString(value: unknown): string | undefined {\n const stringValue = readString(value)?.trim();\n return stringValue ? stringValue : undefined;\n}\n\nexport function readInteger(value: unknown): number | undefined {\n const stringValue = readTrimmedString(value);\n if (!stringValue || !/^-?\\d+$/u.test(stringValue)) {\n return undefined;\n }\n const parsed = Number(stringValue);\n return Number.isSafeInteger(parsed) ? parsed : undefined;\n}\n\nfunction readBearerToken(authorization: string | undefined): string | undefined {\n if (!authorization) {\n return undefined;\n }\n const trimmed = authorization.trimStart();\n if (trimmed.slice(0, 7).toLowerCase() !== \"bearer \") {\n return undefined;\n }\n return trimmed.slice(7);\n}\n\nexport function hasAdminToken(request: IncomingMessage, expectedToken: string): boolean {\n const header = request.headers[ADMIN_TOKEN_HEADER];\n const directToken = Array.isArray(header) ? header[0] : header;\n const providedToken = directToken ?? readBearerToken(request.headers.authorization);\n if (!providedToken) {\n return false;\n }\n\n const provided = Buffer.from(providedToken);\n const expected = Buffer.from(expectedToken);\n return provided.length === expected.length && timingSafeEqual(provided, expected);\n}\n\nexport function adminAuthError(): Response {\n return new Response(\"unauthorized\", {\n headers: { \"www-authenticate\": \"Bearer\" },\n status: 401,\n });\n}\n"]}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/servers/http.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { BlockList, isIP } from \"node:net\";\nimport { CrablineError } from \"../core/errors.js\";\n\nexport type ServerRequestEvent = {\n at: string;\n body?: unknown;\n method: string;\n path: string;\n query: Record<string, string>;\n type: \"admin\" | \"api\";\n};\n\nexport type HttpJsonHandlerResult =\n | Response\n | {\n onWriteFailure?(): Promise<void> | void;\n onWriteSuccess?(): Promise<void> | void;\n response: Response;\n };\n\nexport const ADMIN_TOKEN_HEADER = \"x-crabline-admin-token\";\nexport const DEFAULT_MAX_REQUEST_BODY_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_RESPONSE_BODY_BYTES = 8 * 1024 * 1024;\nexport const DEFAULT_SERVER_SHUTDOWN_GRACE_MS = 250;\nconst DRAINING_REQUESTS = new WeakSet<IncomingMessage>();\nconst LOOPBACK_ADDRESSES = new BlockList();\nLOOPBACK_ADDRESSES.addSubnet(\"127.0.0.0\", 8, \"ipv4\");\nLOOPBACK_ADDRESSES.addAddress(\"::1\", \"ipv6\");\nLOOPBACK_ADDRESSES.addSubnet(\"::ffff:127.0.0.0\", 104, \"ipv6\");\nconst UNSPECIFIED_ADDRESSES = new BlockList();\nUNSPECIFIED_ADDRESSES.addAddress(\"0.0.0.0\", \"ipv4\");\nUNSPECIFIED_ADDRESSES.addAddress(\"::\", \"ipv6\");\nconst HOP_BY_HOP_RESPONSE_HEADERS = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"proxy-connection\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\nexport class InvalidJsonBodyError extends Error {\n constructor(cause: unknown, message = \"Request body is not valid JSON.\") {\n super(message, { cause });\n this.name = \"InvalidJsonBodyError\";\n }\n}\n\nexport class RequestBodyTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`Request body exceeds the ${maxBytes} byte limit.`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\nexport class ResponseBodyTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`Response body exceeds the ${maxBytes} byte limit.`);\n this.name = \"ResponseBodyTooLargeError\";\n }\n}\n\nexport function jsonResponse(value: unknown, status = 200): Response {\n return Response.json(value, { status });\n}\n\nexport function drainRequestBody(request: IncomingMessage): void {\n if (request.readableEnded || DRAINING_REQUESTS.has(request)) {\n return;\n }\n DRAINING_REQUESTS.add(request);\n const ignoreError = () => {};\n request.on(\"error\", ignoreError);\n if (request.destroyed) {\n // IncomingMessage may emit its terminal error after destroyed becomes true.\n return;\n }\n const cleanup = () => {\n DRAINING_REQUESTS.delete(request);\n request.off(\"close\", cleanup);\n request.off(\"end\", cleanup);\n request.off(\"error\", ignoreError);\n };\n\n request.once(\"close\", cleanup);\n request.once(\"end\", cleanup);\n request.resume();\n}\n\nexport async function readBody(\n request: IncomingMessage,\n maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES,\n): Promise<Buffer> {\n requirePositiveSafeInteger(maxBytes, \"maxBytes\");\n if (request.aborted) {\n throw new Error(\"Request body stream was aborted.\");\n }\n const contentLengthHeader = request.headers[\"content-length\"];\n const contentLengthValue = Array.isArray(contentLengthHeader)\n ? contentLengthHeader[0]\n : contentLengthHeader;\n if (contentLengthValue && /^\\d+$/u.test(contentLengthValue)) {\n const contentLength = Number(contentLengthValue);\n if (!Number.isSafeInteger(contentLength) || contentLength > maxBytes) {\n drainRequestBody(request);\n throw new RequestBodyTooLargeError(maxBytes);\n }\n }\n\n return await new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let length = 0;\n let settled = false;\n\n const cleanup = () => {\n request.off(\"aborted\", onAborted);\n request.off(\"data\", onData);\n request.off(\"end\", onEnd);\n request.off(\"error\", onError);\n };\n const fail = (error: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n drainRequestBody(request);\n cleanup();\n reject(error);\n };\n const onAborted = () => fail(new Error(\"Request body stream was aborted.\"));\n const onData = (chunk: Buffer | string) => {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n length += buffer.length;\n if (length > maxBytes) {\n fail(new RequestBodyTooLargeError(maxBytes));\n return;\n }\n chunks.push(buffer);\n };\n const onEnd = () => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(Buffer.concat(chunks, length));\n };\n const onError = (error: Error) => fail(error);\n\n request.on(\"aborted\", onAborted);\n request.on(\"data\", onData);\n request.on(\"end\", onEnd);\n request.on(\"error\", onError);\n if (request.aborted) {\n onAborted();\n }\n });\n}\n\nexport async function parseUnknownRequestBody(\n request: IncomingMessage,\n maxBytes = DEFAULT_MAX_REQUEST_BODY_BYTES,\n): Promise<unknown> {\n const body = await readBody(request, maxBytes);\n if (body.length === 0) {\n return {};\n }\n const contentType = request.headers[\"content-type\"] ?? \"\";\n const isJson = Array.isArray(contentType)\n ? contentType.some(isJsonMediaType)\n : isJsonMediaType(contentType);\n if (isJson) {\n try {\n return JSON.parse(body.toString(\"utf8\")) as unknown;\n } catch (error) {\n throw new InvalidJsonBodyError(error);\n }\n }\n const params = new URLSearchParams(body.toString(\"utf8\"));\n return Object.fromEntries(params.entries());\n}\n\nexport function isJsonMediaType(value: string): boolean {\n const mediaType = value.split(\";\", 1)[0]?.trim().toLowerCase();\n return (\n mediaType === \"application/json\" ||\n /^[a-z0-9!#$%&'*+.^_`|~-]+\\/[a-z0-9!#$%&'*+.^_`|~-]+\\+json$/u.test(mediaType ?? \"\")\n );\n}\n\nexport async function parseRequestBody(request: IncomingMessage): Promise<Record<string, unknown>> {\n const body = await parseUnknownRequestBody(request);\n if (!isJsonObject(body)) {\n throw new InvalidJsonBodyError(undefined, \"Request body must be a JSON object.\");\n }\n return body;\n}\n\nexport function isJsonObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function queryRecord(url: URL): Record<string, string> {\n return Object.fromEntries(url.searchParams.entries());\n}\n\nexport function formatUrlHost(host: string): string {\n return host.includes(\":\") ? `[${host}]` : host;\n}\n\nfunction normalizeHost(host: string): string {\n return host\n .trim()\n .replace(/^\\[(.*)\\]$/u, \"$1\")\n .toLowerCase()\n .replace(/\\.$/u, \"\");\n}\n\nexport function isLoopbackAddress(address: string): boolean {\n const normalized = normalizeHost(address);\n const family = isIP(normalized);\n return family === 4\n ? LOOPBACK_ADDRESSES.check(normalized, \"ipv4\")\n : family === 6\n ? LOOPBACK_ADDRESSES.check(normalized, \"ipv6\")\n : false;\n}\n\nexport function isLoopbackHost(host: string): boolean {\n const normalized = normalizeHost(host);\n if (normalized === \"localhost\" || normalized.endsWith(\".localhost\")) {\n return true;\n }\n return isLoopbackAddress(normalized);\n}\n\nexport function advertisedHostForBindAddress(host: string, boundAddress: string): string {\n const normalizedBoundAddress = normalizeHost(boundAddress);\n const boundFamily = isIP(normalizedBoundAddress);\n if (boundFamily === 4 && UNSPECIFIED_ADDRESSES.check(normalizedBoundAddress, \"ipv4\")) {\n return \"127.0.0.1\";\n }\n if (boundFamily === 6 && UNSPECIFIED_ADDRESSES.check(normalizedBoundAddress, \"ipv6\")) {\n return \"::1\";\n }\n return isLoopbackHost(host) && isLoopbackAddress(boundAddress) ? boundAddress : host;\n}\n\nexport function assertLoopbackBindAddress(\n configuredHost: string,\n boundAddress: string,\n serverName: string,\n): void {\n if (isLoopbackHost(configuredHost) && !isLoopbackAddress(boundAddress)) {\n throw new CrablineError(\n `${serverName} resolved a loopback hostname to non-loopback address ${boundAddress}.`,\n { kind: \"connectivity\" },\n );\n }\n}\n\nexport function writeFetchResponseHeaders(response: ServerResponse, fetchResponse: Response): void {\n const preserveRepresentationLength =\n response.req?.method === \"HEAD\" || fetchResponse.status === 304;\n const connectionHeaders = new Set(\n (fetchResponse.headers.get(\"connection\") ?? \"\")\n .split(\",\")\n .map((name) => name.trim().toLowerCase())\n .filter(Boolean),\n );\n if (connectionHeaders.has(\"close\")) {\n response.shouldKeepAlive = false;\n }\n\n for (const [name, value] of fetchResponse.headers) {\n const normalizedName = name.toLowerCase();\n if (\n normalizedName === \"set-cookie\" ||\n HOP_BY_HOP_RESPONSE_HEADERS.has(normalizedName) ||\n connectionHeaders.has(normalizedName) ||\n (normalizedName === \"content-length\" && !preserveRepresentationLength)\n ) {\n continue;\n }\n response.setHeader(name, value);\n }\n\n const setCookies = fetchResponse.headers.getSetCookie();\n if (setCookies.length > 0 && !connectionHeaders.has(\"set-cookie\")) {\n response.setHeader(\"set-cookie\", setCookies);\n }\n}\n\nexport async function writeResponse(\n response: ServerResponse,\n fetchResponse: Response,\n maxBytes = DEFAULT_MAX_RESPONSE_BODY_BYTES,\n): Promise<void> {\n requirePositiveSafeInteger(maxBytes, \"maxResponseBodyBytes\");\n const reader = fetchResponse.body?.getReader();\n let cancellation: Promise<void> | undefined;\n let rejectStopped!: (error: Error) => void;\n let stoppedError: Error | undefined;\n const stopped = new Promise<never>((_, reject) => {\n rejectStopped = reject;\n });\n void stopped.catch(() => {});\n\n const cancelBody = (reason: Error) => {\n if (!reader || cancellation) {\n return;\n }\n cancellation = reader.cancel(reason).catch(() => {});\n };\n const stop = (error: Error) => {\n if (stoppedError) {\n return;\n }\n stoppedError = error;\n cancelBody(error);\n rejectStopped(error);\n };\n const onClose = () => {\n if (!response.writableFinished) {\n stop(new Error(\"HTTP response closed before delivery completed.\"));\n }\n };\n const onError = (error: Error) => stop(error);\n const onRequestAborted = () => stop(new Error(\"HTTP response request was aborted.\"));\n response.once(\"close\", onClose);\n response.once(\"error\", onError);\n response.req?.once(\"aborted\", onRequestAborted);\n\n try {\n if (\n response.destroyed ||\n response.req?.aborted ||\n response.req?.socket.destroyed ||\n response.socket?.destroyed\n ) {\n throw new Error(\"HTTP response closed before delivery completed.\");\n }\n\n if (response.req?.method === \"HEAD\") {\n cancelBody(new Error(\"HEAD response body is not transferred.\"));\n response.statusCode = fetchResponse.status;\n writeFetchResponseHeaders(response, fetchResponse);\n let onFinish!: () => void;\n const finished = new Promise<void>((resolve) => {\n onFinish = resolve;\n response.once(\"finish\", onFinish);\n });\n try {\n response.end();\n await Promise.race([finished, stopped]);\n } finally {\n response.off(\"finish\", onFinish);\n }\n return;\n }\n\n const chunks: Buffer[] = [];\n let bodyLength = 0;\n if (reader) {\n while (true) {\n const chunk = await Promise.race([reader.read(), stopped]);\n if (chunk.done) {\n break;\n }\n if (chunk.value.byteLength > 0) {\n if (bodyLength + chunk.value.byteLength > maxBytes) {\n throw new ResponseBodyTooLargeError(maxBytes);\n }\n const buffer = Buffer.from(chunk.value);\n chunks.push(buffer);\n bodyLength += buffer.length;\n }\n }\n }\n\n const body = Buffer.concat(chunks, bodyLength);\n response.statusCode = fetchResponse.status;\n writeFetchResponseHeaders(response, fetchResponse);\n let onFinish!: () => void;\n const finished = new Promise<void>((resolve) => {\n onFinish = resolve;\n response.once(\"finish\", onFinish);\n });\n try {\n response.end(body);\n await Promise.race([finished, stopped]);\n } finally {\n response.off(\"finish\", onFinish);\n }\n } catch (error) {\n cancelBody(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n response.off(\"close\", onClose);\n response.off(\"error\", onError);\n response.req?.off(\"aborted\", onRequestAborted);\n }\n}\n\nexport function closeServer(\n server: Server,\n graceMs = DEFAULT_SERVER_SHUTDOWN_GRACE_MS,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const closeIdleInterval = setInterval(() => server.closeIdleConnections(), 25);\n closeIdleInterval.unref();\n const forceCloseTimer = setTimeout(() => {\n server.closeAllConnections();\n }, graceMs);\n forceCloseTimer.unref();\n server.close((error) => {\n clearInterval(closeIdleInterval);\n clearTimeout(forceCloseTimer);\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections();\n });\n}\n\nexport async function startHttpJsonServer(params: {\n handle: (request: IncomingMessage, response: ServerResponse) => Promise<HttpJsonHandlerResult>;\n handleError?: (error: unknown, request: IncomingMessage) => Response | undefined;\n host: string;\n maxResponseBodyBytes?: number | undefined;\n port: number;\n serverName: string;\n}): Promise<{ baseUrl: string; close(): Promise<void>; server: Server }> {\n const maxResponseBodyBytes = requirePositiveSafeInteger(\n params.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,\n \"maxResponseBodyBytes\",\n );\n const handleRequest = async (request: IncomingMessage, response: ServerResponse) => {\n try {\n const result = await params.handle(request, response);\n drainRequestBody(request);\n const handled = result instanceof Response ? { response: result } : result;\n try {\n await writeResponse(response, handled.response, maxResponseBodyBytes);\n } catch (error) {\n await handled.onWriteFailure?.();\n throw error;\n }\n try {\n await handled.onWriteSuccess?.();\n } catch {\n // Delivery is already committed; lifecycle failures must not trigger another response.\n }\n } catch (error) {\n drainRequestBody(request);\n let handled: Response | undefined;\n try {\n handled = params.handleError?.(error, request);\n } catch {\n handled = undefined;\n }\n try {\n await writeResponse(\n response,\n handled ??\n jsonResponse(\n {\n error: \"internal server error\",\n ok: false,\n },\n 500,\n ),\n maxResponseBodyBytes,\n );\n } catch {\n response.destroy();\n }\n }\n };\n const server = createServer((request, response) => {\n void handleRequest(request, response);\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(params.port, params.host, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n throw new CrablineError(`Unable to resolve ${params.serverName} local server address.`, {\n kind: \"connectivity\",\n });\n }\n try {\n assertLoopbackBindAddress(params.host, address.address, params.serverName);\n } catch (error) {\n await closeServer(server);\n throw error;\n }\n const advertisedHost = advertisedHostForBindAddress(params.host, address.address);\n return {\n baseUrl: `http://${formatUrlHost(advertisedHost)}:${address.port}`,\n async close() {\n await closeServer(server);\n },\n server,\n };\n}\n\nfunction requirePositiveSafeInteger(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new RangeError(`${name} must be a positive safe integer.`);\n }\n return value;\n}\n\nexport function readString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n return value.toString();\n }\n return undefined;\n}\n\nexport function readTrimmedString(value: unknown): string | undefined {\n const stringValue = readString(value)?.trim();\n return stringValue ? stringValue : undefined;\n}\n\nexport function readInteger(value: unknown): number | undefined {\n const stringValue = readTrimmedString(value);\n if (!stringValue || !/^-?\\d+$/u.test(stringValue)) {\n return undefined;\n }\n const parsed = Number(stringValue);\n return Number.isSafeInteger(parsed) ? parsed : undefined;\n}\n\nfunction readBearerToken(authorization: string | undefined): string | undefined {\n if (!authorization) {\n return undefined;\n }\n const trimmed = authorization.trimStart();\n if (trimmed.slice(0, 7).toLowerCase() !== \"bearer \") {\n return undefined;\n }\n return trimmed.slice(7);\n}\n\nexport function constantTimeTokenEqual(providedToken: string, expectedToken: string): boolean {\n const provided = createHash(\"sha256\").update(providedToken).digest();\n const expected = createHash(\"sha256\").update(expectedToken).digest();\n return timingSafeEqual(provided, expected);\n}\n\nexport function hasAdminToken(request: IncomingMessage, expectedToken: string): boolean {\n const header = request.headers[ADMIN_TOKEN_HEADER];\n const directToken = Array.isArray(header) ? header[0] : header;\n const providedToken = directToken ?? readBearerToken(request.headers.authorization);\n if (!providedToken) {\n return false;\n }\n\n return constantTimeTokenEqual(providedToken, expectedToken);\n}\n\nexport function adminAuthError(): Response {\n return new Response(\"unauthorized\", {\n headers: { \"www-authenticate\": \"Bearer\" },\n status: 401,\n });\n}\n"]}
@@ -29,6 +29,9 @@ export type StartMatrixServerParams = {
29
29
  botUserId?: string | undefined;
30
30
  deviceId?: string | undefined;
31
31
  host?: string | undefined;
32
+ maxCommittedRooms?: number | undefined;
33
+ maxCommittedUsers?: number | undefined;
34
+ maxSyncResponseBytes?: number | undefined;
32
35
  onEvent?: ServerEventObserver | undefined;
33
36
  port?: number | undefined;
34
37
  recorderPath?: string | undefined;