@interopio/gateway-server 0.7.0-beta → 0.8.0-beta

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/dist/index.js CHANGED
@@ -9,11 +9,10 @@ var server_exports = {};
9
9
  __export(server_exports, {
10
10
  Factory: () => Factory
11
11
  });
12
- import * as ws from "ws";
13
12
  import http2 from "node:http";
14
13
  import https from "node:https";
15
14
  import { readFileSync } from "node:fs";
16
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
15
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
17
16
  import { IOGateway as IOGateway6 } from "@interopio/gateway";
18
17
 
19
18
  // src/logger.ts
@@ -21,13 +20,41 @@ import { IOGateway } from "@interopio/gateway";
21
20
  function getLogger(name) {
22
21
  return IOGateway.Logging.getLogger(`gateway.server.${name}`);
23
22
  }
23
+ function regexAwareReplacer(_key, value) {
24
+ return value instanceof RegExp ? value.toString() : value;
25
+ }
24
26
 
25
27
  // src/gateway/ws/core.ts
26
28
  import { IOGateway as IOGateway2 } from "@interopio/gateway";
29
+ import { AsyncLocalStorage } from "node:async_hooks";
27
30
  var GatewayEncoders = IOGateway2.Encoding;
28
31
  var log = getLogger("ws");
29
32
  var codec = GatewayEncoders.json();
