@liphium/neoroute-ts 0.1.0
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/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/.zed/settings.json +3 -0
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +15 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/examples/http_client/src/main.d.ts +2 -0
- package/dist/examples/http_client/src/main.d.ts.map +1 -0
- package/dist/examples/http_client/src/main.js +51 -0
- package/dist/examples/http_client/src/main.js.map +1 -0
- package/dist/examples/http_client/src/messages.d.ts +9 -0
- package/dist/examples/http_client/src/messages.d.ts.map +1 -0
- package/dist/examples/http_client/src/messages.js +2 -0
- package/dist/examples/http_client/src/messages.js.map +1 -0
- package/dist/examples/http_client/src/requests.d.ts +5 -0
- package/dist/examples/http_client/src/requests.d.ts.map +1 -0
- package/dist/examples/http_client/src/requests.js +56 -0
- package/dist/examples/http_client/src/requests.js.map +1 -0
- package/dist/examples/web_socket_client/src/main.d.ts +2 -0
- package/dist/examples/web_socket_client/src/main.d.ts.map +1 -0
- package/dist/examples/web_socket_client/src/main.js +59 -0
- package/dist/examples/web_socket_client/src/main.js.map +1 -0
- package/dist/examples/web_socket_client/src/messages.d.ts +14 -0
- package/dist/examples/web_socket_client/src/messages.d.ts.map +1 -0
- package/dist/examples/web_socket_client/src/messages.js +3 -0
- package/dist/examples/web_socket_client/src/messages.js.map +1 -0
- package/dist/examples/web_socket_client/src/receiver.d.ts +3 -0
- package/dist/examples/web_socket_client/src/receiver.d.ts.map +1 -0
- package/dist/examples/web_socket_client/src/receiver.js +8 -0
- package/dist/examples/web_socket_client/src/receiver.js.map +1 -0
- package/dist/examples/web_socket_client/src/requests.d.ts +4 -0
- package/dist/examples/web_socket_client/src/requests.d.ts.map +1 -0
- package/dist/examples/web_socket_client/src/requests.js +32 -0
- package/dist/examples/web_socket_client/src/requests.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +24 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +7 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +11 -0
- package/dist/logger.js.map +1 -0
- package/dist/receiver.d.ts +12 -0
- package/dist/receiver.d.ts.map +1 -0
- package/dist/receiver.js +49 -0
- package/dist/receiver.js.map +1 -0
- package/dist/sender.d.ts +26 -0
- package/dist/sender.d.ts.map +1 -0
- package/dist/sender.js +135 -0
- package/dist/sender.js.map +1 -0
- package/dist/src/client.d.ts +8 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +15 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.d.ts +5 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +2 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces.d.ts +24 -0
- package/dist/src/interfaces.d.ts.map +1 -0
- package/dist/src/interfaces.js +7 -0
- package/dist/src/interfaces.js.map +1 -0
- package/dist/src/logger.d.ts +9 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +11 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/receiver.d.ts +12 -0
- package/dist/src/receiver.d.ts.map +1 -0
- package/dist/src/receiver.js +49 -0
- package/dist/src/receiver.js.map +1 -0
- package/dist/src/sender.d.ts +26 -0
- package/dist/src/sender.d.ts.map +1 -0
- package/dist/src/sender.js +135 -0
- package/dist/src/sender.js.map +1 -0
- package/dist/src/transporter/transporter_http.d.ts +6 -0
- package/dist/src/transporter/transporter_http.d.ts.map +1 -0
- package/dist/src/transporter/transporter_http.js +30 -0
- package/dist/src/transporter/transporter_http.js.map +1 -0
- package/dist/src/transporter/transporter_websocket.d.ts +17 -0
- package/dist/src/transporter/transporter_websocket.d.ts.map +1 -0
- package/dist/src/transporter/transporter_websocket.js +93 -0
- package/dist/src/transporter/transporter_websocket.js.map +1 -0
- package/dist/src/user_error.d.ts +4 -0
- package/dist/src/user_error.d.ts.map +1 -0
- package/dist/src/user_error.js +11 -0
- package/dist/src/user_error.js.map +1 -0
- package/dist/src/util.d.ts +9 -0
- package/dist/src/util.d.ts.map +1 -0
- package/dist/src/util.js +42 -0
- package/dist/src/util.js.map +1 -0
- package/dist/transporter/transporter_http.d.ts +6 -0
- package/dist/transporter/transporter_http.d.ts.map +1 -0
- package/dist/transporter/transporter_http.js +30 -0
- package/dist/transporter/transporter_http.js.map +1 -0
- package/dist/transporter/transporter_websocket.d.ts +17 -0
- package/dist/transporter/transporter_websocket.d.ts.map +1 -0
- package/dist/transporter/transporter_websocket.js +93 -0
- package/dist/transporter/transporter_websocket.js.map +1 -0
- package/dist/user_error.d.ts +4 -0
- package/dist/user_error.d.ts.map +1 -0
- package/dist/user_error.js +11 -0
- package/dist/user_error.js.map +1 -0
- package/dist/util.d.ts +9 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +42 -0
- package/dist/util.js.map +1 -0
- package/examples/http_client/package.json +20 -0
- package/examples/http_client/src/main.ts +59 -0
- package/examples/http_client/src/messages.ts +9 -0
- package/examples/http_client/src/requests.ts +64 -0
- package/examples/http_client/tsconfig.json +44 -0
- package/examples/web_socket_client/package.json +20 -0
- package/examples/web_socket_client/src/main.ts +72 -0
- package/examples/web_socket_client/src/messages.ts +22 -0
- package/examples/web_socket_client/src/receiver.ts +8 -0
- package/examples/web_socket_client/src/requests.ts +51 -0
- package/examples/web_socket_client/tsconfig.json +44 -0
- package/package.json +36 -0
- package/pnpm-workspace.yaml +5 -0
- package/readme.md +49 -0
- package/src/client.ts +17 -0
- package/src/config.ts +4 -0
- package/src/index.ts +13 -0
- package/src/interfaces.ts +31 -0
- package/src/logger.ts +18 -0
- package/src/receiver.ts +56 -0
- package/src/sender.ts +203 -0
- package/src/transporter/transporter_http.ts +37 -0
- package/src/transporter/transporter_websocket.ts +115 -0
- package/src/user_error.ts +21 -0
- package/src/util.ts +58 -0
- package/tsconfig.json +44 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web_socket_example",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "How to use the WebSocket client from neoroute-ts",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node --loader ts-node/esm src/main.ts"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@liphium/neoroute-ts": "workspace:*"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"private": "true",
|
|
16
|
+
"packageManager": "pnpm@10.32.1",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^26.0.1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
newPromiseWithResolver,
|
|
3
|
+
Receiver,
|
|
4
|
+
WebSocketTransporter,
|
|
5
|
+
} from '@liphium/neoroute-ts';
|
|
6
|
+
import { registerReceiver } from './receiver.js';
|
|
7
|
+
import * as readline from 'node:readline';
|
|
8
|
+
import { sendEchoRequest, sendSubmitPunRequest } from './requests.js';
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const { resolve, reject, promise } = newPromiseWithResolver<void>();
|
|
12
|
+
const r = new Receiver({
|
|
13
|
+
errorHandler: (err: Error) => {
|
|
14
|
+
console.log('error with receiver', err);
|
|
15
|
+
reject(err);
|
|
16
|
+
},
|
|
17
|
+
requestTimeout: 1000, // time.Second * 1 in Millisekunden
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
registerReceiver(r);
|
|
21
|
+
|
|
22
|
+
const t = new WebSocketTransporter(r, {
|
|
23
|
+
onOpen: () => {
|
|
24
|
+
resolve();
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
t.connect('ws://localhost:6121/');
|
|
30
|
+
await promise;
|
|
31
|
+
console.log('Connected to server.');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(`failed to connect to server: ${err}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rl = readline.createInterface({
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stdout,
|
|
40
|
+
prompt: '',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log('Type exit to quit the program.');
|
|
44
|
+
console.log('Type "Pun " followed by a pun to submit it to the server.');
|
|
45
|
+
console.log(
|
|
46
|
+
'Type "Echo " followed by a message to receive a echo message from the server.',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
rl.on('line', (line) => {
|
|
50
|
+
const input = line.trim();
|
|
51
|
+
|
|
52
|
+
if (input.startsWith('Pun ')) {
|
|
53
|
+
const pun = input.slice(4);
|
|
54
|
+
console.log('Sending pun to server');
|
|
55
|
+
sendSubmitPunRequest(r, pun);
|
|
56
|
+
} else if (input.startsWith('Echo ')) {
|
|
57
|
+
const msg = input.slice(5);
|
|
58
|
+
console.log('Sending echo to server');
|
|
59
|
+
sendEchoRequest(r, msg);
|
|
60
|
+
} else if (input === 'exit') {
|
|
61
|
+
t.close();
|
|
62
|
+
rl.close();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
rl.on('close', () => {
|
|
67
|
+
console.log('Connection closed');
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Outgoing
|
|
2
|
+
|
|
3
|
+
export interface EchoRequest {
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface SubmitPunRequest {
|
|
8
|
+
pun: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Response
|
|
12
|
+
|
|
13
|
+
export interface EchoResponse {
|
|
14
|
+
requestNumber: number;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Event
|
|
19
|
+
|
|
20
|
+
export interface NewPunEvent {
|
|
21
|
+
pun: string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Receiver, Ctx } from '@liphium/neoroute-ts';
|
|
2
|
+
import { type NewPunEvent } from './messages.js';
|
|
3
|
+
|
|
4
|
+
export function registerReceiver(receiver: Receiver): void {
|
|
5
|
+
receiver.receive('new_pun_submitted', (_: Ctx, req: NewPunEvent) => {
|
|
6
|
+
console.log(`A new pun was submitted by someone, it is ${req.pun}`);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Receiver, UserError } from '@liphium/neoroute-ts';
|
|
2
|
+
import {
|
|
3
|
+
type SubmitPunRequest,
|
|
4
|
+
type EchoRequest,
|
|
5
|
+
type EchoResponse,
|
|
6
|
+
} from './messages.js';
|
|
7
|
+
|
|
8
|
+
export async function sendSubmitPunRequest(
|
|
9
|
+
receiver: Receiver,
|
|
10
|
+
pun: string,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
try {
|
|
13
|
+
const requestPayload: SubmitPunRequest = { pun: pun };
|
|
14
|
+
|
|
15
|
+
// sendOk throws an error if it fails, otherwise it's successful
|
|
16
|
+
const res = await receiver.sendOk<SubmitPunRequest>(
|
|
17
|
+
'submit_pun',
|
|
18
|
+
requestPayload,
|
|
19
|
+
);
|
|
20
|
+
if (res instanceof UserError) {
|
|
21
|
+
console.log("Couldn't submit pun because:", res.message);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('Pun submitted successfully!');
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.log('Failed to send submit pun request:', err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function sendEchoRequest(
|
|
32
|
+
receiver: Receiver,
|
|
33
|
+
message: string,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
try {
|
|
36
|
+
const requestPayload: EchoRequest = { message: message };
|
|
37
|
+
|
|
38
|
+
const res = await receiver.send<EchoResponse, EchoRequest>(
|
|
39
|
+
'echo',
|
|
40
|
+
requestPayload,
|
|
41
|
+
);
|
|
42
|
+
if (res instanceof UserError) {
|
|
43
|
+
console.log('Echo failed because:', res.message);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Received ${res.requestNumber}. echo: ${res.message}`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.log('Failed to send echo request:', err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
"types": ["node"],
|
|
13
|
+
// For nodejs:
|
|
14
|
+
// "lib": ["esnext"],
|
|
15
|
+
// "types": ["node"],
|
|
16
|
+
// and npm install -D @types/node
|
|
17
|
+
|
|
18
|
+
// Other Outputs
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"declarationMap": true,
|
|
22
|
+
|
|
23
|
+
// Stricter Typechecking Options
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
|
|
27
|
+
// Style Options
|
|
28
|
+
// "noImplicitReturns": true,
|
|
29
|
+
// "noImplicitOverride": true,
|
|
30
|
+
// "noUnusedLocals": true,
|
|
31
|
+
// "noUnusedParameters": true,
|
|
32
|
+
// "noFallthroughCasesInSwitch": true,
|
|
33
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
+
|
|
35
|
+
// Recommended Options
|
|
36
|
+
"strict": true,
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"verbatimModuleSyntax": true,
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"noUncheckedSideEffectImports": true,
|
|
41
|
+
"moduleDetection": "force",
|
|
42
|
+
"skipLibCheck": true,
|
|
43
|
+
},
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liphium/neoroute-ts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"directory": "https://github.com/Liphium/neoroute-ts.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/Liphium/neoroute-ts#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/Liphium/neoroute-ts/issues"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"description": "Talk to neoroute servers.",
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^26.0.1",
|
|
24
|
+
"prettier": "^3.9.4",
|
|
25
|
+
"ts-node": "^10.9.2",
|
|
26
|
+
"typescript": "^6.0.3"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@msgpack/msgpack": "^3.1.3"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"format": "prettier --write .",
|
|
34
|
+
"check": "prettier --check ."
|
|
35
|
+
}
|
|
36
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Neoroute-TS
|
|
2
|
+
|
|
3
|
+
## Examples
|
|
4
|
+
|
|
5
|
+
This repository contains two interactive command-line examples to test the `@liphium/neoroute-client` TypeScript library. These clients are designed to communicate with a local Neoroute Go server running on port `6121`.
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
Before running the examples, ensure you have the following ready:
|
|
10
|
+
|
|
11
|
+
1. **Node.js** (v18 or higher recommended).
|
|
12
|
+
2. The corresponding **[Neoroute Example Server](https://github.com/Liphium/neoroute/tree/main/examples)** must be running locally (`go run .`).
|
|
13
|
+
3. The main library (`@liphium/neoroute-ts-client`) must be built. If you make changes to the core library, simply run `npm run build` in the base directory. The examples use a local symlink and will automatically use the updated build.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### 1. WebSocket Client (`examples/web_socket_client`)
|
|
18
|
+
|
|
19
|
+
This client tests the persistent WebSocket connection, event listening, and basic request/response flows.
|
|
20
|
+
|
|
21
|
+
#### Setup & Run
|
|
22
|
+
|
|
23
|
+
Navigate to the WebSocket client directory and install the dependencies:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd examples/web_socket_client
|
|
27
|
+
npm install
|
|
28
|
+
npx tsx src/main.ts
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### 2. HTTP Client (`examples/http_client`)
|
|
34
|
+
|
|
35
|
+
This client tests the stateless HTTP transporter, routing groups, and middleware authentication (token validation via URL parameters).
|
|
36
|
+
|
|
37
|
+
#### Setup & Run
|
|
38
|
+
|
|
39
|
+
Navigate to the WebSocket client directory and install the dependencies:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd examples/http_client
|
|
43
|
+
npm install
|
|
44
|
+
npx tsx src/main.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Development Note
|
|
48
|
+
|
|
49
|
+
Both clients use `tsx` to execute the TypeScript files directly without a manual compilation step.
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class Ctx {
|
|
2
|
+
private _data: Uint8Array;
|
|
3
|
+
private _name: string;
|
|
4
|
+
|
|
5
|
+
constructor(data: Uint8Array, name: string) {
|
|
6
|
+
this._data = data;
|
|
7
|
+
this._name = name;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public data(): Uint8Array {
|
|
11
|
+
return this._data;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public name(): string {
|
|
15
|
+
return this._name;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/config.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './interfaces.js';
|
|
2
|
+
export * from './config.js';
|
|
3
|
+
export * from './logger.js';
|
|
4
|
+
export * from './client.js';
|
|
5
|
+
|
|
6
|
+
export * from './sender.js';
|
|
7
|
+
export * from './receiver.js';
|
|
8
|
+
export * from './util.js';
|
|
9
|
+
|
|
10
|
+
export * from './user_error.js';
|
|
11
|
+
|
|
12
|
+
export * from './transporter/transporter_http.js';
|
|
13
|
+
export * from './transporter/transporter_websocket.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Incoming messages
|
|
2
|
+
|
|
3
|
+
export enum MessageType {
|
|
4
|
+
Response,
|
|
5
|
+
Event,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Message {
|
|
9
|
+
type: MessageType; // Response or event
|
|
10
|
+
data: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Event {
|
|
14
|
+
name: string;
|
|
15
|
+
data: Uint8Array;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Response {
|
|
19
|
+
id: number;
|
|
20
|
+
has_data: boolean;
|
|
21
|
+
error: boolean;
|
|
22
|
+
data: Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Outgoing messages
|
|
26
|
+
|
|
27
|
+
export interface Request {
|
|
28
|
+
id: number;
|
|
29
|
+
route: string;
|
|
30
|
+
data: Uint8Array;
|
|
31
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
debug(msg: string, ...args: any[]): void;
|
|
3
|
+
info(msg: string, ...args: any[]): void;
|
|
4
|
+
warn(msg: string, ...args: any[]): void;
|
|
5
|
+
error(msg: string, ...args: any[]): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Default logger falls back to the standard console
|
|
9
|
+
export let logger: Logger = {
|
|
10
|
+
debug: console.debug,
|
|
11
|
+
info: console.info,
|
|
12
|
+
warn: console.warn,
|
|
13
|
+
error: console.error,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function setLogger(l: Logger) {
|
|
17
|
+
logger = l;
|
|
18
|
+
}
|
package/src/receiver.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { decode } from '@msgpack/msgpack';
|
|
2
|
+
import { Ctx } from './client.js';
|
|
3
|
+
import type { Config } from './config.js';
|
|
4
|
+
import { Sender } from './sender.js';
|
|
5
|
+
import { type Event, MessageType, type Message } from './interfaces.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
export class Receiver extends Sender {
|
|
9
|
+
private receiver: Map<string, (c: Ctx) => void> = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(config: Config) {
|
|
12
|
+
super(config);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
protected override handleOther(message: Message): void {
|
|
16
|
+
if (message.type === MessageType.Event) {
|
|
17
|
+
this.handleEvent(message.data);
|
|
18
|
+
} else {
|
|
19
|
+
super.handleOther(message);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private handleEvent(eventBytes: Uint8Array): void {
|
|
24
|
+
let ev: Event;
|
|
25
|
+
try {
|
|
26
|
+
ev = decode(eventBytes) as Event;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.info('failed to unmarshal event', error);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const receiverFunc = this.receiver.get(ev.name);
|
|
33
|
+
if (!receiverFunc) {
|
|
34
|
+
logger.info('received event for non existing receiver', ev.name);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const c = new Ctx(ev.data, ev.name);
|
|
39
|
+
receiverFunc(c);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Handle receiving of an event. Only one handleFunc can exist at the same time.
|
|
43
|
+
public receive<E>(
|
|
44
|
+
eventName: string,
|
|
45
|
+
handleFunc: (c: Ctx, data: E) => void,
|
|
46
|
+
): void {
|
|
47
|
+
this.receiver.set(eventName, (c: Ctx) => {
|
|
48
|
+
try {
|
|
49
|
+
const data = decode(c.data()) as E;
|
|
50
|
+
handleFunc(c, data);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.info('failed to unmarshal event data', error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/sender.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { decode, encode } from '@msgpack/msgpack';
|
|
2
|
+
import {
|
|
3
|
+
type Message,
|
|
4
|
+
type Response,
|
|
5
|
+
type Request,
|
|
6
|
+
MessageType,
|
|
7
|
+
} from './interfaces.js';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
import type { Config } from './config.js';
|
|
10
|
+
import {
|
|
11
|
+
marshalRequestData,
|
|
12
|
+
unmarshalResponseData,
|
|
13
|
+
withTimeout,
|
|
14
|
+
} from './util.js';
|
|
15
|
+
import { UserError } from './user_error.js';
|
|
16
|
+
|
|
17
|
+
export class Sender {
|
|
18
|
+
private config: Config;
|
|
19
|
+
private sendFunc?: (data: Uint8Array) => Promise<void>;
|
|
20
|
+
private requestId: number = 0;
|
|
21
|
+
|
|
22
|
+
// Maps a request ID to a resolve function to fulfill the promise
|
|
23
|
+
private waiters: Map<number, (resp: Response) => void> = new Map();
|
|
24
|
+
|
|
25
|
+
constructor(config: Config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
if (!this.config.requestTimeout) {
|
|
28
|
+
this.config.requestTimeout = 5000; // default timeout 5 seconds
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getRequestId(): number {
|
|
33
|
+
this.requestId++;
|
|
34
|
+
return this.requestId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public setSendFunc(sendFunc: (data: Uint8Array) => Promise<void>): void {
|
|
38
|
+
this.sendFunc = sendFunc;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public handle(reqData: Uint8Array): void {
|
|
42
|
+
let message: Message;
|
|
43
|
+
try {
|
|
44
|
+
message = decode(reqData) as Message;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.info('failed to unmarshal message', error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (message.type === MessageType.Response) {
|
|
51
|
+
this.handleResponse(message.data);
|
|
52
|
+
} else {
|
|
53
|
+
this.handleOther(message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public handleResponse(respBytes: Uint8Array): void {
|
|
58
|
+
let resp: Response;
|
|
59
|
+
try {
|
|
60
|
+
resp = decode(respBytes) as Response;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.info('failed to unmarshal response', error);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (resp.id === -1 && resp.error) {
|
|
67
|
+
const decoder = new TextDecoder();
|
|
68
|
+
this.config.errorHandler(new Error(decoder.decode(resp.data)));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolveFunc = this.waiters.get(resp.id);
|
|
73
|
+
if (!resolveFunc) {
|
|
74
|
+
logger.info('received response for non existing waiter', resp.id);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolveFunc(resp);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected handleOther(message: Message): void {
|
|
82
|
+
logger.info('received unsupported message type', message.type);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public removeResponseWaiter(requestId: number): void {
|
|
86
|
+
this.waiters.delete(requestId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async sendRequest(
|
|
90
|
+
route: string,
|
|
91
|
+
reqData: Uint8Array,
|
|
92
|
+
wantResponse: boolean,
|
|
93
|
+
): Promise<{ promise: Promise<Response>; reqId: number }> {
|
|
94
|
+
const reqId = this.getRequestId();
|
|
95
|
+
|
|
96
|
+
if (!this.sendFunc) {
|
|
97
|
+
throw new Error('sendFunc is not set');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const req: Request = {
|
|
101
|
+
id: reqId,
|
|
102
|
+
route: route,
|
|
103
|
+
data: reqData,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let reqBytes: Uint8Array;
|
|
107
|
+
try {
|
|
108
|
+
reqBytes = encode(req);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
throw new Error(`failed to marshal request: ${err}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let respPromise: Promise<Response> = Promise.resolve({} as Response);
|
|
114
|
+
if (wantResponse) {
|
|
115
|
+
respPromise = new Promise<Response>((resolve) => {
|
|
116
|
+
this.waiters.set(reqId, resolve);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await this.sendFunc(reqBytes);
|
|
121
|
+
return { promise: respPromise, reqId: reqId };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public getConfig(): Config {
|
|
125
|
+
return this.config;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Internal helper for making send functions easier to write.
|
|
129
|
+
private async sendInternal<RQ, RS>(
|
|
130
|
+
route: string,
|
|
131
|
+
req: RQ,
|
|
132
|
+
waitForResponse: boolean,
|
|
133
|
+
hasResponse: boolean,
|
|
134
|
+
): Promise<RS | UserError | undefined> {
|
|
135
|
+
const { promise: respPromise, reqId } = await this.sendRequest(
|
|
136
|
+
route,
|
|
137
|
+
req != undefined ? marshalRequestData<RQ>(req) : new Uint8Array(),
|
|
138
|
+
true,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// When we want to wait for response, properly serialize it
|
|
142
|
+
if (waitForResponse) {
|
|
143
|
+
const timeout = this.config.requestTimeout ?? 5000;
|
|
144
|
+
const res = await withTimeout(respPromise, timeout);
|
|
145
|
+
this.removeResponseWaiter(reqId);
|
|
146
|
+
|
|
147
|
+
if (res.error) {
|
|
148
|
+
const decoder = new TextDecoder();
|
|
149
|
+
return new UserError(decoder.decode(res.data));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hasResponse) {
|
|
153
|
+
return unmarshalResponseData<RS>(res.data);
|
|
154
|
+
} else {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
// Otherwise signal that nothing will be waited for
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public send<RS, RQ>(route: string, req: RQ): Promise<RS | UserError> {
|
|
164
|
+
return this.sendInternal<RQ, RS>(route, req, true, true) as Promise<
|
|
165
|
+
RS | UserError
|
|
166
|
+
>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public sendOk<RQ>(route: string, req: RQ): Promise<UserError> {
|
|
170
|
+
return this.sendInternal<RQ, unknown>(
|
|
171
|
+
route,
|
|
172
|
+
req,
|
|
173
|
+
true,
|
|
174
|
+
false,
|
|
175
|
+
) as Promise<UserError>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public async sendOkNoRequest(route: string): Promise<UserError> {
|
|
179
|
+
return this.sendInternal(
|
|
180
|
+
route,
|
|
181
|
+
undefined,
|
|
182
|
+
false,
|
|
183
|
+
false,
|
|
184
|
+
) as Promise<UserError>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public async sendNoRequest<RS>(route: string): Promise<RS | UserError> {
|
|
188
|
+
return this.sendInternal<undefined, RS>(
|
|
189
|
+
route,
|
|
190
|
+
undefined,
|
|
191
|
+
true,
|
|
192
|
+
true,
|
|
193
|
+
) as Promise<RS | UserError>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public async sendNoResponse<RQ>(route: string, req: RQ): Promise<void> {
|
|
197
|
+
await this.sendInternal<RQ, undefined>(route, req, false, false);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public async sendPing(route: string): Promise<void> {
|
|
201
|
+
await this.sendInternal(route, undefined, false, false);
|
|
202
|
+
}
|
|
203
|
+
}
|