Skip to content

Commit 33485df

Browse files
committed
deploy: 4fd0d72
1 parent acbc2e8 commit 33485df

File tree

8 files changed

+1582
-0
lines changed

8 files changed

+1582
-0
lines changed

main/browser-echo/index.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Iroh browser echo demo</title>
5+
<script src="./main.js" type="module"></script>
6+
<link rel="stylesheet" href="./style.css" />
7+
</head>
8+
9+
<body>
10+
<header>
11+
<h1>
12+
iroh in the browser
13+
</h1>
14+
This example runs iroh in the browser, compiled to web assembly. The example uses a custom <i>Echo protocol</i>: You connect to remote node, send some data, and the remote node sends back the same data.
15+
The protocol may be used both from the browser and from the command line, and browser and cli nodes can connect to each other.
16+
<br>
17+
<a href="https://github.com/n0-computer/iroh-examples/tree/main/browser-echo" target="_blank">
18+
Source code
19+
</a>
20+
</header>
21+
<main></main>
22+
<div class="spawned" style="display:none">
23+
<form id="connect">
24+
<h3>connect to another node</h3>
25+
<div class="fields">
26+
<div>
27+
<label for="node-id">node id to connect to:</label>
28+
<input name="node-id" placeholder="node id" />
29+
</div>
30+
<div>
31+
<label for="payload">text to send to the node:</label>
32+
<input name="payload" placeholder="string to send" />
33+
</div>
34+
</div>
35+
<div>
36+
<button type="submit">connect</button>
37+
</div>
38+
</form>
39+
<div id="outgoing" class="connections"">
40+
<h2>outgoing connections</h2>
41+
</div>
42+
<div id="incoming" class="connections"">
43+
<h2>incoming connections</h2>
44+
</div>
45+
</div>
46+
</body>
47+
</html>

main/browser-echo/main.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import init, { EchoNode } from "./wasm/browser_echo.js";
2+
await init();
3+
4+
log("launching iroh endpoint …");
5+
6+
const node = await EchoNode.spawn();
7+
8+
log("iroh endpoint launched");
9+
log("our node id: " + node.node_id());
10+
11+
log("connect from the command line:");
12+
log("git clone https://github.com/n0-computer/iroh-examples.git", "cmd");
13+
log("cd iroh-examples/browser-echo", "cmd");
14+
log(
15+
`cargo run --features cli -- connect ${node.node_id()} "hi from cli"`,
16+
"cmd",
17+
);
18+
const link = createConnectLink(node.node_id(), "hi from browser");
19+
log(`connect from the browser: ${link}`);
20+
log("waiting for connections …");
21+
22+
// show the form and connection logs
23+
document.querySelector(".spawned").style = "display: block";
24+
// initiate outgoing connections on form submit
25+
document.querySelector("form#connect").onsubmit = onConnectSubmit;
26+
// fill the connect form
27+
fillFormFromUrlAndSubmit();
28+
29+
// log events for incoming connections
30+
(async () => {
31+
const $incoming = document.querySelector("#incoming");
32+
for await (const event of node.events()) {
33+
console.log("incoming event", event);
34+
const nodeId = event.node_id;
35+
delete event.node_id;
36+
logNodeEvent($incoming, nodeId, JSON.stringify(event));
37+
}
38+
})();
39+
40+
// initiate outgoing connections on form submit
41+
async function onConnectSubmit(e) {
42+
e.preventDefault();
43+
const data = new FormData(e.target);
44+
const nodeId = data.get("node-id");
45+
const payload = data.get("payload");
46+
if (!nodeId || !payload) return;
47+
48+
const $outgoing = document.querySelector("#outgoing");
49+
try {
50+
logNodeEvent($outgoing, nodeId, "connecting …");
51+
const stream = node.connect(nodeId, payload);
52+
for await (const event of stream) {
53+
logNodeEvent($outgoing, nodeId, JSON.stringify(event));
54+
}
55+
} catch (err) {
56+
logNodeEvent($outgoing, nodeId, `connection failed: ${err}`, "error");
57+
}
58+
}
59+
60+
function log(line, className, parent) {
61+
const time = new Date().toISOString().substring(11, 22);
62+
if (!parent) parent = document.querySelector("main");
63+
const el = document.createElement("div");
64+
line = `<span class=time>${time}: </span>${line}`;
65+
el.innerHTML = line;
66+
if (className) el.classList.add(className);
67+
parent.appendChild(el);
68+
}
69+
70+
function logNodeEvent(container, nodeId, event, className) {
71+
let nodeDiv = container.querySelector(`.node-${nodeId}`);
72+
if (!nodeDiv) {
73+
nodeDiv = document.createElement("div");
74+
nodeDiv.classList.add("node");
75+
nodeDiv.classList.add(`node-${nodeId}`);
76+
const heading = document.createElement("h3");
77+
heading.innerText = nodeId;
78+
nodeDiv.appendChild(heading);
79+
container.appendChild(nodeDiv);
80+
}
81+
log(`${event}`, className, nodeDiv);
82+
}
83+
84+
function fillFormFromUrlAndSubmit() {
85+
const $form = document.querySelector("form#connect");
86+
const url = new URL(document.location);
87+
$form.querySelector("[name=node-id]").value =
88+
url.searchParams.get("connect") || "";
89+
$form.querySelector("[name=payload]").value =
90+
url.searchParams.get("payload") || "";
91+
document.querySelector("form#connect").requestSubmit();
92+
}
93+
94+
function createConnectLink(nodeId, payload) {
95+
const ourUrl = new URL(document.location);
96+
ourUrl.searchParams.set("connect", nodeId);
97+
ourUrl.searchParams.set("payload", payload);
98+
return `<a href="${ourUrl}" target="_blank">click here</a>`;
99+
}

