@nmtjs/ws-transport 0.15.0-beta.1 → 0.15.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -0
- package/dist/injectables.js +2 -0
- package/dist/runtimes/bun.js +60 -0
- package/dist/runtimes/deno.js +83 -0
- package/dist/runtimes/node.js +66 -0
- package/dist/server.js +119 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +15 -0
- package/package.json +9 -9
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ProxyableTransportType } from '@nmtjs/gateway';
|
|
2
|
+
import createAdapter from 'crossws/adapters/bun';
|
|
3
|
+
import * as injectables from "../injectables.js";
|
|
4
|
+
import { createWSTransportWorker } from "../server.js";
|
|
5
|
+
import { InternalServerErrorHttpResponse, NotFoundHttpResponse, StatusResponse, } from "../utils.js";
|
|
6
|
+
function adapterFactory(params) {
|
|
7
|
+
const adapter = createAdapter({ hooks: params.wsHooks });
|
|
8
|
+
let server = null;
|
|
9
|
+
function createServer() {
|
|
10
|
+
return globalThis.Bun.serve(
|
|
11
|
+
// @ts-expect-error ts bs
|
|
12
|
+
{
|
|
13
|
+
...params.runtime?.server,
|
|
14
|
+
unix: params.listen.unix,
|
|
15
|
+
port: params.listen.port,
|
|
16
|
+
hostname: params.listen.hostname,
|
|
17
|
+
reusePort: params.listen.reusePort,
|
|
18
|
+
tls: params.tls
|
|
19
|
+
? {
|
|
20
|
+
cert: params.tls.cert,
|
|
21
|
+
key: params.tls.key,
|
|
22
|
+
passphrase: params.tls.passphrase,
|
|
23
|
+
}
|
|
24
|
+
: undefined,
|
|
25
|
+
websocket: { ...params.runtime?.ws, ...adapter.websocket },
|
|
26
|
+
routes: { '/healthy': StatusResponse() },
|
|
27
|
+
async fetch(request, server) {
|
|
28
|
+
try {
|
|
29
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
|
30
|
+
return await adapter.handleUpgrade(request, server);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error('Error in WebSocket fetch handler', err);
|
|
35
|
+
return InternalServerErrorHttpResponse();
|
|
36
|
+
}
|
|
37
|
+
return NotFoundHttpResponse();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
start: async () => {
|
|
43
|
+
server = createServer();
|
|
44
|
+
return server.url.href;
|
|
45
|
+
},
|
|
46
|
+
stop: async () => {
|
|
47
|
+
if (server) {
|
|
48
|
+
await server.stop();
|
|
49
|
+
server = null;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export const WsTransport = {
|
|
55
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
56
|
+
injectables,
|
|
57
|
+
factory(options) {
|
|
58
|
+
return createWSTransportWorker(adapterFactory, options);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ProxyableTransportType } from '@nmtjs/gateway';
|
|
2
|
+
import createAdapter from 'crossws/adapters/deno';
|
|
3
|
+
import * as injectables from "../injectables.js";
|
|
4
|
+
import { createWSTransportWorker } from "../server.js";
|
|
5
|
+
import { InternalServerErrorHttpResponse, NotFoundHttpResponse, StatusResponse, } from "../utils.js";
|
|
6
|
+
function adapterFactory(params) {
|
|
7
|
+
const adapter = createAdapter({ hooks: params.wsHooks });
|
|
8
|
+
let server = null;
|
|
9
|
+
function createServer() {
|
|
10
|
+
const listenOptions = params.listen.unix
|
|
11
|
+
? { path: params.listen.unix }
|
|
12
|
+
: {
|
|
13
|
+
port: params.listen.port,
|
|
14
|
+
hostname: params.listen.hostname,
|
|
15
|
+
reusePort: params.listen.reusePort,
|
|
16
|
+
};
|
|
17
|
+
const options = {
|
|
18
|
+
...listenOptions,
|
|
19
|
+
tls: params.tls
|
|
20
|
+
? {
|
|
21
|
+
cert: params.tls.cert,
|
|
22
|
+
key: params.tls.key,
|
|
23
|
+
passphrase: params.tls.passphrase,
|
|
24
|
+
}
|
|
25
|
+
: undefined,
|
|
26
|
+
};
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const server = globalThis.Deno.serve({
|
|
29
|
+
...params.runtime?.server,
|
|
30
|
+
...options,
|
|
31
|
+
handler: async (request, info) => {
|
|
32
|
+
const url = new URL(request.url);
|
|
33
|
+
if (url.pathname === '/healthy')
|
|
34
|
+
return StatusResponse();
|
|
35
|
+
try {
|
|
36
|
+
if (request.headers.get('upgrade') === 'websocket') {
|
|
37
|
+
return await adapter.handleUpgrade(request, info);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('Error in WebSocket fetch handler', err);
|
|
42
|
+
return InternalServerErrorHttpResponse();
|
|
43
|
+
}
|
|
44
|
+
return NotFoundHttpResponse();
|
|
45
|
+
},
|
|
46
|
+
onListen(addr) {
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
resolve({ server, addr });
|
|
49
|
+
}, 1);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
start: async () => {
|
|
56
|
+
const { server: _server, addr } = await createServer();
|
|
57
|
+
server = _server;
|
|
58
|
+
const proto = params.tls ? 'https' : 'http';
|
|
59
|
+
switch (addr.transport) {
|
|
60
|
+
case 'unix':
|
|
61
|
+
return `${proto}+unix://${addr.path}`;
|
|
62
|
+
case 'tcp': {
|
|
63
|
+
return `${proto}://${addr.hostname}:${addr.port}`;
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unsupported address transport`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
stop: async () => {
|
|
70
|
+
if (server) {
|
|
71
|
+
await server.shutdown();
|
|
72
|
+
server = null;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export const WsTransport = {
|
|
78
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
79
|
+
injectables,
|
|
80
|
+
factory(options) {
|
|
81
|
+
return createWSTransportWorker(adapterFactory, options);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ProxyableTransportType } from '@nmtjs/gateway';
|
|
2
|
+
import createAdapter from 'crossws/adapters/uws';
|
|
3
|
+
import * as injectables from "../injectables.js";
|
|
4
|
+
import { createWSTransportWorker } from "../server.js";
|
|
5
|
+
import { StatusResponse } from "../utils.js";
|
|
6
|
+
import { App, SSLApp, us_socket_local_port } from 'uWebSockets.js';
|
|
7
|
+
const statusResponse = StatusResponse();
|
|
8
|
+
const statusResponseBuffer = await statusResponse.arrayBuffer();
|
|
9
|
+
function adapterFactory(params) {
|
|
10
|
+
const adapter = createAdapter({ hooks: params.wsHooks });
|
|
11
|
+
const server = params.tls
|
|
12
|
+
? SSLApp({
|
|
13
|
+
passphrase: params.tls.passphrase,
|
|
14
|
+
key_file_name: params.tls.key,
|
|
15
|
+
cert_file_name: params.tls.cert,
|
|
16
|
+
})
|
|
17
|
+
: App();
|
|
18
|
+
server
|
|
19
|
+
.ws('/*', { ...params.runtime?.ws, ...adapter.websocket })
|
|
20
|
+
.get('/healthy', (res) => {
|
|
21
|
+
res.cork(() => {
|
|
22
|
+
res
|
|
23
|
+
.writeStatus(`${statusResponse.status} ${statusResponse.statusText}`)
|
|
24
|
+
.end(statusResponseBuffer);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
start: () => new Promise((resolve, reject) => {
|
|
29
|
+
const proto = params.tls ? 'https' : 'http';
|
|
30
|
+
if (params.listen.unix) {
|
|
31
|
+
server.listen_unix((socket) => {
|
|
32
|
+
if (socket) {
|
|
33
|
+
resolve(`${proto}+unix://` + params.listen.unix);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
reject(new Error('Failed to start WebSockets server'));
|
|
37
|
+
}
|
|
38
|
+
}, params.listen.unix);
|
|
39
|
+
}
|
|
40
|
+
else if (typeof params.listen.port === 'number') {
|
|
41
|
+
const hostname = params.listen.hostname || '127.0.0.1';
|
|
42
|
+
server.listen(hostname, params.listen.port, (socket) => {
|
|
43
|
+
if (socket) {
|
|
44
|
+
resolve(`${proto}://${hostname}:${us_socket_local_port(socket)}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
reject(new Error('Failed to start WebSockets server'));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reject(new Error('Invalid listen parameters'));
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
stop: () => {
|
|
56
|
+
server.close();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export const WsTransport = {
|
|
61
|
+
proxyable: ProxyableTransportType.WebSocket,
|
|
62
|
+
injectables,
|
|
63
|
+
factory(options) {
|
|
64
|
+
return createWSTransportWorker(adapterFactory, options);
|
|
65
|
+
},
|
|
66
|
+
};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ConnectionType, ProtocolVersion } from '@nmtjs/protocol';
|
|
2
|
+
import { defineHooks } from 'crossws';
|
|
3
|
+
import { InternalServerErrorHttpResponse, NotFoundHttpResponse, } from "./utils.js";
|
|
4
|
+
export function createWSTransportWorker(adapterFactory, options) {
|
|
5
|
+
return new WsTransportServer(adapterFactory, options);
|
|
6
|
+
}
|
|
7
|
+
export class WsTransportServer {
|
|
8
|
+
adapterFactory;
|
|
9
|
+
options;
|
|
10
|
+
#server;
|
|
11
|
+
params;
|
|
12
|
+
clients = new Map();
|
|
13
|
+
constructor(adapterFactory, options) {
|
|
14
|
+
this.adapterFactory = adapterFactory;
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.#server = this.createServer();
|
|
17
|
+
}
|
|
18
|
+
async start(hooks) {
|
|
19
|
+
this.params = hooks;
|
|
20
|
+
return await this.#server.start();
|
|
21
|
+
}
|
|
22
|
+
async stop() {
|
|
23
|
+
for (const peer of this.clients.values()) {
|
|
24
|
+
try {
|
|
25
|
+
peer.close(1001, 'Transport stopped');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(`Failed to close WebSocket connection ${peer.context.connectionId}`, error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
this.clients.clear();
|
|
32
|
+
await this.#server.stop();
|
|
33
|
+
}
|
|
34
|
+
send(connectionId, buffer) {
|
|
35
|
+
const peer = this.clients.get(connectionId);
|
|
36
|
+
if (!peer)
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
const result = peer.send(buffer);
|
|
40
|
+
if (typeof result === 'boolean')
|
|
41
|
+
return result;
|
|
42
|
+
if (typeof result === 'number')
|
|
43
|
+
return result > 0;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(`Failed to send data over WebSocket connection ${connectionId}`, error);
|
|
48
|
+
this.clients.delete(connectionId);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
createWsHooks() {
|
|
53
|
+
return defineHooks({
|
|
54
|
+
upgrade: async (req) => {
|
|
55
|
+
const url = new URL(req.url);
|
|
56
|
+
if (url.pathname !== '/') {
|
|
57
|
+
return NotFoundHttpResponse();
|
|
58
|
+
}
|
|
59
|
+
const request = {
|
|
60
|
+
url,
|
|
61
|
+
headers: req.headers,
|
|
62
|
+
method: req.method,
|
|
63
|
+
};
|
|
64
|
+
const accept = url.searchParams.get('accept') ?? req.headers.get('accept');
|
|
65
|
+
const contentType = url.searchParams.get('content-type') ??
|
|
66
|
+
req.headers.get('content-type');
|
|
67
|
+
try {
|
|
68
|
+
const connection = await this.params.onConnect({
|
|
69
|
+
type: ConnectionType.Bidirectional,
|
|
70
|
+
protocolVersion: ProtocolVersion.v1,
|
|
71
|
+
accept,
|
|
72
|
+
contentType,
|
|
73
|
+
data: request,
|
|
74
|
+
});
|
|
75
|
+
return { context: { connectionId: connection.id } };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Failed to upgrade WebSocket connection', error);
|
|
79
|
+
return InternalServerErrorHttpResponse();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
open: (peer) => {
|
|
83
|
+
const { connectionId } = peer.context;
|
|
84
|
+
this.clients.set(connectionId, peer);
|
|
85
|
+
},
|
|
86
|
+
message: async (peer, message) => {
|
|
87
|
+
const data = message.arrayBuffer();
|
|
88
|
+
try {
|
|
89
|
+
await this.params.onMessage({
|
|
90
|
+
connectionId: peer.context.connectionId,
|
|
91
|
+
data,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error(`Error while processing message from ${peer.context.connectionId}`, error);
|
|
96
|
+
this.clients.delete(peer.context.connectionId);
|
|
97
|
+
peer.close(1011, 'Internal error');
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
error: (peer, error) => {
|
|
101
|
+
console.error(`WebSocket error on connection ${peer.context.connectionId}`, error);
|
|
102
|
+
},
|
|
103
|
+
close: async (peer) => {
|
|
104
|
+
this.clients.delete(peer.context.connectionId);
|
|
105
|
+
try {
|
|
106
|
+
await this.params.onDisconnect(peer.context.connectionId);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(`Failed to dispose WebSocket connection ${peer.context.connectionId}`, error);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
createServer() {
|
|
115
|
+
const hooks = this.createWsHooks();
|
|
116
|
+
const opts = { ...this.options, wsHooks: hooks };
|
|
117
|
+
return this.adapterFactory(opts);
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ErrorCode } from '@nmtjs/protocol';
|
|
2
|
+
import { ProtocolError } from '@nmtjs/protocol/server';
|
|
3
|
+
export const InternalError = (message = 'Internal Server Error') => new ProtocolError(ErrorCode.InternalServerError, message);
|
|
4
|
+
export const NotFoundError = (message = 'Not Found') => new ProtocolError(ErrorCode.NotFound, message);
|
|
5
|
+
export const ForbiddenError = (message = 'Forbidden') => new ProtocolError(ErrorCode.Forbidden, message);
|
|
6
|
+
export const RequestTimeoutError = (message = 'Request Timeout') => new ProtocolError(ErrorCode.RequestTimeout, message);
|
|
7
|
+
export const NotFoundHttpResponse = () => new Response('Not Found', {
|
|
8
|
+
status: 404,
|
|
9
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
10
|
+
});
|
|
11
|
+
export const InternalServerErrorHttpResponse = () => new Response('Internal Server Error', {
|
|
12
|
+
status: 500,
|
|
13
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
14
|
+
});
|
|
15
|
+
export const StatusResponse = () => new Response('OK', { status: 200 });
|
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/
|
|
18
|
-
"@nmtjs/
|
|
15
|
+
"@nmtjs/client": "0.15.0-beta.2",
|
|
16
|
+
"@nmtjs/common": "0.15.0-beta.2",
|
|
17
|
+
"@nmtjs/protocol": "0.15.0-beta.2",
|
|
18
|
+
"@nmtjs/gateway": "0.15.0-beta.2"
|
|
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/
|
|
27
|
-
"@nmtjs/
|
|
28
|
-
"@nmtjs/
|
|
25
|
+
"@nmtjs/common": "0.15.0-beta.2",
|
|
26
|
+
"@nmtjs/gateway": "0.15.0-beta.2",
|
|
27
|
+
"@nmtjs/protocol": "0.15.0-beta.2",
|
|
28
|
+
"@nmtjs/core": "0.15.0-beta.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependenciesMeta": {
|
|
31
31
|
"@types/bun": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"LICENSE.md",
|
|
52
52
|
"README.md"
|
|
53
53
|
],
|
|
54
|
-
"version": "0.15.0-beta.
|
|
54
|
+
"version": "0.15.0-beta.2",
|
|
55
55
|
"scripts": {
|
|
56
56
|
"clean-build": "rm -rf ./dist",
|
|
57
57
|
"build": "tsc",
|