@nmtjs/ws-transport 0.12.6 → 0.12.7

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/package.json CHANGED
@@ -12,26 +12,25 @@
12
12
  "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.52.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "@nmtjs/common": "0.12.6",
16
- "@nmtjs/protocol": "0.12.6",
17
- "@nmtjs/core": "0.12.6"
15
+ "@nmtjs/common": "0.12.7",
16
+ "@nmtjs/core": "0.12.7",
17
+ "@nmtjs/protocol": "0.12.7"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/node": "^20",
21
- "@nmtjs/protocol": "0.12.6",
22
- "@nmtjs/client": "0.12.6",
23
- "@nmtjs/common": "0.12.6"
21
+ "@nmtjs/client": "0.12.7",
22
+ "@nmtjs/protocol": "0.12.7",
23
+ "@nmtjs/common": "0.12.7"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=20"
27
27
  },
28
28
  "files": [
29
- "src",
30
29
  "dist",
31
30
  "LICENSE.md",
32
31
  "README.md"
33
32
  ],
34
- "version": "0.12.6",
33
+ "version": "0.12.7",
35
34
  "scripts": {
36
35
  "build": "tsc",
37
36
  "type-check": "tsc --noEmit"
package/src/http.ts DELETED
@@ -1,150 +0,0 @@
1
- import { createMetadataKey } from '@nmtjs/core'
2
- import { ErrorCode } from '@nmtjs/protocol'
3
-
4
- export enum HttpCode {
5
- Continue = 100,
6
- SwitchingProtocols = 101,
7
- Processing = 102,
8
- EarlyHints = 103,
9
- OK = 200,
10
- Created = 201,
11
- Accepted = 202,
12
- NonAuthoritativeInformation = 203,
13
- NoContent = 204,
14
- ResetContent = 205,
15
- PartialContent = 206,
16
- MultiStatus = 207,
17
- AlreadyReported = 208,
18
- IMUsed = 226,
19
- MultipleChoices = 300,
20
- MovedPermanently = 301,
21
- Found = 302,
22
- SeeOther = 303,
23
- NotModified = 304,
24
- UseProxy = 305,
25
- TemporaryRedirect = 307,
26
- PermanentRedirect = 308,
27
- BadRequest = 400,
28
- Unauthorized = 401,
29
- PaymentRequired = 402,
30
- Forbidden = 403,
31
- NotFound = 404,
32
- MethodNotAllowed = 405,
33
- NotAcceptable = 406,
34
- ProxyAuthenticationRequired = 407,
35
- RequestTimeout = 408,
36
- Conflict = 409,
37
- Gone = 410,
38
- LengthRequired = 411,
39
- PreconditionFailed = 412,
40
- PayloadTooLarge = 413,
41
- URITooLong = 414,
42
- UnsupportedMediaType = 415,
43
- RangeNotSatisfiable = 416,
44
- ExpectationFailed = 417,
45
- ImATeapot = 418,
46
- MisdirectedRequest = 421,
47
- UnprocessableEntity = 422,
48
- Locked = 423,
49
- FailedDependency = 424,
50
- TooEarly = 425,
51
- UpgradeRequired = 426,
52
- PreconditionRequired = 428,
53
- TooManyRequests = 429,
54
- RequestHeaderFieldsTooLarge = 431,
55
- UnavailableForLegalReasons = 451,
56
- InternalServerError = 500,
57
- NotImplemented = 501,
58
- BadGateway = 502,
59
- ServiceUnavailable = 503,
60
- GatewayTimeout = 504,
61
- HTTPVersionNotSupported = 505,
62
- VariantAlsoNegotiates = 506,
63
- InsufficientStorage = 507,
64
- LoopDetected = 508,
65
- NotExtended = 510,
66
- NetworkAuthenticationRequired = 511,
67
- }
68
-
69
- export const HttpStatusText: Record<HttpCode, string> = {
70
- [HttpCode.Continue]: 'Continue',
71
- [HttpCode.SwitchingProtocols]: 'Switching Protocols',
72
- [HttpCode.Processing]: 'Processing',
73
- [HttpCode.EarlyHints]: 'Early Hints',
74
- [HttpCode.OK]: 'OK',
75
- [HttpCode.Created]: 'Created',
76
- [HttpCode.Accepted]: 'Accepted',
77
- [HttpCode.NonAuthoritativeInformation]: 'Non-Authoritative Information',
78
- [HttpCode.NoContent]: 'No Content',
79
- [HttpCode.ResetContent]: 'Reset Content',
80
- [HttpCode.PartialContent]: 'Partial Content',
81
- [HttpCode.MultiStatus]: 'Multi-Status',
82
- [HttpCode.AlreadyReported]: 'Already Reported',
83
- [HttpCode.IMUsed]: 'IM Used',
84
- [HttpCode.MultipleChoices]: 'Multiple Choices',
85
- [HttpCode.MovedPermanently]: 'Moved Permanently',
86
- [HttpCode.Found]: 'Found',
87
- [HttpCode.SeeOther]: 'See Other',
88
- [HttpCode.NotModified]: 'Not Modified',
89
- [HttpCode.UseProxy]: 'Use Proxy',
90
- [HttpCode.TemporaryRedirect]: 'Temporary Redirect',
91
- [HttpCode.PermanentRedirect]: 'Permanent Redirect',
92
- [HttpCode.BadRequest]: 'Bad Request',
93
- [HttpCode.Unauthorized]: 'Unauthorized',
94
- [HttpCode.PaymentRequired]: 'Payment Required',
95
- [HttpCode.Forbidden]: 'Forbidden',
96
- [HttpCode.NotFound]: 'Not Found',
97
- [HttpCode.MethodNotAllowed]: 'Method Not Allowed',
98
- [HttpCode.NotAcceptable]: 'Not Acceptable',
99
- [HttpCode.ProxyAuthenticationRequired]: 'Proxy Authentication Required',
100
- [HttpCode.RequestTimeout]: 'Request Timeout',
101
- [HttpCode.Conflict]: 'Conflict',
102
- [HttpCode.Gone]: 'Gone',
103
- [HttpCode.LengthRequired]: 'Length Required',
104
- [HttpCode.PreconditionFailed]: 'Precondition Failed',
105
- [HttpCode.PayloadTooLarge]: 'Payload Too Large',
106
- [HttpCode.URITooLong]: 'URI Too Long',
107
- [HttpCode.UnsupportedMediaType]: 'Unsupported Media Type',
108
- [HttpCode.RangeNotSatisfiable]: 'Range Not Satisfiable',
109
- [HttpCode.ExpectationFailed]: 'Expectation Failed',
110
- [HttpCode.ImATeapot]: "I'm a Teapot",
111
- [HttpCode.MisdirectedRequest]: 'Misdirected Request',
112
- [HttpCode.UnprocessableEntity]: 'Unprocessable Entity',
113
- [HttpCode.Locked]: 'Locked',
114
- [HttpCode.FailedDependency]: 'Failed Dependency',
115
- [HttpCode.TooEarly]: 'Too Early',
116
- [HttpCode.UpgradeRequired]: 'Upgrade Required',
117
- [HttpCode.PreconditionRequired]: 'Precondition Required',
118
- [HttpCode.TooManyRequests]: 'Too Many Requests',
119
- [HttpCode.RequestHeaderFieldsTooLarge]: 'Request Header Fields Too Large',
120
- [HttpCode.UnavailableForLegalReasons]: 'Unavailable For Legal Reasons',
121
- [HttpCode.InternalServerError]: 'Internal Server Error',
122
- [HttpCode.NotImplemented]: 'Not Implemented',
123
- [HttpCode.BadGateway]: 'Bad Gateway',
124
- [HttpCode.ServiceUnavailable]: 'Service Unavailable',
125
- [HttpCode.GatewayTimeout]: 'Gateway Timeout',
126
- [HttpCode.HTTPVersionNotSupported]: 'HTTP Version Not Supported',
127
- [HttpCode.VariantAlsoNegotiates]: 'Variant Also Negotiates',
128
- [HttpCode.InsufficientStorage]: 'Insufficient Storage',
129
- [HttpCode.LoopDetected]: 'Loop Detected',
130
- [HttpCode.NotExtended]: 'Not Extended',
131
- [HttpCode.NetworkAuthenticationRequired]: 'Network Authentication Required',
132
- }
133
-
134
- export const HttpCodeMap = {
135
- [ErrorCode.ValidationError]: HttpCode.BadRequest,
136
- [ErrorCode.BadRequest]: HttpCode.BadRequest,
137
- [ErrorCode.NotFound]: HttpCode.NotFound,
138
- [ErrorCode.Forbidden]: HttpCode.Forbidden,
139
- [ErrorCode.Unauthorized]: HttpCode.Unauthorized,
140
- [ErrorCode.InternalServerError]: HttpCode.InternalServerError,
141
- [ErrorCode.NotAcceptable]: HttpCode.NotAcceptable,
142
- [ErrorCode.RequestTimeout]: HttpCode.RequestTimeout,
143
- [ErrorCode.GatewayTimeout]: HttpCode.GatewayTimeout,
144
- [ErrorCode.ServiceUnavailable]: HttpCode.ServiceUnavailable,
145
- [ErrorCode.ClientRequestError]: HttpCode.BadRequest,
146
- [ErrorCode.ConnectionError]: HttpCode.NotAcceptable,
147
- }
148
-
149
- export const AllowedHttpMethod =
150
- createMetadataKey<Array<'get' | 'post'>>('http:method')
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './injectables.ts'
2
- export * from './server.ts'
3
- export * from './transport.ts'
4
- export * from './types.ts'
5
- export * from './utils.ts'
@@ -1,17 +0,0 @@
1
- import { createLazyInjectable, type LazyInjectable, Scope } from '@nmtjs/core'
2
- import { ProtocolInjectables } from '@nmtjs/protocol/server'
3
- import type { WsUserData } from './types.ts'
4
-
5
- const connectionData = ProtocolInjectables.connectionData as LazyInjectable<
6
- WsUserData['request'],
7
- Scope.Connection
8
- >
9
-
10
- const httpResponseHeaders = createLazyInjectable<Headers, Scope.Call>(
11
- Scope.Call,
12
- )
13
-
14
- export const WsTransportInjectables = {
15
- connectionData,
16
- httpResponseHeaders,
17
- } as const
package/src/server.ts DELETED
@@ -1,545 +0,0 @@
1
- import {
2
- App,
3
- type HttpRequest,
4
- type HttpResponse,
5
- SSLApp,
6
- type TemplatedApp,
7
- us_socket_local_port,
8
- } from 'uWebSockets.js'
9
- import { randomUUID } from 'node:crypto'
10
- import { once } from 'node:events'
11
- import { Duplex, Readable } from 'node:stream'
12
- import { Scope } from '@nmtjs/core'
13
- import {
14
- ClientMessageType,
15
- decodeNumber,
16
- ErrorCode,
17
- ProtocolBlob,
18
- type ServerMessageType,
19
- } from '@nmtjs/protocol'
20
- import {
21
- Connection,
22
- getFormat,
23
- isIterableResult,
24
- type ProtocolApiCallOptions,
25
- ProtocolClientStream,
26
- ProtocolError,
27
- ProtocolInjectables,
28
- type Transport,
29
- type TransportPluginContext,
30
- UnsupportedContentTypeError,
31
- UnsupportedFormatError,
32
- } from '@nmtjs/protocol/server'
33
- import {
34
- AllowedHttpMethod,
35
- HttpCode,
36
- HttpCodeMap,
37
- HttpStatusText,
38
- } from './http.ts'
39
- import { WsTransportInjectables } from './injectables.ts'
40
- import type {
41
- WsConnectionData,
42
- WsTransportOptions,
43
- WsTransportSocket,
44
- WsUserData,
45
- } from './types.ts'
46
- import {
47
- getRequestBody,
48
- getRequestData,
49
- readableToArrayBuffer,
50
- send,
51
- setHeaders,
52
- } from './utils.ts'
53
-
54
- const DEFAULT_ALLOWED_METHODS = ['post'] as ('get' | 'post')[]
55
-
56
- export class WsTransportServer implements Transport<WsConnectionData> {
57
- protected server!: TemplatedApp
58
- protected clients: Map<string, WsTransportSocket> = new Map()
59
-
60
- constructor(
61
- protected readonly context: TransportPluginContext,
62
- protected readonly options: WsTransportOptions,
63
- ) {
64
- this.server = this.options.tls ? SSLApp(options.tls!) : App()
65
- this.server
66
- .options('/*', (res, req) => {
67
- this.applyCors(res, req)
68
- res.writeStatus('200 OK')
69
- res.endWithoutBody()
70
- })
71
- .get('/healthy', (res, req) => {
72
- this.applyCors(res, req)
73
- res.writeHeader('Content-Type', 'text/plain')
74
- res.end('OK')
75
- })
76
- .ws<WsUserData>('/api', {
77
- sendPingsAutomatically: true,
78
- maxPayloadLength: this.options.maxPayloadLength,
79
- upgrade: async (res, req, socketContext) => {
80
- const ac = new AbortController()
81
-
82
- res.onAborted(ac.abort.bind(ac))
83
-
84
- const requestData = getRequestData(req, res)
85
- const contentType =
86
- requestData.query.get('content-type') ||
87
- requestData.headers.get('content-type')
88
- const acceptType =
89
- requestData.query.get('accept') || requestData.headers.get('accept')
90
-
91
- const connectionId = randomUUID()
92
- const controller = new AbortController()
93
- try {
94
- const { context } = await this.protocol.addConnection(
95
- this,
96
- { id: connectionId, data: { type: 'ws' } },
97
- { acceptType, contentType },
98
- )
99
- context.container.provide(
100
- WsTransportInjectables.connectionData,
101
- requestData,
102
- )
103
- context.container.provide(
104
- ProtocolInjectables.connectionAbortSignal,
105
- controller.signal,
106
- )
107
-
108
- if (!ac.signal.aborted) {
109
- res.cork(() => {
110
- res.upgrade(
111
- {
112
- id: connectionId,
113
- request: requestData,
114
- contentType,
115
- acceptType,
116
- backpressure: null,
117
- context,
118
- controller,
119
- } as WsUserData,
120
- req.getHeader('sec-websocket-key'),
121
- req.getHeader('sec-websocket-protocol'),
122
- req.getHeader('sec-websocket-extensions'),
123
- socketContext,
124
- )
125
- })
126
- }
127
- } catch (error) {
128
- this.logger.debug(
129
- new Error('Failed to upgrade connection', { cause: error }),
130
- )
131
- if (!ac.signal.aborted) {
132
- res.cork(() => {
133
- res.writeStatus('500 Internal Server Error')
134
- res.endWithoutBody()
135
- })
136
- }
137
- }
138
- },
139
- open: (ws: WsTransportSocket) => {
140
- const { id } = ws.getUserData()
141
- this.logger.debug('Connection %s opened', id)
142
- this.clients.set(id, ws)
143
- },
144
- message: async (ws: WsTransportSocket, buffer) => {
145
- const messageType = decodeNumber(buffer, 'Uint8')
146
- if (messageType in this === false) {
147
- ws.end(1011, 'Unknown message type')
148
- } else {
149
- try {
150
- await this[messageType](
151
- ws,
152
- buffer.slice(Uint8Array.BYTES_PER_ELEMENT),
153
- )
154
- } catch (error: any) {
155
- this.logError(error, 'Error while processing message')
156
- }
157
- }
158
- },
159
- drain: (ws: WsTransportSocket) => {
160
- const data = ws.getUserData()
161
- data.backpressure?.resolve()
162
- data.backpressure = null
163
- },
164
- close: async (ws: WsTransportSocket, code, message) => {
165
- const { id, controller } = ws.getUserData()
166
- controller.abort()
167
- this.logger.debug(
168
- 'Connection %s closed with code %s: %s',
169
- id,
170
- code,
171
- Buffer.from(message).toString(),
172
- )
173
- this.clients.delete(id)
174
- await this.protocol.removeConnection(id)
175
- },
176
- })
177
- .get('/api/:namespace/:procedure', this.httpHandler.bind(this))
178
- .post('/api/:namespace/:procedure', this.httpHandler.bind(this))
179
- }
180
-
181
- send(
182
- connection: Connection<WsConnectionData>,
183
- messageType: ServerMessageType,
184
- buffer: ArrayBuffer,
185
- ) {
186
- const ws = this.clients.get(connection.id)
187
- if (ws) send(ws, messageType, buffer)
188
- }
189
-
190
- async start() {
191
- return new Promise<void>((resolve, reject) => {
192
- const { hostname = '127.0.0.1', port = 0, unix } = this.options
193
- if (unix) {
194
- this.server.listen_unix((socket) => {
195
- if (socket) {
196
- this.logger.info('Server started on unix://%s', unix)
197
- resolve()
198
- } else {
199
- reject(new Error('Failed to start WebSockets server'))
200
- }
201
- }, unix)
202
- } else {
203
- this.server.listen(hostname, port, (socket) => {
204
- if (socket) {
205
- this.logger.info(
206
- 'WebSocket Server started on %s:%s',
207
- hostname,
208
- us_socket_local_port(socket),
209
- )
210
- resolve()
211
- } else {
212
- reject(new Error('Failed to start WebSockets server'))
213
- }
214
- })
215
- }
216
- })
217
- }
218
-
219
- async stop() {
220
- this.server.close()
221
- }
222
-
223
- // TODO: decompose this mess
224
- protected async httpHandler(res: HttpResponse, req: HttpRequest) {
225
- this.applyCors(res, req)
226
-
227
- const controller = new AbortController()
228
-
229
- res.onAborted(controller.abort.bind(controller))
230
-
231
- const method = req.getMethod() as 'get' | 'post'
232
- const namespace = req.getParameter('namespace')
233
- const procedure = req.getParameter('procedure')
234
- const requestData = getRequestData(req, res)
235
-
236
- if (!namespace || !procedure) {
237
- const status = HttpCode.NotFound
238
- const text = HttpStatusText[status]
239
- return void res.cork(() => {
240
- if (controller.signal.aborted) return
241
- res.writeStatus(`${status} ${text}`)
242
- res.endWithoutBody()
243
- })
244
- }
245
-
246
- const isBlob = requestData.headers.get('x-neemata-blob') === 'true'
247
-
248
- const contentType = requestData.headers.get('content-type')
249
- const acceptType = requestData.headers.get('accept')
250
- const connectionId = randomUUID()
251
- const connection = new Connection<WsConnectionData>({
252
- id: connectionId,
253
- data: { type: 'http' },
254
- })
255
- const responseHeaders = new Headers()
256
- const container = this.context.container.fork(Scope.Call)
257
- container.provide(ProtocolInjectables.connection, connection)
258
- container.provide(
259
- ProtocolInjectables.connectionAbortSignal,
260
- controller.signal,
261
- )
262
- container.provide(WsTransportInjectables.connectionData, requestData)
263
- container.provide(
264
- WsTransportInjectables.httpResponseHeaders,
265
- responseHeaders,
266
- )
267
-
268
- const body = method === 'post' ? getRequestBody(res) : undefined
269
-
270
- const metadata: ProtocolApiCallOptions['metadata'] = (metadata) => {
271
- const allowHttpMethod =
272
- metadata.get(AllowedHttpMethod) ?? DEFAULT_ALLOWED_METHODS
273
- if (!allowHttpMethod.includes(method)) {
274
- throw new ProtocolError(ErrorCode.NotFound)
275
- }
276
- }
277
- let format: ReturnType<typeof getFormat>
278
- try {
279
- format = getFormat(this.context.format, {
280
- acceptType,
281
- contentType: isBlob ? '*/*' : contentType,
282
- })
283
-
284
- let payload: any
285
-
286
- if (body) {
287
- if (isBlob) {
288
- const type = contentType || 'application/octet-stream'
289
- const contentLength = requestData.headers.get('content-length')
290
- const size = contentLength
291
- ? Number.parseInt(contentLength)
292
- : undefined
293
- const stream = new ProtocolClientStream(-1, { size, type })
294
- body.pipe(stream)
295
- payload = stream
296
- } else {
297
- const buffer = await readableToArrayBuffer(body)
298
- if (buffer.byteLength > 0) {
299
- payload = format.decoder.decode(buffer)
300
- }
301
- }
302
- }
303
-
304
- const result = await this.protocol.call({
305
- connection,
306
- namespace,
307
- procedure,
308
- payload,
309
- metadata,
310
- container,
311
- signal: controller.signal,
312
- })
313
-
314
- if (isIterableResult(result)) {
315
- res.cork(() => {
316
- if (controller.signal.aborted) return
317
- const status = HttpCode.NotImplemented
318
- const text = HttpStatusText[status]
319
- res.writeStatus(`${status} ${text}`)
320
- res.end()
321
- })
322
- } else {
323
- const { output } = result
324
-
325
- if (output instanceof ProtocolBlob) {
326
- const { source, metadata } = output
327
- const { type } = metadata
328
-
329
- let stream: Readable
330
-
331
- if (source instanceof ReadableStream) {
332
- stream = Readable.fromWeb(source as any)
333
- } else if (source instanceof Readable || source instanceof Duplex) {
334
- stream = Readable.from(source)
335
- } else {
336
- throw new Error('Invalid stream source')
337
- }
338
-
339
- res.cork(() => {
340
- if (controller.signal.aborted) return
341
- responseHeaders.set('X-Neemata-Blob', 'true')
342
- responseHeaders.set('Content-Type', type)
343
- if (metadata.size)
344
- res.writeHeader('Content-Length', metadata.size.toString())
345
- setHeaders(res, responseHeaders)
346
- })
347
-
348
- controller.signal.addEventListener('abort', () => stream.destroy(), {
349
- once: true,
350
- })
351
-
352
- stream.on('data', (chunk) => {
353
- if (controller.signal.aborted) return
354
- const buf = Buffer.from(chunk)
355
- const ab = buf.buffer.slice(
356
- buf.byteOffset,
357
- buf.byteOffset + buf.byteLength,
358
- )
359
- const ok = res.write(ab)
360
- if (!ok) {
361
- stream.pause()
362
- res.onWritable(() => {
363
- stream.resume()
364
- return true
365
- })
366
- }
367
- })
368
- await once(stream, 'end')
369
- if (stream.readableAborted) {
370
- res.end(undefined, true)
371
- } else {
372
- res.end()
373
- }
374
- } else {
375
- res.cork(() => {
376
- if (controller.signal.aborted) return
377
- const status = HttpCode.OK
378
- const text = HttpStatusText[status]
379
- const buffer = format.encoder.encode(output)
380
- res.writeStatus(`${status} ${text}`)
381
- responseHeaders.set('Content-Type', format.encoder.contentType)
382
- setHeaders(res, responseHeaders)
383
- res.end(buffer)
384
- })
385
- }
386
- }
387
- } catch (error) {
388
- if (controller.signal.aborted) return
389
- if (error instanceof UnsupportedFormatError) {
390
- res.cork(() => {
391
- if (controller.signal.aborted) return
392
- const status =
393
- error instanceof UnsupportedContentTypeError
394
- ? HttpCode.UnsupportedMediaType
395
- : HttpCode.NotAcceptable
396
- const text = HttpStatusText[status]
397
- res.writeStatus(`${status} ${text}`)
398
- res.end()
399
- })
400
- } else if (error instanceof ProtocolError) {
401
- res.cork(() => {
402
- if (controller.signal.aborted) return
403
- const status =
404
- error.code in HttpCodeMap
405
- ? HttpCodeMap[error.code]
406
- : HttpCode.InternalServerError
407
- const text = HttpStatusText[status]
408
- res.writeStatus(`${status} ${text}`)
409
- res.end(format!.encoder.encode(error))
410
- })
411
- } else {
412
- this.logError(error, 'Unknown error while processing request')
413
- res.cork(() => {
414
- if (controller.signal.aborted) return
415
- const status = HttpCode.InternalServerError
416
- const text = HttpStatusText[status]
417
- const payload = format!.encoder.encode(
418
- new ProtocolError(
419
- ErrorCode.InternalServerError,
420
- 'Internal Server Error',
421
- ),
422
- )
423
- res.writeStatus(`${status} ${text}`)
424
- res.end(payload)
425
- })
426
- }
427
- } finally {
428
- container.dispose().catch((error) => {
429
- this.logError(error, 'Error while disposing call container')
430
- })
431
- }
432
- }
433
-
434
- protected get protocol() {
435
- return this.context.protocol
436
- }
437
-
438
- protected get logger() {
439
- return this.context.logger
440
- }
441
-
442
- protected async logError(
443
- cause: any,
444
- message = 'Unknown error while processing request',
445
- ) {
446
- this.logger.error(new Error(message, { cause }))
447
- }
448
-
449
- protected applyCors(res: HttpResponse, req: HttpRequest) {
450
- if (this.options.cors === false) return
451
-
452
- const origin = req.getHeader('origin')
453
- if (!origin) return
454
-
455
- let allowed = false
456
-
457
- if (this.options.cors === undefined || this.options.cors === true) {
458
- allowed = true
459
- } else if (Array.isArray(this.options.cors)) {
460
- allowed = this.options.cors.includes(origin)
461
- } else {
462
- allowed = this.options.cors(origin)
463
- }
464
-
465
- if (!allowed) return
466
-
467
- res.writeHeader('Access-Control-Allow-Origin', origin)
468
- res.writeHeader('Access-Control-Allow-Headers', 'Content-Type')
469
- res.writeHeader('Access-Control-Allow-Methods', 'GET, POST')
470
- res.writeHeader('Access-Control-Allow-Credentials', 'true')
471
- }
472
-
473
- protected [ClientMessageType.Rpc](
474
- ws: WsTransportSocket,
475
- buffer: ArrayBuffer,
476
- ) {
477
- const { id } = ws.getUserData()
478
- this.protocol.rpcRaw(id, buffer)
479
- }
480
-
481
- protected [ClientMessageType.RpcAbort](
482
- ws: WsTransportSocket,
483
- buffer: ArrayBuffer,
484
- ) {
485
- const { id } = ws.getUserData()
486
- this.protocol.rpcAbortRaw(id, buffer)
487
- }
488
-
489
- protected [ClientMessageType.RpcStreamAbort](
490
- ws: WsTransportSocket,
491
- buffer: ArrayBuffer,
492
- ) {
493
- const { id } = ws.getUserData()
494
- this.protocol.rpcStreamAbortRaw(id, buffer)
495
- }
496
-
497
- protected [ClientMessageType.ClientStreamPush](
498
- ws: WsTransportSocket,
499
- buffer: ArrayBuffer,
500
- ) {
501
- const { id } = ws.getUserData()
502
- const streamId = decodeNumber(buffer, 'Uint32')
503
- this.protocol.pushClientStream(
504
- id,
505
- streamId,
506
- buffer.slice(Uint32Array.BYTES_PER_ELEMENT),
507
- )
508
- }
509
-
510
- protected [ClientMessageType.ClientStreamEnd](
511
- ws: WsTransportSocket,
512
- buffer: ArrayBuffer,
513
- ) {
514
- const { id } = ws.getUserData()
515
- const streamId = decodeNumber(buffer, 'Uint32')
516
- this.protocol.endClientStream(id, streamId)
517
- }
518
-
519
- protected [ClientMessageType.ClientStreamAbort](
520
- ws: WsTransportSocket,
521
- buffer: ArrayBuffer,
522
- ) {
523
- const { id } = ws.getUserData()
524
- const streamId = decodeNumber(buffer, 'Uint32')
525
- this.protocol.abortClientStream(id, streamId)
526
- }
527
-
528
- protected [ClientMessageType.ServerStreamPull](
529
- ws: WsTransportSocket,
530
- buffer: ArrayBuffer,
531
- ) {
532
- const { id } = ws.getUserData()
533
- const streamId = decodeNumber(buffer, 'Uint32')
534
- this.protocol.pullServerStream(id, streamId)
535
- }
536
-
537
- protected [ClientMessageType.ServerStreamAbort](
538
- ws: WsTransportSocket,
539
- buffer: ArrayBuffer,
540
- ) {
541
- const { id } = ws.getUserData()
542
- const streamId = decodeNumber(buffer, 'Uint32')
543
- this.protocol.abortServerStream(id, streamId)
544
- }
545
- }
package/src/transport.ts DELETED
@@ -1,10 +0,0 @@
1
- import { createTransport } from '@nmtjs/protocol/server'
2
- import { WsTransportServer } from './server.ts'
3
- import type { WsConnectionData, WsTransportOptions } from './types.ts'
4
-
5
- export const WsTransport = createTransport<
6
- WsConnectionData,
7
- WsTransportOptions
8
- >('WsTransport', (context, options) => {
9
- return new WsTransportServer(context, options)
10
- })
package/src/types.ts DELETED
@@ -1,28 +0,0 @@
1
- import type { AppOptions, WebSocket } from 'uWebSockets.js'
2
- import type { InteractivePromise } from '@nmtjs/common'
3
- import type { Connection, ConnectionContext } from '@nmtjs/protocol/server'
4
- import type { RequestData } from './utils.ts'
5
-
6
- export type WsConnectionData = { type: 'ws' | 'http' }
7
-
8
- export type WsUserData = {
9
- id: Connection['id']
10
- backpressure: InteractivePromise<void> | null
11
- request: RequestData
12
- acceptType: string | null
13
- contentType: string | null
14
- context: ConnectionContext
15
- controller: AbortController
16
- }
17
-
18
- export type WsTransportSocket = WebSocket<WsUserData>
19
-
20
- export type WsTransportOptions = {
21
- port?: number
22
- hostname?: string
23
- unix?: string
24
- tls?: AppOptions
25
- cors?: boolean | string[] | ((origin: string) => boolean)
26
- maxPayloadLength?: number
27
- maxStreamChunkLength?: number
28
- }
package/src/utils.ts DELETED
@@ -1,126 +0,0 @@
1
- import type { HttpRequest, HttpResponse } from 'uWebSockets.js'
2
- import { PassThrough, type Readable } from 'node:stream'
3
- import { createPromise } from '@nmtjs/common'
4
- import { concat, ErrorCode, encodeNumber } from '@nmtjs/protocol'
5
- import { ProtocolError } from '@nmtjs/protocol/server'
6
- import type { WsTransportSocket } from './types.ts'
7
-
8
- export const send = (
9
- ws: WsTransportSocket,
10
- type: number,
11
- ...buffers: ArrayBuffer[]
12
- ): boolean | null => {
13
- const data = ws.getUserData()
14
- try {
15
- const buffer = concat(encodeNumber(type, 'Uint8'), ...buffers)
16
- const result = ws.send(buffer, true)
17
- if (result === 0) {
18
- data.backpressure = createPromise()
19
- return false
20
- }
21
- if (result === 2) {
22
- return null
23
- }
24
- return true
25
- } catch {
26
- return null
27
- }
28
- }
29
-
30
- export const toRecord = (input: {
31
- forEach: (cb: (value, key) => void) => void
32
- }) => {
33
- const obj: Record<string, string> = {}
34
- input.forEach((value, key) => {
35
- obj[key] = value
36
- })
37
- return obj
38
- }
39
-
40
- export type RequestData = Readonly<{
41
- url: string
42
- origin: URL | null
43
- method: string
44
- headers: Headers
45
- querystring: string
46
- query: URLSearchParams
47
- remoteAddress: string
48
- proxiedRemoteAddress: string
49
- }>
50
-
51
- export const getRequestData = (
52
- req: HttpRequest,
53
- res: HttpResponse,
54
- ): RequestData => {
55
- const url = req.getUrl()
56
- const method = req.getMethod()
57
- const headers = new Headers()
58
- const querystring = req.getQuery()
59
- const query = new URLSearchParams(querystring)
60
- const origin = headers.get('origin')
61
- const proxiedRemoteAddress = res.getProxiedRemoteAddressAsText()
62
- const remoteAddress = res.getRemoteAddressAsText()
63
-
64
- req.forEach((key, value) => headers.append(key, value))
65
-
66
- return Object.freeze({
67
- url,
68
- origin: origin ? new URL(url, origin) : null,
69
- method,
70
- headers,
71
- querystring,
72
- query,
73
- remoteAddress: Buffer.from(remoteAddress).toString(),
74
- proxiedRemoteAddress: Buffer.from(proxiedRemoteAddress).toString(),
75
- })
76
- }
77
-
78
- export function getRequestBody(res: HttpResponse) {
79
- const stream = new PassThrough()
80
- res.onData((chunk, isLast) => {
81
- stream.write(Buffer.from(chunk))
82
- if (isLast) stream.end()
83
- })
84
- res.onAborted(() => stream.destroy())
85
- return stream
86
- }
87
-
88
- export function setHeaders(res: HttpResponse, headers: Headers) {
89
- headers.forEach((value, key) => {
90
- if (key === 'set-cookie') return
91
- res.writeHeader(key, value)
92
- })
93
- const cookies = headers.getSetCookie()
94
- if (cookies) {
95
- for (const cookie of cookies) {
96
- res.writeHeader('set-cookie', cookie)
97
- }
98
- }
99
- }
100
-
101
- export function readableToArrayBuffer(stream: Readable): Promise<ArrayBuffer> {
102
- return new Promise((resolve, reject) => {
103
- const chunks: Buffer[] = []
104
- stream.on('data', (chunk) => {
105
- chunks.push(chunk)
106
- })
107
- stream.on('end', () => {
108
- resolve(Buffer.concat(chunks).buffer)
109
- })
110
- stream.on('error', (error) => {
111
- reject(error)
112
- })
113
- })
114
- }
115
-
116
- export const InternalError = (message = 'Internal Server Error') =>
117
- new ProtocolError(ErrorCode.InternalServerError, message)
118
-
119
- export const NotFoundError = (message = 'Not Found') =>
120
- new ProtocolError(ErrorCode.NotFound, message)
121
-
122
- export const ForbiddenError = (message = 'Forbidden') =>
123
- new ProtocolError(ErrorCode.Forbidden, message)
124
-
125
- export const RequestTimeoutError = (message = 'Request Timeout') =>
126
- new ProtocolError(ErrorCode.RequestTimeout, message)