@morojs/moro 1.8.12 → 1.8.13

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 (84) hide show
  1. package/README.md +4 -2
  2. package/dist/core/cluster/thread-cluster.d.ts +25 -0
  3. package/dist/core/cluster/thread-cluster.js +95 -3
  4. package/dist/core/cluster/thread-cluster.js.map +1 -1
  5. package/dist/core/config/config-validator.js +15 -0
  6. package/dist/core/config/config-validator.js.map +1 -1
  7. package/dist/core/config/index.js +19 -2
  8. package/dist/core/config/index.js.map +1 -1
  9. package/dist/core/framework.d.ts +26 -0
  10. package/dist/core/framework.js +195 -6
  11. package/dist/core/framework.js.map +1 -1
  12. package/dist/core/http/http-server.d.ts +17 -0
  13. package/dist/core/http/http-server.js +21 -1
  14. package/dist/core/http/http-server.js.map +1 -1
  15. package/dist/core/http/http2-server.d.ts +13 -0
  16. package/dist/core/http/http2-server.js +11 -0
  17. package/dist/core/http/http2-server.js.map +1 -1
  18. package/dist/core/http/moro-engine-server.d.ts +28 -0
  19. package/dist/core/http/moro-engine-server.js +115 -11
  20. package/dist/core/http/moro-engine-server.js.map +1 -1
  21. package/dist/core/http/utils/body-type.d.ts +12 -0
  22. package/dist/core/http/utils/body-type.js +70 -0
  23. package/dist/core/http/utils/body-type.js.map +1 -0
  24. package/dist/core/http/utils/ssl-config.d.ts +11 -0
  25. package/dist/core/http/utils/ssl-config.js +3 -0
  26. package/dist/core/http/utils/ssl-config.js.map +1 -1
  27. package/dist/core/http/utils/uws-worker-clustering.js +5 -4
  28. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
  29. package/dist/core/http/uws-http-server.d.ts +3 -0
  30. package/dist/core/http/uws-http-server.js +22 -1
  31. package/dist/core/http/uws-http-server.js.map +1 -1
  32. package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
  33. package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
  34. package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
  35. package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
  36. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
  37. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  38. package/dist/core/middleware/built-in/compression/core.js +5 -0
  39. package/dist/core/middleware/built-in/compression/core.js.map +1 -1
  40. package/dist/core/middleware/built-in/session/core.d.ts +1 -0
  41. package/dist/core/middleware/built-in/session/core.js.map +1 -1
  42. package/dist/core/middleware/built-in/static/core.d.ts +18 -0
  43. package/dist/core/middleware/built-in/static/core.js +128 -7
  44. package/dist/core/middleware/built-in/static/core.js.map +1 -1
  45. package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
  46. package/dist/core/networking/adapters/engine-adapter.js +40 -1
  47. package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
  48. package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
  49. package/dist/core/networking/adapters/socketio-adapter.js +10 -1
  50. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  51. package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
  52. package/dist/core/networking/adapters/uws-adapter.js +54 -3
  53. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  54. package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
  55. package/dist/core/networking/adapters/ws-adapter.js +97 -12
  56. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  57. package/dist/core/networking/websocket-adapter.d.ts +19 -1
  58. package/dist/core/networking/websocket-adapter.js.map +1 -1
  59. package/dist/core/routing/index.d.ts +12 -8
  60. package/dist/core/routing/index.js +7 -2
  61. package/dist/core/routing/index.js.map +1 -1
  62. package/dist/core/routing/unified-router.d.ts +46 -8
  63. package/dist/core/routing/unified-router.js +108 -32
  64. package/dist/core/routing/unified-router.js.map +1 -1
  65. package/dist/core/utilities/package-utils.d.ts +7 -0
  66. package/dist/core/utilities/package-utils.js +2 -0
  67. package/dist/core/utilities/package-utils.js.map +1 -1
  68. package/dist/core/workers/worker-manager.js +2 -2
  69. package/dist/core/workers/worker-manager.js.map +1 -1
  70. package/dist/index.d.ts +4 -3
  71. package/dist/index.js +2 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/moro.d.ts +12 -1
  74. package/dist/moro.js +63 -7
  75. package/dist/moro.js.map +1 -1
  76. package/dist/types/cache.d.ts +2 -0
  77. package/dist/types/cache.js.map +1 -1
  78. package/dist/types/config.d.ts +9 -0
  79. package/dist/types/config.js.map +1 -1
  80. package/dist/types/http.d.ts +6 -0
  81. package/dist/types/http.js.map +1 -1
  82. package/dist/types/session.d.ts +1 -0
  83. package/dist/types/session.js.map +1 -1
  84. package/package.json +1 -1
@@ -2,6 +2,12 @@ import * as fs from 'fs/promises';
2
2
  import { createReadStream } from 'fs';
3
3
  import * as path from 'path';
4
4
  import * as crypto from 'crypto';
