@nmtjs/ws-transport 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +20 -15
- package/dist/server.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/server.ts +26 -16
- package/src/types.ts +1 -0
package/dist/server.js
CHANGED
|
@@ -34,6 +34,7 @@ export class WsTransportServer {
|
|
|
34
34
|
const contentType = requestData.query.get("content-type") || requestData.headers.get("content-type");
|
|
35
35
|
const acceptType = requestData.query.get("accept") || requestData.headers.get("accept");
|
|
36
36
|
const connectionId = randomUUID();
|
|
37
|
+
const controller = new AbortController();
|
|
37
38
|
try {
|
|
38
39
|
const { context } = await this.protocol.addConnection(this, {
|
|
39
40
|
id: connectionId,
|
|
@@ -43,6 +44,7 @@ export class WsTransportServer {
|
|
|
43
44
|
contentType
|
|
44
45
|
});
|
|
45
46
|
context.container.provide(WsTransportInjectables.connectionData, requestData);
|
|
47
|
+
context.container.provide(ProtocolInjectables.connectionAbortSignal, controller.signal);
|
|
46
48
|
if (!ac.signal.aborted) {
|
|
47
49
|
res.cork(() => {
|
|
48
50
|
res.upgrade({
|
|
@@ -51,7 +53,8 @@ export class WsTransportServer {
|
|
|
51
53
|
contentType,
|
|
52
54
|
acceptType,
|
|
53
55
|
backpressure: null,
|
|
54
|
-
context
|
|
56
|
+
context,
|
|
57
|
+
controller
|
|
55
58
|
}, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), socketContext);
|
|
56
59
|
});
|
|
57
60
|
}
|
|
@@ -88,7 +91,8 @@ export class WsTransportServer {
|
|
|
88
91
|
data.backpressure = null;
|
|
89
92
|
},
|
|
90
93
|
close: async (ws, code, message) => {
|
|
91
|
-
const { id } = ws.getUserData();
|
|
94
|
+
const { id, controller } = ws.getUserData();
|
|
95
|
+
controller.abort();
|
|
92
96
|
this.logger.debug("Connection %s closed with code %s: %s", id, code, Buffer.from(message).toString());
|
|
93
97
|
this.clients.delete(id);
|
|
94
98
|
await this.protocol.removeConnection(id);
|
|
@@ -128,8 +132,8 @@ export class WsTransportServer {
|
|
|
128
132
|
}
|
|
129
133
|
async httpHandler(res, req) {
|
|
130
134
|
this.applyCors(res, req);
|
|
131
|
-
const
|
|
132
|
-
res.onAborted(
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
res.onAborted(controller.abort.bind(controller));
|
|
133
137
|
const method = req.getMethod();
|
|
134
138
|
const namespace = req.getParameter("namespace");
|
|
135
139
|
const procedure = req.getParameter("procedure");
|
|
@@ -138,7 +142,7 @@ export class WsTransportServer {
|
|
|
138
142
|
const status = HttpCode.NotFound;
|
|
139
143
|
const text = HttpStatusText[status];
|
|
140
144
|
return void res.cork(() => {
|
|
141
|
-
if (
|
|
145
|
+
if (controller.signal.aborted) return;
|
|
142
146
|
res.writeStatus(`${status} ${text}`);
|
|
143
147
|
res.endWithoutBody();
|
|
144
148
|
});
|
|
@@ -154,6 +158,7 @@ export class WsTransportServer {
|
|
|
154
158
|
const responseHeaders = new Headers();
|
|
155
159
|
const container = this.context.container.fork(Scope.Call);
|
|
156
160
|
container.provide(ProtocolInjectables.connection, connection);
|
|
161
|
+
container.provide(ProtocolInjectables.connectionAbortSignal, controller.signal);
|
|
157
162
|
container.provide(WsTransportInjectables.connectionData, requestData);
|
|
158
163
|
container.provide(WsTransportInjectables.httpResponseHeaders, responseHeaders);
|
|
159
164
|
const body = method === "post" ? getRequestBody(res) : undefined;
|
|
@@ -195,11 +200,11 @@ export class WsTransportServer {
|
|
|
195
200
|
payload,
|
|
196
201
|
metadata,
|
|
197
202
|
container,
|
|
198
|
-
signal:
|
|
203
|
+
signal: controller.signal
|
|
199
204
|
});
|
|
200
205
|
if (isIterableResult(result) || isSubscriptionResult(result)) {
|
|
201
206
|
res.cork(() => {
|
|
202
|
-
if (
|
|
207
|
+
if (controller.signal.aborted) return;
|
|
203
208
|
const status = HttpCode.NotImplemented;
|
|
204
209
|
const text = HttpStatusText[status];
|
|
205
210
|
res.writeStatus(`${status} ${text}`);
|
|
@@ -219,15 +224,15 @@ export class WsTransportServer {
|
|
|
219
224
|
throw new Error("Invalid stream source");
|
|
220
225
|
}
|
|
221
226
|
res.cork(() => {
|
|
222
|
-
if (
|
|
227
|
+
if (controller.signal.aborted) return;
|
|
223
228
|
responseHeaders.set("X-Neemata-Blob", "true");
|
|
224
229
|
responseHeaders.set("Content-Type", type);
|
|
225
230
|
if (metadata.size) res.writeHeader("Content-Length", metadata.size.toString());
|
|
226
231
|
setHeaders(res, responseHeaders);
|
|
227
232
|
});
|
|
228
|
-
|
|
233
|
+
controller.signal.addEventListener("abort", () => stream.destroy(), { once: true });
|
|
229
234
|
stream.on("data", (chunk) => {
|
|
230
|
-
if (
|
|
235
|
+
if (controller.signal.aborted) return;
|
|
231
236
|
const buf = Buffer.from(chunk);
|
|
232
237
|
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
233
238
|
const ok = res.write(ab);
|
|
@@ -247,7 +252,7 @@ export class WsTransportServer {
|
|
|
247
252
|
}
|
|
248
253
|
} else {
|
|
249
254
|
res.cork(() => {
|
|
250
|
-
if (
|
|
255
|
+
if (controller.signal.aborted) return;
|
|
251
256
|
const status = HttpCode.OK;
|
|
252
257
|
const text = HttpStatusText[status];
|
|
253
258
|
const buffer = format.encoder.encode(output);
|
|
@@ -259,10 +264,10 @@ export class WsTransportServer {
|
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
} catch (error) {
|
|
262
|
-
if (
|
|
267
|
+
if (controller.signal.aborted) return;
|
|
263
268
|
if (error instanceof UnsupportedFormatError) {
|
|
264
269
|
res.cork(() => {
|
|
265
|
-
if (
|
|
270
|
+
if (controller.signal.aborted) return;
|
|
266
271
|
const status = error instanceof UnsupportedContentTypeError ? HttpCode.UnsupportedMediaType : HttpCode.NotAcceptable;
|
|
267
272
|
const text = HttpStatusText[status];
|
|
268
273
|
res.writeStatus(`${status} ${text}`);
|
|
@@ -270,7 +275,7 @@ export class WsTransportServer {
|
|
|
270
275
|
});
|
|
271
276
|
} else if (error instanceof ProtocolError) {
|
|
272
277
|
res.cork(() => {
|
|
273
|
-
if (
|
|
278
|
+
if (controller.signal.aborted) return;
|
|
274
279
|
const status = error.code in HttpCodeMap ? HttpCodeMap[error.code] : HttpCode.InternalServerError;
|
|
275
280
|
const text = HttpStatusText[status];
|
|
276
281
|
res.writeStatus(`${status} ${text}`);
|
|
@@ -279,7 +284,7 @@ export class WsTransportServer {
|
|
|
279
284
|
} else {
|
|
280
285
|
this.logError(error, "Unknown error while processing request");
|
|
281
286
|
res.cork(() => {
|
|
282
|
-
if (
|
|
287
|
+
if (controller.signal.aborted) return;
|
|
283
288
|
const status = HttpCode.InternalServerError;
|
|
284
289
|
const text = HttpStatusText[status];
|
|
285
290
|
const payload = format.encoder.encode(new ProtocolError(ErrorCode.InternalServerError, "Internal Server Error"));
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","names":["context: TransportPluginContext","options: WsTransportOptions","ws: WsTransportSocket","error: any","connection: Connection<WsConnectionData>","messageType: ServerMessageType","buffer: ArrayBuffer","res: HttpResponse","req: HttpRequest","metadata: ProtocolApiCallOptions['metadata']","format: ReturnType<typeof getFormat>","payload: any","stream: Readable","cause: any"],"sources":["../src/server.ts"],"sourcesContent":["import {\n App,\n type HttpRequest,\n type HttpResponse,\n SSLApp,\n type TemplatedApp,\n us_socket_local_port,\n} from 'uWebSockets.js'\nimport { randomUUID } from 'node:crypto'\nimport { once } from 'node:events'\nimport { Duplex, Readable } from 'node:stream'\nimport { Scope } from '@nmtjs/core'\nimport {\n ClientMessageType,\n decodeNumber,\n ErrorCode,\n ProtocolBlob,\n type ServerMessageType,\n} from '@nmtjs/protocol'\nimport {\n Connection,\n getFormat,\n isIterableResult,\n isSubscriptionResult,\n type ProtocolApiCallOptions,\n ProtocolClientStream,\n ProtocolError,\n ProtocolInjectables,\n type Transport,\n type TransportPluginContext,\n UnsupportedContentTypeError,\n UnsupportedFormatError,\n} from '@nmtjs/protocol/server'\nimport {\n AllowedHttpMethod,\n HttpCode,\n HttpCodeMap,\n HttpStatusText,\n} from './http.ts'\nimport { WsTransportInjectables } from './injectables.ts'\nimport type {\n WsConnectionData,\n WsTransportOptions,\n WsTransportSocket,\n WsUserData,\n} from './types.ts'\nimport {\n getRequestBody,\n getRequestData,\n readableToArrayBuffer,\n send,\n setHeaders,\n} from './utils.ts'\n\nconst DEFAULT_ALLOWED_METHODS = ['post'] as ('get' | 'post')[]\n\nexport class WsTransportServer implements Transport<WsConnectionData> {\n protected server!: TemplatedApp\n protected clients: Map<string, WsTransportSocket> = new Map()\n\n constructor(\n protected readonly context: TransportPluginContext,\n protected readonly options: WsTransportOptions,\n ) {\n this.server = this.options.tls ? SSLApp(options.tls!) : App()\n this.server\n .options('/*', (res, req) => {\n this.applyCors(res, req)\n res.writeStatus('200 OK')\n res.endWithoutBody()\n })\n .get('/healthy', (res, req) => {\n this.applyCors(res, req)\n res.writeHeader('Content-Type', 'text/plain')\n res.end('OK')\n })\n .ws<WsUserData>('/api', {\n sendPingsAutomatically: true,\n maxPayloadLength: this.options.maxPayloadLength,\n upgrade: async (res, req, socketContext) => {\n const ac = new AbortController()\n\n res.onAborted(ac.abort.bind(ac))\n\n const requestData = getRequestData(req, res)\n const contentType =\n requestData.query.get('content-type') ||\n requestData.headers.get('content-type')\n const acceptType =\n requestData.query.get('accept') || requestData.headers.get('accept')\n\n const connectionId = randomUUID()\n\n try {\n const { context } = await this.protocol.addConnection(\n this,\n { id: connectionId, data: { type: 'ws' } },\n { acceptType, contentType },\n )\n context.container.provide(\n WsTransportInjectables.connectionData,\n requestData,\n )\n if (!ac.signal.aborted) {\n res.cork(() => {\n res.upgrade(\n {\n id: connectionId,\n request: requestData,\n contentType,\n acceptType,\n backpressure: null,\n context,\n } as WsUserData,\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n socketContext,\n )\n })\n }\n } catch (error) {\n this.logger.debug(\n new Error('Failed to upgrade connection', { cause: error }),\n )\n if (!ac.signal.aborted) {\n res.cork(() => {\n res.writeStatus('500 Internal Server Error')\n res.endWithoutBody()\n })\n }\n }\n },\n open: (ws: WsTransportSocket) => {\n const { id } = ws.getUserData()\n this.logger.debug('Connection %s opened', id)\n this.clients.set(id, ws)\n },\n message: async (ws: WsTransportSocket, buffer) => {\n const messageType = decodeNumber(buffer, 'Uint8')\n if (messageType in this === false) {\n ws.end(1011, 'Unknown message type')\n } else {\n try {\n await this[messageType](\n ws,\n buffer.slice(Uint8Array.BYTES_PER_ELEMENT),\n )\n } catch (error: any) {\n this.logError(error, 'Error while processing message')\n }\n }\n },\n drain: (ws: WsTransportSocket) => {\n const data = ws.getUserData()\n data.backpressure?.resolve()\n data.backpressure = null\n },\n close: async (ws: WsTransportSocket, code, message) => {\n const { id } = ws.getUserData()\n\n this.logger.debug(\n 'Connection %s closed with code %s: %s',\n id,\n code,\n Buffer.from(message).toString(),\n )\n this.clients.delete(id)\n await this.protocol.removeConnection(id)\n },\n })\n .get('/api/:namespace/:procedure', this.httpHandler.bind(this))\n .post('/api/:namespace/:procedure', this.httpHandler.bind(this))\n }\n\n send(\n connection: Connection<WsConnectionData>,\n messageType: ServerMessageType,\n buffer: ArrayBuffer,\n ) {\n const ws = this.clients.get(connection.id)\n if (ws) send(ws, messageType, buffer)\n }\n\n async start() {\n return new Promise<void>((resolve, reject) => {\n const { hostname = '127.0.0.1', port = 0, unix } = this.options\n if (unix) {\n this.server.listen_unix((socket) => {\n if (socket) {\n this.logger.info('Server started on unix://%s', unix)\n resolve()\n } else {\n reject(new Error('Failed to start WebSockets server'))\n }\n }, unix)\n } else {\n this.server.listen(hostname, port, (socket) => {\n if (socket) {\n this.logger.info(\n 'WebSocket Server started on %s:%s',\n hostname,\n us_socket_local_port(socket),\n )\n resolve()\n } else {\n reject(new Error('Failed to start WebSockets server'))\n }\n })\n }\n })\n }\n\n async stop() {\n this.server.close()\n }\n\n // TODO: decompose this mess\n protected async httpHandler(res: HttpResponse, req: HttpRequest) {\n this.applyCors(res, req)\n\n const ac = new AbortController()\n\n res.onAborted(ac.abort.bind(ac))\n\n const method = req.getMethod() as 'get' | 'post'\n const namespace = req.getParameter('namespace')\n const procedure = req.getParameter('procedure')\n const requestData = getRequestData(req, res)\n\n if (!namespace || !procedure) {\n const status = HttpCode.NotFound\n const text = HttpStatusText[status]\n return void res.cork(() => {\n if (ac.signal.aborted) return\n res.writeStatus(`${status} ${text}`)\n res.endWithoutBody()\n })\n }\n\n const isBlob = requestData.headers.get('x-neemata-blob') === 'true'\n\n const contentType = requestData.headers.get('content-type')\n const acceptType = requestData.headers.get('accept')\n const connectionId = randomUUID()\n const connection = new Connection<WsConnectionData>({\n id: connectionId,\n data: { type: 'http' },\n })\n const responseHeaders = new Headers()\n const container = this.context.container.fork(Scope.Call)\n container.provide(ProtocolInjectables.connection, connection)\n container.provide(WsTransportInjectables.connectionData, requestData)\n container.provide(\n WsTransportInjectables.httpResponseHeaders,\n responseHeaders,\n )\n\n const body = method === 'post' ? getRequestBody(res) : undefined\n\n const metadata: ProtocolApiCallOptions['metadata'] = (metadata) => {\n const allowHttpMethod =\n metadata.get(AllowedHttpMethod) ?? DEFAULT_ALLOWED_METHODS\n if (!allowHttpMethod.includes(method)) {\n throw new ProtocolError(ErrorCode.NotFound)\n }\n }\n let format: ReturnType<typeof getFormat>\n try {\n format = getFormat(this.context.format, {\n acceptType,\n contentType: isBlob ? '*/*' : contentType,\n })\n\n let payload: any\n\n if (body) {\n if (isBlob) {\n const type = contentType || 'application/octet-stream'\n const contentLength = requestData.headers.get('content-length')\n const size = contentLength\n ? Number.parseInt(contentLength)\n : undefined\n const stream = new ProtocolClientStream(-1, { size, type })\n body.pipe(stream)\n payload = stream\n } else {\n const buffer = await readableToArrayBuffer(body)\n if (buffer.byteLength > 0) {\n payload = format.decoder.decode(buffer)\n }\n }\n }\n\n const result = await this.protocol.call({\n connection,\n namespace,\n procedure,\n payload,\n metadata,\n container,\n signal: ac.signal,\n })\n\n if (isIterableResult(result) || isSubscriptionResult(result)) {\n res.cork(() => {\n if (ac.signal.aborted) return\n const status = HttpCode.NotImplemented\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end()\n })\n } else {\n const { output } = result\n\n if (output instanceof ProtocolBlob) {\n const { source, metadata } = output\n const { type } = metadata\n\n let stream: Readable\n\n if (source instanceof ReadableStream) {\n stream = Readable.fromWeb(source as any)\n } else if (source instanceof Readable || source instanceof Duplex) {\n stream = Readable.from(source)\n } else {\n throw new Error('Invalid stream source')\n }\n\n res.cork(() => {\n if (ac.signal.aborted) return\n responseHeaders.set('X-Neemata-Blob', 'true')\n responseHeaders.set('Content-Type', type)\n if (metadata.size)\n res.writeHeader('Content-Length', metadata.size.toString())\n setHeaders(res, responseHeaders)\n })\n\n ac.signal.addEventListener('abort', () => stream.destroy(), {\n once: true,\n })\n\n stream.on('data', (chunk) => {\n if (ac.signal.aborted) return\n const buf = Buffer.from(chunk)\n const ab = buf.buffer.slice(\n buf.byteOffset,\n buf.byteOffset + buf.byteLength,\n )\n const ok = res.write(ab)\n if (!ok) {\n stream.pause()\n res.onWritable(() => {\n stream.resume()\n return true\n })\n }\n })\n await once(stream, 'end')\n if (stream.readableAborted) {\n res.end(undefined, true)\n } else {\n res.end()\n }\n } else {\n res.cork(() => {\n if (ac.signal.aborted) return\n const status = HttpCode.OK\n const text = HttpStatusText[status]\n const buffer = format.encoder.encode(output)\n res.writeStatus(`${status} ${text}`)\n responseHeaders.set('Content-Type', format.encoder.contentType)\n setHeaders(res, responseHeaders)\n res.end(buffer)\n })\n }\n }\n } catch (error) {\n if (ac.signal.aborted) return\n if (error instanceof UnsupportedFormatError) {\n res.cork(() => {\n if (ac.signal.aborted) return\n const status =\n error instanceof UnsupportedContentTypeError\n ? HttpCode.UnsupportedMediaType\n : HttpCode.NotAcceptable\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end()\n })\n } else if (error instanceof ProtocolError) {\n res.cork(() => {\n if (ac.signal.aborted) return\n const status =\n error.code in HttpCodeMap\n ? HttpCodeMap[error.code]\n : HttpCode.InternalServerError\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end(format!.encoder.encode(error))\n })\n } else {\n this.logError(error, 'Unknown error while processing request')\n res.cork(() => {\n if (ac.signal.aborted) return\n const status = HttpCode.InternalServerError\n const text = HttpStatusText[status]\n const payload = format!.encoder.encode(\n new ProtocolError(\n ErrorCode.InternalServerError,\n 'Internal Server Error',\n ),\n )\n res.writeStatus(`${status} ${text}`)\n res.end(payload)\n })\n }\n } finally {\n container.dispose().catch((error) => {\n this.logError(error, 'Error while disposing call container')\n })\n }\n }\n\n protected get protocol() {\n return this.context.protocol\n }\n\n protected get logger() {\n return this.context.logger\n }\n\n protected async logError(\n cause: any,\n message = 'Unknown error while processing request',\n ) {\n this.logger.error(new Error(message, { cause }))\n }\n\n protected applyCors(res: HttpResponse, req: HttpRequest) {\n if (this.options.cors === false) return\n\n const origin = req.getHeader('origin')\n if (!origin) return\n\n let allowed = false\n\n if (this.options.cors === undefined || this.options.cors === true) {\n allowed = true\n } else if (Array.isArray(this.options.cors)) {\n allowed = this.options.cors.includes(origin)\n } else {\n allowed = this.options.cors(origin)\n }\n\n if (!allowed) return\n\n res.writeHeader('Access-Control-Allow-Origin', origin)\n res.writeHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.writeHeader('Access-Control-Allow-Methods', 'GET, POST')\n res.writeHeader('Access-Control-Allow-Credentials', 'true')\n }\n\n protected [ClientMessageType.Rpc](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcRaw(id, buffer)\n }\n\n protected [ClientMessageType.RpcAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcAbortRaw(id, buffer)\n }\n\n protected [ClientMessageType.RpcStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcStreamAbortRaw(id, buffer)\n }\n\n protected [ClientMessageType.ClientStreamPush](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.pushClientStream(\n id,\n streamId,\n buffer.slice(Uint32Array.BYTES_PER_ELEMENT),\n )\n }\n\n protected [ClientMessageType.ClientStreamEnd](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.endClientStream(id, streamId)\n }\n\n protected [ClientMessageType.ClientStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.abortClientStream(id, streamId)\n }\n\n protected [ClientMessageType.ServerStreamPull](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.pullServerStream(id, streamId)\n }\n\n protected [ClientMessageType.ServerStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.abortServerStream(id, streamId)\n }\n}\n"],"version":3,"file":"server.js"}
|
|
1
|
+
{"mappings":"AAAA,SACE,KAGA,QAEA,4BACK,gBAAgB;AACvB,SAAS,kBAAkB,aAAa;AACxC,SAAS,YAAY,aAAa;AAClC,SAAS,QAAQ,gBAAgB,aAAa;AAC9C,SAAS,aAAa,aAAa;AACnC,SACE,mBACA,cACA,WACA,oBAEK,iBAAiB;AACxB,SACE,YACA,WACA,kBACA,sBAEA,sBACA,eACA,qBAGA,6BACA,8BACK,wBAAwB;AAC/B,SACE,mBACA,UACA,aACA,sBACK,WAAW;AAClB,SAAS,8BAA8B,kBAAkB;AAOzD,SACE,gBACA,gBACA,uBACA,MACA,kBACK,YAAY;AAEnB,MAAM,0BAA0B,CAAC,MAAO;AAExC,OAAO,MAAM,kBAAyD;CACpE,AAAU;CACV,AAAU,UAA0C,IAAI;CAExD,YACqBA,SACAC,SACnB;OAFmB;OACA;AAEnB,OAAK,SAAS,KAAK,QAAQ,MAAM,OAAO,QAAQ,IAAK,GAAG,KAAK;AAC7D,OAAK,OACF,QAAQ,MAAM,CAAC,KAAK,QAAQ;AAC3B,QAAK,UAAU,KAAK,IAAI;AACxB,OAAI,YAAY,SAAS;AACzB,OAAI,gBAAgB;EACrB,EAAC,CACD,IAAI,YAAY,CAAC,KAAK,QAAQ;AAC7B,QAAK,UAAU,KAAK,IAAI;AACxB,OAAI,YAAY,gBAAgB,aAAa;AAC7C,OAAI,IAAI,KAAK;EACd,EAAC,CACD,GAAe,QAAQ;GACtB,wBAAwB;GACxB,kBAAkB,KAAK,QAAQ;GAC/B,SAAS,OAAO,KAAK,KAAK,kBAAkB;IAC1C,MAAM,KAAK,IAAI;AAEf,QAAI,UAAU,GAAG,MAAM,KAAK,GAAG,CAAC;IAEhC,MAAM,cAAc,eAAe,KAAK,IAAI;IAC5C,MAAM,cACJ,YAAY,MAAM,IAAI,eAAe,IACrC,YAAY,QAAQ,IAAI,eAAe;IACzC,MAAM,aACJ,YAAY,MAAM,IAAI,SAAS,IAAI,YAAY,QAAQ,IAAI,SAAS;IAEtE,MAAM,eAAe,YAAY;IACjC,MAAM,aAAa,IAAI;AACvB,QAAI;KACF,MAAM,EAAE,SAAS,GAAG,MAAM,KAAK,SAAS,cACtC,MACA;MAAE,IAAI;MAAc,MAAM,EAAE,MAAM,KAAM;KAAE,GAC1C;MAAE;MAAY;KAAa,EAC5B;AACD,aAAQ,UAAU,QAChB,uBAAuB,gBACvB,YACD;AACD,aAAQ,UAAU,QAChB,oBAAoB,uBACpB,WAAW,OACZ;AAED,UAAK,GAAG,OAAO,SAAS;AACtB,UAAI,KAAK,MAAM;AACb,WAAI,QACF;QACE,IAAI;QACJ,SAAS;QACT;QACA;QACA,cAAc;QACd;QACA;OACD,GACD,IAAI,UAAU,oBAAoB,EAClC,IAAI,UAAU,yBAAyB,EACvC,IAAI,UAAU,2BAA2B,EACzC,cACD;MACF,EAAC;KACH;IACF,SAAQ,OAAO;AACd,UAAK,OAAO,MACV,IAAI,MAAM,gCAAgC,EAAE,OAAO,MAAO,GAC3D;AACD,UAAK,GAAG,OAAO,SAAS;AACtB,UAAI,KAAK,MAAM;AACb,WAAI,YAAY,4BAA4B;AAC5C,WAAI,gBAAgB;MACrB,EAAC;KACH;IACF;GACF;GACD,MAAM,CAACC,OAA0B;IAC/B,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;AAC/B,SAAK,OAAO,MAAM,wBAAwB,GAAG;AAC7C,SAAK,QAAQ,IAAI,IAAI,GAAG;GACzB;GACD,SAAS,OAAOA,IAAuB,WAAW;IAChD,MAAM,cAAc,aAAa,QAAQ,QAAQ;AACjD,QAAI,eAAe,SAAS,OAAO;AACjC,QAAG,IAAI,MAAM,uBAAuB;IACrC,OAAM;AACL,SAAI;AACF,YAAM,KAAK,aACT,IACA,OAAO,MAAM,WAAW,kBAAkB,CAC3C;KACF,SAAQC,OAAY;AACnB,WAAK,SAAS,OAAO,iCAAiC;KACvD;IACF;GACF;GACD,OAAO,CAACD,OAA0B;IAChC,MAAM,OAAO,GAAG,aAAa;AAC7B,SAAK,cAAc,SAAS;AAC5B,SAAK,eAAe;GACrB;GACD,OAAO,OAAOA,IAAuB,MAAM,YAAY;IACrD,MAAM,EAAE,IAAI,YAAY,GAAG,GAAG,aAAa;AAC3C,eAAW,OAAO;AAClB,SAAK,OAAO,MACV,yCACA,IACA,MACA,OAAO,KAAK,QAAQ,CAAC,UAAU,CAChC;AACD,SAAK,QAAQ,OAAO,GAAG;AACvB,UAAM,KAAK,SAAS,iBAAiB,GAAG;GACzC;EACF,EAAC,CACD,IAAI,8BAA8B,KAAK,YAAY,KAAK,KAAK,CAAC,CAC9D,KAAK,8BAA8B,KAAK,YAAY,KAAK,KAAK,CAAC;CACnE;CAED,KACEE,YACAC,aACAC,QACA;EACA,MAAM,KAAK,KAAK,QAAQ,IAAI,WAAW,GAAG;AAC1C,MAAI,GAAI,MAAK,IAAI,aAAa,OAAO;CACtC;CAED,MAAM,QAAQ;AACZ,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;GAC5C,MAAM,EAAE,WAAW,aAAa,OAAO,GAAG,MAAM,GAAG,KAAK;AACxD,OAAI,MAAM;AACR,SAAK,OAAO,YAAY,CAAC,WAAW;AAClC,SAAI,QAAQ;AACV,WAAK,OAAO,KAAK,+BAA+B,KAAK;AACrD,eAAS;KACV,OAAM;AACL,aAAO,IAAI,MAAM,qCAAqC;KACvD;IACF,GAAE,KAAK;GACT,OAAM;AACL,SAAK,OAAO,OAAO,UAAU,MAAM,CAAC,WAAW;AAC7C,SAAI,QAAQ;AACV,WAAK,OAAO,KACV,qCACA,UACA,qBAAqB,OAAO,CAC7B;AACD,eAAS;KACV,OAAM;AACL,aAAO,IAAI,MAAM,qCAAqC;KACvD;IACF,EAAC;GACH;EACF;CACF;CAED,MAAM,OAAO;AACX,OAAK,OAAO,OAAO;CACpB;CAGD,MAAgB,YAAYC,KAAmBC,KAAkB;AAC/D,OAAK,UAAU,KAAK,IAAI;EAExB,MAAM,aAAa,IAAI;AAEvB,MAAI,UAAU,WAAW,MAAM,KAAK,WAAW,CAAC;EAEhD,MAAM,SAAS,IAAI,WAAW;EAC9B,MAAM,YAAY,IAAI,aAAa,YAAY;EAC/C,MAAM,YAAY,IAAI,aAAa,YAAY;EAC/C,MAAM,cAAc,eAAe,KAAK,IAAI;AAE5C,OAAK,cAAc,WAAW;GAC5B,MAAM,SAAS,SAAS;GACxB,MAAM,OAAO,eAAe;AAC5B,eAAY,IAAI,KAAK,MAAM;AACzB,QAAI,WAAW,OAAO,QAAS;AAC/B,QAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,QAAI,gBAAgB;GACrB,EAAC;EACH;EAED,MAAM,SAAS,YAAY,QAAQ,IAAI,iBAAiB,KAAK;EAE7D,MAAM,cAAc,YAAY,QAAQ,IAAI,eAAe;EAC3D,MAAM,aAAa,YAAY,QAAQ,IAAI,SAAS;EACpD,MAAM,eAAe,YAAY;EACjC,MAAM,aAAa,IAAI,WAA6B;GAClD,IAAI;GACJ,MAAM,EAAE,MAAM,OAAQ;EACvB;EACD,MAAM,kBAAkB,IAAI;EAC5B,MAAM,YAAY,KAAK,QAAQ,UAAU,KAAK,MAAM,KAAK;AACzD,YAAU,QAAQ,oBAAoB,YAAY,WAAW;AAC7D,YAAU,QACR,oBAAoB,uBACpB,WAAW,OACZ;AACD,YAAU,QAAQ,uBAAuB,gBAAgB,YAAY;AACrE,YAAU,QACR,uBAAuB,qBACvB,gBACD;EAED,MAAM,OAAO,WAAW,SAAS,eAAe,IAAI,GAAG;EAEvD,MAAMC,WAA+C,CAAC,aAAa;GACjE,MAAM,kBACJ,SAAS,IAAI,kBAAkB,IAAI;AACrC,QAAK,gBAAgB,SAAS,OAAO,EAAE;AACrC,UAAM,IAAI,cAAc,UAAU;GACnC;EACF;EACD,IAAIC;AACJ,MAAI;AACF,YAAS,UAAU,KAAK,QAAQ,QAAQ;IACtC;IACA,aAAa,SAAS,QAAQ;GAC/B,EAAC;GAEF,IAAIC;AAEJ,OAAI,MAAM;AACR,QAAI,QAAQ;KACV,MAAM,OAAO,eAAe;KAC5B,MAAM,gBAAgB,YAAY,QAAQ,IAAI,iBAAiB;KAC/D,MAAM,OAAO,gBACT,OAAO,SAAS,cAAc,GAC9B;KACJ,MAAM,SAAS,IAAI,sBAAsB,GAAG;MAAE;MAAM;KAAM;AAC1D,UAAK,KAAK,OAAO;AACjB,eAAU;IACX,OAAM;KACL,MAAM,SAAS,MAAM,sBAAsB,KAAK;AAChD,SAAI,OAAO,aAAa,GAAG;AACzB,gBAAU,OAAO,QAAQ,OAAO,OAAO;KACxC;IACF;GACF;GAED,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK;IACtC;IACA;IACA;IACA;IACA;IACA;IACA,QAAQ,WAAW;GACpB,EAAC;AAEF,OAAI,iBAAiB,OAAO,IAAI,qBAAqB,OAAO,EAAE;AAC5D,QAAI,KAAK,MAAM;AACb,SAAI,WAAW,OAAO,QAAS;KAC/B,MAAM,SAAS,SAAS;KACxB,MAAM,OAAO,eAAe;AAC5B,SAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,SAAI,KAAK;IACV,EAAC;GACH,OAAM;IACL,MAAM,EAAE,QAAQ,GAAG;AAEnB,QAAI,kBAAkB,cAAc;KAClC,MAAM,EAAE,QAAQ,UAAU,GAAG;KAC7B,MAAM,EAAE,MAAM,GAAG;KAEjB,IAAIC;AAEJ,SAAI,kBAAkB,gBAAgB;AACpC,eAAS,SAAS,QAAQ,OAAc;KACzC,WAAU,kBAAkB,YAAY,kBAAkB,QAAQ;AACjE,eAAS,SAAS,KAAK,OAAO;KAC/B,OAAM;AACL,YAAM,IAAI,MAAM;KACjB;AAED,SAAI,KAAK,MAAM;AACb,UAAI,WAAW,OAAO,QAAS;AAC/B,sBAAgB,IAAI,kBAAkB,OAAO;AAC7C,sBAAgB,IAAI,gBAAgB,KAAK;AACzC,UAAI,SAAS,KACX,KAAI,YAAY,kBAAkB,SAAS,KAAK,UAAU,CAAC;AAC7D,iBAAW,KAAK,gBAAgB;KACjC,EAAC;AAEF,gBAAW,OAAO,iBAAiB,SAAS,MAAM,OAAO,SAAS,EAAE,EAClE,MAAM,KACP,EAAC;AAEF,YAAO,GAAG,QAAQ,CAAC,UAAU;AAC3B,UAAI,WAAW,OAAO,QAAS;MAC/B,MAAM,MAAM,OAAO,KAAK,MAAM;MAC9B,MAAM,KAAK,IAAI,OAAO,MACpB,IAAI,YACJ,IAAI,aAAa,IAAI,WACtB;MACD,MAAM,KAAK,IAAI,MAAM,GAAG;AACxB,WAAK,IAAI;AACP,cAAO,OAAO;AACd,WAAI,WAAW,MAAM;AACnB,eAAO,QAAQ;AACf,eAAO;OACR,EAAC;MACH;KACF,EAAC;AACF,WAAM,KAAK,QAAQ,MAAM;AACzB,SAAI,OAAO,iBAAiB;AAC1B,UAAI,IAAI,WAAW,KAAK;KACzB,OAAM;AACL,UAAI,KAAK;KACV;IACF,OAAM;AACL,SAAI,KAAK,MAAM;AACb,UAAI,WAAW,OAAO,QAAS;MAC/B,MAAM,SAAS,SAAS;MACxB,MAAM,OAAO,eAAe;MAC5B,MAAM,SAAS,OAAO,QAAQ,OAAO,OAAO;AAC5C,UAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,sBAAgB,IAAI,gBAAgB,OAAO,QAAQ,YAAY;AAC/D,iBAAW,KAAK,gBAAgB;AAChC,UAAI,IAAI,OAAO;KAChB,EAAC;IACH;GACF;EACF,SAAQ,OAAO;AACd,OAAI,WAAW,OAAO,QAAS;AAC/B,OAAI,iBAAiB,wBAAwB;AAC3C,QAAI,KAAK,MAAM;AACb,SAAI,WAAW,OAAO,QAAS;KAC/B,MAAM,SACJ,iBAAiB,8BACb,SAAS,uBACT,SAAS;KACf,MAAM,OAAO,eAAe;AAC5B,SAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,SAAI,KAAK;IACV,EAAC;GACH,WAAU,iBAAiB,eAAe;AACzC,QAAI,KAAK,MAAM;AACb,SAAI,WAAW,OAAO,QAAS;KAC/B,MAAM,SACJ,MAAM,QAAQ,cACV,YAAY,MAAM,QAClB,SAAS;KACf,MAAM,OAAO,eAAe;AAC5B,SAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,SAAI,IAAI,OAAQ,QAAQ,OAAO,MAAM,CAAC;IACvC,EAAC;GACH,OAAM;AACL,SAAK,SAAS,OAAO,yCAAyC;AAC9D,QAAI,KAAK,MAAM;AACb,SAAI,WAAW,OAAO,QAAS;KAC/B,MAAM,SAAS,SAAS;KACxB,MAAM,OAAO,eAAe;KAC5B,MAAM,UAAU,OAAQ,QAAQ,OAC9B,IAAI,cACF,UAAU,qBACV,yBAEH;AACD,SAAI,aAAa,EAAE,OAAO,GAAG,KAAK,EAAE;AACpC,SAAI,IAAI,QAAQ;IACjB,EAAC;GACH;EACF,UAAS;AACR,aAAU,SAAS,CAAC,MAAM,CAAC,UAAU;AACnC,SAAK,SAAS,OAAO,uCAAuC;GAC7D,EAAC;EACH;CACF;CAED,IAAc,WAAW;AACvB,SAAO,KAAK,QAAQ;CACrB;CAED,IAAc,SAAS;AACrB,SAAO,KAAK,QAAQ;CACrB;CAED,MAAgB,SACdC,OACA,UAAU,0CACV;AACA,OAAK,OAAO,MAAM,IAAI,MAAM,SAAS,EAAE,MAAO,GAAE;CACjD;CAED,AAAU,UAAUN,KAAmBC,KAAkB;AACvD,MAAI,KAAK,QAAQ,SAAS,MAAO;EAEjC,MAAM,SAAS,IAAI,UAAU,SAAS;AACtC,OAAK,OAAQ;EAEb,IAAI,UAAU;AAEd,MAAI,KAAK,QAAQ,SAAS,aAAa,KAAK,QAAQ,SAAS,MAAM;AACjE,aAAU;EACX,WAAU,MAAM,QAAQ,KAAK,QAAQ,KAAK,EAAE;AAC3C,aAAU,KAAK,QAAQ,KAAK,SAAS,OAAO;EAC7C,OAAM;AACL,aAAU,KAAK,QAAQ,KAAK,OAAO;EACpC;AAED,OAAK,QAAS;AAEd,MAAI,YAAY,+BAA+B,OAAO;AACtD,MAAI,YAAY,gCAAgC,eAAe;AAC/D,MAAI,YAAY,gCAAgC,YAAY;AAC5D,MAAI,YAAY,oCAAoC,OAAO;CAC5D;CAED,CAAW,kBAAkB,KAC3BN,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;AAC/B,OAAK,SAAS,OAAO,IAAI,OAAO;CACjC;CAED,CAAW,kBAAkB,UAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;AAC/B,OAAK,SAAS,YAAY,IAAI,OAAO;CACtC;CAED,CAAW,kBAAkB,gBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;AAC/B,OAAK,SAAS,kBAAkB,IAAI,OAAO;CAC5C;CAED,CAAW,kBAAkB,kBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;EAC/B,MAAM,WAAW,aAAa,QAAQ,SAAS;AAC/C,OAAK,SAAS,iBACZ,IACA,UACA,OAAO,MAAM,YAAY,kBAAkB,CAC5C;CACF;CAED,CAAW,kBAAkB,iBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;EAC/B,MAAM,WAAW,aAAa,QAAQ,SAAS;AAC/C,OAAK,SAAS,gBAAgB,IAAI,SAAS;CAC5C;CAED,CAAW,kBAAkB,mBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;EAC/B,MAAM,WAAW,aAAa,QAAQ,SAAS;AAC/C,OAAK,SAAS,kBAAkB,IAAI,SAAS;CAC9C;CAED,CAAW,kBAAkB,kBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;EAC/B,MAAM,WAAW,aAAa,QAAQ,SAAS;AAC/C,OAAK,SAAS,iBAAiB,IAAI,SAAS;CAC7C;CAED,CAAW,kBAAkB,mBAC3BJ,IACAI,QACA;EACA,MAAM,EAAE,IAAI,GAAG,GAAG,aAAa;EAC/B,MAAM,WAAW,aAAa,QAAQ,SAAS;AAC/C,OAAK,SAAS,kBAAkB,IAAI,SAAS;CAC9C;AACF","names":["context: TransportPluginContext","options: WsTransportOptions","ws: WsTransportSocket","error: any","connection: Connection<WsConnectionData>","messageType: ServerMessageType","buffer: ArrayBuffer","res: HttpResponse","req: HttpRequest","metadata: ProtocolApiCallOptions['metadata']","format: ReturnType<typeof getFormat>","payload: any","stream: Readable","cause: any"],"sources":["../src/server.ts"],"sourcesContent":["import {\n App,\n type HttpRequest,\n type HttpResponse,\n SSLApp,\n type TemplatedApp,\n us_socket_local_port,\n} from 'uWebSockets.js'\nimport { randomUUID } from 'node:crypto'\nimport { once } from 'node:events'\nimport { Duplex, Readable } from 'node:stream'\nimport { Scope } from '@nmtjs/core'\nimport {\n ClientMessageType,\n decodeNumber,\n ErrorCode,\n ProtocolBlob,\n type ServerMessageType,\n} from '@nmtjs/protocol'\nimport {\n Connection,\n getFormat,\n isIterableResult,\n isSubscriptionResult,\n type ProtocolApiCallOptions,\n ProtocolClientStream,\n ProtocolError,\n ProtocolInjectables,\n type Transport,\n type TransportPluginContext,\n UnsupportedContentTypeError,\n UnsupportedFormatError,\n} from '@nmtjs/protocol/server'\nimport {\n AllowedHttpMethod,\n HttpCode,\n HttpCodeMap,\n HttpStatusText,\n} from './http.ts'\nimport { WsTransportInjectables } from './injectables.ts'\nimport type {\n WsConnectionData,\n WsTransportOptions,\n WsTransportSocket,\n WsUserData,\n} from './types.ts'\nimport {\n getRequestBody,\n getRequestData,\n readableToArrayBuffer,\n send,\n setHeaders,\n} from './utils.ts'\n\nconst DEFAULT_ALLOWED_METHODS = ['post'] as ('get' | 'post')[]\n\nexport class WsTransportServer implements Transport<WsConnectionData> {\n protected server!: TemplatedApp\n protected clients: Map<string, WsTransportSocket> = new Map()\n\n constructor(\n protected readonly context: TransportPluginContext,\n protected readonly options: WsTransportOptions,\n ) {\n this.server = this.options.tls ? SSLApp(options.tls!) : App()\n this.server\n .options('/*', (res, req) => {\n this.applyCors(res, req)\n res.writeStatus('200 OK')\n res.endWithoutBody()\n })\n .get('/healthy', (res, req) => {\n this.applyCors(res, req)\n res.writeHeader('Content-Type', 'text/plain')\n res.end('OK')\n })\n .ws<WsUserData>('/api', {\n sendPingsAutomatically: true,\n maxPayloadLength: this.options.maxPayloadLength,\n upgrade: async (res, req, socketContext) => {\n const ac = new AbortController()\n\n res.onAborted(ac.abort.bind(ac))\n\n const requestData = getRequestData(req, res)\n const contentType =\n requestData.query.get('content-type') ||\n requestData.headers.get('content-type')\n const acceptType =\n requestData.query.get('accept') || requestData.headers.get('accept')\n\n const connectionId = randomUUID()\n const controller = new AbortController()\n try {\n const { context } = await this.protocol.addConnection(\n this,\n { id: connectionId, data: { type: 'ws' } },\n { acceptType, contentType },\n )\n context.container.provide(\n WsTransportInjectables.connectionData,\n requestData,\n )\n context.container.provide(\n ProtocolInjectables.connectionAbortSignal,\n controller.signal,\n )\n\n if (!ac.signal.aborted) {\n res.cork(() => {\n res.upgrade(\n {\n id: connectionId,\n request: requestData,\n contentType,\n acceptType,\n backpressure: null,\n context,\n controller,\n } as WsUserData,\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n socketContext,\n )\n })\n }\n } catch (error) {\n this.logger.debug(\n new Error('Failed to upgrade connection', { cause: error }),\n )\n if (!ac.signal.aborted) {\n res.cork(() => {\n res.writeStatus('500 Internal Server Error')\n res.endWithoutBody()\n })\n }\n }\n },\n open: (ws: WsTransportSocket) => {\n const { id } = ws.getUserData()\n this.logger.debug('Connection %s opened', id)\n this.clients.set(id, ws)\n },\n message: async (ws: WsTransportSocket, buffer) => {\n const messageType = decodeNumber(buffer, 'Uint8')\n if (messageType in this === false) {\n ws.end(1011, 'Unknown message type')\n } else {\n try {\n await this[messageType](\n ws,\n buffer.slice(Uint8Array.BYTES_PER_ELEMENT),\n )\n } catch (error: any) {\n this.logError(error, 'Error while processing message')\n }\n }\n },\n drain: (ws: WsTransportSocket) => {\n const data = ws.getUserData()\n data.backpressure?.resolve()\n data.backpressure = null\n },\n close: async (ws: WsTransportSocket, code, message) => {\n const { id, controller } = ws.getUserData()\n controller.abort()\n this.logger.debug(\n 'Connection %s closed with code %s: %s',\n id,\n code,\n Buffer.from(message).toString(),\n )\n this.clients.delete(id)\n await this.protocol.removeConnection(id)\n },\n })\n .get('/api/:namespace/:procedure', this.httpHandler.bind(this))\n .post('/api/:namespace/:procedure', this.httpHandler.bind(this))\n }\n\n send(\n connection: Connection<WsConnectionData>,\n messageType: ServerMessageType,\n buffer: ArrayBuffer,\n ) {\n const ws = this.clients.get(connection.id)\n if (ws) send(ws, messageType, buffer)\n }\n\n async start() {\n return new Promise<void>((resolve, reject) => {\n const { hostname = '127.0.0.1', port = 0, unix } = this.options\n if (unix) {\n this.server.listen_unix((socket) => {\n if (socket) {\n this.logger.info('Server started on unix://%s', unix)\n resolve()\n } else {\n reject(new Error('Failed to start WebSockets server'))\n }\n }, unix)\n } else {\n this.server.listen(hostname, port, (socket) => {\n if (socket) {\n this.logger.info(\n 'WebSocket Server started on %s:%s',\n hostname,\n us_socket_local_port(socket),\n )\n resolve()\n } else {\n reject(new Error('Failed to start WebSockets server'))\n }\n })\n }\n })\n }\n\n async stop() {\n this.server.close()\n }\n\n // TODO: decompose this mess\n protected async httpHandler(res: HttpResponse, req: HttpRequest) {\n this.applyCors(res, req)\n\n const controller = new AbortController()\n\n res.onAborted(controller.abort.bind(controller))\n\n const method = req.getMethod() as 'get' | 'post'\n const namespace = req.getParameter('namespace')\n const procedure = req.getParameter('procedure')\n const requestData = getRequestData(req, res)\n\n if (!namespace || !procedure) {\n const status = HttpCode.NotFound\n const text = HttpStatusText[status]\n return void res.cork(() => {\n if (controller.signal.aborted) return\n res.writeStatus(`${status} ${text}`)\n res.endWithoutBody()\n })\n }\n\n const isBlob = requestData.headers.get('x-neemata-blob') === 'true'\n\n const contentType = requestData.headers.get('content-type')\n const acceptType = requestData.headers.get('accept')\n const connectionId = randomUUID()\n const connection = new Connection<WsConnectionData>({\n id: connectionId,\n data: { type: 'http' },\n })\n const responseHeaders = new Headers()\n const container = this.context.container.fork(Scope.Call)\n container.provide(ProtocolInjectables.connection, connection)\n container.provide(\n ProtocolInjectables.connectionAbortSignal,\n controller.signal,\n )\n container.provide(WsTransportInjectables.connectionData, requestData)\n container.provide(\n WsTransportInjectables.httpResponseHeaders,\n responseHeaders,\n )\n\n const body = method === 'post' ? getRequestBody(res) : undefined\n\n const metadata: ProtocolApiCallOptions['metadata'] = (metadata) => {\n const allowHttpMethod =\n metadata.get(AllowedHttpMethod) ?? DEFAULT_ALLOWED_METHODS\n if (!allowHttpMethod.includes(method)) {\n throw new ProtocolError(ErrorCode.NotFound)\n }\n }\n let format: ReturnType<typeof getFormat>\n try {\n format = getFormat(this.context.format, {\n acceptType,\n contentType: isBlob ? '*/*' : contentType,\n })\n\n let payload: any\n\n if (body) {\n if (isBlob) {\n const type = contentType || 'application/octet-stream'\n const contentLength = requestData.headers.get('content-length')\n const size = contentLength\n ? Number.parseInt(contentLength)\n : undefined\n const stream = new ProtocolClientStream(-1, { size, type })\n body.pipe(stream)\n payload = stream\n } else {\n const buffer = await readableToArrayBuffer(body)\n if (buffer.byteLength > 0) {\n payload = format.decoder.decode(buffer)\n }\n }\n }\n\n const result = await this.protocol.call({\n connection,\n namespace,\n procedure,\n payload,\n metadata,\n container,\n signal: controller.signal,\n })\n\n if (isIterableResult(result) || isSubscriptionResult(result)) {\n res.cork(() => {\n if (controller.signal.aborted) return\n const status = HttpCode.NotImplemented\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end()\n })\n } else {\n const { output } = result\n\n if (output instanceof ProtocolBlob) {\n const { source, metadata } = output\n const { type } = metadata\n\n let stream: Readable\n\n if (source instanceof ReadableStream) {\n stream = Readable.fromWeb(source as any)\n } else if (source instanceof Readable || source instanceof Duplex) {\n stream = Readable.from(source)\n } else {\n throw new Error('Invalid stream source')\n }\n\n res.cork(() => {\n if (controller.signal.aborted) return\n responseHeaders.set('X-Neemata-Blob', 'true')\n responseHeaders.set('Content-Type', type)\n if (metadata.size)\n res.writeHeader('Content-Length', metadata.size.toString())\n setHeaders(res, responseHeaders)\n })\n\n controller.signal.addEventListener('abort', () => stream.destroy(), {\n once: true,\n })\n\n stream.on('data', (chunk) => {\n if (controller.signal.aborted) return\n const buf = Buffer.from(chunk)\n const ab = buf.buffer.slice(\n buf.byteOffset,\n buf.byteOffset + buf.byteLength,\n )\n const ok = res.write(ab)\n if (!ok) {\n stream.pause()\n res.onWritable(() => {\n stream.resume()\n return true\n })\n }\n })\n await once(stream, 'end')\n if (stream.readableAborted) {\n res.end(undefined, true)\n } else {\n res.end()\n }\n } else {\n res.cork(() => {\n if (controller.signal.aborted) return\n const status = HttpCode.OK\n const text = HttpStatusText[status]\n const buffer = format.encoder.encode(output)\n res.writeStatus(`${status} ${text}`)\n responseHeaders.set('Content-Type', format.encoder.contentType)\n setHeaders(res, responseHeaders)\n res.end(buffer)\n })\n }\n }\n } catch (error) {\n if (controller.signal.aborted) return\n if (error instanceof UnsupportedFormatError) {\n res.cork(() => {\n if (controller.signal.aborted) return\n const status =\n error instanceof UnsupportedContentTypeError\n ? HttpCode.UnsupportedMediaType\n : HttpCode.NotAcceptable\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end()\n })\n } else if (error instanceof ProtocolError) {\n res.cork(() => {\n if (controller.signal.aborted) return\n const status =\n error.code in HttpCodeMap\n ? HttpCodeMap[error.code]\n : HttpCode.InternalServerError\n const text = HttpStatusText[status]\n res.writeStatus(`${status} ${text}`)\n res.end(format!.encoder.encode(error))\n })\n } else {\n this.logError(error, 'Unknown error while processing request')\n res.cork(() => {\n if (controller.signal.aborted) return\n const status = HttpCode.InternalServerError\n const text = HttpStatusText[status]\n const payload = format!.encoder.encode(\n new ProtocolError(\n ErrorCode.InternalServerError,\n 'Internal Server Error',\n ),\n )\n res.writeStatus(`${status} ${text}`)\n res.end(payload)\n })\n }\n } finally {\n container.dispose().catch((error) => {\n this.logError(error, 'Error while disposing call container')\n })\n }\n }\n\n protected get protocol() {\n return this.context.protocol\n }\n\n protected get logger() {\n return this.context.logger\n }\n\n protected async logError(\n cause: any,\n message = 'Unknown error while processing request',\n ) {\n this.logger.error(new Error(message, { cause }))\n }\n\n protected applyCors(res: HttpResponse, req: HttpRequest) {\n if (this.options.cors === false) return\n\n const origin = req.getHeader('origin')\n if (!origin) return\n\n let allowed = false\n\n if (this.options.cors === undefined || this.options.cors === true) {\n allowed = true\n } else if (Array.isArray(this.options.cors)) {\n allowed = this.options.cors.includes(origin)\n } else {\n allowed = this.options.cors(origin)\n }\n\n if (!allowed) return\n\n res.writeHeader('Access-Control-Allow-Origin', origin)\n res.writeHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.writeHeader('Access-Control-Allow-Methods', 'GET, POST')\n res.writeHeader('Access-Control-Allow-Credentials', 'true')\n }\n\n protected [ClientMessageType.Rpc](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcRaw(id, buffer)\n }\n\n protected [ClientMessageType.RpcAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcAbortRaw(id, buffer)\n }\n\n protected [ClientMessageType.RpcStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n this.protocol.rpcStreamAbortRaw(id, buffer)\n }\n\n protected [ClientMessageType.ClientStreamPush](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.pushClientStream(\n id,\n streamId,\n buffer.slice(Uint32Array.BYTES_PER_ELEMENT),\n )\n }\n\n protected [ClientMessageType.ClientStreamEnd](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.endClientStream(id, streamId)\n }\n\n protected [ClientMessageType.ClientStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.abortClientStream(id, streamId)\n }\n\n protected [ClientMessageType.ServerStreamPull](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.pullServerStream(id, streamId)\n }\n\n protected [ClientMessageType.ServerStreamAbort](\n ws: WsTransportSocket,\n buffer: ArrayBuffer,\n ) {\n const { id } = ws.getUserData()\n const streamId = decodeNumber(buffer, 'Uint32')\n this.protocol.abortServerStream(id, streamId)\n }\n}\n"],"version":3,"file":"server.js"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { AppOptions, WebSocket } from 'uWebSockets.js'\nimport type { Connection, ConnectionContext } from '@nmtjs/protocol/server'\nimport type { InteractivePromise } from '../../common/src/index.ts'\nimport type { RequestData } from './utils.ts'\n\nexport type WsConnectionData = { type: 'ws' | 'http' }\n\nexport type WsUserData = {\n id: Connection['id']\n backpressure: InteractivePromise<void> | null\n request: RequestData\n acceptType: string | null\n contentType: string | null\n context: ConnectionContext\n}\n\nexport type WsTransportSocket = WebSocket<WsUserData>\n\nexport type WsTransportOptions = {\n port?: number\n hostname?: string\n unix?: string\n tls?: AppOptions\n cors?: boolean | string[] | ((origin: string) => boolean)\n maxPayloadLength?: number\n maxStreamChunkLength?: number\n}\n"],"version":3,"file":"types.js"}
|
|
1
|
+
{"mappings":"","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { AppOptions, WebSocket } from 'uWebSockets.js'\nimport type { Connection, ConnectionContext } from '@nmtjs/protocol/server'\nimport type { InteractivePromise } from '../../common/src/index.ts'\nimport type { RequestData } from './utils.ts'\n\nexport type WsConnectionData = { type: 'ws' | 'http' }\n\nexport type WsUserData = {\n id: Connection['id']\n backpressure: InteractivePromise<void> | null\n request: RequestData\n acceptType: string | null\n contentType: string | null\n context: ConnectionContext\n controller: AbortController\n}\n\nexport type WsTransportSocket = WebSocket<WsUserData>\n\nexport type WsTransportOptions = {\n port?: number\n hostname?: string\n unix?: string\n tls?: AppOptions\n cors?: boolean | string[] | ((origin: string) => boolean)\n maxPayloadLength?: number\n maxStreamChunkLength?: number\n}\n"],"version":3,"file":"types.js"}
|
package/package.json
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.52.0"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@nmtjs/
|
|
15
|
-
"@nmtjs/
|
|
16
|
-
"@nmtjs/
|
|
14
|
+
"@nmtjs/core": "0.11.4",
|
|
15
|
+
"@nmtjs/protocol": "0.11.4",
|
|
16
|
+
"@nmtjs/common": "0.11.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^20",
|
|
20
|
-
"@nmtjs/
|
|
21
|
-
"@nmtjs/
|
|
22
|
-
"@nmtjs/
|
|
20
|
+
"@nmtjs/common": "0.11.4",
|
|
21
|
+
"@nmtjs/client": "0.11.4",
|
|
22
|
+
"@nmtjs/protocol": "0.11.4"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=20"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"LICENSE.md",
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
|
-
"version": "0.11.
|
|
33
|
+
"version": "0.11.4",
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "neemata-build --root ./src './**/*.ts'",
|
|
36
36
|
"type-check": "tsc --noEmit"
|
package/src/server.ts
CHANGED
|
@@ -90,7 +90,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
90
90
|
requestData.query.get('accept') || requestData.headers.get('accept')
|
|
91
91
|
|
|
92
92
|
const connectionId = randomUUID()
|
|
93
|
-
|
|
93
|
+
const controller = new AbortController()
|
|
94
94
|
try {
|
|
95
95
|
const { context } = await this.protocol.addConnection(
|
|
96
96
|
this,
|
|
@@ -101,6 +101,11 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
101
101
|
WsTransportInjectables.connectionData,
|
|
102
102
|
requestData,
|
|
103
103
|
)
|
|
104
|
+
context.container.provide(
|
|
105
|
+
ProtocolInjectables.connectionAbortSignal,
|
|
106
|
+
controller.signal,
|
|
107
|
+
)
|
|
108
|
+
|
|
104
109
|
if (!ac.signal.aborted) {
|
|
105
110
|
res.cork(() => {
|
|
106
111
|
res.upgrade(
|
|
@@ -111,6 +116,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
111
116
|
acceptType,
|
|
112
117
|
backpressure: null,
|
|
113
118
|
context,
|
|
119
|
+
controller,
|
|
114
120
|
} as WsUserData,
|
|
115
121
|
req.getHeader('sec-websocket-key'),
|
|
116
122
|
req.getHeader('sec-websocket-protocol'),
|
|
@@ -157,8 +163,8 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
157
163
|
data.backpressure = null
|
|
158
164
|
},
|
|
159
165
|
close: async (ws: WsTransportSocket, code, message) => {
|
|
160
|
-
const { id } = ws.getUserData()
|
|
161
|
-
|
|
166
|
+
const { id, controller } = ws.getUserData()
|
|
167
|
+
controller.abort()
|
|
162
168
|
this.logger.debug(
|
|
163
169
|
'Connection %s closed with code %s: %s',
|
|
164
170
|
id,
|
|
@@ -219,9 +225,9 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
219
225
|
protected async httpHandler(res: HttpResponse, req: HttpRequest) {
|
|
220
226
|
this.applyCors(res, req)
|
|
221
227
|
|
|
222
|
-
const
|
|
228
|
+
const controller = new AbortController()
|
|
223
229
|
|
|
224
|
-
res.onAborted(
|
|
230
|
+
res.onAborted(controller.abort.bind(controller))
|
|
225
231
|
|
|
226
232
|
const method = req.getMethod() as 'get' | 'post'
|
|
227
233
|
const namespace = req.getParameter('namespace')
|
|
@@ -232,7 +238,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
232
238
|
const status = HttpCode.NotFound
|
|
233
239
|
const text = HttpStatusText[status]
|
|
234
240
|
return void res.cork(() => {
|
|
235
|
-
if (
|
|
241
|
+
if (controller.signal.aborted) return
|
|
236
242
|
res.writeStatus(`${status} ${text}`)
|
|
237
243
|
res.endWithoutBody()
|
|
238
244
|
})
|
|
@@ -250,6 +256,10 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
250
256
|
const responseHeaders = new Headers()
|
|
251
257
|
const container = this.context.container.fork(Scope.Call)
|
|
252
258
|
container.provide(ProtocolInjectables.connection, connection)
|
|
259
|
+
container.provide(
|
|
260
|
+
ProtocolInjectables.connectionAbortSignal,
|
|
261
|
+
controller.signal,
|
|
262
|
+
)
|
|
253
263
|
container.provide(WsTransportInjectables.connectionData, requestData)
|
|
254
264
|
container.provide(
|
|
255
265
|
WsTransportInjectables.httpResponseHeaders,
|
|
@@ -299,12 +309,12 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
299
309
|
payload,
|
|
300
310
|
metadata,
|
|
301
311
|
container,
|
|
302
|
-
signal:
|
|
312
|
+
signal: controller.signal,
|
|
303
313
|
})
|
|
304
314
|
|
|
305
315
|
if (isIterableResult(result) || isSubscriptionResult(result)) {
|
|
306
316
|
res.cork(() => {
|
|
307
|
-
if (
|
|
317
|
+
if (controller.signal.aborted) return
|
|
308
318
|
const status = HttpCode.NotImplemented
|
|
309
319
|
const text = HttpStatusText[status]
|
|
310
320
|
res.writeStatus(`${status} ${text}`)
|
|
@@ -328,7 +338,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
328
338
|
}
|
|
329
339
|
|
|
330
340
|
res.cork(() => {
|
|
331
|
-
if (
|
|
341
|
+
if (controller.signal.aborted) return
|
|
332
342
|
responseHeaders.set('X-Neemata-Blob', 'true')
|
|
333
343
|
responseHeaders.set('Content-Type', type)
|
|
334
344
|
if (metadata.size)
|
|
@@ -336,12 +346,12 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
336
346
|
setHeaders(res, responseHeaders)
|
|
337
347
|
})
|
|
338
348
|
|
|
339
|
-
|
|
349
|
+
controller.signal.addEventListener('abort', () => stream.destroy(), {
|
|
340
350
|
once: true,
|
|
341
351
|
})
|
|
342
352
|
|
|
343
353
|
stream.on('data', (chunk) => {
|
|
344
|
-
if (
|
|
354
|
+
if (controller.signal.aborted) return
|
|
345
355
|
const buf = Buffer.from(chunk)
|
|
346
356
|
const ab = buf.buffer.slice(
|
|
347
357
|
buf.byteOffset,
|
|
@@ -364,7 +374,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
364
374
|
}
|
|
365
375
|
} else {
|
|
366
376
|
res.cork(() => {
|
|
367
|
-
if (
|
|
377
|
+
if (controller.signal.aborted) return
|
|
368
378
|
const status = HttpCode.OK
|
|
369
379
|
const text = HttpStatusText[status]
|
|
370
380
|
const buffer = format.encoder.encode(output)
|
|
@@ -376,10 +386,10 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
376
386
|
}
|
|
377
387
|
}
|
|
378
388
|
} catch (error) {
|
|
379
|
-
if (
|
|
389
|
+
if (controller.signal.aborted) return
|
|
380
390
|
if (error instanceof UnsupportedFormatError) {
|
|
381
391
|
res.cork(() => {
|
|
382
|
-
if (
|
|
392
|
+
if (controller.signal.aborted) return
|
|
383
393
|
const status =
|
|
384
394
|
error instanceof UnsupportedContentTypeError
|
|
385
395
|
? HttpCode.UnsupportedMediaType
|
|
@@ -390,7 +400,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
390
400
|
})
|
|
391
401
|
} else if (error instanceof ProtocolError) {
|
|
392
402
|
res.cork(() => {
|
|
393
|
-
if (
|
|
403
|
+
if (controller.signal.aborted) return
|
|
394
404
|
const status =
|
|
395
405
|
error.code in HttpCodeMap
|
|
396
406
|
? HttpCodeMap[error.code]
|
|
@@ -402,7 +412,7 @@ export class WsTransportServer implements Transport<WsConnectionData> {
|
|
|
402
412
|
} else {
|
|
403
413
|
this.logError(error, 'Unknown error while processing request')
|
|
404
414
|
res.cork(() => {
|
|
405
|
-
if (
|
|
415
|
+
if (controller.signal.aborted) return
|
|
406
416
|
const status = HttpCode.InternalServerError
|
|
407
417
|
const text = HttpStatusText[status]
|
|
408
418
|
const payload = format!.encoder.encode(
|