main/browser-echo/style.css

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
body {
2+
margin: 0;
3+
padding: 2rem;
4+
color: #eee;
5+
background: #222;
6+
font-family: ui-monospace, Menlo, "Cascadia Mono", "Segoe UI Mono",
7+
"Roboto Mono", "Source Code Pro", monospace;
8+
font-size: 16px;
9+
line-height: 1.4;
10+
}
11+
12+
* {
13+
box-sizing: border-box;
14+
}
15+
16+
header,
17+
main,
18+
.connections,
19+
form {
20+
max-width: 80rem;
21+
padding: 0.5rem;
22+
margin: 0.5rem;
23+
}
24+
25+
.error {
26+
color: #f9a;
27+
}
28+
29+
.cmd {
30+
padding-left: 8rem;
31+
color: #bda;
32+
font-size: 0.9rem;
33+
}
34+
.cmd .time {
35+
display: none;
36+
}
37+
38+
a {
39+
color: #eac;
40+
}
41+
42+
form {
43+
color: #888;
44+
background: #333;
45+
}
46+
form .fields {
47+
display: grid;
48+
grid-template-columns: 1fr 1fr;
49+
grid-gap: 1rem;
50+
margin: .5rem 0;
51+
}
52+
form h3 {
53+
grid-co
54+
}
55+
label {
56+
display: block;
57+
}
58+
59+
input,
60+
button {
61+
background: #33222f;
62+
padding: 0.5rem;
63+
color: #fff;
64+
border: 1px solid #735;
65+
font-family: inherit;
66+
font-size: 0.9rem;
67+
}
68+
69+
input:focus,
70+
button:focus,
71+
button:hover,
72+
button:active {
73+
outline: none;
74+
border-color: #957;
75+
background: #44333f;
76+
}
77+
78+
input {
79+
width: 100%;
80+
}
81+
82+
.time {
83+
font-size: 0.9rem;
84+
color: #888;
85+
}
86+
87+
.node {
88+
}
89+
90+
.node h3 {
91+
font-size: 1rem;
92+
font-weight: bold;
93+
margin: 0;
94+
padding: 0;
95+
color: #dac;
96+
}
97+
98+
.connections {
99+
margin: 0.5rem;
100+
padding: 0.5rem;
101+
background: #333;
102+
}
103+
104+
h2, h3 {
105+
color: #999;
106+
font-size: 1rem;
107+
font-weight: bold;
108+
margin: 0;
109+
padding: 0;
110+
}
111+
112+
h1 {
113+
font-size: 1.2rem;
114+
color: #eac;
115+
margin: 0 0 1rem 0;
116+
padding: 0;
117+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export function start(): void;
4+
/**
5+
* The `ReadableStreamType` enum.
6+
*
7+
* *This API requires the following crate features to be activated: `ReadableStreamType`*
8+
*/
9+
type ReadableStreamType = "bytes";
10+
export class EchoNode {
11+
private constructor();
12+
free(): void;
13+
static spawn(): Promise<EchoNode>;
14+
events(): ReadableStream;
15+
node_id(): string;
16+
connect(node_id: string, payload: string): ReadableStream;
17+
remote_info(): any[];
18+
}
19+
export class IntoUnderlyingByteSource {
20+
private constructor();
21+
free(): void;
22+
start(controller: ReadableByteStreamController): void;
23+
pull(controller: ReadableByteStreamController): Promise<any>;
24+
cancel(): void;
25+
readonly type: ReadableStreamType;
26+
readonly autoAllocateChunkSize: number;
27+
}
28+
export class IntoUnderlyingSink {
29+
private constructor();
30+
free(): void;
31+
write(chunk: any): Promise<any>;
32+
close(): Promise<any>;
33+
abort(reason: any): Promise<any>;
34+
}
35+
export class IntoUnderlyingSource {
36+
private constructor();
37+
free(): void;
38+
pull(controller: ReadableStreamDefaultController): Promise<any>;
39+
cancel(): void;
40+
}
41+
42+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
43+
44+
export interface InitOutput {
45+
readonly memory: WebAssembly.Memory;
46+
readonly start: () => void;
47+
readonly __wbg_echonode_free: (a: number, b: number) => void;
48+
readonly echonode_spawn: () => number;
49+
readonly echonode_events: (a: number) => number;
50+
readonly echonode_node_id: (a: number, b: number) => void;
51+
readonly echonode_connect: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
52+
readonly echonode_remote_info: (a: number, b: number) => void;
53+
readonly __wbg_intounderlyingbytesource_free: (a: number, b: number) => void;
54+
readonly intounderlyingbytesource_type: (a: number) => number;
55+
readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number;
56+
readonly intounderlyingbytesource_start: (a: number, b: number) => void;
57+
readonly intounderlyingbytesource_pull: (a: number, b: number) => number;
58+
readonly intounderlyingbytesource_cancel: (a: number) => void;
59+
readonly __wbg_intounderlyingsource_free: (a: number, b: number) => void;
60+
readonly intounderlyingsource_pull: (a: number, b: number) => number;
61+
readonly intounderlyingsource_cancel: (a: number) => void;
62+
readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void;
63+
readonly intounderlyingsink_write: (a: number, b: number) => number;
64+
readonly intounderlyingsink_close: (a: number) => number;
65+
readonly intounderlyingsink_abort: (a: number, b: number) => number;
66+
readonly ring_core_0_17_8_bn_mul_mont: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
67+
readonly __wbindgen_export_0: (a: number) => void;
68+
readonly __wbindgen_export_1: (a: number, b: number, c: number) => void;
69+
readonly __wbindgen_export_2: (a: number, b: number) => number;
70+
readonly __wbindgen_export_3: (a: number, b: number, c: number, d: number) => number;
71+
readonly __wbindgen_export_4: WebAssembly.Table;
72+
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
73+
readonly __wbindgen_export_5: (a: number, b: number, c: number) => void;
74+
readonly __wbindgen_export_6: (a: number, b: number) => void;
75+
readonly __wbindgen_export_7: (a: number, b: number, c: number) => void;
76+
readonly __wbindgen_export_8: (a: number, b: number, c: number, d: number) => void;
77+
readonly __wbindgen_start: () => void;
78+
}
79+
80+
export type SyncInitInput = BufferSource | WebAssembly.Module;
81+
/**
82+
* Instantiates the given `module`, which can either be bytes or
83+
* a precompiled `WebAssembly.Module`.
84+
*
85+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
86+
*
87+
* @returns {InitOutput}
88+
*/
89+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
90+
91+
/**
92+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
93+
* for everything else, calls `WebAssembly.instantiate` directly.
94+
*
95+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
96+
*
97+
* @returns {Promise<InitOutput>}
98+
*/
99+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

0 commit comments

Comments
 (0)