5
+ import { isCompressible, negotiateEncoding } from '../../../http/utils/compression.js';
6
+ /** Sidecar suffix per encoding, in preference order. */
7
+ const SIDECARS = [
8
+ { encoding: 'br', suffix: '.br' },
9
+ { encoding: 'gzip', suffix: '.gz' },
10
+ ];
5
11
  export class StaticCore {
6
12
  root;
7
13
  prefix;
@@ -11,26 +17,68 @@ export class StaticCore {
11
17
  etag;
12
18
  lastModified;
13
19
  acceptRanges;
20
+ precompressed;
14
21
  /** Files at or below this size are read in one go instead of streamed. */
15
22
  static STREAM_THRESHOLD = 512 * 1024;
16
23
  mimeTypes = {
24
+ // Documents / text
17
25
  '.html': 'text/html',
26
+ '.htm': 'text/html',
27
+ '.xhtml': 'application/xhtml+xml',
18
28
  '.css': 'text/css',
19
29
  '.js': 'application/javascript',
30
+ '.mjs': 'application/javascript',
31
+ '.cjs': 'application/javascript',
32
+ '.map': 'application/json',
20
33
  '.json': 'application/json',
34
+ '.jsonld': 'application/ld+json',
35
+ '.webmanifest': 'application/manifest+json',
36
+ '.xml': 'application/xml',
37
+ '.txt': 'text/plain',
38
+ '.md': 'text/markdown',
39
+ '.csv': 'text/csv',
40
+ '.yaml': 'application/yaml',
41
+ '.yml': 'application/yaml',
42
+ '.pdf': 'application/pdf',
43
+ '.wasm': 'application/wasm',
44
+ // Images
21
45
  '.png': 'image/png',
46
+ '.apng': 'image/apng',
22
47
  '.jpg': 'image/jpeg',
23
48
  '.jpeg': 'image/jpeg',
24
49
  '.gif': 'image/gif',
50
+ '.webp': 'image/webp',
51
+ '.avif': 'image/avif',
25
52
  '.svg': 'image/svg+xml',
26
53
  '.ico': 'image/x-icon',
27
- '.pdf': 'application/pdf',
28
- '.txt': 'text/plain',
29
- '.xml': 'application/xml',
54
+ '.bmp': 'image/bmp',
55
+ '.tif': 'image/tiff',
56
+ '.tiff': 'image/tiff',
57
+ // Fonts
30
58
  '.woff': 'font/woff',
31
59
  '.woff2': 'font/woff2',
32
60
  '.ttf': 'font/ttf',
61
+ '.otf': 'font/otf',
33
62
  '.eot': 'application/vnd.ms-fontobject',
63
+ // Audio / video
64
+ '.mp3': 'audio/mpeg',
65
+ '.wav': 'audio/wav',
66
+ '.flac': 'audio/flac',
67
+ '.m4a': 'audio/mp4',
68
+ '.aac': 'audio/aac',
69
+ '.oga': 'audio/ogg',
70
+ '.opus': 'audio/ogg',
71
+ '.weba': 'audio/webm',
72
+ '.mp4': 'video/mp4',
73
+ '.m4v': 'video/mp4',
74
+ '.webm': 'video/webm',
75
+ '.ogg': 'video/ogg',
76
+ '.ogv': 'video/ogg',
77
+ '.mov': 'video/quicktime',
78
+ // Archives
79
+ '.zip': 'application/zip',
80
+ '.gz': 'application/gzip',
81
+ '.tar': 'application/x-tar',
34
82
  };
35
83
  constructor(options) {
36
84
  this.root = path.resolve(options.root);
@@ -41,6 +89,7 @@ export class StaticCore {
41
89
  this.etag = options.etag !== false;
42
90
  this.lastModified = options.lastModified !== false;
43
91
  this.acceptRanges = options.acceptRanges !== false;
92
+ this.precompressed = options.precompressed === true;
44
93
  }
45
94
  async handleRequest(req, res) {
46
95
  // Only handle GET and HEAD requests
@@ -119,6 +168,18 @@ export class StaticCore {
119
168
  const baseMimeType = this.mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
120
169
  const contentType = this.addCharsetIfNeeded(baseMimeType);
121
170
  res.setHeader('Content-Type', contentType);
171
+ // Precompressed sidecar (app.js.br / app.js.gz next to app.js). Chosen
172
+ // before the validators so the ETag names the encoded representation;
173
+ // Range requests keep the identity file (byte offsets refer to it).
174
+ let sidecar = null;
175
+ if (this.precompressed) {
176
+ // The response now varies on Accept-Encoding whether or not a sidecar
177
+ // exists for this particular file - caches must key on it either way.
178
+ res.setHeader('Vary', 'Accept-Encoding');
179
+ if (!req.headers.range) {
180
+ sidecar = await this.findSidecar(filePath, stats.mtime, req.headers['accept-encoding']);
181
+ }
182
+ }
122
183
  // Cache headers
123
184
  if (this.maxAge > 0) {
124
185
  res.setHeader('Cache-Control', `public, max-age=${this.maxAge}`);
@@ -131,10 +192,12 @@ export class StaticCore {
131
192
  }
132
193
  let etag;
133
194
  if (this.etag) {
195
+ // A strong ETag must differ between representations, so the encoded
196
+ // sidecar gets its own (RFC 9110 8.8.1)
134
197
  etag = `"${crypto
135
198
  .createHash('md5')
136
199
  .update(`${stats.mtime.getTime()}-${stats.size}`)
137
- .digest('hex')}"`;
200
+ .digest('hex')}${sidecar ? `-${sidecar.encoding}` : ''}"`;
138
201
  res.setHeader('ETag', etag);
139
202
  }
140
203
  // Conditional requests. Content-Length is deliberately not set for a 304 —
@@ -167,9 +230,24 @@ export class StaticCore {
167
230
  }
168
231
  return true;
169
232
  }
170
- res.setHeader('Content-Length', stats.size);
233
+ if (sidecar) {
234
+ res.setHeader('Content-Encoding', sidecar.encoding);
235
+ res.setHeader('Content-Length', sidecar.stats.size);
236
+ if (req.method === 'HEAD') {
237
+ res.end();
238
+ return true;
239
+ }
240
+ if (sidecar.stats.size <= StaticCore.STREAM_THRESHOLD || !StaticCore.canStream(res)) {
241
+ res.end(await fs.readFile(sidecar.path));
242
+ }
243
+ else {
244
+ await this.streamFile(sidecar.path, res);
245
+ }
246
+ return true;
247
+ }
171
248
  // Handle HEAD requests
172
249
  if (req.method === 'HEAD') {
250
+ res.setHeader('Content-Length', stats.size);
173
251
  res.end();
174
252
  return true;
175
253
  }
@@ -178,9 +256,19 @@ export class StaticCore {
178
256
  // doesn't cost its full size in resident memory per request.
179
257
  if (stats.size <= StaticCore.STREAM_THRESHOLD || !StaticCore.canStream(res)) {
180
258
  const data = await fs.readFile(filePath);
181
- res.end(data);
259
+ if (isCompressible(baseMimeType) && typeof res.send === 'function') {
260
+ // Through send() the body joins the server's response compression
261
+ // (performance.compression / the compression middleware) exactly
262
+ // like a handler's body would; send() sets Content-Length itself.
263
+ res.send(data);
264
+ }
265
+ else {
266
+ res.setHeader('Content-Length', stats.size);
267
+ res.end(data);
268
+ }
182
269
  }
183
270
  else {
271
+ res.setHeader('Content-Length', stats.size);
184
272
  await this.streamFile(filePath, res);
185
273
  }
186
274
  return true;
@@ -190,6 +278,36 @@ export class StaticCore {
190
278
  return true;
191
279
  }
192
280
  }
281
+ /**
282
+ * The best precompressed sidecar for `filePath` that the client accepts, or
283
+ * null. A sidecar must be a regular file INSIDE the root (its real path is
284
+ * checked like the source's, so a symlinked `.br` cannot reach outside) and
285
+ * at least as new as the source, so a replaced asset never ships a stale
286
+ * compressed body.
287
+ */
288
+ async findSidecar(filePath, sourceMtime, acceptEncoding) {
289
+ if (!acceptEncoding)
290
+ return null;
291
+ const rootWithSep = this.root.endsWith(path.sep) ? this.root : this.root + path.sep;
292
+ for (const { encoding, suffix } of SIDECARS) {
293
+ if (negotiateEncoding(acceptEncoding, [encoding]) !== encoding)
294
+ continue;
295
+ const candidate = filePath + suffix;
296
+ try {
297
+ const real = await fs.realpath(candidate);
298
+ if (!real.startsWith(rootWithSep))
299
+ continue;
300
+ const st = await fs.stat(real);
301
+ if (!st.isFile() || st.mtime.getTime() < sourceMtime.getTime())
302
+ continue;
303
+ return { path: real, encoding, stats: { size: st.size } };
304
+ }
305
+ catch {
306
+ // no sidecar for this encoding
307
+ }
308
+ }
309
+ return null;
310
+ }
193
311
  /**
194
312
  * Whether the client's cached copy is still current. `If-None-Match` wins
195
313
  * over `If-Modified-Since` when both are present (RFC 9110 13.1.3).
@@ -344,9 +462,12 @@ export class StaticCore {
344
462
  'application/json',
345
463
  'application/javascript',
346
464
  'application/xml',
465
+ 'application/yaml',
347
466
  'image/svg+xml',
348
467
  ];
349
- const needsCharset = textTypes.some(type => mimeType.startsWith(type));
468
+ const needsCharset = textTypes.some(type => mimeType.startsWith(type)) ||
469
+ mimeType.endsWith('+json') ||
470
+ mimeType.endsWith('+xml');
350
471
  return needsCharset ? `${mimeType}; charset=utf-8` : mimeType;
351
472
  }
352
473
  getRoot() {
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/static/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Static File Serving Core Logic\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport * as fs from 'fs/promises';\nimport { createReadStream } from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\n\nexport interface StaticOptions {\n root: string;\n /**\n * URL path the files are served under, e.g. `/assets`. Requests outside the\n * prefix fall through untouched; inside it, the prefix is stripped before the\n * path is resolved against `root` (`/assets/app.css` -> `<root>/app.css`).\n * Matching is exact and case-sensitive. Defaults to the URL root.\n */\n prefix?: string;\n maxAge?: number;\n index?: string[];\n dotfiles?: 'allow' | 'deny' | 'ignore';\n etag?: boolean;\n /**\n * Send `Last-Modified` and answer `If-Modified-Since` with a 304.\n * Default true.\n */\n lastModified?: boolean;\n /**\n * Advertise `Accept-Ranges: bytes` and serve `Range` requests as 206\n * responses — what media seeking and resumable downloads need. A request for\n * multiple ranges is answered with the whole entity. Default true.\n */\n acceptRanges?: boolean;\n}\n\nexport class StaticCore {\n private root: string;\n private prefix: string;\n private maxAge: number;\n private index: string[];\n private dotfiles: 'allow' | 'deny' | 'ignore';\n private etag: boolean;\n private lastModified: boolean;\n private acceptRanges: boolean;\n\n /** Files at or below this size are read in one go instead of streamed. */\n private static readonly STREAM_THRESHOLD = 512 * 1024;\n\n private mimeTypes: Record<string, string> = {\n '.html': 'text/html',\n '.css': 'text/css',\n '.js': 'application/javascript',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.pdf': 'application/pdf',\n '.txt': 'text/plain',\n '.xml': 'application/xml',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.eot': 'application/vnd.ms-fontobject',\n };\n\n constructor(options: StaticOptions) {\n this.root = path.resolve(options.root);\n this.prefix = StaticCore.normalizePrefix(options.prefix);\n this.maxAge = options.maxAge || 0;\n this.index = options.index || ['index.html', 'index.htm'];\n this.dotfiles = options.dotfiles || 'ignore';\n this.etag = options.etag !== false;\n this.lastModified = options.lastModified !== false;\n this.acceptRanges = options.acceptRanges !== false;\n }\n\n async handleRequest(req: HttpRequest, res: HttpResponse): Promise<boolean> {\n // Only handle GET and HEAD requests\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n return false;\n }\n\n // Strip the mount prefix; anything outside it is not ours to serve\n const urlPath = this.stripPrefix(req.path);\n if (urlPath === null) {\n return false;\n }\n\n try {\n let filePath = path.join(this.root, urlPath);\n\n // Security: prevent directory traversal. The trailing separator is required —\n // a bare prefix check would allow sibling dirs like `/app/static-backups` to\n // satisfy `startsWith('/app/static')` and escape the root.\n const rootWithSep = this.root.endsWith(path.sep) ? this.root : this.root + path.sep;\n if (filePath !== this.root && !filePath.startsWith(rootWithSep)) {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n }\n\n // Security: resolve symlinks and re-check path to prevent symlink-based traversal\n try {\n const realPath = await fs.realpath(filePath);\n if (realPath !== this.root && !realPath.startsWith(rootWithSep)) {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n }\n filePath = realPath;\n } catch {\n // realpath fails if file doesn't exist — handled by stat below\n }\n\n // Handle dotfiles\n const basename = path.basename(filePath);\n if (basename.startsWith('.')) {\n if (this.dotfiles === 'deny') {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n } else if (this.dotfiles === 'ignore') {\n return false;\n }\n }\n\n let stats;\n try {\n stats = await fs.stat(filePath);\n } catch {\n return false; // File not found, let other middleware handle\n }\n\n // Handle directories\n if (stats.isDirectory()) {\n let indexFound = false;\n\n for (const indexFile of this.index) {\n const indexPath = path.join(filePath, indexFile);\n try {\n const indexStats = await fs.stat(indexPath);\n if (indexStats.isFile()) {\n filePath = indexPath;\n stats = indexStats;\n indexFound = true;\n break;\n }\n } catch {\n // Continue to next index file\n }\n }\n\n if (!indexFound) {\n return false;\n }\n }\n\n // Get mime type and add charset for text files\n const ext = path.extname(filePath);\n const baseMimeType = this.mimeTypes[ext.toLowerCase()] || 'application/octet-stream';\n const contentType = this.addCharsetIfNeeded(baseMimeType);\n\n res.setHeader('Content-Type', contentType);\n\n // Cache headers\n if (this.maxAge > 0) {\n res.setHeader('Cache-Control', `public, max-age=${this.maxAge}`);\n }\n\n if (this.lastModified) {\n res.setHeader('Last-Modified', stats.mtime.toUTCString());\n }\n\n if (this.acceptRanges) {\n res.setHeader('Accept-Ranges', 'bytes');\n }\n\n let etag: string | undefined;\n if (this.etag) {\n etag = `\"${crypto\n .createHash('md5')\n .update(`${stats.mtime.getTime()}-${stats.size}`)\n .digest('hex')}\"`;\n res.setHeader('ETag', etag);\n }\n\n // Conditional requests. Content-Length is deliberately not set for a 304 —\n // the response carries no body.\n if (this.isNotModified(req, stats.mtime, etag)) {\n res.statusCode = 304;\n res.end();\n return true;\n }\n\n const range = this.acceptRanges ? this.resolveRange(req, stats, etag) : null;\n\n if (range === 'unsatisfiable') {\n res.setHeader('Content-Range', `bytes */${stats.size}`);\n res.status(416).json({ success: false, error: 'Range not satisfiable' });\n return true;\n }\n\n if (range) {\n const chunkSize = range.end - range.start + 1;\n res.statusCode = 206;\n res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${stats.size}`);\n res.setHeader('Content-Length', chunkSize);\n\n if (req.method === 'HEAD') {\n res.end();\n return true;\n }\n\n if (StaticCore.canStream(res)) {\n await this.streamFile(filePath, res, range);\n } else {\n res.end(await StaticCore.readSlice(filePath, range.start, chunkSize));\n }\n return true;\n }\n\n res.setHeader('Content-Length', stats.size);\n\n // Handle HEAD requests\n if (req.method === 'HEAD') {\n res.end();\n return true;\n }\n\n // Small files take the one-read/one-write path — the common case for\n // assets, and the cheapest. Larger ones stream, so serving a big file\n // doesn't cost its full size in resident memory per request.\n if (stats.size <= StaticCore.STREAM_THRESHOLD || !StaticCore.canStream(res)) {\n const data = await fs.readFile(filePath);\n res.end(data);\n } else {\n await this.streamFile(filePath, res);\n }\n return true;\n } catch {\n res.status(500).json({ success: false, error: 'Internal server error' });\n return true;\n }\n }\n\n /**\n * Whether the client's cached copy is still current. `If-None-Match` wins\n * over `If-Modified-Since` when both are present (RFC 9110 13.1.3).\n */\n private isNotModified(req: HttpRequest, mtime: Date, etag?: string): boolean {\n const ifNoneMatch = req.headers['if-none-match'];\n if (etag && ifNoneMatch) {\n if (ifNoneMatch === '*') return true;\n return ifNoneMatch\n .split(',')\n .some(candidate => candidate.trim().replace(/^W\\//, '') === etag);\n }\n\n if (this.lastModified) {\n const ifModifiedSince = req.headers['if-modified-since'];\n if (ifModifiedSince) {\n const since = Date.parse(ifModifiedSince);\n // HTTP dates carry second precision, so compare at that resolution\n if (!Number.isNaN(since)) {\n return Math.floor(mtime.getTime() / 1000) <= Math.floor(since / 1000);\n }\n }\n }\n\n return false;\n }\n\n /**\n * The byte range to serve, `'unsatisfiable'` for a 416, or null to send the\n * whole entity — which is also the answer for a multi-range request and for\n * an `If-Range` that no longer matches.\n */\n private resolveRange(\n req: HttpRequest,\n stats: { size: number; mtime: Date },\n etag?: string\n ): { start: number; end: number } | 'unsatisfiable' | null {\n const rangeHeader = req.headers.range;\n if (!rangeHeader) return null;\n\n const ifRange = req.headers['if-range'];\n if (ifRange) {\n const asDate = Date.parse(ifRange);\n const matches =\n (etag !== undefined && ifRange.trim() === etag) ||\n (!Number.isNaN(asDate) &&\n Math.floor(stats.mtime.getTime() / 1000) <= Math.floor(asDate / 1000));\n if (!matches) return null;\n }\n\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n if (!match) return null;\n\n const size = stats.size;\n const [, rawStart, rawEnd] = match;\n let start: number;\n let end: number;\n\n if (rawStart === '') {\n // Suffix form: the last N bytes\n const suffix = parseInt(rawEnd as string, 10);\n if (!suffix) return 'unsatisfiable';\n start = Math.max(size - suffix, 0);\n end = size - 1;\n } else {\n start = parseInt(rawStart as string, 10);\n end = rawEnd === '' ? size - 1 : Math.min(parseInt(rawEnd as string, 10), size - 1);\n }\n\n if (Number.isNaN(start) || Number.isNaN(end) || start > end || start >= size) {\n return 'unsatisfiable';\n }\n\n return { start, end };\n }\n\n /**\n * Pipe a file (or a slice of one) to the response, closing it on abort.\n * Resolves once the response is done — the caller must not return before\n * then, or the router would see an untouched response and 404 the request.\n */\n private streamFile(\n filePath: string,\n res: HttpResponse,\n range?: { start: number; end: number }\n ): Promise<void> {\n const stream = range\n ? createReadStream(filePath, { start: range.start, end: range.end })\n : createReadStream(filePath);\n\n return new Promise<void>(resolve => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n\n // A client that disconnects mid-download must not leak the descriptor\n (res as any).once?.('close', () => {\n stream.destroy();\n done();\n });\n\n stream.on('error', () => {\n if (!res.headersSent) {\n res.status(500).json({ success: false, error: 'Internal server error' });\n } else {\n res.end();\n }\n done();\n });\n\n stream.on('end', done);\n stream.pipe(res as any);\n });\n }\n\n /** Read just the requested slice — the fallback where piping isn't available. */\n private static async readSlice(filePath: string, start: number, length: number): Promise<Buffer> {\n const handle = await fs.open(filePath, 'r');\n try {\n const buffer = Buffer.allocUnsafe(length);\n const { bytesRead } = await handle.read(buffer, 0, length, start);\n return bytesRead === length ? buffer : buffer.subarray(0, bytesRead);\n } finally {\n await handle.close();\n }\n }\n\n /** HTTP/2 responses are plain objects with no stream plumbing to pipe into. */\n private static canStream(res: HttpResponse): boolean {\n const writable = res as any;\n return typeof writable.write === 'function' && typeof writable.on === 'function';\n }\n\n /**\n * `/assets/` -> `/assets`, `/` and `''` -> `''` (serve from the URL root).\n * A prefix given without a leading slash still mounts where you'd expect.\n */\n private static normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n let normalized = prefix.trim();\n if (!normalized.startsWith('/')) normalized = `/${normalized}`;\n while (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized === '/' ? '' : normalized;\n }\n\n /**\n * Request path relative to the mount point, or `null` when the request falls\n * outside it. `/assets` itself maps to `/` so the directory index still\n * resolves; `/assetsfoo` is a different route and is left alone.\n */\n private stripPrefix(reqPath: string): string | null {\n if (!this.prefix) return reqPath;\n if (reqPath === this.prefix) return '/';\n if (reqPath.startsWith(`${this.prefix}/`)) return reqPath.slice(this.prefix.length);\n return null;\n }\n\n private addCharsetIfNeeded(mimeType: string): string {\n const textTypes = [\n 'text/',\n 'application/json',\n 'application/javascript',\n 'application/xml',\n 'image/svg+xml',\n ];\n const needsCharset = textTypes.some(type => mimeType.startsWith(type));\n return needsCharset ? `${mimeType}; charset=utf-8` : mimeType;\n }\n\n getRoot(): string {\n return this.root;\n }\n\n getPrefix(): string {\n return this.prefix;\n }\n}\n"]}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../../src/core/middleware/built-in/static/core.ts"],"names":[],"mappings":"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","sourcesContent":["// Static File Serving Core Logic\nimport { HttpRequest, HttpResponse } from '../../../../types/http.js';\nimport * as fs from 'fs/promises';\nimport { createReadStream } from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\nimport { isCompressible, negotiateEncoding } from '../../../http/utils/compression.js';\n\nexport interface StaticOptions {\n root: string;\n /**\n * URL path the files are served under, e.g. `/assets`. Requests outside the\n * prefix fall through untouched; inside it, the prefix is stripped before the\n * path is resolved against `root` (`/assets/app.css` -> `<root>/app.css`).\n * Matching is exact and case-sensitive. Defaults to the URL root.\n */\n prefix?: string;\n maxAge?: number;\n index?: string[];\n dotfiles?: 'allow' | 'deny' | 'ignore';\n etag?: boolean;\n /**\n * Send `Last-Modified` and answer `If-Modified-Since` with a 304.\n * Default true.\n */\n lastModified?: boolean;\n /**\n * Advertise `Accept-Ranges: bytes` and serve `Range` requests as 206\n * responses — what media seeking and resumable downloads need. A request for\n * multiple ranges is answered with the whole entity. Default true.\n */\n acceptRanges?: boolean;\n /**\n * Serve precompressed sidecars: when `app.js.br` or `app.js.gz` sits next\n * to `app.js` and the client accepts that encoding, the sidecar is sent\n * with the original's Content-Type, the matching Content-Encoding and\n * `Vary: Accept-Encoding` (brotli preferred, then gzip). A sidecar older\n * than its source is ignored, so a replaced file is never served stale.\n * Range requests always get the identity file. Default false.\n */\n precompressed?: boolean;\n}\n\n/** Sidecar suffix per encoding, in preference order. */\nconst SIDECARS: Array<{ encoding: 'br' | 'gzip'; suffix: string }> = [\n { encoding: 'br', suffix: '.br' },\n { encoding: 'gzip', suffix: '.gz' },\n];\n\nexport class StaticCore {\n private root: string;\n private prefix: string;\n private maxAge: number;\n private index: string[];\n private dotfiles: 'allow' | 'deny' | 'ignore';\n private etag: boolean;\n private lastModified: boolean;\n private acceptRanges: boolean;\n private precompressed: boolean;\n\n /** Files at or below this size are read in one go instead of streamed. */\n private static readonly STREAM_THRESHOLD = 512 * 1024;\n\n private mimeTypes: Record<string, string> = {\n // Documents / text\n '.html': 'text/html',\n '.htm': 'text/html',\n '.xhtml': 'application/xhtml+xml',\n '.css': 'text/css',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.cjs': 'application/javascript',\n '.map': 'application/json',\n '.json': 'application/json',\n '.jsonld': 'application/ld+json',\n '.webmanifest': 'application/manifest+json',\n '.xml': 'application/xml',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.csv': 'text/csv',\n '.yaml': 'application/yaml',\n '.yml': 'application/yaml',\n '.pdf': 'application/pdf',\n '.wasm': 'application/wasm',\n // Images\n '.png': 'image/png',\n '.apng': 'image/apng',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.bmp': 'image/bmp',\n '.tif': 'image/tiff',\n '.tiff': 'image/tiff',\n // Fonts\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.otf': 'font/otf',\n '.eot': 'application/vnd.ms-fontobject',\n // Audio / video\n '.mp3': 'audio/mpeg',\n '.wav': 'audio/wav',\n '.flac': 'audio/flac',\n '.m4a': 'audio/mp4',\n '.aac': 'audio/aac',\n '.oga': 'audio/ogg',\n '.opus': 'audio/ogg',\n '.weba': 'audio/webm',\n '.mp4': 'video/mp4',\n '.m4v': 'video/mp4',\n '.webm': 'video/webm',\n '.ogg': 'video/ogg',\n '.ogv': 'video/ogg',\n '.mov': 'video/quicktime',\n // Archives\n '.zip': 'application/zip',\n '.gz': 'application/gzip',\n '.tar': 'application/x-tar',\n };\n\n constructor(options: StaticOptions) {\n this.root = path.resolve(options.root);\n this.prefix = StaticCore.normalizePrefix(options.prefix);\n this.maxAge = options.maxAge || 0;\n this.index = options.index || ['index.html', 'index.htm'];\n this.dotfiles = options.dotfiles || 'ignore';\n this.etag = options.etag !== false;\n this.lastModified = options.lastModified !== false;\n this.acceptRanges = options.acceptRanges !== false;\n this.precompressed = options.precompressed === true;\n }\n\n async handleRequest(req: HttpRequest, res: HttpResponse): Promise<boolean> {\n // Only handle GET and HEAD requests\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n return false;\n }\n\n // Strip the mount prefix; anything outside it is not ours to serve\n const urlPath = this.stripPrefix(req.path);\n if (urlPath === null) {\n return false;\n }\n\n try {\n let filePath = path.join(this.root, urlPath);\n\n // Security: prevent directory traversal. The trailing separator is required —\n // a bare prefix check would allow sibling dirs like `/app/static-backups` to\n // satisfy `startsWith('/app/static')` and escape the root.\n const rootWithSep = this.root.endsWith(path.sep) ? this.root : this.root + path.sep;\n if (filePath !== this.root && !filePath.startsWith(rootWithSep)) {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n }\n\n // Security: resolve symlinks and re-check path to prevent symlink-based traversal\n try {\n const realPath = await fs.realpath(filePath);\n if (realPath !== this.root && !realPath.startsWith(rootWithSep)) {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n }\n filePath = realPath;\n } catch {\n // realpath fails if file doesn't exist — handled by stat below\n }\n\n // Handle dotfiles\n const basename = path.basename(filePath);\n if (basename.startsWith('.')) {\n if (this.dotfiles === 'deny') {\n res.status(403).json({ success: false, error: 'Forbidden' });\n return true;\n } else if (this.dotfiles === 'ignore') {\n return false;\n }\n }\n\n let stats;\n try {\n stats = await fs.stat(filePath);\n } catch {\n return false; // File not found, let other middleware handle\n }\n\n // Handle directories\n if (stats.isDirectory()) {\n let indexFound = false;\n\n for (const indexFile of this.index) {\n const indexPath = path.join(filePath, indexFile);\n try {\n const indexStats = await fs.stat(indexPath);\n if (indexStats.isFile()) {\n filePath = indexPath;\n stats = indexStats;\n indexFound = true;\n break;\n }\n } catch {\n // Continue to next index file\n }\n }\n\n if (!indexFound) {\n return false;\n }\n }\n\n // Get mime type and add charset for text files\n const ext = path.extname(filePath);\n const baseMimeType = this.mimeTypes[ext.toLowerCase()] || 'application/octet-stream';\n const contentType = this.addCharsetIfNeeded(baseMimeType);\n\n res.setHeader('Content-Type', contentType);\n\n // Precompressed sidecar (app.js.br / app.js.gz next to app.js). Chosen\n // before the validators so the ETag names the encoded representation;\n // Range requests keep the identity file (byte offsets refer to it).\n let sidecar: { path: string; encoding: string; stats: { size: number } } | null = null;\n if (this.precompressed) {\n // The response now varies on Accept-Encoding whether or not a sidecar\n // exists for this particular file - caches must key on it either way.\n res.setHeader('Vary', 'Accept-Encoding');\n if (!req.headers.range) {\n sidecar = await this.findSidecar(filePath, stats.mtime, req.headers['accept-encoding']);\n }\n }\n\n // Cache headers\n if (this.maxAge > 0) {\n res.setHeader('Cache-Control', `public, max-age=${this.maxAge}`);\n }\n\n if (this.lastModified) {\n res.setHeader('Last-Modified', stats.mtime.toUTCString());\n }\n\n if (this.acceptRanges) {\n res.setHeader('Accept-Ranges', 'bytes');\n }\n\n let etag: string | undefined;\n if (this.etag) {\n // A strong ETag must differ between representations, so the encoded\n // sidecar gets its own (RFC 9110 8.8.1)\n etag = `\"${crypto\n .createHash('md5')\n .update(`${stats.mtime.getTime()}-${stats.size}`)\n .digest('hex')}${sidecar ? `-${sidecar.encoding}` : ''}\"`;\n res.setHeader('ETag', etag);\n }\n\n // Conditional requests. Content-Length is deliberately not set for a 304 —\n // the response carries no body.\n if (this.isNotModified(req, stats.mtime, etag)) {\n res.statusCode = 304;\n res.end();\n return true;\n }\n\n const range = this.acceptRanges ? this.resolveRange(req, stats, etag) : null;\n\n if (range === 'unsatisfiable') {\n res.setHeader('Content-Range', `bytes */${stats.size}`);\n res.status(416).json({ success: false, error: 'Range not satisfiable' });\n return true;\n }\n\n if (range) {\n const chunkSize = range.end - range.start + 1;\n res.statusCode = 206;\n res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${stats.size}`);\n res.setHeader('Content-Length', chunkSize);\n\n if (req.method === 'HEAD') {\n res.end();\n return true;\n }\n\n if (StaticCore.canStream(res)) {\n await this.streamFile(filePath, res, range);\n } else {\n res.end(await StaticCore.readSlice(filePath, range.start, chunkSize));\n }\n return true;\n }\n\n if (sidecar) {\n res.setHeader('Content-Encoding', sidecar.encoding);\n res.setHeader('Content-Length', sidecar.stats.size);\n if (req.method === 'HEAD') {\n res.end();\n return true;\n }\n if (sidecar.stats.size <= StaticCore.STREAM_THRESHOLD || !StaticCore.canStream(res)) {\n res.end(await fs.readFile(sidecar.path));\n } else {\n await this.streamFile(sidecar.path, res);\n }\n return true;\n }\n\n // Handle HEAD requests\n if (req.method === 'HEAD') {\n res.setHeader('Content-Length', stats.size);\n res.end();\n return true;\n }\n\n // Small files take the one-read/one-write path — the common case for\n // assets, and the cheapest. Larger ones stream, so serving a big file\n // doesn't cost its full size in resident memory per request.\n if (stats.size <= StaticCore.STREAM_THRESHOLD || !StaticCore.canStream(res)) {\n const data = await fs.readFile(filePath);\n if (isCompressible(baseMimeType) && typeof (res as any).send === 'function') {\n // Through send() the body joins the server's response compression\n // (performance.compression / the compression middleware) exactly\n // like a handler's body would; send() sets Content-Length itself.\n (res as any).send(data);\n } else {\n res.setHeader('Content-Length', stats.size);\n res.end(data);\n }\n } else {\n res.setHeader('Content-Length', stats.size);\n await this.streamFile(filePath, res);\n }\n return true;\n } catch {\n res.status(500).json({ success: false, error: 'Internal server error' });\n return true;\n }\n }\n\n /**\n * The best precompressed sidecar for `filePath` that the client accepts, or\n * null. A sidecar must be a regular file INSIDE the root (its real path is\n * checked like the source's, so a symlinked `.br` cannot reach outside) and\n * at least as new as the source, so a replaced asset never ships a stale\n * compressed body.\n */\n private async findSidecar(\n filePath: string,\n sourceMtime: Date,\n acceptEncoding: string | undefined\n ): Promise<{ path: string; encoding: string; stats: { size: number } } | null> {\n if (!acceptEncoding) return null;\n const rootWithSep = this.root.endsWith(path.sep) ? this.root : this.root + path.sep;\n for (const { encoding, suffix } of SIDECARS) {\n if (negotiateEncoding(acceptEncoding, [encoding]) !== encoding) continue;\n const candidate = filePath + suffix;\n try {\n const real = await fs.realpath(candidate);\n if (!real.startsWith(rootWithSep)) continue;\n const st = await fs.stat(real);\n if (!st.isFile() || st.mtime.getTime() < sourceMtime.getTime()) continue;\n return { path: real, encoding, stats: { size: st.size } };\n } catch {\n // no sidecar for this encoding\n }\n }\n return null;\n }\n\n /**\n * Whether the client's cached copy is still current. `If-None-Match` wins\n * over `If-Modified-Since` when both are present (RFC 9110 13.1.3).\n */\n private isNotModified(req: HttpRequest, mtime: Date, etag?: string): boolean {\n const ifNoneMatch = req.headers['if-none-match'];\n if (etag && ifNoneMatch) {\n if (ifNoneMatch === '*') return true;\n return ifNoneMatch\n .split(',')\n .some(candidate => candidate.trim().replace(/^W\\//, '') === etag);\n }\n\n if (this.lastModified) {\n const ifModifiedSince = req.headers['if-modified-since'];\n if (ifModifiedSince) {\n const since = Date.parse(ifModifiedSince);\n // HTTP dates carry second precision, so compare at that resolution\n if (!Number.isNaN(since)) {\n return Math.floor(mtime.getTime() / 1000) <= Math.floor(since / 1000);\n }\n }\n }\n\n return false;\n }\n\n /**\n * The byte range to serve, `'unsatisfiable'` for a 416, or null to send the\n * whole entity — which is also the answer for a multi-range request and for\n * an `If-Range` that no longer matches.\n */\n private resolveRange(\n req: HttpRequest,\n stats: { size: number; mtime: Date },\n etag?: string\n ): { start: number; end: number } | 'unsatisfiable' | null {\n const rangeHeader = req.headers.range;\n if (!rangeHeader) return null;\n\n const ifRange = req.headers['if-range'];\n if (ifRange) {\n const asDate = Date.parse(ifRange);\n const matches =\n (etag !== undefined && ifRange.trim() === etag) ||\n (!Number.isNaN(asDate) &&\n Math.floor(stats.mtime.getTime() / 1000) <= Math.floor(asDate / 1000));\n if (!matches) return null;\n }\n\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n if (!match) return null;\n\n const size = stats.size;\n const [, rawStart, rawEnd] = match;\n let start: number;\n let end: number;\n\n if (rawStart === '') {\n // Suffix form: the last N bytes\n const suffix = parseInt(rawEnd as string, 10);\n if (!suffix) return 'unsatisfiable';\n start = Math.max(size - suffix, 0);\n end = size - 1;\n } else {\n start = parseInt(rawStart as string, 10);\n end = rawEnd === '' ? size - 1 : Math.min(parseInt(rawEnd as string, 10), size - 1);\n }\n\n if (Number.isNaN(start) || Number.isNaN(end) || start > end || start >= size) {\n return 'unsatisfiable';\n }\n\n return { start, end };\n }\n\n /**\n * Pipe a file (or a slice of one) to the response, closing it on abort.\n * Resolves once the response is done — the caller must not return before\n * then, or the router would see an untouched response and 404 the request.\n */\n private streamFile(\n filePath: string,\n res: HttpResponse,\n range?: { start: number; end: number }\n ): Promise<void> {\n const stream = range\n ? createReadStream(filePath, { start: range.start, end: range.end })\n : createReadStream(filePath);\n\n return new Promise<void>(resolve => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n\n // A client that disconnects mid-download must not leak the descriptor\n (res as any).once?.('close', () => {\n stream.destroy();\n done();\n });\n\n stream.on('error', () => {\n if (!res.headersSent) {\n res.status(500).json({ success: false, error: 'Internal server error' });\n } else {\n res.end();\n }\n done();\n });\n\n stream.on('end', done);\n stream.pipe(res as any);\n });\n }\n\n /** Read just the requested slice — the fallback where piping isn't available. */\n private static async readSlice(filePath: string, start: number, length: number): Promise<Buffer> {\n const handle = await fs.open(filePath, 'r');\n try {\n const buffer = Buffer.allocUnsafe(length);\n const { bytesRead } = await handle.read(buffer, 0, length, start);\n return bytesRead === length ? buffer : buffer.subarray(0, bytesRead);\n } finally {\n await handle.close();\n }\n }\n\n /** HTTP/2 responses are plain objects with no stream plumbing to pipe into. */\n private static canStream(res: HttpResponse): boolean {\n const writable = res as any;\n return typeof writable.write === 'function' && typeof writable.on === 'function';\n }\n\n /**\n * `/assets/` -> `/assets`, `/` and `''` -> `''` (serve from the URL root).\n * A prefix given without a leading slash still mounts where you'd expect.\n */\n private static normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n let normalized = prefix.trim();\n if (!normalized.startsWith('/')) normalized = `/${normalized}`;\n while (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized === '/' ? '' : normalized;\n }\n\n /**\n * Request path relative to the mount point, or `null` when the request falls\n * outside it. `/assets` itself maps to `/` so the directory index still\n * resolves; `/assetsfoo` is a different route and is left alone.\n */\n private stripPrefix(reqPath: string): string | null {\n if (!this.prefix) return reqPath;\n if (reqPath === this.prefix) return '/';\n if (reqPath.startsWith(`${this.prefix}/`)) return reqPath.slice(this.prefix.length);\n return null;\n }\n\n private addCharsetIfNeeded(mimeType: string): string {\n const textTypes = [\n 'text/',\n 'application/json',\n 'application/javascript',\n 'application/xml',\n 'application/yaml',\n 'image/svg+xml',\n ];\n const needsCharset =\n textTypes.some(type => mimeType.startsWith(type)) ||\n mimeType.endsWith('+json') ||\n mimeType.endsWith('+xml');\n return needsCharset ? `${mimeType}; charset=utf-8` : mimeType;\n }\n\n getRoot(): string {\n return this.root;\n }\n\n getPrefix(): string {\n return this.prefix;\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { WebSocketAdapter, WebSocketAdapterOptions, WebSocketNamespace } from '../websocket-adapter.js';
1
+ import { WebSocketAdapter, WebSocketAdapterOptions, WebSocketNamespace, WebSocketNamespaceOptions } from '../websocket-adapter.js';
2
2
  export declare class EngineWebSocketAdapter implements WebSocketAdapter {
3
3
  private server;
4
4
  private namespaces;
@@ -13,7 +13,7 @@ export declare class EngineWebSocketAdapter implements WebSocketAdapter {
13
13
  private handleClose;
14
14
  private generateId;
15
15
  private getDefaultNamespaceImpl;
16
- createNamespace(namespace: string): WebSocketNamespace;
16
+ createNamespace(namespace: string, options?: WebSocketNamespaceOptions): WebSocketNamespace;
17
17
  getDefaultNamespace(): WebSocketNamespace;
18
18
  close(): Promise<void>;
19
19
  setCompression(_enabled: boolean, _options?: any): void;
@@ -52,12 +52,21 @@ export class EngineWebSocketAdapter {
52
52
  const nsPath = (path || '/').split(/[?#]/)[0] || '/';
53
53
  const ns = this.namespaces.get(nsPath) ??
54
54
  this.getDefaultNamespaceImpl();
55
+ connection.raw = ns.raw;
55
56
  ns.handleConnection(connection);
56
57
  }
57
58
  handleMessage(wsId, data, isBinary) {
58
59
  const connection = this.byWsId.get(wsId);
59
60
  if (!connection)
60
61
  return;
62
+ if (connection.raw) {
63
+ // Raw namespace: no envelope, no parse - the frame is the message
64
+ if (isBinary)
65
+ connection.handleBinary(Buffer.isBuffer(data) ? data : Buffer.from(data));
66
+ else
67
+ connection.handleRawText(typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'));
68
+ return;
69
+ }
61
70
  if (isBinary) {
62
71
  connection.handleBinary(data);
63
72
  return;
@@ -91,12 +100,14 @@ export class EngineWebSocketAdapter {
91
100
  getDefaultNamespaceImpl() {
92
101
  return this.createNamespace('/');
93
102
  }
94
- createNamespace(namespace) {
103
+ createNamespace(namespace, options) {
95
104
  let ns = this.namespaces.get(namespace);
96
105
  if (!ns) {
97
106
  ns = new EngineNamespaceWrapper(namespace, this.connections);
98
107
  this.namespaces.set(namespace, ns);
99
108
  }
109
+ if (options?.raw)
110
+ ns.raw = true;
100
111
  return ns;
101
112
  }
102
113
  getDefaultNamespace() {
@@ -130,6 +141,8 @@ export class EngineWebSocketAdapter {
130
141
  class EngineNamespaceWrapper {
131
142
  namespace;
132
143
  connections;
144
+ /** Raw framing for connections routed here (see WebSocketNamespaceOptions) */
145
+ raw = false;
133
146
  connectionHandlers = [];
134
147
  middlewares = [];
135
148
  logger = createFrameworkLogger('ENGINE_WS_NS');
@@ -212,6 +225,8 @@ class EngineConnectionWrapper {
212
225
  namespaces;
213
226
  data = {};
214
227
  connected = true;
228
+ /** Raw framing (set from the namespace at open) */
229
+ raw = false;
215
230
  headers;
216
231
  query;
217
232
  rooms = new Set();
@@ -271,6 +286,30 @@ class EngineConnectionWrapper {
271
286
  compressedEmit(event, data) {
272
287
  this.emit(event, data);
273
288
  }
289
+ send(data, isBinary) {
290
+ if (!this.connected)
291
+ return;
292
+ try {
293
+ const binary = isBinary ?? typeof data !== 'string';
294
+ const sent = this.server.wsSend(this.wsId, data, binary);
295
+ if (!sent)
296
+ this.logger.warn(`Backpressure for connection ${this.id}`, 'Backpressure');
297
+ }
298
+ catch (error) {
299
+ this.logger.error(`Failed to send frame: ${error instanceof Error ? error.message : String(error)}`, 'Send');
300
+ }
301
+ }
302
+ /** Raw namespace: a text frame reaches the 'message' handlers verbatim */
303
+ handleRawText(text) {
304
+ if (!this.connected)
305
+ return;
306
+ for (const handler of this.anyHandlers)
307
+ this.runHandler(handler, 'message', text);
308
+ const handlers = this.eventHandlers.get('message');
309
+ if (handlers)
310
+ for (const handler of handlers)
311
+ this.runHandler(handler, text);
312
+ }
274
313
  // A user handler throwing must not kill the connection, skip sibling
275
314
  // handlers, or (via the native callback) get swallowed silently.
276
315
  runHandler(fn, ...args) {
@@ -1 +1 @@
1
- {"version":3,"file":"engine-adapter.js","sourceRoot":"","sources":["../../../../src/core/networking/adapters/engine-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["// @morojs/engine WebSocket adapter.\n// Bridges the native engine's RFC 6455 WebSocket callbacks (onWsOpen/Message/\n// Close, wsSend/wsClose on MoroEngineServer) to Moro's WebSocketAdapter model.\n// Message framing mirrors the uWS adapter: JSON { event, data } envelopes with\n// a default namespace, so app.websocket() handlers work identically.\n\nimport crypto from 'crypto';\nimport {\n WebSocketAdapter,\n WebSocketAdapterOptions,\n WebSocketNamespace,\n WebSocketConnection,\n WebSocketEmitter,\n WebSocketMiddleware,\n} from '../websocket-adapter.js';\nimport { createFrameworkLogger } from '../../logger/index.js';\n\n// Parse a raw upgrade query string (`orgId=1&x=y`, with or without a leading\n// `?`) into a flat object for `socket.handshake.query`. Last value wins on\n// duplicate keys, matching URLSearchParams / Socket.IO.\nfunction parseQueryString(qs: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!qs) return out;\n const params = new URLSearchParams(qs.startsWith('?') ? qs.slice(1) : qs);\n for (const [key, value] of params) out[key] = value;\n return out;\n}\n\nexport class EngineWebSocketAdapter implements WebSocketAdapter {\n private server: any; // MoroEngineServer\n private namespaces = new Map<string, EngineNamespaceWrapper>();\n private connections = new Map<string, EngineConnectionWrapper>();\n private byWsId = new Map<number, EngineConnectionWrapper>();\n private logger = createFrameworkLogger('ENGINE_WS');\n private customIdGenerator?: () => string;\n private connectionCounter = 0;\n\n async initialize(httpServer: any, _options: WebSocketAdapterOptions = {}): Promise<void> {\n if (!httpServer || typeof httpServer.enableWebSocket !== 'function') {\n throw new Error(\n 'EngineWebSocketAdapter requires the @morojs/engine HTTP server (MoroEngineServer)'\n );\n }\n this.server = httpServer;\n\n // Register the native WS bridge: the engine drives these on the event loop.\n this.server.enableWebSocket({\n onOpen: (\n wsId: number,\n path: string,\n info?: { ip: string; headers: Record<string, string>; query?: string }\n ) => this.handleOpen(wsId, path, info),\n onMessage: (wsId: number, data: any, isBinary: boolean) =>\n this.handleMessage(wsId, data, isBinary),\n onClose: (wsId: number, code: number) => this.handleClose(wsId, code),\n });\n\n this.createNamespace('/');\n this.logger.info('Engine WebSocket adapter initialized', 'Init');\n }\n\n private handleOpen(\n wsId: number,\n path: string,\n info?: { ip: string; headers: Record<string, string>; query?: string }\n ): void {\n const id = this.customIdGenerator ? this.customIdGenerator() : this.generateId();\n const connection = new EngineConnectionWrapper(\n this.server,\n wsId,\n id,\n path,\n this.namespaces,\n info\n );\n this.connections.set(id, connection);\n this.byWsId.set(wsId, connection);\n // Route to the namespace whose registered path matches the upgrade URL\n // (Socket.IO parity for `app.websocket('/name', ...)`), falling back to the\n // default namespace when nothing matches. Query/hash are stripped so a\n // client connecting to `/name?token=…` still resolves to the `/name`\n // namespace.\n const nsPath = (path || '/').split(/[?#]/)[0] || '/';\n const ns =\n (this.namespaces.get(nsPath) as EngineNamespaceWrapper | undefined) ??\n this.getDefaultNamespaceImpl();\n ns.handleConnection(connection);\n }\n\n private handleMessage(wsId: number, data: any, isBinary: boolean): void {\n const connection = this.byWsId.get(wsId);\n if (!connection) return;\n if (isBinary) {\n connection.handleBinary(data);\n return;\n }\n // Text frames carry JSON { event, data } envelopes (uWS-adapter parity)\n const text = typeof data === 'string' ? data : Buffer.from(data).toString('utf-8');\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n this.logger.warn(`Non-JSON message from ${connection.id}`, 'Message');\n return;\n }\n connection.handleMessage(parsed);\n }\n\n private handleClose(wsId: number, code: number): void {\n const connection = this.byWsId.get(wsId);\n if (!connection) return;\n connection.connected = false;\n connection.handleDisconnect(code);\n this.byWsId.delete(wsId);\n this.connections.delete(connection.id);\n }\n\n private generateId(): string {\n return `${Date.now().toString(36)}-${(++this.connectionCounter).toString(36)}-${crypto\n .randomBytes(4)\n .toString('hex')}`;\n }\n\n private getDefaultNamespaceImpl(): EngineNamespaceWrapper {\n return this.createNamespace('/') as EngineNamespaceWrapper;\n }\n\n createNamespace(namespace: string): WebSocketNamespace {\n let ns = this.namespaces.get(namespace);\n if (!ns) {\n ns = new EngineNamespaceWrapper(namespace, this.connections);\n this.namespaces.set(namespace, ns);\n }\n return ns;\n }\n\n getDefaultNamespace(): WebSocketNamespace {\n return this.createNamespace('/');\n }\n\n async close(): Promise<void> {\n for (const conn of this.connections.values()) {\n conn.disconnect();\n }\n this.connections.clear();\n this.byWsId.clear();\n }\n\n setCompression(_enabled: boolean, _options?: any): void {\n // Compression (permessage-deflate) is negotiated natively; no-op for now.\n }\n\n setCustomIdGenerator(generator: () => string): void {\n this.customIdGenerator = generator;\n }\n\n getAdapterName(): string {\n return '@morojs/engine';\n }\n\n getConnectionCount(): number {\n let count = 0;\n for (const conn of this.connections.values()) {\n if (conn.connected) count++;\n }\n return count;\n }\n}\n\nclass EngineNamespaceWrapper implements WebSocketNamespace {\n private connectionHandlers: ((socket: WebSocketConnection) => void)[] = [];\n private middlewares: WebSocketMiddleware[] = [];\n private logger = createFrameworkLogger('ENGINE_WS_NS');\n\n constructor(\n private namespace: string,\n private connections: Map<string, EngineConnectionWrapper>\n ) {}\n\n on(event: 'connection', handler: (socket: WebSocketConnection) => void): void {\n if (event === 'connection') this.connectionHandlers.push(handler);\n }\n\n handleConnection(connection: EngineConnectionWrapper): void {\n this.runMiddlewares(connection, err => {\n if (err) {\n connection.disconnect(true);\n return;\n }\n // A throwing connection handler must not take down the socket/loop.\n for (const handler of this.connectionHandlers) {\n try {\n handler(connection);\n } catch (error) {\n this.logger.error(\n `WebSocket connection handler threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Connection'\n );\n }\n }\n });\n }\n\n private runMiddlewares(\n connection: EngineConnectionWrapper,\n finalCallback: (err?: Error) => void\n ): void {\n let index = 0;\n const next = (err?: Error) => {\n if (err) return finalCallback(err);\n if (index >= this.middlewares.length) return finalCallback();\n const middleware = this.middlewares[index++];\n middleware?.(connection, next);\n };\n next();\n }\n\n emit(event: string, data: any): void {\n for (const connection of this.connections.values()) {\n if (connection.connected) connection.emit(event, data);\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.connections, {\n rooms: Array.isArray(room) ? room : [room],\n });\n }\n\n except(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.connections, {\n exceptRooms: Array.isArray(room) ? room : [room],\n });\n }\n\n getSockets(): WebSocketConnection[] {\n const result: WebSocketConnection[] = [];\n for (const conn of this.connections.values()) if (conn.connected) result.push(conn);\n return result;\n }\n\n getConnectionCount(): number {\n let count = 0;\n for (const conn of this.connections.values()) if (conn.connected) count++;\n return count;\n }\n\n use(middleware: WebSocketMiddleware): void {\n this.middlewares.push(middleware);\n }\n}\n\nclass EngineConnectionWrapper implements WebSocketConnection {\n public data: Record<string, any> = {};\n public connected = true;\n public readonly headers: Record<string, string>;\n public readonly query: Record<string, string>;\n private rooms = new Set<string>();\n private eventHandlers = new Map<\n string,\n Array<(data: any, callback?: (response?: any) => void) => void>\n >();\n private anyHandlers: Array<(event: string, ...args: any[]) => void> = [];\n private disconnectHandlers: Array<(reason?: any) => void> = [];\n private logger = createFrameworkLogger('ENGINE_WS_CONN');\n private readonly _ip: string;\n\n constructor(\n private server: any,\n private wsId: number,\n public readonly id: string,\n public readonly path: string,\n private namespaces: Map<string, EngineNamespaceWrapper>,\n handshake?: { ip: string; headers: Record<string, string>; query?: string }\n ) {\n // Handshake snapshot (captured before the upgrade invalidated the reqId):\n // real values for IP-based rate limiting and header/token connection auth.\n this._ip = handshake?.ip ?? '';\n this.headers = handshake?.headers ?? {};\n this.query = parseQueryString(handshake?.query ?? '');\n }\n\n get ip(): string {\n return this._ip;\n }\n\n // Socket.IO-parity handshake view so `socket.handshake.query`/`.headers`\n // handlers (e.g. reading an `orgId` query param) work against the engine\n // adapter unchanged.\n get handshake(): {\n query: Record<string, string>;\n headers: Record<string, string>;\n address: string;\n } {\n return { query: this.query, headers: this.headers, address: this._ip };\n }\n\n on(event: string, handler: (data: any, callback?: (response?: any) => void) => void): void {\n if (event === 'disconnect') {\n this.disconnectHandlers.push(handler as any);\n return;\n }\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = [];\n this.eventHandlers.set(event, handlers);\n }\n handlers.push(handler);\n }\n\n onAny(handler: (event: string, ...args: any[]) => void): void {\n this.anyHandlers.push(handler);\n }\n\n emit(event: string, data: any): void {\n if (!this.connected) return;\n try {\n const sent = this.server.wsSend(this.wsId, JSON.stringify({ event, data }), false);\n if (!sent) this.logger.warn(`Backpressure for connection ${this.id}`, 'Backpressure');\n } catch (error) {\n this.logger.error(\n `Failed to emit ${event}: ${error instanceof Error ? error.message : String(error)}`,\n 'Emit'\n );\n }\n }\n\n compressedEmit(event: string, data: any): void {\n this.emit(event, data);\n }\n\n // A user handler throwing must not kill the connection, skip sibling\n // handlers, or (via the native callback) get swallowed silently.\n private runHandler(fn: (...args: any[]) => void, ...args: any[]): void {\n try {\n fn(...args);\n } catch (error) {\n this.logger.error(\n `WebSocket handler threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Handler'\n );\n }\n }\n\n handleMessage(data: any): void {\n // A frame delivered after the socket was closed (e.g. a second frame in the\n // same TCP read whose first frame's handler called disconnect()) must not\n // reach app handlers on a dead socket.\n if (!this.connected) return;\n if (!data || typeof data !== 'object') return;\n const { event, data: eventData, callback: hasCallback } = data;\n if (!event) return;\n\n const callback = hasCallback\n ? (response?: any) => this.emit(`${event}:response`, response)\n : undefined;\n\n for (const handler of this.anyHandlers) this.runHandler(handler, event, eventData);\n const handlers = this.eventHandlers.get(event);\n if (handlers) for (const handler of handlers) this.runHandler(handler, eventData, callback);\n }\n\n handleBinary(data: any): void {\n if (!this.connected) return;\n const handlers = this.eventHandlers.get('binary');\n if (handlers) for (const handler of handlers) this.runHandler(handler, data);\n }\n\n handleDisconnect(reason?: any): void {\n for (const handler of this.disconnectHandlers) this.runHandler(handler, reason);\n }\n\n join(room: string | string[]): void {\n for (const r of Array.isArray(room) ? room : [room]) this.rooms.add(r);\n }\n\n leave(room: string | string[]): void {\n for (const r of Array.isArray(room) ? room : [room]) this.rooms.delete(r);\n }\n\n getRooms(): Set<string> {\n return new Set(this.rooms);\n }\n\n isInRoom(room: string): boolean {\n return this.rooms.has(room);\n }\n\n disconnect(_close?: boolean): void {\n if (!this.connected) return;\n this.connected = false;\n try {\n this.server.wsClose(this.wsId, 1000, '');\n } catch {\n // already gone\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.allConnections(), {\n rooms: Array.isArray(room) ? room : [room],\n });\n }\n\n // Emit to everyone in the default namespace except this socket\n get broadcast(): WebSocketEmitter {\n return new EngineEmitterWrapper(this.allConnections(), { exceptId: this.id });\n }\n\n private allConnections(): Map<string, EngineConnectionWrapper> {\n return (this.namespaces.get('/') as any)?.['connections'] ?? new Map();\n }\n}\n\nclass EngineEmitterWrapper implements WebSocketEmitter {\n constructor(\n private connections: Map<string, EngineConnectionWrapper>,\n private filter: { rooms?: string[]; exceptRooms?: string[]; exceptId?: string }\n ) {}\n\n emit(event: string, data: any): void {\n for (const conn of this.connections.values()) {\n if (!conn.connected) continue;\n if (this.filter.exceptId && conn.id === this.filter.exceptId) continue;\n if (this.filter.rooms && !this.filter.rooms.some(r => conn.isInRoom(r))) continue;\n if (this.filter.exceptRooms && this.filter.exceptRooms.some(r => conn.isInRoom(r))) continue;\n conn.emit(event, data);\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n const rooms = Array.isArray(room) ? room : [room];\n return new EngineEmitterWrapper(this.connections, {\n ...this.filter,\n rooms: [...(this.filter.rooms ?? []), ...rooms],\n });\n }\n\n except(room: string | string[]): WebSocketEmitter {\n const rooms = Array.isArray(room) ? room : [room];\n return new EngineEmitterWrapper(this.connections, {\n ...this.filter,\n exceptRooms: [...(this.filter.exceptRooms ?? []), ...rooms],\n });\n }\n\n compress(_compress: boolean): WebSocketEmitter {\n return this; // native negotiation; chaining preserved\n }\n}\n"]}
1
+ {"version":3,"file":"engine-adapter.js","sourceRoot":"","sources":["../../../../src/core/networking/adapters/engine-adapter.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AAErE,OAAO,MAAM,MAAM,QAAQ,CAAC;AAU5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,6EAA6E;AAC7E,2EAA2E;AAC3E,wDAAwD;AACxD,SAAS,gBAAgB,CAAC,EAAU;IAClC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACpB,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACpD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,OAAO,sBAAsB;IACzB,MAAM,CAAM,CAAC,mBAAmB;IAChC,UAAU,GAAG,IAAI,GAAG,EAAkC,CAAC;IACvD,WAAW,GAAG,IAAI,GAAG,EAAmC,CAAC;IACzD,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAC;IACpD,MAAM,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC5C,iBAAiB,CAAgB;IACjC,iBAAiB,GAAG,CAAC,CAAC;IAE9B,KAAK,CAAC,UAAU,CAAC,UAAe,EAAE,WAAoC,EAAE;QACtE,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;QAEzB,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;YAC1B,MAAM,EAAE,CACN,IAAY,EACZ,IAAY,EACZ,IAAsE,EACtE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;YACtC,SAAS,EAAE,CAAC,IAAY,EAAE,IAAS,EAAE,QAAiB,EAAE,EAAE,CACxD,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;YAC1C,OAAO,EAAE,CAAC,IAAY,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC;SACtE,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,EAAE,MAAM,CAAC,CAAC;IACnE,CAAC;IAEO,UAAU,CAChB,IAAY,EACZ,IAAY,EACZ,IAAsE;QAEtE,MAAM,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACjF,MAAM,UAAU,GAAG,IAAI,uBAAuB,CAC5C,IAAI,CAAC,MAAM,EACX,IAAI,EACJ,EAAE,EACF,IAAI,EACJ,IAAI,CAAC,UAAU,EACf,IAAI,CACL,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAClC,uEAAuE;QACvE,4EAA4E;QAC5E,uEAAuE;QACvE,qEAAqE;QACrE,aAAa;QACb,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;QACrD,MAAM,EAAE,GACL,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAwC;YACnE,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACjC,UAAU,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC;QACxB,EAAE,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAEO,aAAa,CAAC,IAAY,EAAE,IAAS,EAAE,QAAiB;QAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,IAAI,UAAU,CAAC,GAAG,EAAE,CAAC;YACnB,kEAAkE;YAClE,IAAI,QAAQ;gBAAE,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;;gBAEtF,UAAU,CAAC,aAAa,CACtB,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACtE,CAAC;YACJ,OAAO;QACT,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,wEAAwE;QACxE,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnF,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,UAAU,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;YACtE,OAAO;QACT,CAAC;QACD,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,IAAY;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,UAAU,CAAC,SAAS,GAAG,KAAK,CAAC;QAC7B,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAEO,UAAU;QAChB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,MAAM;aACnF,WAAW,CAAC,CAAC,CAAC;aACd,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IACvB,CAAC;IAEO,uBAAuB;QAC7B,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAA2B,CAAC;IAC7D,CAAC;IAED,eAAe,CAAC,SAAiB,EAAE,OAAmC;QACpE,IAAI,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,EAAE,GAAG,IAAI,sBAAsB,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAC7D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,OAAO,EAAE,GAAG;YAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,cAAc,CAAC,QAAiB,EAAE,QAAc;QAC9C,0EAA0E;IAC5E,CAAC;IAED,oBAAoB,CAAC,SAAuB;QAC1C,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;IACrC,CAAC;IAED,cAAc;QACZ,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,kBAAkB;QAChB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,SAAS;gBAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,MAAM,sBAAsB;IAQhB;IACA;IARV,8EAA8E;IAC9E,GAAG,GAAG,KAAK,CAAC;IACJ,kBAAkB,GAA8C,EAAE,CAAC;IACnE,WAAW,GAA0B,EAAE,CAAC;IACxC,MAAM,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAEvD,YACU,SAAiB,EACjB,WAAiD;QADjD,cAAS,GAAT,SAAS,CAAQ;QACjB,gBAAW,GAAX,WAAW,CAAsC;IACxD,CAAC;IAEJ,EAAE,CAAC,KAAmB,EAAE,OAA8C;QACpE,IAAI,KAAK,KAAK,YAAY;YAAE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;IAED,gBAAgB,CAAC,UAAmC;QAClD,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE;YACpC,IAAI,GAAG,EAAE,CAAC;gBACR,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC5B,OAAO;YACT,CAAC;YACD,oEAAoE;YACpE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC9C,IAAI,CAAC;oBACH,OAAO,CAAC,UAAU,CAAC,CAAC;gBACtB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,uCAAuC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/F,YAAY,CACb,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,cAAc,CACpB,UAAmC,EACnC,aAAoC;QAEpC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;YAC3B,IAAI,GAAG;gBAAE,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM;gBAAE,OAAO,aAAa,EAAE,CAAC;YAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7C,UAAU,EAAE,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,IAAI,EAAE,CAAC;IACT,CAAC;IAED,IAAI,CAAC,KAAa,EAAE,IAAS;QAC3B,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YACnD,IAAI,UAAU,CAAC,SAAS;gBAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,EAAE,CAAC,IAAuB;QACxB,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE;YAChD,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,IAAuB;QAC5B,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE;YAChD,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SACjD,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,MAAM,MAAM,GAA0B,EAAE,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;YAAE,IAAI,IAAI,CAAC,SAAS;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kBAAkB;QAChB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;YAAE,IAAI,IAAI,CAAC,SAAS;gBAAE,KAAK,EAAE,CAAC;QAC1E,OAAO,KAAK,CAAC;IACf,CAAC;IAED,GAAG,CAAC,UAA+B;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;CACF;AAED,MAAM,uBAAuB;IAkBjB;IACA;IACQ;IACA;IACR;IArBH,IAAI,GAAwB,EAAE,CAAC;IAC/B,SAAS,GAAG,IAAI,CAAC;IACxB,mDAAmD;IAC5C,GAAG,GAAG,KAAK,CAAC;IACH,OAAO,CAAyB;IAChC,KAAK,CAAyB;IACtC,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAC1B,aAAa,GAAG,IAAI,GAAG,EAG5B,CAAC;IACI,WAAW,GAAmD,EAAE,CAAC;IACjE,kBAAkB,GAAkC,EAAE,CAAC;IACvD,MAAM,GAAG,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;IACxC,GAAG,CAAS;IAE7B,YACU,MAAW,EACX,IAAY,EACJ,EAAU,EACV,IAAY,EACpB,UAA+C,EACvD,SAA2E;QALnE,WAAM,GAAN,MAAM,CAAK;QACX,SAAI,GAAJ,IAAI,CAAQ;QACJ,OAAE,GAAF,EAAE,CAAQ;QACV,SAAI,GAAJ,IAAI,CAAQ;QACpB,eAAU,GAAV,UAAU,CAAqC;QAGvD,0EAA0E;QAC1E,2EAA2E;QAC3E,IAAI,CAAC,GAAG,GAAG,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,SAAS,EAAE,OAAO,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,GAAG,gBAAgB,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,yEAAyE;IACzE,yEAAyE;IACzE,qBAAqB;IACrB,IAAI,SAAS;QAKX,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;IACzE,CAAC;IAED,EAAE,CAAC,KAAa,EAAE,OAAiE;QACjF,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;YAC3B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAc,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QACD,IAAI,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAgD;QACpD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,CAAC,KAAa,EAAE,IAAS;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;YACnF,IAAI,CAAC,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,kBAAkB,KAAK,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACpF,MAAM,CACP,CAAC;QACJ,CAAC;IACH,CAAC;IAED,cAAc,CAAC,KAAa,EAAE,IAAS;QACrC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,IAAgD,EAAE,QAAkB;QACvE,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC;YACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YACzD,IAAI,CAAC,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,IAAI,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACjF,MAAM,CACP,CAAC;QACJ,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,aAAa,CAAC,IAAY;QACxB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,QAAQ;YAAE,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/E,CAAC;IAED,qEAAqE;IACrE,iEAAiE;IACzD,UAAU,CAAC,EAA4B,EAAE,GAAG,IAAW;QAC7D,IAAI,CAAC;YACH,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACpF,SAAS,CACV,CAAC;QACJ,CAAC;IACH,CAAC;IAED,aAAa,CAAC,IAAS;QACrB,4EAA4E;QAC5E,0EAA0E;QAC1E,uCAAuC;QACvC,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC9C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;QAC/D,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,QAAQ,GAAG,WAAW;YAC1B,CAAC,CAAC,CAAC,QAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,WAAW,EAAE,QAAQ,CAAC;YAC9D,CAAC,CAAC,SAAS,CAAC;QAEd,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,QAAQ;YAAE,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC9F,CAAC;IAED,YAAY,CAAC,IAAS;QACpB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,QAAQ;YAAE,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/E,CAAC;IAED,gBAAgB,CAAC,MAAY;QAC3B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,kBAAkB;YAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC;IAED,IAAI,CAAC,IAAuB;QAC1B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,IAAuB;QAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,UAAU,CAAC,MAAgB;QACzB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IAED,EAAE,CAAC,IAAuB;QACxB,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE;YACrD,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,+DAA+D;IAC/D,IAAI,SAAS;QACX,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAChF,CAAC;IAEO,cAAc;QACpB,OAAQ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAS,EAAE,CAAC,aAAa,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;IACzE,CAAC;CACF;AAED,MAAM,oBAAoB;IAEd;IACA;IAFV,YACU,WAAiD,EACjD,MAAuE;QADvE,gBAAW,GAAX,WAAW,CAAsC;QACjD,WAAM,GAAN,MAAM,CAAiE;IAC9E,CAAC;IAEJ,IAAI,CAAC,KAAa,EAAE,IAAS;QAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,CAAC,SAAS;gBAAE,SAAS;YAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,SAAS;YACvE,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YAClF,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC7F,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,EAAE,CAAC,IAAuB;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE;YAChD,GAAG,IAAI,CAAC,MAAM;YACd,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC;SAChD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,IAAuB;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE;YAChD,GAAG,IAAI,CAAC,MAAM;YACd,WAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,SAAkB;QACzB,OAAO,IAAI,CAAC,CAAC,yCAAyC;IACxD,CAAC;CACF","sourcesContent":["// @morojs/engine WebSocket adapter.\n// Bridges the native engine's RFC 6455 WebSocket callbacks (onWsOpen/Message/\n// Close, wsSend/wsClose on MoroEngineServer) to Moro's WebSocketAdapter model.\n// Message framing mirrors the uWS adapter: JSON { event, data } envelopes with\n// a default namespace, so app.websocket() handlers work identically.\n\nimport crypto from 'crypto';\nimport {\n WebSocketAdapter,\n WebSocketAdapterOptions,\n WebSocketNamespace,\n WebSocketNamespaceOptions,\n WebSocketConnection,\n WebSocketEmitter,\n WebSocketMiddleware,\n} from '../websocket-adapter.js';\nimport { createFrameworkLogger } from '../../logger/index.js';\n\n// Parse a raw upgrade query string (`orgId=1&x=y`, with or without a leading\n// `?`) into a flat object for `socket.handshake.query`. Last value wins on\n// duplicate keys, matching URLSearchParams / Socket.IO.\nfunction parseQueryString(qs: string): Record<string, string> {\n const out: Record<string, string> = {};\n if (!qs) return out;\n const params = new URLSearchParams(qs.startsWith('?') ? qs.slice(1) : qs);\n for (const [key, value] of params) out[key] = value;\n return out;\n}\n\nexport class EngineWebSocketAdapter implements WebSocketAdapter {\n private server: any; // MoroEngineServer\n private namespaces = new Map<string, EngineNamespaceWrapper>();\n private connections = new Map<string, EngineConnectionWrapper>();\n private byWsId = new Map<number, EngineConnectionWrapper>();\n private logger = createFrameworkLogger('ENGINE_WS');\n private customIdGenerator?: () => string;\n private connectionCounter = 0;\n\n async initialize(httpServer: any, _options: WebSocketAdapterOptions = {}): Promise<void> {\n if (!httpServer || typeof httpServer.enableWebSocket !== 'function') {\n throw new Error(\n 'EngineWebSocketAdapter requires the @morojs/engine HTTP server (MoroEngineServer)'\n );\n }\n this.server = httpServer;\n\n // Register the native WS bridge: the engine drives these on the event loop.\n this.server.enableWebSocket({\n onOpen: (\n wsId: number,\n path: string,\n info?: { ip: string; headers: Record<string, string>; query?: string }\n ) => this.handleOpen(wsId, path, info),\n onMessage: (wsId: number, data: any, isBinary: boolean) =>\n this.handleMessage(wsId, data, isBinary),\n onClose: (wsId: number, code: number) => this.handleClose(wsId, code),\n });\n\n this.createNamespace('/');\n this.logger.info('Engine WebSocket adapter initialized', 'Init');\n }\n\n private handleOpen(\n wsId: number,\n path: string,\n info?: { ip: string; headers: Record<string, string>; query?: string }\n ): void {\n const id = this.customIdGenerator ? this.customIdGenerator() : this.generateId();\n const connection = new EngineConnectionWrapper(\n this.server,\n wsId,\n id,\n path,\n this.namespaces,\n info\n );\n this.connections.set(id, connection);\n this.byWsId.set(wsId, connection);\n // Route to the namespace whose registered path matches the upgrade URL\n // (Socket.IO parity for `app.websocket('/name', ...)`), falling back to the\n // default namespace when nothing matches. Query/hash are stripped so a\n // client connecting to `/name?token=…` still resolves to the `/name`\n // namespace.\n const nsPath = (path || '/').split(/[?#]/)[0] || '/';\n const ns =\n (this.namespaces.get(nsPath) as EngineNamespaceWrapper | undefined) ??\n this.getDefaultNamespaceImpl();\n connection.raw = ns.raw;\n ns.handleConnection(connection);\n }\n\n private handleMessage(wsId: number, data: any, isBinary: boolean): void {\n const connection = this.byWsId.get(wsId);\n if (!connection) return;\n if (connection.raw) {\n // Raw namespace: no envelope, no parse - the frame is the message\n if (isBinary) connection.handleBinary(Buffer.isBuffer(data) ? data : Buffer.from(data));\n else\n connection.handleRawText(\n typeof data === 'string' ? data : Buffer.from(data).toString('utf-8')\n );\n return;\n }\n if (isBinary) {\n connection.handleBinary(data);\n return;\n }\n // Text frames carry JSON { event, data } envelopes (uWS-adapter parity)\n const text = typeof data === 'string' ? data : Buffer.from(data).toString('utf-8');\n let parsed: any;\n try {\n parsed = JSON.parse(text);\n } catch {\n this.logger.warn(`Non-JSON message from ${connection.id}`, 'Message');\n return;\n }\n connection.handleMessage(parsed);\n }\n\n private handleClose(wsId: number, code: number): void {\n const connection = this.byWsId.get(wsId);\n if (!connection) return;\n connection.connected = false;\n connection.handleDisconnect(code);\n this.byWsId.delete(wsId);\n this.connections.delete(connection.id);\n }\n\n private generateId(): string {\n return `${Date.now().toString(36)}-${(++this.connectionCounter).toString(36)}-${crypto\n .randomBytes(4)\n .toString('hex')}`;\n }\n\n private getDefaultNamespaceImpl(): EngineNamespaceWrapper {\n return this.createNamespace('/') as EngineNamespaceWrapper;\n }\n\n createNamespace(namespace: string, options?: WebSocketNamespaceOptions): WebSocketNamespace {\n let ns = this.namespaces.get(namespace);\n if (!ns) {\n ns = new EngineNamespaceWrapper(namespace, this.connections);\n this.namespaces.set(namespace, ns);\n }\n if (options?.raw) ns.raw = true;\n return ns;\n }\n\n getDefaultNamespace(): WebSocketNamespace {\n return this.createNamespace('/');\n }\n\n async close(): Promise<void> {\n for (const conn of this.connections.values()) {\n conn.disconnect();\n }\n this.connections.clear();\n this.byWsId.clear();\n }\n\n setCompression(_enabled: boolean, _options?: any): void {\n // Compression (permessage-deflate) is negotiated natively; no-op for now.\n }\n\n setCustomIdGenerator(generator: () => string): void {\n this.customIdGenerator = generator;\n }\n\n getAdapterName(): string {\n return '@morojs/engine';\n }\n\n getConnectionCount(): number {\n let count = 0;\n for (const conn of this.connections.values()) {\n if (conn.connected) count++;\n }\n return count;\n }\n}\n\nclass EngineNamespaceWrapper implements WebSocketNamespace {\n /** Raw framing for connections routed here (see WebSocketNamespaceOptions) */\n raw = false;\n private connectionHandlers: ((socket: WebSocketConnection) => void)[] = [];\n private middlewares: WebSocketMiddleware[] = [];\n private logger = createFrameworkLogger('ENGINE_WS_NS');\n\n constructor(\n private namespace: string,\n private connections: Map<string, EngineConnectionWrapper>\n ) {}\n\n on(event: 'connection', handler: (socket: WebSocketConnection) => void): void {\n if (event === 'connection') this.connectionHandlers.push(handler);\n }\n\n handleConnection(connection: EngineConnectionWrapper): void {\n this.runMiddlewares(connection, err => {\n if (err) {\n connection.disconnect(true);\n return;\n }\n // A throwing connection handler must not take down the socket/loop.\n for (const handler of this.connectionHandlers) {\n try {\n handler(connection);\n } catch (error) {\n this.logger.error(\n `WebSocket connection handler threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Connection'\n );\n }\n }\n });\n }\n\n private runMiddlewares(\n connection: EngineConnectionWrapper,\n finalCallback: (err?: Error) => void\n ): void {\n let index = 0;\n const next = (err?: Error) => {\n if (err) return finalCallback(err);\n if (index >= this.middlewares.length) return finalCallback();\n const middleware = this.middlewares[index++];\n middleware?.(connection, next);\n };\n next();\n }\n\n emit(event: string, data: any): void {\n for (const connection of this.connections.values()) {\n if (connection.connected) connection.emit(event, data);\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.connections, {\n rooms: Array.isArray(room) ? room : [room],\n });\n }\n\n except(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.connections, {\n exceptRooms: Array.isArray(room) ? room : [room],\n });\n }\n\n getSockets(): WebSocketConnection[] {\n const result: WebSocketConnection[] = [];\n for (const conn of this.connections.values()) if (conn.connected) result.push(conn);\n return result;\n }\n\n getConnectionCount(): number {\n let count = 0;\n for (const conn of this.connections.values()) if (conn.connected) count++;\n return count;\n }\n\n use(middleware: WebSocketMiddleware): void {\n this.middlewares.push(middleware);\n }\n}\n\nclass EngineConnectionWrapper implements WebSocketConnection {\n public data: Record<string, any> = {};\n public connected = true;\n /** Raw framing (set from the namespace at open) */\n public raw = false;\n public readonly headers: Record<string, string>;\n public readonly query: Record<string, string>;\n private rooms = new Set<string>();\n private eventHandlers = new Map<\n string,\n Array<(data: any, callback?: (response?: any) => void) => void>\n >();\n private anyHandlers: Array<(event: string, ...args: any[]) => void> = [];\n private disconnectHandlers: Array<(reason?: any) => void> = [];\n private logger = createFrameworkLogger('ENGINE_WS_CONN');\n private readonly _ip: string;\n\n constructor(\n private server: any,\n private wsId: number,\n public readonly id: string,\n public readonly path: string,\n private namespaces: Map<string, EngineNamespaceWrapper>,\n handshake?: { ip: string; headers: Record<string, string>; query?: string }\n ) {\n // Handshake snapshot (captured before the upgrade invalidated the reqId):\n // real values for IP-based rate limiting and header/token connection auth.\n this._ip = handshake?.ip ?? '';\n this.headers = handshake?.headers ?? {};\n this.query = parseQueryString(handshake?.query ?? '');\n }\n\n get ip(): string {\n return this._ip;\n }\n\n // Socket.IO-parity handshake view so `socket.handshake.query`/`.headers`\n // handlers (e.g. reading an `orgId` query param) work against the engine\n // adapter unchanged.\n get handshake(): {\n query: Record<string, string>;\n headers: Record<string, string>;\n address: string;\n } {\n return { query: this.query, headers: this.headers, address: this._ip };\n }\n\n on(event: string, handler: (data: any, callback?: (response?: any) => void) => void): void {\n if (event === 'disconnect') {\n this.disconnectHandlers.push(handler as any);\n return;\n }\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = [];\n this.eventHandlers.set(event, handlers);\n }\n handlers.push(handler);\n }\n\n onAny(handler: (event: string, ...args: any[]) => void): void {\n this.anyHandlers.push(handler);\n }\n\n emit(event: string, data: any): void {\n if (!this.connected) return;\n try {\n const sent = this.server.wsSend(this.wsId, JSON.stringify({ event, data }), false);\n if (!sent) this.logger.warn(`Backpressure for connection ${this.id}`, 'Backpressure');\n } catch (error) {\n this.logger.error(\n `Failed to emit ${event}: ${error instanceof Error ? error.message : String(error)}`,\n 'Emit'\n );\n }\n }\n\n compressedEmit(event: string, data: any): void {\n this.emit(event, data);\n }\n\n send(data: string | Buffer | ArrayBuffer | Uint8Array, isBinary?: boolean): void {\n if (!this.connected) return;\n try {\n const binary = isBinary ?? typeof data !== 'string';\n const sent = this.server.wsSend(this.wsId, data, binary);\n if (!sent) this.logger.warn(`Backpressure for connection ${this.id}`, 'Backpressure');\n } catch (error) {\n this.logger.error(\n `Failed to send frame: ${error instanceof Error ? error.message : String(error)}`,\n 'Send'\n );\n }\n }\n\n /** Raw namespace: a text frame reaches the 'message' handlers verbatim */\n handleRawText(text: string): void {\n if (!this.connected) return;\n for (const handler of this.anyHandlers) this.runHandler(handler, 'message', text);\n const handlers = this.eventHandlers.get('message');\n if (handlers) for (const handler of handlers) this.runHandler(handler, text);\n }\n\n // A user handler throwing must not kill the connection, skip sibling\n // handlers, or (via the native callback) get swallowed silently.\n private runHandler(fn: (...args: any[]) => void, ...args: any[]): void {\n try {\n fn(...args);\n } catch (error) {\n this.logger.error(\n `WebSocket handler threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Handler'\n );\n }\n }\n\n handleMessage(data: any): void {\n // A frame delivered after the socket was closed (e.g. a second frame in the\n // same TCP read whose first frame's handler called disconnect()) must not\n // reach app handlers on a dead socket.\n if (!this.connected) return;\n if (!data || typeof data !== 'object') return;\n const { event, data: eventData, callback: hasCallback } = data;\n if (!event) return;\n\n const callback = hasCallback\n ? (response?: any) => this.emit(`${event}:response`, response)\n : undefined;\n\n for (const handler of this.anyHandlers) this.runHandler(handler, event, eventData);\n const handlers = this.eventHandlers.get(event);\n if (handlers) for (const handler of handlers) this.runHandler(handler, eventData, callback);\n }\n\n handleBinary(data: any): void {\n if (!this.connected) return;\n const handlers = this.eventHandlers.get('binary');\n if (handlers) for (const handler of handlers) this.runHandler(handler, data);\n }\n\n handleDisconnect(reason?: any): void {\n for (const handler of this.disconnectHandlers) this.runHandler(handler, reason);\n }\n\n join(room: string | string[]): void {\n for (const r of Array.isArray(room) ? room : [room]) this.rooms.add(r);\n }\n\n leave(room: string | string[]): void {\n for (const r of Array.isArray(room) ? room : [room]) this.rooms.delete(r);\n }\n\n getRooms(): Set<string> {\n return new Set(this.rooms);\n }\n\n isInRoom(room: string): boolean {\n return this.rooms.has(room);\n }\n\n disconnect(_close?: boolean): void {\n if (!this.connected) return;\n this.connected = false;\n try {\n this.server.wsClose(this.wsId, 1000, '');\n } catch {\n // already gone\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n return new EngineEmitterWrapper(this.allConnections(), {\n rooms: Array.isArray(room) ? room : [room],\n });\n }\n\n // Emit to everyone in the default namespace except this socket\n get broadcast(): WebSocketEmitter {\n return new EngineEmitterWrapper(this.allConnections(), { exceptId: this.id });\n }\n\n private allConnections(): Map<string, EngineConnectionWrapper> {\n return (this.namespaces.get('/') as any)?.['connections'] ?? new Map();\n }\n}\n\nclass EngineEmitterWrapper implements WebSocketEmitter {\n constructor(\n private connections: Map<string, EngineConnectionWrapper>,\n private filter: { rooms?: string[]; exceptRooms?: string[]; exceptId?: string }\n ) {}\n\n emit(event: string, data: any): void {\n for (const conn of this.connections.values()) {\n if (!conn.connected) continue;\n if (this.filter.exceptId && conn.id === this.filter.exceptId) continue;\n if (this.filter.rooms && !this.filter.rooms.some(r => conn.isInRoom(r))) continue;\n if (this.filter.exceptRooms && this.filter.exceptRooms.some(r => conn.isInRoom(r))) continue;\n conn.emit(event, data);\n }\n }\n\n to(room: string | string[]): WebSocketEmitter {\n const rooms = Array.isArray(room) ? room : [room];\n return new EngineEmitterWrapper(this.connections, {\n ...this.filter,\n rooms: [...(this.filter.rooms ?? []), ...rooms],\n });\n }\n\n except(room: string | string[]): WebSocketEmitter {\n const rooms = Array.isArray(room) ? room : [room];\n return new EngineEmitterWrapper(this.connections, {\n ...this.filter,\n exceptRooms: [...(this.filter.exceptRooms ?? []), ...rooms],\n });\n }\n\n compress(_compress: boolean): WebSocketEmitter {\n return this; // native negotiation; chaining preserved\n }\n}\n"]}