@nmtjs/ws-client 0.2.2 → 0.7.1
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/README.md +9 -1
- package/dist/index.js +53 -62
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/{index.ts → src/index.ts} +5 -3
package/README.md
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
# NeemataJS - RPC application server for real-time applications (proof of concept)
|
|
2
|
+
|
|
3
|
+
### Built with following in mind:
|
|
4
|
+
- transport-agnostic (like WebSockets, WebTransport, .etc)
|
|
5
|
+
- format-agnostic (like JSON, MessagePack, BSON, .etc)
|
|
6
|
+
- binary data streaming and event subscriptions
|
|
7
|
+
- contract-based API
|
|
8
|
+
- end-to-end type safety
|
|
9
|
+
- CPU-intensive task execution on separate workers
|
package/dist/index.js
CHANGED
|
@@ -1,66 +1,57 @@
|
|
|
1
|
-
import { EventEmitter } from
|
|
2
|
-
import { concat, decodeNumber, encodeNumber, ServerMessageType } from
|
|
1
|
+
import { EventEmitter } from "@nmtjs/protocol/client";
|
|
2
|
+
import { concat, decodeNumber, encodeNumber, ServerMessageType } from "@nmtjs/protocol/common";
|
|
3
3
|
export class WebSocketClientTransport extends EventEmitter {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.#webSocket.close();
|
|
53
|
-
return _once(this.#webSocket, 'close');
|
|
54
|
-
}
|
|
55
|
-
async send(messageType, buffer) {
|
|
56
|
-
if (this.#connecting) await this.#connecting;
|
|
57
|
-
this.#webSocket.send(concat(encodeNumber(messageType, 'Uint8'), buffer));
|
|
58
|
-
}
|
|
4
|
+
#webSocket = null;
|
|
5
|
+
#connecting = null;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super();
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
connect(auth = undefined, contentType) {
|
|
11
|
+
const wsUrl = new URL(this.options.origin);
|
|
12
|
+
wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
13
|
+
wsUrl.pathname = "/api";
|
|
14
|
+
wsUrl.searchParams.set("content-type", contentType);
|
|
15
|
+
wsUrl.searchParams.set("accept", contentType);
|
|
16
|
+
if (auth) wsUrl.searchParams.set("auth", auth);
|
|
17
|
+
const ws = this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString());
|
|
18
|
+
ws.binaryType = "arraybuffer";
|
|
19
|
+
ws.addEventListener("message", ({ data }) => {
|
|
20
|
+
const buffer = data;
|
|
21
|
+
const type = decodeNumber(buffer, "Uint8");
|
|
22
|
+
if (type in ServerMessageType) {
|
|
23
|
+
this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.#webSocket = ws;
|
|
27
|
+
this.#connecting = new Promise((resolve, reject) => {
|
|
28
|
+
ws.addEventListener("open", () => {
|
|
29
|
+
this.emit("connected");
|
|
30
|
+
resolve();
|
|
31
|
+
}, { once: true });
|
|
32
|
+
ws.addEventListener("error", (event) => reject(new Error("WebSocket error", { cause: event })), { once: true });
|
|
33
|
+
ws.addEventListener("close", (event) => {
|
|
34
|
+
this.emit("disconnected");
|
|
35
|
+
this.#webSocket = null;
|
|
36
|
+
if (this.options.autoreconnect === true) {
|
|
37
|
+
setTimeout(() => this.connect(auth, contentType), 1e3);
|
|
38
|
+
}
|
|
39
|
+
}, { once: true });
|
|
40
|
+
});
|
|
41
|
+
return this.#connecting;
|
|
42
|
+
}
|
|
43
|
+
async disconnect() {
|
|
44
|
+
if (this.#webSocket === null) return;
|
|
45
|
+
this.#webSocket.close();
|
|
46
|
+
return _once(this.#webSocket, "close");
|
|
47
|
+
}
|
|
48
|
+
async send(messageType, buffer) {
|
|
49
|
+
if (this.#connecting) await this.#connecting;
|
|
50
|
+
this.#webSocket.send(concat(encodeNumber(messageType, "Uint8"), buffer));
|
|
51
|
+
}
|
|
59
52
|
}
|
|
60
53
|
function _once(target, event) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
});
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
target.addEventListener(event, () => resolve(), { once: true });
|
|
56
|
+
});
|
|
66
57
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"mappings":"AAAA,SAEE,oBAEK,wBAAwB;AAC/B,SAEE,QACA,cACA,cACA,yBACK,wBAAwB;AAsB/B,OAAO,MAAM,iCACH,aAEV;CACE,aAA+B;CAC/B,cAAoC;CAEpC,YAA6BA,SAA0C;AACrE,SAAO;OADoB;CAE5B;CAED,QACEC,OAA2B,WAC3BC,aACe;EACf,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ;AACnC,QAAM,WAAW,MAAM,aAAa,WAAW,SAAS;AACxD,QAAM,WAAW;AACjB,QAAM,aAAa,IAAI,gBAAgB,YAAY;AACnD,QAAM,aAAa,IAAI,UAAU,YAAY;AAC7C,MAAI,KAAM,OAAM,aAAa,IAAI,QAAQ,KAAK;EAE9C,MAAM,KACJ,KAAK,QAAQ,YAAY,MAAM,IAAI,IAAI,UAAU,MAAM,UAAU;AAEnE,KAAG,aAAa;AAEhB,KAAG,iBAAiB,WAAW,CAAC,EAAE,MAAM,KAAK;GAC3C,MAAMC,SAAsB;GAC5B,MAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,OAAI,QAAQ,mBAAmB;AAC7B,SAAK,MAAM,EAAE,KAAK,GAAG,OAAO,MAAM,WAAW,kBAAkB,CAAC;GACjE;EACF,EAAC;AAEF,OAAKC,aAAa;AAElB,OAAKC,cAAc,IAAI,QAAQ,CAAC,SAAS,WAAW;AAClD,MAAG,iBACD,QACA,MAAM;AACJ,SAAK,KAAK,YAAY;AACtB,aAAS;GACV,GACD,EAAE,MAAM,KAAM,EACf;AAED,MAAG,iBACD,SACA,CAAC,UAAU,OAAO,IAAI,MAAM,mBAAmB,EAAE,OAAO,MAAO,GAAE,EACjE,EAAE,MAAM,KAAM,EACf;AAED,MAAG,iBACD,SACA,CAAC,UAAU;AACT,SAAK,KAAK,eAAe;AACzB,SAAKD,aAAa;AAClB,QAAI,KAAK,QAAQ,kBAAkB,MAAM;AACvC,gBAAW,MAAM,KAAK,QAAQ,MAAM,YAAY,EAAE,IAAK;IACxD;GACF,GACD,EAAE,MAAM,KAAM,EACf;EACF;AAED,SAAO,KAAKC;CACb;CAED,MAAM,aAA4B;AAChC,MAAI,KAAKD,eAAe,KAAM;AAC9B,OAAKA,WAAY,OAAO;AACxB,SAAO,MAAM,KAAKA,YAAY,QAAQ;CACvC;CAED,MAAM,KACJE,aACAH,QACe;AACf,MAAI,KAAKE,YAAa,OAAM,KAAKA;AACjC,OAAKD,WAAY,KAAK,OAAO,aAAa,aAAa,QAAQ,EAAE,OAAO,CAAC;CAC1E;AACF;AAED,SAAS,MAAMG,QAAqBC,OAAe;AACjD,QAAO,IAAI,QAAc,CAAC,YAAY;AACpC,SAAO,iBAAiB,OAAO,MAAM,SAAS,EAAE,EAAE,MAAM,KAAM,EAAC;CAChE;AACF","names":["options: WebSocketClientTransportOptions","auth: string | undefined","contentType: BaseClientFormat['contentType']","buffer: ArrayBuffer","#webSocket","#connecting","messageType: ClientMessageType","target: EventTarget","event: string"],"sources":["src/index.ts"],"sourcesContent":["import {\n type BaseClientFormat,\n EventEmitter,\n type ProtocolTransport,\n} from '@nmtjs/protocol/client'\nimport {\n type ClientMessageType,\n concat,\n decodeNumber,\n encodeNumber,\n ServerMessageType,\n} from '@nmtjs/protocol/common'\n\nexport type WebSocketClientTransportOptions = {\n /**\n * The origin of the server\n * @example 'http://localhost:3000'\n */\n origin: string\n /**\n * Whether to autoreconnect on close\n * @default true\n */\n autoreconnect?: boolean\n /**\n * Custom WebSocket class\n * @default globalThis.WebSocket\n */\n wsFactory?: (url: URL) => WebSocket\n\n debug?: boolean\n}\n\nexport class WebSocketClientTransport\n extends EventEmitter\n implements ProtocolTransport\n{\n #webSocket: WebSocket | null = null\n #connecting: Promise<void> | null = null\n\n constructor(private readonly options: WebSocketClientTransportOptions) {\n super()\n }\n\n connect(\n auth: string | undefined = undefined,\n contentType: BaseClientFormat['contentType'],\n ): Promise<void> {\n const wsUrl = new URL(this.options.origin)\n wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'\n wsUrl.pathname = '/api'\n wsUrl.searchParams.set('content-type', contentType)\n wsUrl.searchParams.set('accept', contentType)\n if (auth) wsUrl.searchParams.set('auth', auth)\n\n const ws =\n this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString())\n\n ws.binaryType = 'arraybuffer'\n\n ws.addEventListener('message', ({ data }) => {\n const buffer: ArrayBuffer = data\n const type = decodeNumber(buffer, 'Uint8')\n if (type in ServerMessageType) {\n this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT))\n }\n })\n\n this.#webSocket = ws\n\n this.#connecting = new Promise((resolve, reject) => {\n ws.addEventListener(\n 'open',\n () => {\n this.emit('connected')\n resolve()\n },\n { once: true },\n )\n\n ws.addEventListener(\n 'error',\n (event) => reject(new Error('WebSocket error', { cause: event })),\n { once: true },\n )\n\n ws.addEventListener(\n 'close',\n (event) => {\n this.emit('disconnected')\n this.#webSocket = null\n if (this.options.autoreconnect === true) {\n setTimeout(() => this.connect(auth, contentType), 1000)\n }\n },\n { once: true },\n )\n })\n\n return this.#connecting\n }\n\n async disconnect(): Promise<void> {\n if (this.#webSocket === null) return\n this.#webSocket!.close()\n return _once(this.#webSocket, 'close')\n }\n\n async send(\n messageType: ClientMessageType,\n buffer: ArrayBuffer,\n ): Promise<void> {\n if (this.#connecting) await this.#connecting\n this.#webSocket!.send(concat(encodeNumber(messageType, 'Uint8'), buffer))\n }\n}\n\nfunction _once(target: EventTarget, event: string) {\n return new Promise<void>((resolve) => {\n target.addEventListener(event, () => resolve(), { once: true })\n })\n}\n"],"version":3}
|
package/package.json
CHANGED
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"import": "./dist/index.js"
|
|
8
8
|
}
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@nmtjs/client": "
|
|
12
|
-
"@nmtjs/common": "
|
|
13
|
-
"@nmtjs/protocol": "
|
|
11
|
+
"@nmtjs/client": "0.7.1",
|
|
12
|
+
"@nmtjs/common": "0.7.1",
|
|
13
|
+
"@nmtjs/protocol": "0.7.1"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"
|
|
16
|
+
"src",
|
|
17
17
|
"dist",
|
|
18
18
|
"LICENSE.md",
|
|
19
19
|
"README.md"
|
|
20
20
|
],
|
|
21
|
-
"version": "0.
|
|
21
|
+
"version": "0.7.1",
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "neemata-build
|
|
23
|
+
"build": "neemata-build --root=./src './*.ts'",
|
|
24
24
|
"type-check": "tsc --noEmit"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -68,11 +68,11 @@ export class WebSocketClientTransport
|
|
|
68
68
|
|
|
69
69
|
this.#webSocket = ws
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
this.#connecting = new Promise((resolve, reject) => {
|
|
72
72
|
ws.addEventListener(
|
|
73
73
|
'open',
|
|
74
74
|
() => {
|
|
75
|
-
this.emit('
|
|
75
|
+
this.emit('connected')
|
|
76
76
|
resolve()
|
|
77
77
|
},
|
|
78
78
|
{ once: true },
|
|
@@ -87,7 +87,7 @@ export class WebSocketClientTransport
|
|
|
87
87
|
ws.addEventListener(
|
|
88
88
|
'close',
|
|
89
89
|
(event) => {
|
|
90
|
-
this.emit('
|
|
90
|
+
this.emit('disconnected')
|
|
91
91
|
this.#webSocket = null
|
|
92
92
|
if (this.options.autoreconnect === true) {
|
|
93
93
|
setTimeout(() => this.connect(auth, contentType), 1000)
|
|
@@ -96,6 +96,8 @@ export class WebSocketClientTransport
|
|
|
96
96
|
{ once: true },
|
|
97
97
|
)
|
|
98
98
|
})
|
|
99
|
+
|
|
100
|
+
return this.#connecting
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
async disconnect(): Promise<void> {
|