Skip to content

Commit c16ea9f

Browse files
committed
Add demo for fetch() usage
1 parent fc86f5f commit c16ea9f

3 files changed

Lines changed: 120 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ how much glue you want to write.
121121
from your ROS 2 message types; and every capability
122122
is also a plain HTTP endpoint —
123123
`curl -X POST http://<host>/capability/call/<name>` — so shell
124-
scripts, Postman, and AI-agent tool-use just work.
124+
scripts, Postman, AI-agent tool-use, and even a bare browser
125+
`fetch()` (CORS-enabled) just work.
125126
_New in `2.0.0-beta.0`._
126127

127128
```ts

demo/web/javascript/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,31 @@ stream — no SDK, no WebSocket. It works cross-origin (`:8080` → `:9001`)
8484
because the demo also enables CORS (`new HttpTransport({ sse: true, cors:
8585
true })`); in production, pass your site's origin instead of `true`.
8686

87+
The same is true for `call` / `publish` from the browser itself: the
88+
**native `fetch()` panel** (section 7) hits these endpoints directly —
89+
the `curl` commands above translate one-to-one to `fetch()` (same method,
90+
URL, headers and JSON body), again cross-origin thanks to CORS:
91+
92+
```js
93+
// service call → 200 + JSON reply
94+
const res = await fetch(
95+
'http://localhost:9001/capability/call/add_two_ints',
96+
{
97+
method: 'POST',
98+
headers: { 'content-type': 'application/json' },
99+
body: JSON.stringify({ a: '7n', b: '35n' }),
100+
},
101+
);
102+
console.log((await res.json()).sum); // '42n'
103+
104+
// topic publish → 204 No Content
105+
await fetch('http://localhost:9001/capability/publish/web_demo_chatter', {
106+
method: 'POST',
107+
headers: { 'content-type': 'application/json' },
108+
body: JSON.stringify({ data: 'hi from fetch()' }),
109+
});
110+
```
111+
87112
> For browser apps, prefer the WebSocket transport for `subscribe` — one
88113
> connection multiplexes every topic. SSE targets the curl / AI-agent /
89114
> server-side persona.

demo/web/javascript/index.html

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,58 @@ <h2>
364364
es.close();</pre>
365365
</div>
366366

367+
<h2>
368+
7. Native <code>fetch()</code> — call &amp; publish over HTTP
369+
<span class="badge">no SDK</span>
370+
</h2>
371+
<p style="font-size: 0.9em; color: #555">
372+
The <code>curl</code> commands in section 5 translate one-to-one to the
373+
browser's <code>fetch()</code> API — same method, URL, headers and JSON
374+
body. Because the runtime sends CORS headers
375+
(<code>new HttpTransport({ cors: true })</code>), these cross-origin
376+
requests succeed straight from the page (served on <code>:8080</code>,
377+
runtime on <code>:9001</code>). <code>call</code> returns
378+
<code>200</code> + a JSON reply; <code>publish</code> returns
379+
<code>204 No Content</code>. The publish lands on the same
380+
<code>/web_demo_chatter</code> topic as panels 2 and 3, so it also shows
381+
up in their logs.
382+
</p>
383+
<div class="panel">
384+
<div class="controls">
385+
<div class="row">
386+
<button id="fetchCallBtn">fetch call <code>/add_two_ints</code></button>
387+
<button id="fetchPubBtn">
388+
fetch publish <code>/web_demo_chatter</code>
389+
</button>
390+
</div>
391+
<div class="log" id="fetchLog"></div>
392+
</div>
393+
<pre
394+
class="code"
395+
><span class="com">// No import — fetch() is built into every browser.</span>
396+
<span class="com">// Service call → 200 + JSON reply:</span>
397+
<span class="kw">const</span> res = <span class="kw">await</span> fetch(
398+
<span class="str">'http://localhost:9001/capability/call/add_two_ints'</span>,
399+
{
400+
method: <span class="str">'POST'</span>,
401+
headers: { <span class="str">'content-type'</span>: <span class="str">'application/json'</span> },
402+
body: JSON.stringify({ a: <span class="str">'7n'</span>, b: <span class="str">'35n'</span> }),
403+
},
404+
);
405+
<span class="kw">const</span> reply = <span class="kw">await</span> res.json();
406+
console.log(reply.sum); <span class="com">// '42n'</span>
407+
408+
<span class="com">// Topic publish → 204 No Content (no body):</span>
409+
<span class="kw">await</span> fetch(
410+
<span class="str">'http://localhost:9001/capability/publish/web_demo_chatter'</span>,
411+
{
412+
method: <span class="str">'POST'</span>,
413+
headers: { <span class="str">'content-type'</span>: <span class="str">'application/json'</span> },
414+
body: JSON.stringify({ data: <span class="str">'hello from fetch()'</span> }),
415+
},
416+
);</pre>
417+
</div>
418+
367419
<script type="module">
368420
import { connect } from '/sdk/index.js';
369421

@@ -570,6 +622,47 @@ <h2>
570622
log('sseLog', 'closed', 'ok');
571623
};
572624

625+
// Native fetch() — call/publish straight to the HTTP transport,
626+
// no SDK and no WebSocket. CORS on the runtime lets these
627+
// cross-origin requests (page on :8080 → runtime on :9001)
628+
// succeed. Independent of the WS/HTTP transport toggle above.
629+
document.getElementById('fetchCallBtn').onclick = async () => {
630+
try {
631+
const res = await fetch(
632+
`${ENDPOINTS.http}/capability/call/add_two_ints`,
633+
{
634+
method: 'POST',
635+
headers: { 'content-type': 'application/json' },
636+
body: JSON.stringify({ a: '7n', b: '35n' }),
637+
}
638+
);
639+
const reply = await res.json();
640+
log('fetchLog', `7 + 35 = ${reply.sum} (HTTP ${res.status})`, 'ok');
641+
} catch (e) {
642+
log('fetchLog', `call error: ${e.message}`, 'err');
643+
}
644+
};
645+
document.getElementById('fetchPubBtn').onclick = async () => {
646+
try {
647+
const res = await fetch(
648+
`${ENDPOINTS.http}/capability/publish/web_demo_chatter`,
649+
{
650+
method: 'POST',
651+
headers: { 'content-type': 'application/json' },
652+
body: JSON.stringify({ data: 'hello from fetch()' }),
653+
}
654+
);
655+
// publish replies 204 No Content on success.
656+
log(
657+
'fetchLog',
658+
`published 'hello from fetch()' (HTTP ${res.status})`,
659+
res.ok ? 'ok' : 'err'
660+
);
661+
} catch (e) {
662+
log('fetchLog', `publish error: ${e.message}`, 'err');
663+
}
664+
};
665+
573666
for (const radio of document.querySelectorAll(
574667
'input[name="transport"]'
575668
)) {

0 commit comments

Comments
 (0)