@nmtjs/ws-transport 0.15.0-beta.3 → 0.15.0-beta.5
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 +10 -9
- package/src/index.ts +4 -0
- package/src/injectables.ts +6 -0
- package/src/runtimes/bun.ts +82 -0
- package/src/runtimes/deno.ts +125 -0
- package/src/runtimes/node.ts +83 -0
- package/src/server.ts +161 -0
- package/src/types.ts +123 -0
- package/src/utils.ts +28 -0
package/package.json
CHANGED
|
@@ -12,20 +12,20 @@
|
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.56.0",
|
|
15
|
-
"@nmtjs/client": "0.15.0-beta.
|
|
16
|
-
"@nmtjs/common": "0.15.0-beta.
|
|
17
|
-
"@nmtjs/gateway": "0.15.0-beta.
|
|
18
|
-
"@nmtjs/protocol": "0.15.0-beta.
|
|
15
|
+
"@nmtjs/client": "0.15.0-beta.5",
|
|
16
|
+
"@nmtjs/common": "0.15.0-beta.5",
|
|
17
|
+
"@nmtjs/gateway": "0.15.0-beta.5",
|
|
18
|
+
"@nmtjs/protocol": "0.15.0-beta.5"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@types/bun": "^1.3.0",
|
|
22
22
|
"@types/deno": "^2.3.0",
|
|
23
23
|
"@types/node": "^24",
|
|
24
24
|
"uWebSockets.js": "^20.56.0",
|
|
25
|
-
"@nmtjs/common": "0.15.0-beta.
|
|
26
|
-
"@nmtjs/core": "0.15.0-beta.
|
|
27
|
-
"@nmtjs/
|
|
28
|
-
"@nmtjs/
|
|
25
|
+
"@nmtjs/common": "0.15.0-beta.5",
|
|
26
|
+
"@nmtjs/core": "0.15.0-beta.5",
|
|
27
|
+
"@nmtjs/protocol": "0.15.0-beta.5",
|
|
28
|
+
"@nmtjs/gateway": "0.15.0-beta.5"
|
|
29
29
|
},
|
|
30
30
|
"peerDependenciesMeta": {
|
|
31
31
|
"@types/bun": {
|
|
@@ -48,10 +48,11 @@
|
|
|
48
48
|
},
|
|
49
49
|
"files": [
|
|
50
50
|
"dist",
|
|
51
|
+
"src",
|
|
51
52
|
"LICENSE.md",
|
|
52
53
|
"README.md"
|
|
53
54
|
],
|
|
54
|
-
"version": "0.15.0-beta.
|
|
55
|
+
"version": "0.15.0-beta.5",
|
|
55
56
|
"scripts": {
|
|
56
57
|
"clean-build": "rm -rf ./dist",
|
|
57
58
|
"build": "tsc --declaration --sourcemap",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Transport } from '@nmtjs/gateway'
|
|
2
|
+
import type { ConnectionType } from '@nmtjs/protocol'
|
|
3
|
+
import { ProxyableTransportType } from '@nmtjs/gateway'
|
|
4
|
+
import createAdapter from 'crossws/adapters/bun'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WsAdapterParams,
|
|
8
|
+
WsAdapterServer,
|
|
9
|
+
WsTransportOptions,
|
|
10
|
+
} from '../types.ts'
|
|
11
|
+
import * as injectables from '../injectables.ts'
|
|
12
|
+
import { createWSTransportWorker } from '../server.ts'
|
|
13
|
+
import {
|
|
14
|
+
InternalServerErrorHttpResponse,
|
|
15
|
+
NotFoundHttpResponse,
|
|
16
|
+
StatusResponse,
|
|
17
|
+
} from '../utils.ts'
|
|
18
|
+
|
|
19
|
+
function adapterFactory(params: WsAdapterParams<'bun'>): WsAdapterServer {
|
|
20
|
+
const adapter = createAdapter({ hooks: params.wsHooks })
|
|
21
|
+
|
|
22
|
+
let server: Bun.Server<any> | null = null
|
|
23
|
+
|
|
24
|
+
function createServer() {
|
|
25
|
+
return globalThis.Bun.serve(
|
|
26
|
+
// @ts-expect-error ts bs
|
|
27
|
+
{
|
|
28
|
+
...params.runtime?.server,
|
|
29
|
+
unix: params.listen.unix,
|
|
30
|
+
port: params.listen.port,
|
|
31
|
+
hostname: params.listen.hostname,
|
|
32
|
+
reusePort: params.listen.reusePort,
|
|
33
|
+
tls: params.tls
|
|
34
|
+
? {
|
|
35
|
+
cert: params.tls.cert,
|
|
36
|
+
key: params.tls.key,
|
|
37
|
+
passphrase: params.tls.passphrase,
|
|
38
|
+
}
|
|
39
|
+
: undefined,
|
|
40
|
+
websocket: { ...params.runtime?.ws, ...adapter.websocket },
|
|
41
|
+
routes: { '/healthy': StatusResponse() },
|
|
42
|
+
async fetch(request, server) {
|
|
43
|
+
try {
|
|
44
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
|
45
|
+
return await adapter.handleUpgrade(request, server)
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('Error in WebSocket fetch handler', err)
|
|
49
|
+
return InternalServerErrorHttpResponse()
|
|
50
|
+
}
|
|
51
|
+
return NotFoundHttpResponse()
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
start: async () => {
|
|
59
|
+
server = createServer()
|
|
60
|
+
return server!.url.href
|
|
61
|
+
},
|
|
62
|
+
stop: async () => {
|
|
63
|
+
if (server) {
|
|
64
|
+
await server.stop()
|
|
65
|
+
server = null
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const WsTransport: Transport<
|
|
72
|
+
ConnectionType.Bidirectional,
|
|
73
|
+
WsTransportOptions<'bun'>,
|
|
74
|
+
typeof injectables,
|
|
75
|
+
ProxyableTransportType.WebSocket
|
|
76
|
+
> = {
|
|
77
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
78
|
+
injectables,
|
|
79
|
+
factory(options) {
|
|
80
|
+
return createWSTransportWorker(adapterFactory, options)
|
|
81
|
+
},
|
|
82
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Transport } from '@nmtjs/gateway'
|
|
2
|
+
import type { ConnectionType } from '@nmtjs/protocol'
|
|
3
|
+
import { ProxyableTransportType } from '@nmtjs/gateway'
|
|
4
|
+
import createAdapter from 'crossws/adapters/deno'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WsAdapterParams,
|
|
8
|
+
WsAdapterServer,
|
|
9
|
+
WsTransportOptions,
|
|
10
|
+
} from '../types.ts'
|
|
11
|
+
import * as injectables from '../injectables.ts'
|
|
12
|
+
import { createWSTransportWorker } from '../server.ts'
|
|
13
|
+
import {
|
|
14
|
+
InternalServerErrorHttpResponse,
|
|
15
|
+
NotFoundHttpResponse,
|
|
16
|
+
StatusResponse,
|
|
17
|
+
} from '../utils.ts'
|
|
18
|
+
|
|
19
|
+
type DenoServer = ReturnType<typeof globalThis.Deno.serve>
|
|
20
|
+
interface DenoNetAddr {
|
|
21
|
+
transport: 'tcp' | 'udp'
|
|
22
|
+
hostname: string
|
|
23
|
+
port: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface DenoUnixAddr {
|
|
27
|
+
transport: 'unix' | 'unixpacket'
|
|
28
|
+
path: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DenoVsockAddr {
|
|
32
|
+
transport: 'vsock'
|
|
33
|
+
cid: number
|
|
34
|
+
port: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type DenoAddr = DenoNetAddr | DenoUnixAddr | DenoVsockAddr
|
|
38
|
+
|
|
39
|
+
function adapterFactory(params: WsAdapterParams<'deno'>): WsAdapterServer {
|
|
40
|
+
const adapter = createAdapter({ hooks: params.wsHooks })
|
|
41
|
+
|
|
42
|
+
let server: DenoServer | null = null
|
|
43
|
+
|
|
44
|
+
function createServer() {
|
|
45
|
+
const listenOptions = params.listen.unix
|
|
46
|
+
? { path: params.listen.unix }
|
|
47
|
+
: {
|
|
48
|
+
port: params.listen.port,
|
|
49
|
+
hostname: params.listen.hostname,
|
|
50
|
+
reusePort: params.listen.reusePort,
|
|
51
|
+
}
|
|
52
|
+
const options = {
|
|
53
|
+
...listenOptions,
|
|
54
|
+
tls: params.tls
|
|
55
|
+
? {
|
|
56
|
+
cert: params.tls.cert,
|
|
57
|
+
key: params.tls.key,
|
|
58
|
+
passphrase: params.tls.passphrase,
|
|
59
|
+
}
|
|
60
|
+
: undefined,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new Promise<{ server: DenoServer; addr: DenoAddr }>((resolve) => {
|
|
64
|
+
const server = globalThis.Deno.serve({
|
|
65
|
+
...params.runtime?.server,
|
|
66
|
+
...options,
|
|
67
|
+
handler: async (request: Request, info: any) => {
|
|
68
|
+
const url = new URL(request.url)
|
|
69
|
+
if (url.pathname === '/healthy') return StatusResponse()
|
|
70
|
+
try {
|
|
71
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
|
72
|
+
return await adapter.handleUpgrade(request, info as any)
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('Error in WebSocket fetch handler', err)
|
|
76
|
+
return InternalServerErrorHttpResponse()
|
|
77
|
+
}
|
|
78
|
+
return NotFoundHttpResponse()
|
|
79
|
+
},
|
|
80
|
+
onListen(addr: DenoAddr) {
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
resolve({ server, addr })
|
|
83
|
+
}, 1)
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
start: async () => {
|
|
91
|
+
const { server: _server, addr } = await createServer()
|
|
92
|
+
server = _server
|
|
93
|
+
const proto = params.tls ? 'https' : 'http'
|
|
94
|
+
|
|
95
|
+
switch (addr.transport) {
|
|
96
|
+
case 'unix':
|
|
97
|
+
return `${proto}+unix://${addr.path}`
|
|
98
|
+
case 'tcp': {
|
|
99
|
+
return `${proto}://${addr.hostname}:${addr.port}`
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
throw new Error(`Unsupported address transport`)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
stop: async () => {
|
|
106
|
+
if (server) {
|
|
107
|
+
await server.shutdown()
|
|
108
|
+
server = null
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const WsTransport: Transport<
|
|
115
|
+
ConnectionType.Bidirectional,
|
|
116
|
+
WsTransportOptions<'deno'>,
|
|
117
|
+
typeof injectables,
|
|
118
|
+
ProxyableTransportType.WebSocket
|
|
119
|
+
> = {
|
|
120
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
121
|
+
injectables,
|
|
122
|
+
factory(options) {
|
|
123
|
+
return createWSTransportWorker(adapterFactory, options)
|
|
124
|
+
},
|
|
125
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Transport } from '@nmtjs/gateway'
|
|
2
|
+
import type { ConnectionType } from '@nmtjs/protocol'
|
|
3
|
+
import { ProxyableTransportType } from '@nmtjs/gateway'
|
|
4
|
+
import createAdapter from 'crossws/adapters/uws'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WsAdapterParams,
|
|
8
|
+
WsAdapterServer,
|
|
9
|
+
WsTransportOptions,
|
|
10
|
+
} from '../types.ts'
|
|
11
|
+
import * as injectables from '../injectables.ts'
|
|
12
|
+
import { createWSTransportWorker } from '../server.ts'
|
|
13
|
+
import { StatusResponse } from '../utils.ts'
|
|
14
|
+
|
|
15
|
+
import { App, SSLApp, us_socket_local_port } from 'uWebSockets.js'
|
|
16
|
+
|
|
17
|
+
const statusResponse = StatusResponse()
|
|
18
|
+
const statusResponseBuffer = await statusResponse.arrayBuffer()
|
|
19
|
+
|
|
20
|
+
function adapterFactory(params: WsAdapterParams<'node'>): WsAdapterServer {
|
|
21
|
+
const adapter = createAdapter({ hooks: params.wsHooks })
|
|
22
|
+
|
|
23
|
+
const server = params.tls
|
|
24
|
+
? SSLApp({
|
|
25
|
+
passphrase: params.tls.passphrase,
|
|
26
|
+
key_file_name: params.tls.key,
|
|
27
|
+
cert_file_name: params.tls.cert,
|
|
28
|
+
})
|
|
29
|
+
: App()
|
|
30
|
+
|
|
31
|
+
server
|
|
32
|
+
.ws('/*', { ...params.runtime?.ws, ...adapter.websocket })
|
|
33
|
+
.get('/healthy', (res) => {
|
|
34
|
+
res.cork(() => {
|
|
35
|
+
res
|
|
36
|
+
.writeStatus(`${statusResponse.status} ${statusResponse.statusText}`)
|
|
37
|
+
.end(statusResponseBuffer)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
start: () =>
|
|
43
|
+
new Promise<string>((resolve, reject) => {
|
|
44
|
+
const proto = params.tls ? 'https' : 'http'
|
|
45
|
+
if (params.listen.unix) {
|
|
46
|
+
server.listen_unix((socket) => {
|
|
47
|
+
if (socket) {
|
|
48
|
+
resolve(`${proto}+unix://` + params.listen.unix)
|
|
49
|
+
} else {
|
|
50
|
+
reject(new Error('Failed to start WebSockets server'))
|
|
51
|
+
}
|
|
52
|
+
}, params.listen.unix)
|
|
53
|
+
} else if (typeof params.listen.port === 'number') {
|
|
54
|
+
const hostname = params.listen.hostname || '127.0.0.1'
|
|
55
|
+
server.listen(hostname, params.listen.port, (socket) => {
|
|
56
|
+
if (socket) {
|
|
57
|
+
resolve(`${proto}://${hostname}:${us_socket_local_port(socket)}`)
|
|
58
|
+
} else {
|
|
59
|
+
reject(new Error('Failed to start WebSockets server'))
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
reject(new Error('Invalid listen parameters'))
|
|
64
|
+
}
|
|
65
|
+
}),
|
|
66
|
+
stop: () => {
|
|
67
|
+
server.close()
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const WsTransport: Transport<
|
|
73
|
+
ConnectionType.Bidirectional,
|
|
74
|
+
WsTransportOptions<'node'>,
|
|
75
|
+
typeof injectables,
|
|
76
|
+
ProxyableTransportType.WebSocket
|
|
77
|
+
> = {
|
|
78
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
79
|
+
injectables,
|
|
80
|
+
factory(options) {
|
|
81
|
+
return createWSTransportWorker(adapterFactory, options)
|
|
82
|
+
},
|
|
83
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { TransportWorker, TransportWorkerParams } from '@nmtjs/gateway'
|
|
2
|
+
import type { Hooks, Peer } from 'crossws'
|
|
3
|
+
import { ConnectionType, ProtocolVersion } from '@nmtjs/protocol'
|
|
4
|
+
import { defineHooks } from 'crossws'
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WsAdapterParams,
|
|
8
|
+
WsAdapterServer,
|
|
9
|
+
WsAdapterServerFactory,
|
|
10
|
+
WsTransportOptions,
|
|
11
|
+
WsTransportServerRequest,
|
|
12
|
+
} from './types.ts'
|
|
13
|
+
import {
|
|
14
|
+
InternalServerErrorHttpResponse,
|
|
15
|
+
NotFoundHttpResponse,
|
|
16
|
+
} from './utils.ts'
|
|
17
|
+
|
|
18
|
+
export function createWSTransportWorker(
|
|
19
|
+
adapterFactory: WsAdapterServerFactory<any>,
|
|
20
|
+
options: WsTransportOptions,
|
|
21
|
+
): TransportWorker<ConnectionType.Bidirectional> {
|
|
22
|
+
return new WsTransportServer(adapterFactory, options)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class WsTransportServer
|
|
26
|
+
implements TransportWorker<ConnectionType.Bidirectional>
|
|
27
|
+
{
|
|
28
|
+
#server: WsAdapterServer
|
|
29
|
+
params!: TransportWorkerParams<ConnectionType.Bidirectional>
|
|
30
|
+
clients = new Map<string, Peer>()
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
protected readonly adapterFactory: WsAdapterServerFactory<any>,
|
|
34
|
+
protected readonly options: WsTransportOptions,
|
|
35
|
+
) {
|
|
36
|
+
this.#server = this.createServer()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async start(
|
|
40
|
+
hooks: TransportWorkerParams<ConnectionType.Bidirectional>,
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
this.params = hooks
|
|
43
|
+
return await this.#server.start()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async stop(): Promise<void> {
|
|
47
|
+
for (const peer of this.clients.values()) {
|
|
48
|
+
try {
|
|
49
|
+
peer.close(1001, 'Transport stopped')
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(
|
|
52
|
+
`Failed to close WebSocket connection ${peer.context.connectionId}`,
|
|
53
|
+
error,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.clients.clear()
|
|
58
|
+
await this.#server.stop()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
send(connectionId: string, buffer: ArrayBufferView) {
|
|
62
|
+
const peer = this.clients.get(connectionId)
|
|
63
|
+
if (!peer) return false
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const result = peer.send(buffer)
|
|
67
|
+
if (typeof result === 'boolean') return result
|
|
68
|
+
if (typeof result === 'number') return result > 0
|
|
69
|
+
return true
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(
|
|
72
|
+
`Failed to send data over WebSocket connection ${connectionId}`,
|
|
73
|
+
error,
|
|
74
|
+
)
|
|
75
|
+
this.clients.delete(connectionId)
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private createWsHooks(): Hooks {
|
|
81
|
+
return defineHooks({
|
|
82
|
+
upgrade: async (req) => {
|
|
83
|
+
const url = new URL(req.url)
|
|
84
|
+
|
|
85
|
+
if (url.pathname !== '/') {
|
|
86
|
+
return NotFoundHttpResponse()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const request: WsTransportServerRequest = {
|
|
90
|
+
url,
|
|
91
|
+
headers: req.headers,
|
|
92
|
+
method: req.method,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const accept =
|
|
96
|
+
url.searchParams.get('accept') ?? req.headers.get('accept')
|
|
97
|
+
const contentType =
|
|
98
|
+
url.searchParams.get('content-type') ??
|
|
99
|
+
req.headers.get('content-type')
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const connection = await this.params.onConnect({
|
|
103
|
+
type: ConnectionType.Bidirectional,
|
|
104
|
+
protocolVersion: ProtocolVersion.v1,
|
|
105
|
+
accept,
|
|
106
|
+
contentType,
|
|
107
|
+
data: request,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return { context: { connectionId: connection.id } }
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Failed to upgrade WebSocket connection', error)
|
|
113
|
+
return InternalServerErrorHttpResponse()
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
open: (peer) => {
|
|
117
|
+
const { connectionId } = peer.context
|
|
118
|
+
this.clients.set(connectionId, peer)
|
|
119
|
+
},
|
|
120
|
+
message: async (peer, message) => {
|
|
121
|
+
const data = message.arrayBuffer() as ArrayBuffer
|
|
122
|
+
try {
|
|
123
|
+
await this.params.onMessage({
|
|
124
|
+
connectionId: peer.context.connectionId,
|
|
125
|
+
data,
|
|
126
|
+
})
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(
|
|
129
|
+
`Error while processing message from ${peer.context.connectionId}`,
|
|
130
|
+
error,
|
|
131
|
+
)
|
|
132
|
+
this.clients.delete(peer.context.connectionId)
|
|
133
|
+
peer.close(1011, 'Internal error')
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
error: (peer, error) => {
|
|
137
|
+
console.error(
|
|
138
|
+
`WebSocket error on connection ${peer.context.connectionId}`,
|
|
139
|
+
error,
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
close: async (peer) => {
|
|
143
|
+
this.clients.delete(peer.context.connectionId)
|
|
144
|
+
try {
|
|
145
|
+
await this.params.onDisconnect(peer.context.connectionId)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(
|
|
148
|
+
`Failed to dispose WebSocket connection ${peer.context.connectionId}`,
|
|
149
|
+
error,
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
}) as Hooks
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private createServer() {
|
|
157
|
+
const hooks = this.createWsHooks()
|
|
158
|
+
const opts: WsAdapterParams = { ...this.options, wsHooks: hooks }
|
|
159
|
+
return this.adapterFactory(opts)
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Async, OneOf } from '@nmtjs/common'
|
|
2
|
+
import type { Hooks } from 'crossws'
|
|
3
|
+
|
|
4
|
+
export type WsTransportServerRequest = {
|
|
5
|
+
url: URL
|
|
6
|
+
method: string
|
|
7
|
+
headers: Headers
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type WsTransportPeerContext = { connectionId: string }
|
|
11
|
+
|
|
12
|
+
declare module 'crossws' {
|
|
13
|
+
interface PeerContext extends WsTransportPeerContext {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type WsTransportOptions<
|
|
17
|
+
R extends keyof WsTransportRuntimes = keyof WsTransportRuntimes,
|
|
18
|
+
> = {
|
|
19
|
+
listen: WsTransportListenOptions
|
|
20
|
+
cors?: WsTransportCorsOptions
|
|
21
|
+
tls?: WsTransportTlsOptions
|
|
22
|
+
runtime?: WsTransportRuntimes[R]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type WsTransportCorsCustomParams = {
|
|
26
|
+
allowMethods?: string[]
|
|
27
|
+
allowHeaders?: string[]
|
|
28
|
+
allowCredentials?: string
|
|
29
|
+
maxAge?: string
|
|
30
|
+
exposeHeaders?: string[]
|
|
31
|
+
requestHeaders?: string[]
|
|
32
|
+
requestMethod?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type WsTransportCorsOptions =
|
|
36
|
+
| true
|
|
37
|
+
| string[]
|
|
38
|
+
| WsTransportCorsCustomParams
|
|
39
|
+
| ((
|
|
40
|
+
origin: string,
|
|
41
|
+
request: WsTransportServerRequest,
|
|
42
|
+
) => boolean | WsTransportCorsCustomParams)
|
|
43
|
+
|
|
44
|
+
export type WsTransportListenOptions = OneOf<
|
|
45
|
+
[{ port: number; hostname?: string; reusePort?: boolean }, { unix: string }]
|
|
46
|
+
>
|
|
47
|
+
|
|
48
|
+
export type WsTransportRuntimeBun = {
|
|
49
|
+
ws?: Partial<
|
|
50
|
+
Pick<
|
|
51
|
+
import('bun').WebSocketHandler<import('crossws').PeerContext>,
|
|
52
|
+
| 'backpressureLimit'
|
|
53
|
+
| 'maxPayloadLength'
|
|
54
|
+
| 'closeOnBackpressureLimit'
|
|
55
|
+
| 'idleTimeout'
|
|
56
|
+
| 'perMessageDeflate'
|
|
57
|
+
| 'sendPings'
|
|
58
|
+
>
|
|
59
|
+
>
|
|
60
|
+
server?: Partial<
|
|
61
|
+
Pick<
|
|
62
|
+
import('bun').ServeOptions,
|
|
63
|
+
'development' | 'id' | 'maxRequestBodySize' | 'idleTimeout' | 'ipv6Only'
|
|
64
|
+
>
|
|
65
|
+
>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type WsTransportRuntimeNode = {
|
|
69
|
+
ws?: Partial<
|
|
70
|
+
Pick<
|
|
71
|
+
import('uWebSockets.js').WebSocketBehavior<import('crossws').PeerContext>,
|
|
72
|
+
| 'maxBackpressure'
|
|
73
|
+
| 'maxPayloadLength'
|
|
74
|
+
| 'maxLifetime'
|
|
75
|
+
| 'closeOnBackpressureLimit'
|
|
76
|
+
| 'idleTimeout'
|
|
77
|
+
| 'compression'
|
|
78
|
+
| 'sendPingsAutomatically'
|
|
79
|
+
>
|
|
80
|
+
>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type WsTransportRuntimeDeno = { server?: {} }
|
|
84
|
+
|
|
85
|
+
export type WsTransportRuntimes = {
|
|
86
|
+
bun: WsTransportRuntimeBun
|
|
87
|
+
node: WsTransportRuntimeNode
|
|
88
|
+
deno: WsTransportRuntimeDeno
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type WsTransportTlsOptions = {
|
|
92
|
+
/**
|
|
93
|
+
* File path or inlined TLS certificate in PEM format (required).
|
|
94
|
+
*/
|
|
95
|
+
cert?: string
|
|
96
|
+
/**
|
|
97
|
+
* File path or inlined TLS private key in PEM format (required).
|
|
98
|
+
*/
|
|
99
|
+
key?: string
|
|
100
|
+
/**
|
|
101
|
+
* Passphrase for the private key (optional).
|
|
102
|
+
*/
|
|
103
|
+
passphrase?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type WsAdapterParams<
|
|
107
|
+
R extends keyof WsTransportRuntimes = keyof WsTransportRuntimes,
|
|
108
|
+
> = {
|
|
109
|
+
listen: WsTransportListenOptions
|
|
110
|
+
wsHooks: Hooks
|
|
111
|
+
cors?: WsTransportCorsOptions
|
|
112
|
+
tls?: WsTransportTlsOptions
|
|
113
|
+
runtime?: WsTransportRuntimes[R]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface WsAdapterServer {
|
|
117
|
+
stop: () => Async<any>
|
|
118
|
+
start: () => Async<string>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type WsAdapterServerFactory<
|
|
122
|
+
R extends keyof WsTransportRuntimes = keyof WsTransportRuntimes,
|
|
123
|
+
> = (params: WsAdapterParams<R>) => WsAdapterServer
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ErrorCode } from '@nmtjs/protocol'
|
|
2
|
+
import { ProtocolError } from '@nmtjs/protocol/server'
|
|
3
|
+
|
|
4
|
+
export const InternalError = (message = 'Internal Server Error') =>
|
|
5
|
+
new ProtocolError(ErrorCode.InternalServerError, message)
|
|
6
|
+
|
|
7
|
+
export const NotFoundError = (message = 'Not Found') =>
|
|
8
|
+
new ProtocolError(ErrorCode.NotFound, message)
|
|
9
|
+
|
|
10
|
+
export const ForbiddenError = (message = 'Forbidden') =>
|
|
11
|
+
new ProtocolError(ErrorCode.Forbidden, message)
|
|
12
|
+
|
|
13
|
+
export const RequestTimeoutError = (message = 'Request Timeout') =>
|
|
14
|
+
new ProtocolError(ErrorCode.RequestTimeout, message)
|
|
15
|
+
|
|
16
|
+
export const NotFoundHttpResponse = () =>
|
|
17
|
+
new Response('Not Found', {
|
|
18
|
+
status: 404,
|
|
19
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const InternalServerErrorHttpResponse = () =>
|
|
23
|
+
new Response('Internal Server Error', {
|
|
24
|
+
status: 500,
|
|
25
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const StatusResponse = () => new Response('OK', { status: 200 })
|