-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathserver.ts
More file actions
142 lines (128 loc) · 4.64 KB
/
server.ts
File metadata and controls
142 lines (128 loc) · 4.64 KB
1
2
3
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (c) 2026 RobotWebTools Contributors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// TypeScript demo server. Run with `npm run server` (which uses tsx) or
// `npx tsx server.ts`. Behaviour matches demo/web/javascript/runtime.js
// — same runtime + same demo nodes — except this side is written in
// TypeScript so the typed SDK story is visible end to end. The static
// page server is Vite (`npm run dev`), parallel to the JS demo's
// separate `node static.js`.
// rclnodejs is a CommonJS module without first-class ESM types; using
// require keeps the server independent of how a downstream project
// configures TypeScript module resolution.
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { createRequire } from 'node:module';
const require_ = createRequire(import.meta.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rclnodejs: any = require_('rclnodejs');
const { createRuntime, WebSocketTransport, HttpTransport } = require_(
'rclnodejs/web/server'
);
const RUNTIME_PORT = Number(process.env.RUNTIME_PORT || 9000);
const HTTP_PORT = Number(process.env.HTTP_PORT || 9001);
// Render the registry as a small human-readable table — see the matching
// helper in demo/web/javascript/runtime.js.
function formatCapabilities(
caps: Record<'call' | 'publish' | 'subscribe', Record<string, string>>
): string {
const rows: Array<[string, string, string]> = [];
for (const verb of ['call', 'publish', 'subscribe'] as const) {
for (const [topic, type] of Object.entries(caps[verb] || {})) {
rows.push([verb, topic, type]);
}
}
if (rows.length === 0) return ' (none)';
const w0 = Math.max(...rows.map((r) => r[0].length));
const w1 = Math.max(...rows.map((r) => r[1].length));
return rows
.map(([v, t, ty]) => ` ${v.padEnd(w0)} ${t.padEnd(w1)} ${ty}`)
.join('\n');
}
async function main(): Promise<void> {
await rclnodejs.init();
const node = rclnodejs.createNode('rclnodejs_web_ts_demo_node');
// Service the browser will call.
node.createService(
'example_interfaces/srv/AddTwoInts',
'/add_two_ints',
(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
request: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
response: any
): void => {
const reply = response.template;
reply.sum = request.a + request.b;
response.send(reply);
}
);
// 1 Hz tick publisher so the browser's subscribe() shows live data
// without the user having to publish first.
const tickPub = node.createPublisher('std_msgs/msg/String', '/web_demo_tick');
let counter = 0;
setInterval(() => {
tickPub.publish({
data: `tick ${counter++} @ ${new Date().toISOString()}`,
});
}, 1000);
rclnodejs.spin(node);
const runtime = createRuntime({
node,
transports: [
new WebSocketTransport({
port: RUNTIME_PORT,
// Dual-stack — see the matching note in the JS demo.
host: '::',
}),
// HTTP for `call` / `publish` (curl, Postman, AI agents).
// Same registry / dispatcher — the L2 seam in action.
new HttpTransport({
port: HTTP_PORT,
host: '::',
}),
],
});
runtime.expose({
call: { '/add_two_ints': 'example_interfaces/srv/AddTwoInts' },
publish: { '/web_demo_chatter': 'std_msgs/msg/String' },
subscribe: {
'/web_demo_tick': 'std_msgs/msg/String',
'/web_demo_chatter': 'std_msgs/msg/String',
},
});
await runtime.start();
const caps = runtime.registry.list();
const total =
Object.keys(caps.call || {}).length +
Object.keys(caps.publish || {}).length +
Object.keys(caps.subscribe || {}).length;
console.log('rclnodejs/web demo running (TypeScript)');
console.log(` WebSocket : ws://localhost:${RUNTIME_PORT}/capability`);
console.log(
` HTTP : http://localhost:${HTTP_PORT}/capability (call / publish, curl-able)`
);
console.log();
console.log(`Exposed capabilities (${total}):`);
console.log(formatCapabilities(caps));
console.log();
console.log(
'Static page: run `npm run dev` in another shell, then open http://localhost:8080/'
);
const stop = async (): Promise<void> => {
console.log('\nstopping…');
await runtime.stop();
rclnodejs.shutdown();
process.exit(0);
};
process.once('SIGINT', stop);
process.once('SIGTERM', stop);
}
main().catch((err: unknown) => {
console.error(err);
process.exit(1);
});