30
- function initClient(socket, authenticationPromise, key, host) {
33
+ function principalName(authentication) {
34
+ let name;
35
+ if (authentication.authenticated) {
36
+ name = authentication.name;
37
+ if (name === void 0) {
38
+ if (authentication["principal"] !== void 0) {
39
+ const principal = authentication["principal"];
40
+ if (typeof principal === "object") {
41
+ name = principal.name;
42
+ }
43
+ if (name === void 0) {
44
+ if (principal === void 0) {
45
+ name = "";
46
+ } else {
47
+ name = String(principal);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ return name;
54
+ }
55
+ function initClient(socket, authenticationPromise, remoteAddress) {
56
+ const key = `${remoteAddress?.address}:${remoteAddress?.port}`;
57
+ const host = remoteAddress?.address ?? "<unknown>";
31
58
  const opts = {
32
59
  key,
33
60
  host,
@@ -35,7 +62,7 @@ function initClient(socket, authenticationPromise, key, host) {
35
62
  onAuthenticate: async () => {
36
63
  const authentication = await authenticationPromise();
37
64
  if (authentication?.authenticated) {
38
- return { type: "success", user: authentication.name };
65
+ return { type: "success", user: principalName(authentication) };
39
66
  }
40
67
  throw new Error(`no valid client authentication ${key}`);
41
68
  },
@@ -68,30 +95,34 @@ function initClient(socket, authenticationPromise, key, host) {
68
95
  log.warn(`${key} failed to create client`, err);
69
96
  }
70
97
  }
71
- async function create(server) {
72
- log.info(`starting gateway on ${server.endpoint}`);
73
- await this.start({ endpoint: server.endpoint });
74
- return async (socket, handshake) => {
75
- const key = handshake.logPrefix;
76
- const host = handshake.remoteAddress;
77
- log.info(`${key} connected on gw from ${host}`);
78
- const client = initClient.call(this, socket, handshake.principal, key, host);
98
+ async function create(environment) {
99
+ log.info(`starting gateway on ${environment.endpoint}`);
100
+ await this.start(environment);
101
+ return async ({ socket, handshake }) => {
102
+ const { logPrefix, remoteAddress, principal: principalPromise } = handshake;
103
+ log.info(`${logPrefix}connected on gw using ${remoteAddress?.address}`);
104
+ const client = initClient.call(this, socket, principalPromise, remoteAddress);
79
105
  if (!client) {
80
- log.error(`${key} gw client init failed`);
106
+ log.error(`${logPrefix}gw client init failed`);
81
107
  socket.terminate();
82
108
  return;
83
109
  }
84
110
  socket.on("error", (err) => {
85
- log.error(`${key} websocket error: ${err}`, err);
111
+ log.error(`${logPrefix}websocket error: ${err}`, err);
86
112
  });
113
+ const contextFn = environment.storage !== void 0 ? AsyncLocalStorage.snapshot() : void 0;
87
114
  socket.on("message", (data, _isBinary) => {
88
115
  if (Array.isArray(data)) {
89
116
  data = Buffer.concat(data);
90
117
  }
91
- client.send(data);
118
+ if (contextFn !== void 0) {
119
+ contextFn(() => client.send(data));
120
+ } else {
121
+ client.send(data);
122
+ }
92
123
  });
93
124
  socket.on("close", (code) => {
94
- log.info(`${key} disconnected from gw. code: ${code}`);
125
+ log.info(`${logPrefix}disconnected from gw. code: ${code}`);
95
126
  client.close();
96
127
  });
97
128
  };
@@ -110,61 +141,50 @@ function compose(...middleware) {
110
141
  }
111
142
  }
112
143
  return async function(ctx, next) {
113
- const dispatch = async (i) => {
144
+ const dispatch = async (i, dispatchedCtx) => {
114
145
  const fn = i === fns.length ? next : fns[i];
115
146
  if (fn === void 0) {
116
147
  return;
117
148
  }
118
149
  let nextCalled = false;
119
150
  let nextResolved = false;
120
- const nextFn = async () => {
151
+ const nextFn = async (nextCtx) => {
121
152
  if (nextCalled) {
122
153
  throw new Error("next() called multiple times");
123
154
  }
124
155
  nextCalled = true;
125
156
  try {
126
- return await dispatch(i + 1);
157
+ return await dispatch(i + 1, nextCtx ?? dispatchedCtx);
127
158
  } finally {
128
159
  nextResolved = true;
129
160
  }
130
161
  };
131
- const result = await fn(ctx, nextFn);
162
+ const result = await fn(dispatchedCtx, nextFn);
132
163
  if (nextCalled && !nextResolved) {
133
164
  throw new Error("middleware resolved before downstream.\n You are probably missing an await or return statement in your middleware function.");
134
165
  }
135
166
  return result;
136
167
  };
137
- return dispatch(0);
168
+ return dispatch(0, ctx);
138
169
  };
139
170
  }
140
171
 
141
- // src/server/exchange.ts
142
- import http from "node:http";
172
+ // src/http/exchange.ts
143
173
  import { Cookie } from "tough-cookie";
144
- function requestToProtocol(request, defaultProtocol) {
145
- let proto = request.headers.get("x-forwarded-proto");
146
- if (Array.isArray(proto)) {
147
- proto = proto[0];
148
- }
149
- if (proto !== void 0) {
150
- return proto.split(",", 1)[0].trim();
151
- }
152
- return defaultProtocol;
153
- }
154
- function requestToHost(request, defaultHost) {
155
- let host = request.headers.get("x-forwarded-for");
174
+ function parseHost(headers2, defaultHost) {
175
+ let host = headers2.get("x-forwarded-for");
156
176
  if (host === void 0) {
157
- host = request.headers.get("x-forwarded-host");
177
+ host = headers2.get("x-forwarded-host");
158
178
  if (Array.isArray(host)) {
159
179
  host = host[0];
160
180
  }
161
181
  if (host) {
162
- const port = request.headers.one("x-forwarded-port");
182
+ const port = headers2.one("x-forwarded-port");
163
183
  if (port) {
164
184
  host = `${host}:${port}`;
165
185
  }
166
186
  }
167
- host ??= request.headers.one("host");
187
+ host ??= headers2.one("host");
168
188
  }
169
189
  if (Array.isArray(host)) {
170
190
  host = host[0];
@@ -174,15 +194,180 @@ function requestToHost(request, defaultHost) {
174
194
  }
175
195
  return defaultHost;
176
196
  }
177
- function parseCookies(request) {
178
- return request.headers.list("cookie").map((s) => s.split(";").map((cs) => Cookie.parse(cs))).flat(1).filter((tc) => tc !== void 0).map((tc) => {
179
- const result = { name: tc.key, value: tc.value };
197
+ function parseProtocol(headers2, defaultProtocol) {
198
+ let proto = headers2.get("x-forwarded-proto");
199
+ if (Array.isArray(proto)) {
200
+ proto = proto[0];
201
+ }
202
+ if (proto !== void 0) {
203
+ return proto.split(",", 1)[0].trim();
204
+ }
205
+ return defaultProtocol;
206
+ }
207
+ var AbstractHttpMessage = class {
208
+ #headers;
209
+ constructor(headers2) {
210
+ this.#headers = headers2;
211
+ }
212
+ get headers() {
213
+ return this.#headers;
214
+ }
215
+ };
216
+ var AbstractHttpRequest = class _AbstractHttpRequest extends AbstractHttpMessage {
217
+ static logIdCounter = 0;
218
+ #id;
219
+ get id() {
220
+ if (this.#id === void 0) {
221
+ this.#id = `${this.initId()}-${++_AbstractHttpRequest.logIdCounter}`;
222
+ }
223
+ return this.#id;
224
+ }
225
+ initId() {
226
+ return "request";
227
+ }
228
+ get cookies() {
229
+ return parseCookies(this.headers);
230
+ }
231
+ parseHost(defaultHost) {
232
+ return parseHost(this.headers, defaultHost);
233
+ }
234
+ parseProtocol(defaultProtocol) {
235
+ return parseProtocol(this.headers, defaultProtocol);
236
+ }
237
+ };
238
+ var AbstractHttpResponse = class extends AbstractHttpMessage {
239
+ get cookies() {
240
+ return parseResponseCookies(this.headers);
241
+ }
242
+ setCookieValue(responseCookie) {
243
+ const cookie = new Cookie({
244
+ key: responseCookie.name,
245
+ value: responseCookie.value,
246
+ maxAge: responseCookie.maxAge,
247
+ domain: responseCookie.domain,
248
+ path: responseCookie.path,
249
+ secure: responseCookie.secure,
250
+ httpOnly: responseCookie.httpOnly,
251
+ sameSite: responseCookie.sameSite
252
+ });
253
+ return cookie.toString();
254
+ }
255
+ };
256
+ function parseHeader(value) {
257
+ const list = [];
258
+ {
259
+ let start2 = 0;
260
+ let end = 0;
261
+ for (let i = 0; i < value.length; i++) {
262
+ switch (value.charCodeAt(i)) {
263
+ case 32:
264
+ if (start2 === end) {
265
+ start2 = end = i + 1;
266
+ }
267
+ break;
268
+ case 44:
269
+ list.push(value.slice(start2, end));
270
+ start2 = end = i + 1;
271
+ break;
272
+ default:
273
+ end = end + 1;
274
+ break;
275
+ }
276
+ }
277
+ list.push(value.slice(start2, end));
278
+ }
279
+ return list;
280
+ }
281
+ function toList(values) {
282
+ if (typeof values === "string") {
283
+ values = [values];
284
+ }
285
+ if (typeof values === "number") {
286
+ values = [String(values)];
287
+ }
288
+ const list = [];
289
+ if (values) {
290
+ for (const value of values) {
291
+ if (value) {
292
+ list.push(...parseHeader(value));
293
+ }
294
+ }
295
+ }
296
+ return list;
297
+ }
298
+ function parseCookies(headers2) {
299
+ return headers2.list("cookie").map((s) => s.split(";").map((cs) => Cookie.parse(cs))).flat(1).filter((tc) => tc !== void 0).map((tc) => {
300
+ const result = Object.freeze({ name: tc.key, value: tc.value });
180
301
  return result;
181
302
  });
182
303
  }
304
+ function parseResponseCookies(headers2) {
305
+ return headers2.list("set-cookie").map((cookie) => {
306
+ const parsed = Cookie.parse(cookie);
307
+ if (parsed) {
308
+ const result = { name: parsed.key, value: parsed.value, maxAge: Number(parsed.maxAge ?? -1) };
309
+ if (parsed.httpOnly) result.httpOnly = true;
310
+ if (parsed.domain) result.domain = parsed.domain;
311
+ if (parsed.path) result.path = parsed.path;
312
+ if (parsed.secure) result.secure = true;
313
+ if (parsed.httpOnly) result.httpOnly = true;
314
+ if (parsed.sameSite) result.sameSite = parsed.sameSite;
315
+ return Object.freeze(result);
316
+ }
317
+ }).filter((cookie) => cookie !== void 0);
318
+ }
319
+ var AbstractHttpHeaders = class {
320
+ constructor() {
321
+ }
322
+ toList(name) {
323
+ const values = this.get(name);
324
+ return toList(values);
325
+ }
326
+ };
327
+ var MapHttpHeaders = class extends Map {
328
+ get(name) {
329
+ return super.get(name.toLowerCase());
330
+ }
331
+ one(name) {
332
+ return this.get(name)?.[0];
333
+ }
334
+ list(name) {
335
+ const values = super.get(name.toLowerCase());
336
+ return toList(values);
337
+ }
338
+ set(name, value) {
339
+ if (typeof value === "number") {
340
+ value = String(value);
341
+ }
342
+ if (typeof value === "string") {
343
+ value = [value];
344
+ }
345
+ if (value) {
346
+ return super.set(name.toLowerCase(), value);
347
+ } else {
348
+ super.delete(name.toLowerCase());
349
+ return this;
350
+ }
351
+ }
352
+ add(name, value) {
353
+ const prev = super.get(name.toLowerCase());
354
+ if (typeof value === "string") {
355
+ value = [value];
356
+ }
357
+ if (prev) {
358
+ value = prev.concat(value);
359
+ }
360
+ this.set(name, value);
361
+ return this;
362
+ }
363
+ };
364
+
365
+ // src/server/exchange.ts
366
+ import http from "node:http";
183
367
  var ExtendedHttpIncomingMessage = class extends http.IncomingMessage {
184
368
  // circular reference to the exchange
185
369
  exchange;
370
+ upgradeHead;
186
371
  get urlBang() {
187
372
  return this.url;
188
373
  }
@@ -190,29 +375,115 @@ var ExtendedHttpIncomingMessage = class extends http.IncomingMessage {
190
375
  return this.socket["encrypted"] === true;
191
376
  }
192
377
  };
193
- var ExtendedHttpServerResponse = class _ExtendedHttpServerResponse extends http.ServerResponse {
194
- static forUpgrade(req, socket, head) {
195
- const res = new _ExtendedHttpServerResponse(req);
196
- res.upgradeHead = head;
197
- res.assignSocket(socket);
198
- return res;
199
- }
200
- upgradeHead;
378
+ var ExtendedHttpServerResponse = class extends http.ServerResponse {
201
379
  markHeadersSent() {
202
380
  this["_header"] = true;
203
381
  }
382
+ getRawHeaderNames() {
383
+ return super["getRawHeaderNames"]();
384
+ }
204
385
  };
205
- var HttpServerRequest = class {
206
- _body;
207
- _url;
208
- _cookies;
209
- #headers;
386
+ var AbstractServerHttpRequest = class extends AbstractHttpRequest {
387
+ };
388
+ var AbstractServerHttpResponse = class extends AbstractHttpResponse {
389
+ #cookies = [];
390
+ #statusCode;
391
+ #state = "new";
392
+ #commitActions = [];
393
+ setStatusCode(statusCode) {
394
+ if (this.#state === "committed") {
395
+ return false;
396
+ } else {
397
+ this.#statusCode = statusCode;
398
+ return true;
399
+ }
400
+ }
401
+ get statusCode() {
402
+ return this.#statusCode;
403
+ }
404
+ addCookie(cookie) {
405
+ if (this.#state === "committed") {
406
+ throw new Error(`Cannot add cookie ${JSON.stringify(cookie)} because HTTP response has already been committed`);
407
+ }
408
+ this.#cookies.push(cookie);
409
+ return this;
410
+ }
411
+ beforeCommit(action) {
412
+ this.#commitActions.push(action);
413
+ }
414
+ get commited() {
415
+ const state = this.#state;
416
+ return state !== "new" && state !== "commit-action-failed";
417
+ }
418
+ async body(body) {
419
+ if (body instanceof ReadableStream) {
420
+ throw new Error("ReadableStream body not supported yet");
421
+ }
422
+ const buffer = await body;
423
+ try {
424
+ return await this.doCommit(async () => {
425
+ return await this.bodyInternal(Promise.resolve(buffer));
426
+ }).catch((error) => {
427
+ throw error;
428
+ });
429
+ } catch (error) {
430
+ throw error;
431
+ }
432
+ }
433
+ async end() {
434
+ if (!this.commited) {
435
+ return this.doCommit();
436
+ } else {
437
+ return Promise.resolve(false);
438
+ }
439
+ }
440
+ doCommit(writeAction) {
441
+ const state = this.#state;
442
+ let allActions = Promise.resolve();
443
+ if (state === "new") {
444
+ this.#state = "committing";
445
+ if (this.#commitActions.length > 0) {
446
+ allActions = this.#commitActions.reduce(
447
+ (acc, cur) => acc.then(() => cur()),
448
+ Promise.resolve()
449
+ ).catch((error) => {
450
+ const state2 = this.#state;
451
+ if (state2 === "committing") {
452
+ this.#state = "commit-action-failed";
453
+ }
454
+ });
455
+ }
456
+ } else if (state === "commit-action-failed") {
457
+ this.#state = "committing";
458
+ } else {
459
+ return Promise.resolve(false);
460
+ }
461
+ allActions = allActions.then(() => {
462
+ this.applyStatusCode();
463
+ this.applyHeaders();
464
+ this.applyCookies();
465
+ this.#state = "committed";
466
+ });
467
+ return allActions.then(async () => {
468
+ return writeAction !== void 0 ? await writeAction() : true;
469
+ });
470
+ }
471
+ applyStatusCode() {
472
+ }
473
+ applyHeaders() {
474
+ }
475
+ applyCookies() {
476
+ }
477
+ };
478
+ var HttpServerRequest = class extends AbstractServerHttpRequest {
479
+ #url;
480
+ #cookies;
210
481
  #req;
211
482
  constructor(req) {
483
+ super(new IncomingMessageHeaders(req));
212
484
  this.#req = req;
213
- this.#headers = new IncomingMessageHeaders(this.#req);
214
485
  }
215
- get unsafeIncomingMessage() {
486
+ getNativeRequest() {
216
487
  return this.#req;
217
488
  }
218
489
  get upgrade() {
@@ -221,15 +492,12 @@ var HttpServerRequest = class {
221
492
  get http2() {
222
493
  return this.#req.httpVersionMajor >= 2;
223
494
  }
224
- get headers() {
225
- return this.#headers;
226
- }
227
495
  get path() {
228
496
  return this.URL?.pathname;
229
497
  }
230
498
  get URL() {
231
- this._url ??= new URL(this.#req.urlBang, `${this.protocol}://${this.host}`);
232
- return this._url;
499
+ this.#url ??= new URL(this.#req.urlBang, `${this.protocol}://${this.host}`);
500
+ return this.#url;
233
501
  }
234
502
  get query() {
235
503
  return this.URL?.search;
@@ -243,7 +511,7 @@ var HttpServerRequest = class {
243
511
  dh = this.#req.headers[":authority"];
244
512
  }
245
513
  dh ??= this.#req.socket.remoteAddress;
246
- return requestToHost(this, dh);
514
+ return super.parseHost(dh);
247
515
  }
248
516
  get protocol() {
249
517
  let dp = void 0;
@@ -251,47 +519,65 @@ var HttpServerRequest = class {
251
519
  dp = this.#req.headers[":scheme"];
252
520
  }
253
521
  dp ??= this.#req.socketEncrypted ? "https" : "http";
254
- return requestToProtocol(this, dp);
522
+ return super.parseProtocol(dp);
255
523
  }
256
524
  get socket() {
257
525
  return this.#req.socket;
258
526
  }
527
+ get remoteAddress() {
528
+ const family = this.#req.socket.remoteFamily;
529
+ const address = this.#req.socket.remoteAddress;
530
+ const port = this.#req.socket.remotePort;
531
+ if (!family || !address || !port) {
532
+ return void 0;
533
+ }
534
+ return { family, address, port };
535
+ }
259
536
  get cookies() {
260
- this._cookies ??= parseCookies(this);
261
- return this._cookies;
537
+ this.#cookies ??= super.cookies;
538
+ return this.#cookies;
262
539
  }
263
540
  get body() {
264
- this._body ??= new Promise((resolve, reject) => {
265
- const chunks = [];
266
- this.#req.on("error", (err) => reject(err)).on("data", (chunk) => chunks.push(chunk)).on("end", () => {
267
- resolve(new Blob(chunks, { type: this.headers.one("content-type") }));
268
- });
269
- });
270
- return this._body;
541
+ return http.IncomingMessage.toWeb(this.#req);
542
+ }
543
+ async blob() {
544
+ const chunks = [];
545
+ if (this.body !== void 0) {
546
+ for await (const chunk of this.body) {
547
+ chunks.push(chunk);
548
+ }
549
+ }
550
+ return new Blob(chunks, { type: this.headers.one("content-type") || "application/octet-stream" });
271
551
  }
272
- get text() {
273
- return this.body.then(async (blob) => await blob.text());
552
+ async text() {
553
+ const blob = await this.blob();
554
+ return await blob.text();
274
555
  }
275
- get formData() {
276
- return this.body.then(async (blob) => {
277
- const text = await blob.text();
278
- const formData = new URLSearchParams(text);
279
- return formData;
280
- });
556
+ async formData() {
557
+ const blob = await this.blob();
558
+ const text = await blob.text();
559
+ return new URLSearchParams(text);
281
560
  }
282
- get json() {
283
- return this.body.then(async (blob) => {
284
- if (blob.size === 0) {
285
- return void 0;
286
- }
287
- const text = await blob.text();
288
- return JSON.parse(text);
289
- });
561
+ async json() {
562
+ const blob = await this.blob();
563
+ if (blob.size === 0) {
564
+ return void 0;
565
+ }
566
+ const text = await blob.text();
567
+ return JSON.parse(text);
568
+ }
569
+ initId() {
570
+ const remoteIp = this.#req.socket.remoteAddress;
571
+ if (!remoteIp) {
572
+ throw new Error("Socket has no remote address");
573
+ }
574
+ return `${remoteIp}:${this.#req.socket.remotePort}`;
290
575
  }
291
576
  };
292
- var IncomingMessageHeaders = class {
577
+ var IncomingMessageHeaders = class extends AbstractHttpHeaders {
293
578
  #msg;
294
579
  constructor(msg) {
580
+ super();
295
581
  this.#msg = msg;
296
582
  }
297
583
  has(name) {
@@ -301,7 +587,7 @@ var IncomingMessageHeaders = class {
301
587
  return this.#msg.headers[name];
302
588
  }
303
589
  list(name) {
304
- return toList(this.#msg.headers[name]);
590
+ return super.toList(name);
305
591
  }
306
592
  one(name) {
307
593
  const value = this.#msg.headers[name];
@@ -314,9 +600,10 @@ var IncomingMessageHeaders = class {
314
600
  return Object.keys(this.#msg.headers).values();
315
601
  }
316
602
  };
317
- var OutgoingMessageHeaders = class {
603
+ var OutgoingMessageHeaders = class extends AbstractHttpHeaders {
318
604
  #msg;
319
605
  constructor(msg) {
606
+ super();
320
607
  this.#msg = msg;
321
608
  }
322
609
  has(name) {
@@ -357,277 +644,238 @@ var OutgoingMessageHeaders = class {
357
644
  return this;
358
645
  }
359
646
  list(name) {
360
- const values = this.get(name);
361
- return toList(values);
647
+ return super.toList(name);
362
648
  }
363
649
  };
364
- var HttpServerResponse = class {
365
- #headers;
650
+ var HttpServerResponse = class extends AbstractServerHttpResponse {
366
651
  #res;
367
652
  constructor(res) {
653
+ super(new OutgoingMessageHeaders(res));
368
654
  this.#res = res;
369
- this.#headers = new OutgoingMessageHeaders(res);
370
- }
371
- get unsafeServerResponse() {
372
- return this.#res;
373
- }
374
- get statusCode() {
375
- return this.#res.statusCode;
376
- }
377
- set statusCode(value) {
378
- if (this.#res.headersSent) {
379
- return;
380
- }
381
- this.#res.statusCode = value;
382
- }
383
- set statusMessage(value) {
384
- this.#res.statusMessage = value;
385
- }
386
- get headers() {
387
- return this.#headers;
388
- }
389
- get cookies() {
390
- return this.headers.list("set-cookie").map((cookie) => {
391
- const parsed = Cookie.parse(cookie);
392
- if (parsed) {
393
- const result = { name: parsed.key, value: parsed.value, maxAge: Number(parsed.maxAge ?? -1) };
394
- if (parsed.httpOnly) result.httpOnly = true;
395
- if (parsed.domain) result.domain = parsed.domain;
396
- if (parsed.path) result.path = parsed.path;
397
- if (parsed.secure) result.secure = true;
398
- if (parsed.httpOnly) result.httpOnly = true;
399
- if (parsed.sameSite) result.sameSite = parsed.sameSite;
400
- return result;
401
- }
402
- }).filter((cookie) => cookie !== void 0);
403
- }
404
- end(chunk) {
405
- if (!this.#res.headersSent) {
406
- return new Promise((resolve, reject) => {
407
- try {
408
- if (chunk === void 0) {
409
- this.#res.end(() => {
410
- resolve(true);
411
- });
412
- } else {
413
- this.#res.end(chunk, () => {
414
- resolve(true);
415
- });
416
- }
417
- } catch (e) {
418
- reject(e instanceof Error ? e : new Error(`end failed: ${e}`));
419
- }
420
- });
421
- } else {
422
- return Promise.resolve(false);
423
- }
424
- }
425
- addCookie(cookie) {
426
- this.headers.add("set-cookie", new Cookie({
427
- key: cookie.name,
428
- value: cookie.value,
429
- maxAge: cookie.maxAge,
430
- domain: cookie.domain,
431
- path: cookie.path,
432
- secure: cookie.secure,
433
- httpOnly: cookie.httpOnly,
434
- sameSite: cookie.sameSite
435
- }).toString());
436
- return this;
437
- }
438
- };
439
- var DefaultWebExchange = class {
440
- request;
441
- response;
442
- constructor(request, response) {
443
- this.request = request;
444
- this.response = response;
445
- }
446
- get method() {
447
- return this.request.method;
448
- }
449
- get path() {
450
- return this.request.path;
451
- }
452
- principal() {
453
- return Promise.resolve(void 0);
454
- }
455
- };
456
- function toList(values) {
457
- if (typeof values === "string") {
458
- values = [values];
459
- }
460
- if (typeof values === "number") {
461
- values = [String(values)];
462
- }
463
- const list = [];
464
- if (values) {
465
- for (const value of values) {
466
- if (value) {
467
- list.push(...parseHeader(value));
468
- }
469
- }
470
- }
471
- return list;
472
- }
473
- function parseHeader(value) {
474
- const list = [];
475
- {
476
- let start2 = 0;
477
- let end = 0;
478
- for (let i = 0; i < value.length; i++) {
479
- switch (value.charCodeAt(i)) {
480
- case 32:
481
- if (start2 === end) {
482
- start2 = end = i + 1;
483
- }
484
- break;
485
- case 44:
486
- list.push(value.slice(start2, end));
487
- start2 = end = i + 1;
488
- break;
489
- default:
490
- end = end + 1;
491
- break;
492
- }
493
- }
494
- list.push(value.slice(start2, end));
495
- }
496
- return list;
497
- }
498
- var MapHttpHeaders = class extends Map {
499
- get(name) {
500
- return super.get(name.toLowerCase());
501
- }
502
- one(name) {
503
- return this.get(name)?.[0];
504
- }
505
- list(name) {
506
- const values = super.get(name.toLowerCase());
507
- return toList(values);
508
- }
509
- set(name, value) {
510
- if (typeof value === "number") {
511
- value = String(value);
512
- }
513
- if (typeof value === "string") {
514
- value = [value];
515
- }
516
- if (value) {
517
- return super.set(name.toLowerCase(), value);
518
- } else {
519
- super.delete(name.toLowerCase());
520
- return this;
521
- }
522
655
  }
523
- add(name, value) {
524
- const prev = super.get(name.toLowerCase());
525
- if (typeof value === "string") {
526
- value = [value];
527
- }
528
- if (prev) {
529
- value = prev.concat(value);
656
+ getNativeResponse() {
657
+ return this.#res;
658
+ }
659
+ get statusCode() {
660
+ const status = super.statusCode;
661
+ return status ?? { value: this.#res.statusCode };
662
+ }
663
+ applyStatusCode() {
664
+ const status = super.statusCode;
665
+ if (status !== void 0) {
666
+ this.#res.statusCode = status.value;
530
667
  }
531
- this.set(name, value);
668
+ }
669
+ addCookie(cookie) {
670
+ this.headers.add("Set-Cookie", super.setCookieValue(cookie));
532
671
  return this;
533
672
  }
534
- };
535
- var MockHttpRequest = class {
536
- #url;
537
- #body;
538
- headers = new MapHttpHeaders();
539
- upgrade = false;
540
- constructor(url, method) {
541
- if (typeof url === "string") {
542
- if (URL.canParse(url)) {
543
- url = new URL(url);
544
- } else if (URL.canParse(url, "http://localhost")) {
545
- url = new URL(url, "http://localhost");
673
+ async bodyInternal(body) {
674
+ if (!this.#res.headersSent) {
675
+ if (body instanceof ReadableStream) {
676
+ throw new Error("ReadableStream body not supported in response");
546
677
  } else {
547
- throw new TypeError("URL cannot parse url");
678
+ const chunk = await body;
679
+ return await new Promise((resolve, reject) => {
680
+ try {
681
+ if (chunk === void 0) {
682
+ this.#res.end(() => {
683
+ resolve(true);
684
+ });
685
+ } else {
686
+ if (!this.headers.has("content-length")) {
687
+ if (typeof chunk === "string") {
688
+ this.headers.set("content-length", Buffer.byteLength(chunk));
689
+ } else {
690
+ this.headers.set("content-length", chunk.byteLength);
691
+ }
692
+ }
693
+ this.#res.end(chunk, () => {
694
+ resolve(true);
695
+ });
696
+ }
697
+ } catch (e) {
698
+ reject(e instanceof Error ? e : new Error(`end failed: ${e}`));
699
+ }
700
+ });
548
701
  }
702
+ } else {
703
+ return false;
549
704
  }
550
- this.#url = url;
551
- this.method = method ?? "GET";
552
- this.headers.set("Host", this.#url.hostname);
553
- this.path = this.#url.pathname ?? "/";
554
705
  }
555
- method;
556
- path;
557
- get host() {
558
- return requestToHost(this, this.#url.host);
706
+ };
707
+ var ServerHttpRequestDecorator = class _ServerHttpRequestDecorator {
708
+ #delegate;
709
+ constructor(request) {
710
+ this.#delegate = request;
711
+ }
712
+ get delegate() {
713
+ return this.#delegate;
714
+ }
715
+ get id() {
716
+ return this.#delegate.id;
717
+ }
718
+ get method() {
719
+ return this.#delegate.method;
720
+ }
721
+ get path() {
722
+ return this.#delegate.path;
559
723
  }
560
724
  get protocol() {
561
- return requestToProtocol(this, this.#url.protocol.slice(0, -1));
725
+ return this.#delegate.protocol;
726
+ }
727
+ get host() {
728
+ return this.#delegate.host;
729
+ }
730
+ get URL() {
731
+ return this.#delegate.URL;
732
+ }
733
+ get headers() {
734
+ return this.#delegate.headers;
562
735
  }
563
736
  get cookies() {
564
- return parseCookies(this);
737
+ return this.#delegate.cookies;
738
+ }
739
+ get remoteAddress() {
740
+ return this.#delegate.remoteAddress;
741
+ }
742
+ get upgrade() {
743
+ return this.#delegate.upgrade;
565
744
  }
566
745
  get body() {
567
- const body = this.#body;
568
- return body ? Promise.resolve(body) : Promise.reject(new Error(`no body set`));
746
+ return this.#delegate.body;
569
747
  }
570
- get json() {
571
- return this.body.then(async (blob) => JSON.parse(await blob.text()));
748
+ async blob() {
749
+ return await this.#delegate.blob();
572
750
  }
573
- get text() {
574
- return this.body.then(async (blob) => await blob.text());
751
+ async text() {
752
+ return await this.#delegate.text();
575
753
  }
576
- set body(value) {
577
- this.#body = value;
578
- if (!this.headers.has("content-type")) {
579
- this.headers.set("content-type", value.type || "application/octet-stream");
580
- }
754
+ async formData() {
755
+ return await this.#delegate.formData();
581
756
  }
582
- get formData() {
583
- return this.body.then(async (b) => new URLSearchParams(await b.text()));
757
+ async json() {
758
+ return await this.#delegate.json();
584
759
  }
585
- get URL() {
586
- return new URL(this.path + this.#url.search + this.#url.hash, `${this.protocol}://${this.host}`);
760
+ toString() {
761
+ return `${_ServerHttpRequestDecorator.name} [delegate: ${this.delegate.toString()}]`;
762
+ }
763
+ static getNativeRequest(request) {
764
+ if (request instanceof AbstractServerHttpRequest) {
765
+ return request.getNativeRequest();
766
+ } else if (request instanceof _ServerHttpRequestDecorator) {
767
+ return _ServerHttpRequestDecorator.getNativeRequest(request.delegate);
768
+ } else {
769
+ throw new Error(`Cannot get native request from ${request.constructor.name}`);
770
+ }
587
771
  }
588
772
  };
589
- var MockHttpResponse = class {
590
- statusCode;
591
- headers = new MapHttpHeaders();
592
- cookies = [];
773
+ var ServerHttpResponseDecorator = class _ServerHttpResponseDecorator {
774
+ #delegate;
775
+ constructor(response) {
776
+ this.#delegate = response;
777
+ }
778
+ get delegate() {
779
+ return this.#delegate;
780
+ }
781
+ setStatusCode(statusCode) {
782
+ return this.#delegate.setStatusCode(statusCode);
783
+ }
784
+ get statusCode() {
785
+ return this.#delegate.statusCode;
786
+ }
787
+ get cookies() {
788
+ return this.#delegate.cookies;
789
+ }
593
790
  addCookie(cookie) {
594
- this.cookies.push(cookie);
791
+ this.#delegate.addCookie(cookie);
595
792
  return this;
596
793
  }
597
- _body;
598
- async end(chunk) {
599
- if (this._body) {
600
- return false;
601
- }
602
- switch (typeof chunk) {
603
- case "string":
604
- this._body = new Blob([chunk], { type: "text/plain" });
605
- break;
606
- case "object":
607
- if (chunk instanceof Blob) {
608
- this._body = chunk;
609
- } else if (chunk !== null) {
610
- this._body = new Blob([JSON.stringify(chunk)], { type: "application/json" });
611
- }
612
- break;
613
- case "undefined":
614
- this._body = new Blob([]);
615
- break;
616
- default:
617
- throw new Error(`Unsupported chunk type: ${typeof chunk}`);
794
+ async end() {
795
+ return await this.#delegate.end();
796
+ }
797
+ async body(body) {
798
+ return await this.#delegate.body(body);
799
+ }
800
+ get headers() {
801
+ return this.#delegate.headers;
802
+ }
803
+ toString() {
804
+ return `${_ServerHttpResponseDecorator.name} [delegate: ${this.delegate.toString()}]`;
805
+ }
806
+ static getNativeResponse(response) {
807
+ if (response instanceof AbstractServerHttpResponse) {
808
+ return response.getNativeResponse();
809
+ } else if (response instanceof _ServerHttpResponseDecorator) {
810
+ return _ServerHttpResponseDecorator.getNativeResponse(response.delegate);
811
+ } else {
812
+ throw new Error(`Cannot get native response from ${response.constructor.name}`);
618
813
  }
619
- return true;
620
814
  }
621
815
  };
622
-
623
- // src/utils.ts
624
- function socketKey(socket) {
625
- const remoteIp = socket.remoteAddress;
626
- if (!remoteIp) {
627
- throw new Error("Socket has no remote address");
816
+ var ServerWebExchangeDecorator = class _ServerWebExchangeDecorator {
817
+ #delegate;
818
+ constructor(exchange) {
819
+ this.#delegate = exchange;
628
820
  }
629
- return `${remoteIp}:${socket.remotePort}`;
630
- }
821
+ get delegate() {
822
+ return this.#delegate;
823
+ }
824
+ get request() {
825
+ return this.#delegate.request;
826
+ }
827
+ get response() {
828
+ return this.#delegate.response;
829
+ }
830
+ attribute(name) {
831
+ return this.#delegate.attribute(name);
832
+ }
833
+ principal() {
834
+ return this.#delegate.principal();
835
+ }
836
+ get logPrefix() {
837
+ return this.#delegate.logPrefix;
838
+ }
839
+ toString() {
840
+ return `${_ServerWebExchangeDecorator.name} [delegate: ${this.delegate}]`;
841
+ }
842
+ };
843
+ var DefaultWebExchange = class {
844
+ request;
845
+ response;
846
+ #attributes = {};
847
+ #logId;
848
+ #logPrefix = "";
849
+ constructor(request, response) {
850
+ this.#attributes[LOG_ID_ATTRIBUTE] = request.id;
851
+ this.request = request;
852
+ this.response = response;
853
+ }
854
+ get method() {
855
+ return this.request.method;
856
+ }
857
+ get path() {
858
+ return this.request.path;
859
+ }
860
+ get attributes() {
861
+ return this.#attributes;
862
+ }
863
+ attribute(name) {
864
+ return this.attributes[name];
865
+ }
866
+ principal() {
867
+ return Promise.resolve(void 0);
868
+ }
869
+ get logPrefix() {
870
+ const value = this.attribute(LOG_ID_ATTRIBUTE);
871
+ if (this.#logId !== value) {
872
+ this.#logId = value;
873
+ this.#logPrefix = value !== void 0 ? `[${value}] ` : "";
874
+ }
875
+ return this.#logPrefix;
876
+ }
877
+ };
878
+ var LOG_ID_ATTRIBUTE = "io.interop.gateway.server.log_id";
631
879
 
632
880
  // src/server/address.ts
633
881
  import { networkInterfaces } from "node:os";
@@ -809,7 +1057,20 @@ async function stop(m) {
809
1057
  return await run(m, "stop");
810
1058
  }
811
1059
 
812
- // src/app/ws-client-verify.ts
1060
+ // src/server/server-header.ts
1061
+ import info from "@interopio/gateway-server/package.json" with { type: "json" };
1062
+ var serverHeader = (server) => {
1063
+ server ??= `${info.name} - v${info.version}`;
1064
+ return async ({ response }, next) => {
1065
+ if (server !== false && !response.headers.has("server")) {
1066
+ response.headers.set("Server", server);
1067
+ }
1068
+ await next();
1069
+ };
1070
+ };
1071
+ var server_header_default = (server) => serverHeader(server);
1072
+
1073
+ // src/server/ws-client-verify.ts
813
1074
  import { IOGateway as IOGateway3 } from "@interopio/gateway";
814
1075
  var log3 = getLogger("gateway.ws.client-verify");
815
1076
  function acceptsMissing(originFilters) {
@@ -881,19 +1142,6 @@ function regexifyOriginFilters(originFilters) {
881
1142
  }
882
1143
  }
883
1144
 
884
- // src/server/server-header.ts
885
- import info from "@interopio/gateway-server/package.json" with { type: "json" };
886
- var serverHeader = (server) => {
887
- server ??= `${info.name} - v${info.version}`;
888
- return async ({ response }, next) => {
889
- if (server !== false && !response.headers.has("server")) {
890
- response.headers.set("Server", server);
891
- }
892
- await next();
893
- };
894
- };
895
- var server_header_default = (server) => serverHeader(server);
896
-
897
1145
  // src/server/util/matchers.ts
898
1146
  var or = (matchers) => {
899
1147
  return async (exchange) => {
@@ -1000,15 +1248,6 @@ upgradeMatcher.toString = () => "websocket upgrade";
1000
1248
 
1001
1249
  // src/app/route.ts
1002
1250
  import { IOGateway as IOGateway4 } from "@interopio/gateway";
1003
- function findSocketRoute({ request }, { sockets: routes }) {
1004
- const path = request.path ?? "/";
1005
- const route = routes.get(path) ?? Array.from(routes.values()).find((route2) => {
1006
- if (path === "/" && route2.default === true) {
1007
- return true;
1008
- }
1009
- });
1010
- return [route, path];
1011
- }
1012
1251
  async function configure(app, config, routes) {
1013
1252
  const applyCors = (matcher, requestMethod, options) => {
1014
1253
  if (options?.cors) {
@@ -1056,6 +1295,93 @@ async function configure(app, config, routes) {
1056
1295
  await app(configurer, config);
1057
1296
  }
1058
1297
 
1298
+ // src/http/status.ts
1299
+ var HttpStatus = class _HttpStatus {
1300
+ static CONTINUE = new _HttpStatus(100, "Continue");
1301
+ static SWITCHING_PROTOCOLS = new _HttpStatus(101, "Switching Protocols");
1302
+ // 2xx Success
1303
+ static OK = new _HttpStatus(200, "OK");
1304
+ static CREATED = new _HttpStatus(201, "Created");
1305
+ static ACCEPTED = new _HttpStatus(202, "Accepted");
1306
+ static NON_AUTHORITATIVE_INFORMATION = new _HttpStatus(203, "Non-Authoritative Information");
1307
+ static NO_CONTENT = new _HttpStatus(204, "No Content");
1308
+ static RESET_CONTENT = new _HttpStatus(205, "Reset Content");
1309
+ static PARTIAL_CONTENT = new _HttpStatus(206, "Partial Content");
1310
+ static MULTI_STATUS = new _HttpStatus(207, "Multi-Status");
1311
+ static IM_USED = new _HttpStatus(226, "IM Used");
1312
+ // 3xx Redirection
1313
+ static MULTIPLE_CHOICES = new _HttpStatus(300, "Multiple Choices");
1314
+ static MOVED_PERMANENTLY = new _HttpStatus(301, "Moved Permanently");
1315
+ // 4xx Client Error
1316
+ static BAD_REQUEST = new _HttpStatus(400, "Bad Request");
1317
+ static UNAUTHORIZED = new _HttpStatus(401, "Unauthorized");
1318
+ static FORBIDDEN = new _HttpStatus(403, "Forbidden");
1319
+ static NOT_FOUND = new _HttpStatus(404, "Not Found");
1320
+ static METHOD_NOT_ALLOWED = new _HttpStatus(405, "Method Not Allowed");
1321
+ static NOT_ACCEPTABLE = new _HttpStatus(406, "Not Acceptable");
1322
+ static PROXY_AUTHENTICATION_REQUIRED = new _HttpStatus(407, "Proxy Authentication Required");
1323
+ static REQUEST_TIMEOUT = new _HttpStatus(408, "Request Timeout");
1324
+ static CONFLICT = new _HttpStatus(409, "Conflict");
1325
+ static GONE = new _HttpStatus(410, "Gone");
1326
+ static LENGTH_REQUIRED = new _HttpStatus(411, "Length Required");
1327
+ static PRECONDITION_FAILED = new _HttpStatus(412, "Precondition Failed");
1328
+ static PAYLOAD_TOO_LARGE = new _HttpStatus(413, "Payload Too Large");
1329
+ static URI_TOO_LONG = new _HttpStatus(414, "URI Too Long");
1330
+ static UNSUPPORTED_MEDIA_TYPE = new _HttpStatus(415, "Unsupported Media Type");
1331
+ static EXPECTATION_FAILED = new _HttpStatus(417, "Expectation Failed");
1332
+ static IM_A_TEAPOT = new _HttpStatus(418, "I'm a teapot");
1333
+ static TOO_EARLY = new _HttpStatus(425, "Too Early");
1334
+ static UPGRADE_REQUIRED = new _HttpStatus(426, "Upgrade Required");
1335
+ static PRECONDITION_REQUIRED = new _HttpStatus(428, "Precondition Required");
1336
+ static TOO_MANY_REQUESTS = new _HttpStatus(429, "Too Many Requests");
1337
+ static REQUEST_HEADER_FIELDS_TOO_LARGE = new _HttpStatus(431, "Request Header Fields Too Large");
1338
+ static UNAVAILABLE_FOR_LEGAL_REASONS = new _HttpStatus(451, "Unavailable For Legal Reasons");
1339
+ // 5xx Server Error
1340
+ static INTERNAL_SERVER_ERROR = new _HttpStatus(500, "Internal Server Error");
1341
+ static NOT_IMPLEMENTED = new _HttpStatus(501, "Not Implemented");
1342
+ static BAD_GATEWAY = new _HttpStatus(502, "Bad Gateway");
1343
+ static SERVICE_UNAVAILABLE = new _HttpStatus(503, "Service Unavailable");
1344
+ static GATEWAY_TIMEOUT = new _HttpStatus(504, "Gateway Timeout");
1345
+ static HTTP_VERSION_NOT_SUPPORTED = new _HttpStatus(505, "HTTP Version Not Supported");
1346
+ static VARIANT_ALSO_NEGOTIATES = new _HttpStatus(506, "Variant Also Negotiates");
1347
+ static INSUFFICIENT_STORAGE = new _HttpStatus(507, "Insufficient Storage");
1348
+ static LOOP_DETECTED = new _HttpStatus(508, "Loop Detected");
1349
+ static NOT_EXTENDED = new _HttpStatus(510, "Not Extended");
1350
+ static NETWORK_AUTHENTICATION_REQUIRED = new _HttpStatus(511, "Network Authentication Required");
1351
+ static #VALUES = [];
1352
+ static {
1353
+ Object.keys(_HttpStatus).filter((key) => key !== "VALUES" && key !== "resolve").forEach((key) => {
1354
+ const value = _HttpStatus[key];
1355
+ if (value instanceof _HttpStatus) {
1356
+ Object.defineProperty(value, "name", { enumerable: true, value: key, writable: false });
1357
+ _HttpStatus.#VALUES.push(value);
1358
+ }
1359
+ });
1360
+ }
1361
+ static resolve(code) {
1362
+ for (const status of _HttpStatus.#VALUES) {
1363
+ if (status.value === code) {
1364
+ return status;
1365
+ }
1366
+ }
1367
+ }
1368
+ #value;
1369
+ #phrase;
1370
+ constructor(value, phrase) {
1371
+ this.#value = value;
1372
+ this.#phrase = phrase;
1373
+ }
1374
+ get value() {
1375
+ return this.#value;
1376
+ }
1377
+ get phrase() {
1378
+ return this.#phrase;
1379
+ }
1380
+ toString() {
1381
+ return `${this.#value} ${this["name"]}`;
1382
+ }
1383
+ };
1384
+
1059
1385
  // src/server/cors.ts
1060
1386
  import { IOGateway as IOGateway5 } from "@interopio/gateway";
1061
1387
  function isSameOrigin(request) {
@@ -1210,7 +1536,7 @@ var corsFilter = (opts) => {
1210
1536
  var cors_default = corsFilter;
1211
1537
  var logger = getLogger("cors");
1212
1538
  function rejectRequest(response) {
1213
- response.statusCode = 403;
1539
+ response.setStatusCode(HttpStatus.FORBIDDEN);
1214
1540
  }
1215
1541
  function handleInternal(exchange, config, preFlightRequest) {
1216
1542
  const { request, response } = exchange;
@@ -1300,70 +1626,243 @@ function checkMethods(config, requestMethod) {
1300
1626
  if (allowedMethods === ALL) {
1301
1627
  return [requestMethod];
1302
1628
  }
1303
- if (IOGateway5.Filtering.valuesMatch(allowedMethods, requestMethod)) {
1304
- return allowedMethods;
1629
+ if (IOGateway5.Filtering.valuesMatch(allowedMethods, requestMethod)) {
1630
+ return allowedMethods;
1631
+ }
1632
+ }
1633
+ }
1634
+ function checkHeaders(config, requestHeaders) {
1635
+ if (requestHeaders === void 0) {
1636
+ return;
1637
+ }
1638
+ if (requestHeaders.length == 0) {
1639
+ return [];
1640
+ }
1641
+ const allowedHeaders = config.allowHeaders;
1642
+ if (allowedHeaders === void 0) {
1643
+ return;
1644
+ }
1645
+ const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
1646
+ const result = [];
1647
+ for (const requestHeader of requestHeaders) {
1648
+ const value = requestHeader?.trim();
1649
+ if (value) {
1650
+ if (allowAnyHeader) {
1651
+ result.push(value);
1652
+ } else {
1653
+ for (const allowedHeader of allowedHeaders) {
1654
+ if (value.toLowerCase() === allowedHeader) {
1655
+ result.push(value);
1656
+ break;
1657
+ }
1658
+ }
1659
+ }
1660
+ }
1661
+ }
1662
+ if (result.length > 0) {
1663
+ return result;
1664
+ }
1665
+ }
1666
+ function trimTrailingSlash(origin) {
1667
+ return origin.endsWith("/") ? origin.slice(0, -1) : origin;
1668
+ }
1669
+ function getMethodToUse(request, isPreFlight) {
1670
+ return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
1671
+ }
1672
+ function getHeadersToUse(request, isPreFlight) {
1673
+ const headers2 = request.headers;
1674
+ return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
1675
+ }
1676
+ var matchingCorsConfigSource = (opts) => {
1677
+ return async (exchange) => {
1678
+ for (const [matcher, config] of opts.mappings) {
1679
+ if ((await matcher(exchange)).match) {
1680
+ logger.debug(`resolved cors config on '${exchange.request.path}' using ${matcher}: ${JSON.stringify(config)}`);
1681
+ return config;
1682
+ }
1683
+ }
1684
+ };
1685
+ };
1686
+
1687
+ // src/mock/server/exchange.ts
1688
+ import stream from "node:stream/web";
1689
+ var MockServerHttpRequest = class extends AbstractHttpRequest {
1690
+ #url;
1691
+ #body;
1692
+ upgrade = false;
1693
+ constructor(url, method) {
1694
+ super(new MapHttpHeaders());
1695
+ if (typeof url === "string") {
1696
+ if (URL.canParse(url)) {
1697
+ url = new URL(url);
1698
+ } else if (URL.canParse(url, "http://localhost")) {
1699
+ url = new URL(url, "http://localhost");
1700
+ } else {
1701
+ throw new TypeError("URL cannot parse url");
1702
+ }
1703
+ }
1704
+ this.#url = url;
1705
+ this.method = method ?? "GET";
1706
+ this.setHeader("Host", this.#url.hostname);
1707
+ this.path = this.#url.pathname ?? "/";
1708
+ }
1709
+ method;
1710
+ path;
1711
+ get host() {
1712
+ return super.parseHost(this.#url.host);
1713
+ }
1714
+ get protocol() {
1715
+ return super.parseProtocol(this.#url.protocol.slice(0, -1));
1716
+ }
1717
+ get body() {
1718
+ if (this.#body !== void 0) {
1719
+ const blob = this.#body;
1720
+ const asyncIterator = async function* () {
1721
+ const bytes = await blob.bytes();
1722
+ yield bytes;
1723
+ }();
1724
+ return stream.ReadableStream.from(asyncIterator);
1725
+ }
1726
+ }
1727
+ blob() {
1728
+ const body = this.#body;
1729
+ return body ? Promise.resolve(body) : Promise.reject(new Error(`no body set`));
1730
+ }
1731
+ async text() {
1732
+ const blob = await this.blob();
1733
+ return await blob.text();
1734
+ }
1735
+ async formData() {
1736
+ const blob = await this.blob();
1737
+ const text = await blob.text();
1738
+ return new URLSearchParams(text);
1739
+ }
1740
+ async json() {
1741
+ const blob = await this.blob();
1742
+ if (blob.size === 0) {
1743
+ return void 0;
1744
+ }
1745
+ const text = await blob.text();
1746
+ return JSON.parse(text);
1747
+ }
1748
+ async writeBody(body) {
1749
+ if (body === void 0) {
1750
+ this.#body = new Blob([]);
1751
+ return;
1752
+ }
1753
+ if (body instanceof ReadableStream) {
1754
+ const chunks = [];
1755
+ const reader = body.getReader();
1756
+ let done = false;
1757
+ while (!done) {
1758
+ const { value, done: readDone } = await reader.read();
1759
+ if (readDone) {
1760
+ done = true;
1761
+ } else {
1762
+ chunks.push(value);
1763
+ }
1764
+ }
1765
+ this.#body = new Blob(chunks);
1766
+ } else {
1767
+ body = await body;
1768
+ if (typeof body === "string") {
1769
+ this.#body = new Blob([body], { type: "text/plain" });
1770
+ } else {
1771
+ this.#body = new Blob([body]);
1772
+ }
1773
+ }
1774
+ if (!this.headers.has("content-type")) {
1775
+ this.setHeader("Content-Type", this.#body.type || "application/octet-stream");
1305
1776
  }
1306
1777
  }
1307
- }
1308
- function checkHeaders(config, requestHeaders) {
1309
- if (requestHeaders === void 0) {
1310
- return;
1778
+ get URL() {
1779
+ return new URL(this.path + this.#url.search + this.#url.hash, `${this.protocol}://${this.host}`);
1311
1780
  }
1312
- if (requestHeaders.length == 0) {
1313
- return [];
1781
+ addHeader(name, value) {
1782
+ this.headers.add(name, value);
1783
+ return this;
1314
1784
  }
1315
- const allowedHeaders = config.allowHeaders;
1316
- if (allowedHeaders === void 0) {
1317
- return;
1785
+ setHeader(name, value) {
1786
+ this.headers.set(name, value);
1787
+ return this;
1318
1788
  }
1319
- const allowAnyHeader = allowedHeaders === ALL || allowedHeaders.includes(ALL);
1320
- const result = [];
1321
- for (const requestHeader of requestHeaders) {
1322
- const value = requestHeader?.trim();
1323
- if (value) {
1324
- if (allowAnyHeader) {
1325
- result.push(value);
1326
- } else {
1327
- for (const allowedHeader of allowedHeaders) {
1328
- if (value.toLowerCase() === allowedHeader) {
1329
- result.push(value);
1330
- break;
1331
- }
1789
+ };
1790
+ var MockServerHttpResponse = class extends AbstractServerHttpResponse {
1791
+ #writeHandler;
1792
+ #body = () => {
1793
+ throw new Error("No content was written to the response body nor end was called on this response.");
1794
+ };
1795
+ constructor() {
1796
+ super(new MapHttpHeaders());
1797
+ this.#writeHandler = async (body) => {
1798
+ const chunks = [];
1799
+ let bodyStream;
1800
+ this.#body = () => {
1801
+ bodyStream ??= stream.ReadableStream.from(chunks);
1802
+ return bodyStream;
1803
+ };
1804
+ const reader = body.getReader();
1805
+ let done = false;
1806
+ do {
1807
+ const { value, done: readDone } = await reader.read();
1808
+ if (readDone) {
1809
+ done = true;
1810
+ } else {
1811
+ chunks.push(value);
1332
1812
  }
1333
- }
1334
- }
1813
+ } while (!done);
1814
+ return true;
1815
+ };
1335
1816
  }
1336
- if (result.length > 0) {
1337
- return result;
1817
+ get statusCode() {
1818
+ return super.statusCode;
1338
1819
  }
1339
- }
1340
- function trimTrailingSlash(origin) {
1341
- return origin.endsWith("/") ? origin.slice(0, -1) : origin;
1342
- }
1343
- function getMethodToUse(request, isPreFlight) {
1344
- return isPreFlight ? request.headers.one("access-control-request-method") : request.method;
1345
- }
1346
- function getHeadersToUse(request, isPreFlight) {
1347
- const headers2 = request.headers;
1348
- return isPreFlight ? headers2.list("access-control-request-headers") : Array.from(headers2.keys());
1349
- }
1350
- var matchingCorsConfigSource = (opts) => {
1351
- return async (exchange) => {
1352
- for (const [matcher, config] of opts.mappings) {
1353
- if ((await matcher(exchange)).match) {
1354
- logger.debug(`resolved cors config on '${exchange.request.path}' using ${matcher}: ${JSON.stringify(config)}`);
1355
- return config;
1356
- }
1820
+ set writeHandler(handler) {
1821
+ this.#body = () => {
1822
+ throw new Error("Not available with custom write handler");
1823
+ };
1824
+ this.#writeHandler = handler;
1825
+ }
1826
+ getNativeResponse() {
1827
+ throw new Error("This is a mock. No running server, no native response.");
1828
+ }
1829
+ applyStatusCode() {
1830
+ }
1831
+ applyHeaders() {
1832
+ }
1833
+ applyCookies() {
1834
+ for (const cookie of this.cookies) {
1835
+ this.headers.add("Set-Cookie", super.setCookieValue(cookie));
1357
1836
  }
1358
- };
1837
+ }
1838
+ bodyInternal(body) {
1839
+ const it = async function* () {
1840
+ const resolved = await body;
1841
+ if (resolved === void 0) {
1842
+ return;
1843
+ }
1844
+ yield resolved;
1845
+ }();
1846
+ return this.#writeHandler(stream.ReadableStream.from(it));
1847
+ }
1848
+ async end() {
1849
+ return this.doCommit(async () => {
1850
+ return await new Promise((resolve, reject) => {
1851
+ this.#writeHandler(stream.ReadableStream.from([]));
1852
+ });
1853
+ });
1854
+ }
1855
+ getBody() {
1856
+ return this.#body();
1857
+ }
1359
1858
  };
1360
1859
 
1361
1860
  // src/app/cors.ts
1362
1861
  function mockUpgradeExchange(path) {
1363
- const request = new MockHttpRequest(path, "GET");
1364
- request.headers.set("Upgrade", "websocket");
1862
+ const request = new MockServerHttpRequest(path, "GET");
1863
+ request.setHeader("Upgrade", "websocket");
1365
1864
  request.upgrade = true;
1366
- return new DefaultWebExchange(request, new MockHttpResponse());
1865
+ return new DefaultWebExchange(request, new MockServerHttpResponse());
1367
1866
  }
1368
1867
  async function createCorsConfigSource(context) {
1369
1868
  const { sockets: routes, cors } = context;
@@ -1380,8 +1879,8 @@ async function createCorsConfigSource(context) {
1380
1879
  routeCorsConfig = combineCorsConfig(routeCorsConfig, {
1381
1880
  allowOrigins: route.originFilters?.allow,
1382
1881
  allowMethods: ["GET", "CONNECT"],
1383
- allowHeaders: ["upgrade", "connection", "origin", "sec-websocket-key", "sec-websocket-version", "sec-websocket-protocol", "sec-websocket-extensions"],
1384
- exposeHeaders: ["sec-websocket-accept", "sec-websocket-protocol", "sec-websocket-extensions"],
1882
+ allowHeaders: ["Upgrade", "Connection", "Origin", "Sec-Websocket-Key", "Sec-Websocket-Version", "Sec-Websocket-Protocol", "Sec-Websocket-Extensions"],
1883
+ exposeHeaders: ["Sec-Websocket-Accept", "Sec-Websocket-Protocol", "Sec-Websocket-Extensions"],
1385
1884
  allowCredentials: route.authorize?.access !== "permitted"
1386
1885
  });
1387
1886
  validatedConfigs.push([and([upgradeMatcher, pattern(path)]), validateCorsConfig(routeCorsConfig)]);
@@ -1624,7 +2123,7 @@ var httpBasicEntryPoint = (opts) => {
1624
2123
  const headerValue = createHeaderValue(opts?.realm ?? DEFAULT_REALM);
1625
2124
  return async (exchange, _error) => {
1626
2125
  const { response } = exchange;
1627
- response.statusCode = 401;
2126
+ response.setStatusCode(HttpStatus.UNAUTHORIZED);
1628
2127
  response.headers.set("WWW-Authenticate", headerValue);
1629
2128
  };
1630
2129
  };
@@ -1644,12 +2143,17 @@ var httpBasicAuthenticationConverter = (opts) => {
1644
2143
  if (parts.length !== 2) {
1645
2144
  return void 0;
1646
2145
  }
1647
- return { type: "UsernamePassword", authenticated: false, principal: parts[0], credentials: parts[1] };
2146
+ return {
2147
+ type: "UsernamePassword",
2148
+ authenticated: false,
2149
+ principal: parts[0],
2150
+ credentials: parts[1]
2151
+ };
1648
2152
  };
1649
2153
  };
1650
2154
 
1651
2155
  // src/server/security/security-context.ts
1652
- import { AsyncLocalStorage } from "node:async_hooks";
2156
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
1653
2157
  var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder {
1654
2158
  static hasSecurityContext(storage) {
1655
2159
  return storage.getStore()?.securityContext !== void 0;
@@ -1661,7 +2165,7 @@ var AsyncStorageSecurityContextHolder = class _AsyncStorageSecurityContextHolder
1661
2165
  delete storage.getStore()?.securityContext;
1662
2166
  }
1663
2167
  static withSecurityContext(securityContext) {
1664
- return (storage = new AsyncLocalStorage()) => {
2168
+ return (storage = new AsyncLocalStorage2()) => {
1665
2169
  storage.getStore().securityContext = securityContext;
1666
2170
  return storage;
1667
2171
  };
@@ -1738,8 +2242,7 @@ function authenticationFilter(opts) {
1738
2242
  var httpStatusEntryPoint = (opts) => {
1739
2243
  return async (exchange, _error) => {
1740
2244
  const response = exchange.response;
1741
- response.statusCode = opts.httpStatus.code;
1742
- response.statusMessage = opts.httpStatus.message;
2245
+ response.setStatusCode(opts.httpStatus);
1743
2246
  };
1744
2247
  };
1745
2248
 
@@ -1747,7 +2250,7 @@ var httpStatusEntryPoint = (opts) => {
1747
2250
  var logger2 = getLogger("auth.entry-point");
1748
2251
  var delegatingEntryPoint = (opts) => {
1749
2252
  const defaultEntryPoint = opts.defaultEntryPoint ?? (async ({ response }, _error) => {
1750
- response.statusCode = 401;
2253
+ response.setStatusCode(HttpStatus.UNAUTHORIZED);
1751
2254
  await response.end();
1752
2255
  });
1753
2256
  return async (exchange, error) => {
@@ -1785,12 +2288,12 @@ function httpBasic(opts) {
1785
2288
  const headers2 = exchange.request.headers;
1786
2289
  const h = headers2.list("X-Requested-With");
1787
2290
  if (h.includes("XMLHttpRequest")) {
1788
- return { match: true };
2291
+ return match();
1789
2292
  }
1790
- return { match: false };
2293
+ return NO_MATCH;
1791
2294
  };
1792
2295
  const defaultEntryPoint = delegatingEntryPoint({
1793
- entryPoints: [[xhrMatcher, httpStatusEntryPoint({ httpStatus: { code: 401 } })]],
2296
+ entryPoints: [[xhrMatcher, httpStatusEntryPoint({ httpStatus: HttpStatus.UNAUTHORIZED })]],
1794
2297
  defaultEntryPoint: httpBasicEntryPoint({})
1795
2298
  });
1796
2299
  const entryPoint = opts.entryPoint ?? defaultEntryPoint;
@@ -1833,7 +2336,7 @@ var DEFAULT_URI = "https://tools.ietf.org/html/rfc6750#section-3.1";
1833
2336
  function invalidToken(message) {
1834
2337
  return {
1835
2338
  errorCode: BearerTokenErrorCodes.invalid_token,
1836
- httpStatus: 401,
2339
+ httpStatus: HttpStatus.UNAUTHORIZED,
1837
2340
  description: message,
1838
2341
  uri: DEFAULT_URI
1839
2342
  };
@@ -1841,7 +2344,7 @@ function invalidToken(message) {
1841
2344
  function invalidRequest(message) {
1842
2345
  return {
1843
2346
  errorCode: BearerTokenErrorCodes.invalid_request,
1844
- httpStatus: 400,
2347
+ httpStatus: HttpStatus.BAD_REQUEST,
1845
2348
  description: message,
1846
2349
  uri: DEFAULT_URI
1847
2350
  };
@@ -1917,7 +2420,10 @@ async function resolveFromBody(exchange, allow = false) {
1917
2420
  if (!allow || "application/x-www-form-urlencoded" !== request.headers.one("content-type") || request.method !== "POST") {
1918
2421
  return;
1919
2422
  }
1920
- return resolveTokens(await exchange.request.formData);
2423
+ const parameters = await exchange.request.formData();
2424
+ if (parameters) {
2425
+ return resolveTokens(parameters);
2426
+ }
1921
2427
  }
1922
2428
  var token_converter_default = serverBearerTokenAuthenticationConverter;
1923
2429
 
@@ -1947,7 +2453,7 @@ function getStatus(authError) {
1947
2453
  return error.httpStatus;
1948
2454
  }
1949
2455
  }
1950
- return 401;
2456
+ return HttpStatus.UNAUTHORIZED;
1951
2457
  }
1952
2458
  function createParameters(authError, realm) {
1953
2459
  const parameters = /* @__PURE__ */ new Map();
@@ -1976,7 +2482,7 @@ var bearerTokenServerAuthenticationEntryPoint = (opts) => {
1976
2482
  const wwwAuthenticate = computeWWWAuthenticate(parameters);
1977
2483
  const { response } = exchange;
1978
2484
  response.headers.set("WWW-Authenticate", wwwAuthenticate);
1979
- response.statusCode = status;
2485
+ response.setStatusCode(status);
1980
2486
  await response.end();
1981
2487
  };
1982
2488
  };
@@ -2063,18 +2569,17 @@ async function commenceAuthentication(exchange, authentication, entryPoint) {
2063
2569
  }
2064
2570
  await entryPoint(exchange, e);
2065
2571
  }
2066
- function httpStatusAccessDeniedHandler(status, message) {
2572
+ function httpStatusAccessDeniedHandler(httpStatus) {
2067
2573
  return async (exchange, _error) => {
2068
- exchange.response.statusCode = status;
2069
- exchange.response.statusMessage = message;
2070
- exchange.response.headers.set("Content-Type", "text/plain");
2071
- const bytes = Buffer.from("Access Denied", "utf-8");
2072
- exchange.response.headers.set("Content-Length", bytes.length);
2073
- await exchange.response.end(bytes);
2574
+ exchange.response.setStatusCode(httpStatus);
2575
+ exchange.response.headers.set("Content-Type", "text/plain; charset=utf-8");
2576
+ const buffer = Buffer.from("Access Denied", "utf-8");
2577
+ exchange.response.headers.set("Content-Length", buffer.length);
2578
+ await exchange.response.body(buffer);
2074
2579
  };
2075
2580
  }
2076
2581
  var errorFilter = (opts) => {
2077
- const accessDeniedHandler = httpStatusAccessDeniedHandler(403, "Forbidden");
2582
+ const accessDeniedHandler = httpStatusAccessDeniedHandler(HttpStatus.FORBIDDEN);
2078
2583
  const authenticationEntryPoint = opts.authenticationEntryPoint ?? httpBasicEntryPoint();
2079
2584
  return async (exchange, next) => {
2080
2585
  try {
@@ -2086,17 +2591,19 @@ var errorFilter = (opts) => {
2086
2591
  await commenceAuthentication(exchange, void 0, authenticationEntryPoint);
2087
2592
  } else {
2088
2593
  if (!principal.authenticated) {
2089
- return accessDeniedHandler(exchange, error);
2594
+ await accessDeniedHandler(exchange, error);
2090
2595
  }
2091
2596
  await commenceAuthentication(exchange, principal, authenticationEntryPoint);
2092
2597
  }
2598
+ return;
2093
2599
  }
2600
+ throw error;
2094
2601
  }
2095
2602
  };
2096
2603
  };
2097
2604
 
2098
2605
  // src/server/security/delegating-authorization-manager.ts
2099
- var logger3 = getLogger("auth");
2606
+ var logger3 = getLogger("security.auth");
2100
2607
  function delegatingAuthorizationManager(opts) {
2101
2608
  const check = async (authentication, exchange) => {
2102
2609
  let decision;
@@ -2139,6 +2646,25 @@ function authorizationFilter(opts) {
2139
2646
  };
2140
2647
  }
2141
2648
 
2649
+ // src/server/security/exchange-filter.ts
2650
+ var SecurityContextServerWebExchange = class extends ServerWebExchangeDecorator {
2651
+ #context;
2652
+ constructor(exchange, context) {
2653
+ super(exchange);
2654
+ this.#context = context;
2655
+ }
2656
+ async principal() {
2657
+ const context = await this.#context();
2658
+ return context?.authentication;
2659
+ }
2660
+ };
2661
+ var exchangeFilter = (opts) => {
2662
+ const storage = opts.storage;
2663
+ return async (exchange, next) => {
2664
+ await next(new SecurityContextServerWebExchange(exchange, async () => await AsyncStorageSecurityContextHolder.getContext(storage)));
2665
+ };
2666
+ };
2667
+
2142
2668
  // src/server/security/config.ts
2143
2669
  var filterOrder = {
2144
2670
  first: Number.MAX_SAFE_INTEGER,
@@ -2147,6 +2673,7 @@ var filterOrder = {
2147
2673
  cors: 3 * 100,
2148
2674
  http_basic: 6 * 100,
2149
2675
  authentication: 8 * 100,
2676
+ security_context_server_web_exchange: 15 * 100,
2150
2677
  error_translation: 18 * 100,
2151
2678
  authorization: 19 * 100,
2152
2679
  last: Number.MAX_SAFE_INTEGER
@@ -2233,9 +2760,12 @@ var config_default = (config, context) => {
2233
2760
  const authenticationConverterMatcher = async (exchange) => {
2234
2761
  try {
2235
2762
  const a = await authenticationConverter(exchange);
2236
- return { match: a !== void 0 };
2763
+ if (a === void 0) {
2764
+ return NO_MATCH;
2765
+ }
2766
+ return match();
2237
2767
  } catch (e) {
2238
- return { match: false };
2768
+ return NO_MATCH;
2239
2769
  }
2240
2770
  };
2241
2771
  const entryPoint = token_entry_point_default({});
@@ -2249,6 +2779,9 @@ var config_default = (config, context) => {
2249
2779
  filter[filterOrderSymbol] = filterOrder.authentication;
2250
2780
  middleware.push(filter);
2251
2781
  }
2782
+ const exchangeF = exchangeFilter({ storage: context.storage });
2783
+ middleware.push(exchangeF);
2784
+ exchangeF[filterOrderSymbol] = filterOrder.security_context_server_web_exchange;
2252
2785
  if (config.authorize !== void 0) {
2253
2786
  const errorFf = errorFilter({ authenticationEntryPoint: this.authenticationEntryPoint });
2254
2787
  errorFf[filterOrderSymbol] = filterOrder.error_translation;
@@ -2342,121 +2875,341 @@ async function httpSecurity(context) {
2342
2875
  return config_default(config, { storage, corsConfigSource });
2343
2876
  }
2344
2877
 
2345
- // src/server.ts
2346
- var logger5 = getLogger("app");
2347
- function secureContextOptions(ssl) {
2348
- const options = {};
2349
- if (ssl.key) options.key = readFileSync(ssl.key);
2350
- if (ssl.cert) options.cert = readFileSync(ssl.cert);
2351
- if (ssl.ca) options.ca = readFileSync(ssl.ca);
2352
- return options;
2353
- }
2354
- async function createListener(context, onSocketError) {
2355
- const storage = context.storage;
2356
- const security = await httpSecurity(context);
2357
- const listener = compose(
2358
- server_header_default(context.serverHeader),
2359
- ...security,
2360
- // websocket upgrade handler
2361
- async (exchange, next) => {
2362
- const [route, path] = findSocketRoute(exchange, context);
2363
- if (route !== void 0) {
2364
- const { request, response } = exchange;
2365
- const upgradeMatchResult = await upgradeMatcher(exchange);
2366
- if ((request.method === "GET" || request.method === "CONNECT") && upgradeMatchResult.match) {
2367
- const socket = response.unsafeServerResponse.socket;
2368
- const host = request.host;
2369
- const info2 = socketKey(socket);
2370
- if (route.wss) {
2371
- socket.removeListener("error", onSocketError);
2372
- const wss = route.wss;
2373
- if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
2374
- logger5.warn(`${info2} dropping ws connection request from ${host} on ${path}. max connections exceeded.`);
2375
- socket.destroy();
2376
- return;
2377
- }
2378
- const origin = request.headers.one("origin");
2379
- if (!acceptsOrigin(origin, route.originFilters)) {
2380
- logger5.info(`${info2} dropping ws connection request from ${host} on ${path}. origin ${origin ?? "<missing>"}`);
2381
- socket.destroy();
2382
- return;
2383
- }
2384
- if (logger5.enabledFor("debug")) {
2385
- logger5.debug(`${info2} accepted new ws connection request from ${host} on ${path}`);
2386
- }
2387
- const upgradeHead = response.unsafeServerResponse.upgradeHead;
2388
- wss.handleUpgrade(request.unsafeIncomingMessage, socket, upgradeHead, (ws2) => {
2389
- response.unsafeServerResponse.markHeadersSent();
2390
- ws2.on("pong", () => ws2["connected"] = true);
2391
- ws2.on("ping", () => {
2392
- });
2393
- wss.emit("connection", ws2, request.unsafeIncomingMessage);
2394
- });
2395
- } else {
2396
- logger5.warn(`${info2} rejected upgrade request from ${host} on ${path}`);
2397
- socket.destroy();
2398
- }
2399
- } else {
2400
- if (route.default) {
2401
- await next();
2402
- return;
2403
- }
2404
- if (logger5.enabledFor("debug")) {
2405
- logger5.debug(`rejecting request for ${path} with method ${request.method} from ${request.socket.remoteAddress}:${request.socket.remotePort} with headers: ${JSON.stringify(request.headers)}`);
2406
- }
2407
- response.statusCode = 426;
2408
- response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
2409
- await response.end(`This service [${request.path}] requires use of the websocket protocol.`);
2410
- }
2878
+ // src/server/handler.ts
2879
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
2880
+ var HttpHeadResponseDecorator = class extends ServerHttpResponseDecorator {
2881
+ };
2882
+ var HandlerAdapter = class {
2883
+ #logger;
2884
+ #enableLoggingRequestDetails = false;
2885
+ #delegate;
2886
+ #storage;
2887
+ constructor(logger7, delegate) {
2888
+ this.#logger = logger7;
2889
+ this.#delegate = delegate;
2890
+ }
2891
+ createExchange(request, response) {
2892
+ const exchange = new DefaultWebExchange(request, response);
2893
+ return exchange;
2894
+ }
2895
+ set storage(storage) {
2896
+ this.#storage = storage;
2897
+ }
2898
+ set enableLoggingRequestDetails(value) {
2899
+ this.#enableLoggingRequestDetails = value;
2900
+ }
2901
+ formatHeaders(headers2) {
2902
+ let result = "{";
2903
+ for (const key of headers2.keys()) {
2904
+ if (!this.#enableLoggingRequestDetails) {
2905
+ result += "masked, ";
2906
+ break;
2411
2907
  } else {
2412
- await next();
2908
+ const value = headers2.get(key);
2909
+ result += `"${key}": "${value}", `;
2413
2910
  }
2414
- },
2415
- ...context.middleware,
2416
- // health check
2417
- async ({ request, response }, next) => {
2418
- if (request.method === "GET" && request.path === "/health") {
2419
- response.statusCode = 200;
2420
- await response.end(http2.STATUS_CODES[200]);
2911
+ }
2912
+ if (result.endsWith(", ")) {
2913
+ result = result.slice(0, -2);
2914
+ }
2915
+ result += "}";
2916
+ return result;
2917
+ }
2918
+ formatRequest(request) {
2919
+ const query = request.URL.search;
2920
+ return `HTTP ${request.method} "${request.path}${query}`;
2921
+ }
2922
+ logRequest(exchange) {
2923
+ if (this.#logger.enabledFor("debug")) {
2924
+ const trace = this.#logger.enabledFor("trace");
2925
+ this.#logger.debug(`${exchange.logPrefix}${this.formatRequest(exchange.request)}${trace ? `, headers: ${this.formatHeaders(exchange.request.headers)}` : ""}"`);
2926
+ }
2927
+ }
2928
+ logResponse(exchange) {
2929
+ if (this.#logger.enabledFor("debug")) {
2930
+ const trace = this.#logger.enabledFor("trace");
2931
+ const status = exchange.response.statusCode;
2932
+ this.#logger.debug(`${exchange.logPrefix}Completed ${status ?? "200 OK"}${trace ? `, headers: ${this.formatHeaders(exchange.response.headers)}` : ""}"`);
2933
+ }
2934
+ }
2935
+ handleUnresolvedError(exchange, error) {
2936
+ const { request, response, logPrefix } = exchange;
2937
+ if (response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR)) {
2938
+ this.#logger.error(`${logPrefix}500 Server Error for ${this.formatRequest(request)}`, error);
2939
+ return;
2940
+ }
2941
+ this.#logger.error(`${logPrefix}Error [${error.message} for ${this.formatRequest(request)}, but already ended (${response.statusCode})`, error);
2942
+ throw error;
2943
+ }
2944
+ async web(exchange) {
2945
+ return await this.#delegate(exchange);
2946
+ }
2947
+ async http(request, response) {
2948
+ const exchange = this.createExchange(request, response);
2949
+ const callback = () => {
2950
+ this.logRequest(exchange);
2951
+ return this.web(exchange).then(() => {
2952
+ this.logResponse(exchange);
2953
+ }).catch((error) => {
2954
+ this.handleUnresolvedError(exchange, error);
2955
+ }).then(async () => {
2956
+ await exchange.response.end();
2957
+ });
2958
+ };
2959
+ await new Promise((resolve, reject) => {
2960
+ if (this.#storage !== void 0) {
2961
+ this.#storage.run({ exchange }, () => {
2962
+ callback().then(() => resolve()).catch((error) => reject(error));
2963
+ });
2421
2964
  } else {
2422
- await next();
2965
+ callback().then(() => resolve()).catch((error) => reject(error));
2423
2966
  }
2424
- },
2425
- // home page
2426
- async ({ request, response }, next) => {
2427
- if (request.method === "GET" && request.path === "/") {
2428
- await response.end(`io.Gateway Server`);
2967
+ });
2968
+ }
2969
+ };
2970
+ var WebHttpHandlerBuilder = class {
2971
+ #webHandler;
2972
+ #storage = new AsyncLocalStorage3();
2973
+ #handlerDecorator;
2974
+ storage(storage) {
2975
+ this.#storage = storage;
2976
+ return this;
2977
+ }
2978
+ httpHandlerDecorator(decorator) {
2979
+ if (this.#handlerDecorator === void 0) {
2980
+ this.#handlerDecorator = decorator;
2981
+ } else {
2982
+ const previousDecorator = this.#handlerDecorator;
2983
+ this.#handlerDecorator = (handler) => {
2984
+ handler = previousDecorator(handler);
2985
+ return decorator(handler);
2986
+ };
2987
+ }
2988
+ return this;
2989
+ }
2990
+ constructor(webHandler) {
2991
+ this.#webHandler = webHandler;
2992
+ }
2993
+ build() {
2994
+ const logger7 = getLogger("http");
2995
+ const adapter = new HandlerAdapter(logger7, this.#webHandler);
2996
+ if (this.#storage !== void 0) adapter.storage = this.#storage;
2997
+ adapter.enableLoggingRequestDetails = false;
2998
+ const adapted = async (request, response) => adapter.http(request, response);
2999
+ return this.#handlerDecorator ? this.#handlerDecorator(adapted) : adapted;
3000
+ }
3001
+ };
3002
+
3003
+ // src/server/ws.ts
3004
+ import { WebSocket, WebSocketServer } from "ws";
3005
+
3006
+ // src/server/socket.ts
3007
+ function createHandshakeInfo(req, protocol) {
3008
+ const exchange = req?.exchange;
3009
+ const request = exchange?.request ?? new HttpServerRequest(req);
3010
+ const principalPromiseProvider = exchange?.principal;
3011
+ const principal = principalPromiseProvider ? principalPromiseProvider.bind(exchange) : async function principal2() {
3012
+ return void 0;
3013
+ };
3014
+ const url = request.URL;
3015
+ const headers2 = new MapHttpHeaders();
3016
+ for (const key of request.headers.keys()) {
3017
+ headers2.set(key, request.headers.list(key));
3018
+ }
3019
+ const cookies = request.cookies;
3020
+ const logPrefix = exchange?.logPrefix ?? `[${request.id}] `;
3021
+ const remoteAddress = request.remoteAddress;
3022
+ const handshake = {
3023
+ url,
3024
+ headers: headers2,
3025
+ cookies,
3026
+ principal,
3027
+ protocol,
3028
+ remoteAddress,
3029
+ logPrefix
3030
+ };
3031
+ return handshake;
3032
+ }
3033
+ function webSockets(context) {
3034
+ const sockets = async (exchange, next) => {
3035
+ const request = exchange.request;
3036
+ const path = request.path ?? "/";
3037
+ const routes = context.sockets;
3038
+ const route = routes.get(path) ?? Array.from(routes.values()).find((route2) => {
3039
+ if (path === "/" && route2.default === true) {
3040
+ return true;
3041
+ }
3042
+ });
3043
+ if (route !== void 0) {
3044
+ const { request: request2, response } = exchange;
3045
+ const upgradeMatchResult = await upgradeMatcher(exchange);
3046
+ if ((request2.method === "GET" || request2.method === "CONNECT") && upgradeMatchResult.match) {
3047
+ if (route.upgradeStrategy !== void 0) {
3048
+ route.upgradeStrategy(exchange);
3049
+ return;
3050
+ } else {
3051
+ throw new Error(`No upgrade strategy defined for route on ${path}`);
3052
+ }
2429
3053
  } else {
2430
- await next();
3054
+ if (route.default) {
3055
+ await next();
3056
+ return;
3057
+ }
3058
+ response.setStatusCode(HttpStatus.UPGRADE_REQUIRED);
3059
+ response.headers.set("Upgrade", "websocket").set("Connection", "Upgrade").set("Content-Type", "text/plain");
3060
+ const buffer = Buffer.from(`This service [${request2.path}] requires use of the websocket protocol.`, "utf-8");
3061
+ await response.body(buffer);
2431
3062
  }
2432
- },
2433
- // not found
2434
- async ({ response }, _next) => {
2435
- response.statusCode = 404;
2436
- await response.end(http2.STATUS_CODES[404]);
3063
+ } else {
3064
+ await next();
2437
3065
  }
2438
- );
2439
- return async (request, response) => {
2440
- request.socket.addListener("error", onSocketError);
2441
- const exchange = new DefaultWebExchange(new HttpServerRequest(request), new HttpServerResponse(response));
2442
- request.exchange = exchange;
2443
- return await storage.run({ exchange }, async () => {
2444
- if (logger5.enabledFor("debug")) {
2445
- const socket = exchange.request.socket;
2446
- if (logger5.enabledFor("debug")) {
2447
- logger5.debug(`received ${exchange.method} request for ${exchange.path} from ${socket.remoteAddress}:${socket.remotePort}`);
2448
- }
3066
+ };
3067
+ return [sockets];
3068
+ }
3069
+
3070
+ // src/server/ws.ts
3071
+ var ExtendedWebSocket = class extends WebSocket {
3072
+ constructor(first, second, options) {
3073
+ super(null, void 0, options);
3074
+ }
3075
+ connected;
3076
+ };
3077
+ var logger5 = getLogger("ws");
3078
+ function upgradeStrategy(path, route, wss, onSocketError) {
3079
+ return (exchange) => {
3080
+ const { logPrefix, request } = exchange;
3081
+ const req = ServerHttpRequestDecorator.getNativeRequest(request);
3082
+ req.exchange = exchange;
3083
+ const { socket, upgradeHead } = req;
3084
+ const host = request.host;
3085
+ socket.removeListener("error", onSocketError);
3086
+ if (route.maxConnections !== void 0 && wss.clients?.size >= route.maxConnections) {
3087
+ logger5.warn(`${logPrefix}dropping ws connection request on ${host}${path}. max connections exceeded.`);
3088
+ socket.destroy();
3089
+ return;
3090
+ }
3091
+ const origin = request.headers.one("origin");
3092
+ if (!acceptsOrigin(origin, route.originFilters)) {
3093
+ if (logger5.enabledFor("info")) {
3094
+ logger5.info(`${logPrefix}dropping ws connection request on ${host}${path}. origin ${origin ?? "<missing>"}`);
2449
3095
  }
2450
- try {
2451
- return await listener(exchange);
2452
- } catch (e) {
2453
- if (logger5.enabledFor("warn")) {
2454
- logger5.warn(`error processing request for ${exchange.path}`, e);
2455
- }
2456
- } finally {
2457
- await exchange.response.end();
3096
+ socket.destroy();
3097
+ return;
3098
+ }
3099
+ if (logger5.enabledFor("debug")) {
3100
+ logger5.debug(`${logPrefix}accepted new ws connection request on ${host}${path}`);
3101
+ }
3102
+ wss.handleUpgrade(req, socket, upgradeHead, (client, req2) => {
3103
+ wss.emit("connection", client, req2);
3104
+ });
3105
+ };
3106
+ }
3107
+ function applyHandshakeHeaders(headers2, response) {
3108
+ const seen = /* @__PURE__ */ new Set();
3109
+ headers2.forEach((header, index) => {
3110
+ if (index === 0 && header.startsWith("HTTP/1.1 101 ")) {
3111
+ response.setStatusCode(HttpStatus.SWITCHING_PROTOCOLS);
3112
+ return;
3113
+ }
3114
+ const [name, value] = header.split(": ");
3115
+ if (response.headers.has(name)) {
3116
+ headers2[index] = `${name}: ${response.headers.one(name)}`;
3117
+ } else {
3118
+ response.headers.set(name, value);
3119
+ }
3120
+ seen.add(name.toLowerCase());
3121
+ });
3122
+ const nativeResponse = ServerHttpResponseDecorator.getNativeResponse(response);
3123
+ for (const name of nativeResponse.getRawHeaderNames()) {
3124
+ const nameLowerCase = name.toLowerCase();
3125
+ if (!seen.has(nameLowerCase)) {
3126
+ const value = response.headers.get(nameLowerCase);
3127
+ if (value !== void 0) {
3128
+ headers2.push(`${name}: ${value}`);
3129
+ }
3130
+ }
3131
+ }
3132
+ nativeResponse.markHeadersSent();
3133
+ }
3134
+ async function initRoute(path, route, endpoint, storage, onSocketError) {
3135
+ try {
3136
+ logger5.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
3137
+ const wss = new WebSocketServer({
3138
+ noServer: true,
3139
+ WebSocket: ExtendedWebSocket,
3140
+ autoPong: false
3141
+ });
3142
+ const handler = await route.factory({ endpoint, storage });
3143
+ wss.on("error", (err) => {
3144
+ logger5.error(`error starting the ws server for [${path}]`, err);
3145
+ }).on("listening", () => {
3146
+ logger5.info(`ws server for [${path}] is listening`);
3147
+ }).on("headers", (headers2, request) => {
3148
+ if (request.exchange !== void 0) {
3149
+ const { response } = request.exchange;
3150
+ applyHandshakeHeaders(headers2, response);
2458
3151
  }
3152
+ }).on("connection", (socket, request) => {
3153
+ const handshake = createHandshakeInfo(request, socket.protocol);
3154
+ socket.on("pong", () => socket.connected = true);
3155
+ socket.on("ping", () => {
3156
+ });
3157
+ handler({ socket, handshake });
2459
3158
  });
3159
+ const pingInterval = route.ping;
3160
+ if (pingInterval) {
3161
+ const pingIntervalId = setInterval(() => {
3162
+ for (const client of wss.clients) {
3163
+ if (client.connected === false) {
3164
+ client.terminate();
3165
+ }
3166
+ client.connected = false;
3167
+ client.ping();
3168
+ }
3169
+ }, pingInterval);
3170
+ wss.on("close", () => {
3171
+ clearInterval(pingIntervalId);
3172
+ });
3173
+ }
3174
+ route.upgradeStrategy = upgradeStrategy(path, route, wss, onSocketError);
3175
+ route.close = async () => {
3176
+ await handler.close?.call(handler);
3177
+ logger5.info(`stopping ws server for [${path}]. clients: ${wss.clients?.size ?? 0}`);
3178
+ wss.clients?.forEach((client) => {
3179
+ client.terminate();
3180
+ });
3181
+ wss.close();
3182
+ };
3183
+ } catch (e) {
3184
+ logger5.warn(`failed to init route ${path}`, e);
3185
+ }
3186
+ }
3187
+
3188
+ // src/server.ts
3189
+ var logger6 = getLogger("app");
3190
+ function secureContextOptions(ssl) {
3191
+ const options = {};
3192
+ if (ssl.key) options.key = readFileSync(ssl.key);
3193
+ if (ssl.cert) options.cert = readFileSync(ssl.cert);
3194
+ if (ssl.ca) options.ca = readFileSync(ssl.ca);
3195
+ return options;
3196
+ }
3197
+ async function createListener(builder, onSocketError) {
3198
+ const httpHandler = builder.build();
3199
+ return async (req, resOrUpgradeHead) => {
3200
+ req.socket.addListener("error", onSocketError);
3201
+ let res;
3202
+ if (resOrUpgradeHead instanceof ExtendedHttpServerResponse) {
3203
+ res = resOrUpgradeHead;
3204
+ } else {
3205
+ req.upgradeHead = resOrUpgradeHead;
3206
+ res = new ExtendedHttpServerResponse(req);
3207
+ res.assignSocket(req.socket);
3208
+ }
3209
+ const request = new HttpServerRequest(req);
3210
+ const response = new HttpServerResponse(res);
3211
+ const decoratedResponse = request.method === "HEAD" ? new HttpHeadResponseDecorator(response) : response;
3212
+ await httpHandler(request, decoratedResponse);
2460
3213
  };
2461
3214
  }
2462
3215
  function promisify(fn) {
@@ -2481,8 +3234,44 @@ function memoryMonitor(config) {
2481
3234
  });
2482
3235
  }
2483
3236
  }
2484
- function regexAwareReplacer(_key, value) {
2485
- return value instanceof RegExp ? value.toString() : value;
3237
+ async function initBuilder(context) {
3238
+ const storage = context.storage;
3239
+ const security = await httpSecurity(context);
3240
+ const sockets = webSockets(context);
3241
+ const handler = compose(
3242
+ server_header_default(context.serverHeader),
3243
+ ...security,
3244
+ ...sockets,
3245
+ ...context.middleware,
3246
+ // health check
3247
+ async ({ request, response }, next) => {
3248
+ if (request.method === "GET" && request.path === "/health") {
3249
+ response.setStatusCode(HttpStatus.OK);
3250
+ const buffer = Buffer.from("UP", "utf-8");
3251
+ response.headers.set("Content-Type", "text/plain; charset=utf-8");
3252
+ await response.body(buffer);
3253
+ } else {
3254
+ await next();
3255
+ }
3256
+ },
3257
+ // home page
3258
+ async ({ request, response }, next) => {
3259
+ if (request.method === "GET" && request.path === "/") {
3260
+ response.setStatusCode(HttpStatus.OK);
3261
+ const buffer = Buffer.from("io.Gateway Server", "utf-8");
3262
+ response.headers.set("Content-Type", "text/plain; charset=utf-8");
3263
+ await response.body(buffer);
3264
+ } else {
3265
+ await next();
3266
+ }
3267
+ },
3268
+ // not found
3269
+ async ({ response }, _next) => {
3270
+ response.setStatusCode(HttpStatus.NOT_FOUND);
3271
+ await response.end();
3272
+ }
3273
+ );
3274
+ return new WebHttpHandlerBuilder(handler).storage(storage);
2486
3275
  }
2487
3276
  var Factory = async (options) => {
2488
3277
  const ssl = options.ssl;
@@ -2494,7 +3283,7 @@ var Factory = async (options) => {
2494
3283
  cors: [],
2495
3284
  authConfig: options.auth,
2496
3285
  authorize: [],
2497
- storage: new AsyncLocalStorage2(),
3286
+ storage: new AsyncLocalStorage4(),
2498
3287
  sockets: /* @__PURE__ */ new Map()
2499
3288
  };
2500
3289
  const gw = IOGateway6.Factory({ ...options.gateway });
@@ -2509,8 +3298,9 @@ var Factory = async (options) => {
2509
3298
  }
2510
3299
  const ports = portRange(options.port ?? 0);
2511
3300
  const host = options.host;
2512
- const onSocketError = (err) => logger5.error(`socket error: ${err}`, err);
2513
- const listener = await createListener(context, onSocketError);
3301
+ const onSocketError = (err) => logger6.error(`socket error: ${err}`, err);
3302
+ const builder = await initBuilder(context);
3303
+ const listener = await createListener(builder, onSocketError);
2514
3304
  const serverP = new Promise((resolve, reject) => {
2515
3305
  const server2 = createServer({
2516
3306
  IncomingMessage: ExtendedHttpIncomingMessage,
@@ -2518,95 +3308,45 @@ var Factory = async (options) => {
2518
3308
  }, listener);
2519
3309
  server2.on("error", (e) => {
2520
3310
  if (e["code"] === "EADDRINUSE") {
2521
- logger5.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
3311
+ logger6.debug(`port ${e["port"]} already in use on address ${e["address"]}`);
2522
3312
  const { value: port } = ports.next();
2523
3313
  if (port) {
2524
- logger5.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
3314
+ logger6.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
2525
3315
  server2.close();
2526
3316
  server2.listen(port, host);
2527
3317
  } else {
2528
- logger5.warn(`all configured port(s) ${options.port} are in use. closing...`);
3318
+ logger6.warn(`all configured port(s) ${options.port} are in use. closing...`);
2529
3319
  server2.close();
2530
3320
  reject(e);
2531
3321
  }
2532
3322
  } else {
2533
- logger5.error(`server error: ${e.message}`, e);
3323
+ logger6.error(`server error: ${e.message}`, e);
2534
3324
  reject(e);
2535
3325
  }
2536
3326
  });
2537
3327
  server2.on("listening", async () => {
2538
3328
  const info2 = server2.address();
2539
3329
  for (const [path, route] of context.sockets) {
2540
- try {
2541
- logger5.info(`creating ws server for [${path}]. max connections: ${route.maxConnections ?? "<unlimited>"}, origin filters: ${route.originFilters ? JSON.stringify(route.originFilters, regexAwareReplacer) : "<none>"}`);
2542
- const wss = new ws.WebSocketServer({ noServer: true });
2543
- const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info2.port}${path}`;
2544
- const handler = await route.factory({ endpoint });
2545
- wss.on("error", (err) => {
2546
- logger5.error(`error starting the ws server for [${path}]`, err);
2547
- }).on("listening", () => {
2548
- logger5.info(`ws server for [${path}] is listening`);
2549
- }).on("connection", (socket, req) => {
2550
- const { request, principal } = req.exchange;
2551
- const url = request.URL;
2552
- const headers2 = new MapHttpHeaders();
2553
- for (const key of request.headers.keys()) {
2554
- headers2.set(key, request.headers.get(key));
2555
- }
2556
- const cookies = request.cookies;
2557
- const handshake = {
2558
- url,
2559
- headers: headers2,
2560
- cookies,
2561
- principal,
2562
- protocol: socket.protocol,
2563
- remoteAddress: request.socket.remoteAddress,
2564
- remoteFamily: request.socket.remoteFamily,
2565
- remotePort: request.socket.remotePort,
2566
- logPrefix: socketKey(request.socket)
2567
- };
2568
- handler(socket, handshake);
2569
- });
2570
- const pingInterval = route.ping;
2571
- if (pingInterval) {
2572
- const pingIntervalId = setInterval(() => {
2573
- for (const client of wss.clients) {
2574
- if (client["connected"] === false) {
2575
- client.terminate();
2576
- }
2577
- client["connected"] = false;
2578
- client.ping();
2579
- }
2580
- }, pingInterval);
2581
- wss.on("close", () => {
2582
- clearInterval(pingIntervalId);
2583
- });
2584
- }
2585
- route.wss = wss;
2586
- route.close = handler.close?.bind(handler);
2587
- } catch (e) {
2588
- logger5.warn(`failed to init route ${path}`, e);
2589
- }
3330
+ const endpoint = `${ssl ? "wss" : "ws"}://${localIp}:${info2.port}${path}`;
3331
+ await initRoute(path, route, endpoint, context.storage, onSocketError);
2590
3332
  }
2591
- logger5.info(`http server listening on ${info2.address}:${info2.port}`);
3333
+ logger6.info(`http server listening on ${info2.address}:${info2.port}`);
2592
3334
  resolve(server2);
2593
3335
  });
2594
- server2.on("upgrade", (req, socket, head) => {
2595
- socket.on("error", onSocketError);
3336
+ server2.on("upgrade", (req, _socket, head) => {
2596
3337
  try {
2597
- const res = ExtendedHttpServerResponse.forUpgrade(req, socket, head);
2598
- listener(req, res);
3338
+ listener(req, head);
2599
3339
  } catch (err) {
2600
- logger5.error(`upgrade error: ${err}`, err);
3340
+ logger6.error(`upgrade error: ${err}`, err);
2601
3341
  }
2602
3342
  }).on("close", async () => {
2603
- logger5.info(`http server closed.`);
3343
+ logger6.info(`http server closed.`);
2604
3344
  });
2605
3345
  try {
2606
3346
  const { value: port } = ports.next();
2607
3347
  server2.listen(port, host);
2608
3348
  } catch (e) {
2609
- logger5.error(`error starting web socket server`, e);
3349
+ logger6.error(`error starting web socket server`, e);
2610
3350
  reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
2611
3351
  }
2612
3352
  });
@@ -2616,16 +3356,11 @@ var Factory = async (options) => {
2616
3356
  async close() {
2617
3357
  for (const [path, route] of context.sockets) {
2618
3358
  try {
2619
- if (route.close) {
3359
+ if (route.close !== void 0) {
2620
3360
  await route.close();
2621
3361
  }
2622
- logger5.info(`stopping ws server for [${path}]. clients: ${route.wss?.clients?.size ?? 0}`);
2623
- route.wss?.clients?.forEach((client) => {
2624
- client.terminate();
2625
- });
2626
- route.wss?.close();
2627
3362
  } catch (e) {
2628
- logger5.warn(`error closing route ${path}`, e);
3363
+ logger6.warn(`error closing route ${path}`, e);
2629
3364
  }
2630
3365
  }
2631
3366
  await promisify((cb) => {