feat: remove lab.waku.org related examples (#295)
|
@ -14,13 +14,7 @@ jobs:
|
||||||
example:
|
example:
|
||||||
[
|
[
|
||||||
eth-pm,
|
eth-pm,
|
||||||
relay-angular-chat,
|
store-reactjs-chat
|
||||||
relay-reactjs-chat,
|
|
||||||
store-reactjs-chat,
|
|
||||||
web-chat,
|
|
||||||
noise-js,
|
|
||||||
noise-rtc,
|
|
||||||
relay-direct-rtc
|
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
38
README.md
|
@ -6,12 +6,6 @@ Here is the list of the examples using [`js-waku`](https://www.npmjs.com/package
|
||||||
|
|
||||||
See https://examples.waku.org/ for more examples.
|
See https://examples.waku.org/ for more examples.
|
||||||
|
|
||||||
### Web Chat App
|
|
||||||
|
|
||||||
- [code](examples/web-chat)
|
|
||||||
- [website](https://examples.waku.org/web-chat)
|
|
||||||
- Demonstrates: Group chat, React/TypeScript, Relay, Store.
|
|
||||||
|
|
||||||
### Ethereum Private Messaging
|
### Ethereum Private Messaging
|
||||||
|
|
||||||
End-to-end encrypted communication between two Ethereum addresses.
|
End-to-end encrypted communication between two Ethereum addresses.
|
||||||
|
@ -20,38 +14,6 @@ End-to-end encrypted communication between two Ethereum addresses.
|
||||||
- [website](https://examples.waku.org/eth-pm)
|
- [website](https://examples.waku.org/eth-pm)
|
||||||
- Demonstrates: Private Messaging, React/TypeScript, Light Client, Signature with Web3, Asymmetric Encryption.
|
- Demonstrates: Private Messaging, React/TypeScript, Light Client, Signature with Web3, Asymmetric Encryption.
|
||||||
|
|
||||||
### Waku Light Client in JavaScript
|
|
||||||
|
|
||||||
Send messages between several users (or just one) using light client targetted protocols.
|
|
||||||
|
|
||||||
- [code](examples/light-js)
|
|
||||||
- [website](https://examples.waku.org/light-js)
|
|
||||||
- Demonstrates: Waku Light node: Filter + Light Push, Pure Javascript/HTML using ESM/unpkg bundle.
|
|
||||||
|
|
||||||
### Minimal Angular (v13) Waku Relay
|
|
||||||
|
|
||||||
A barebone messaging app to illustrate the seamless integration of `js-waku` into AngularJS.
|
|
||||||
|
|
||||||
- [code](examples/relay-angular-chat)
|
|
||||||
- [website](https://examples.waku.org/relay-angular-chat)
|
|
||||||
- Demonstrates: Group messaging, Angular, Waku Relay, Protobuf using `protobufjs`, No async/await syntax.
|
|
||||||
|
|
||||||
### Waku Relay in JavaScript
|
|
||||||
|
|
||||||
This example uses Waku Relay to send and receive simple text messages.
|
|
||||||
|
|
||||||
- [code](examples/relay-js)
|
|
||||||
- [website](https://examples.waku.org/relay-js)
|
|
||||||
- Demonstrates: Waku Relay, Pure Javascript/HTML using ESM/unpkg bundle.
|
|
||||||
|
|
||||||
### Waku Relay in ReactJS
|
|
||||||
|
|
||||||
A barebone chat app to illustrate the seamless integration of `js-waku` into ReactJS.
|
|
||||||
|
|
||||||
- [code](examples/relay-reactjs-chat)
|
|
||||||
- [website](https://examples.waku.org/relay-reactjs-chat)
|
|
||||||
- Demonstrates: Group chat, React/JavaScript, Waku Relay, Protobuf using `protobufjs`.
|
|
||||||
|
|
||||||
### Using [RLN](https://rfc.vac.dev/spec/32/) in JavaScript
|
### Using [RLN](https://rfc.vac.dev/spec/32/) in JavaScript
|
||||||
|
|
||||||
> Rate limiting nullifier (RLN) is a construct based on zero-knowledge proofs
|
> Rate limiting nullifier (RLN) is a construct based on zero-knowledge proofs
|
||||||
|
|
|
@ -36,21 +36,13 @@ pipeline {
|
||||||
stage('Examples') {
|
stage('Examples') {
|
||||||
parallel {
|
parallel {
|
||||||
stage('eth-pm') { steps { script { buildExample() } } }
|
stage('eth-pm') { steps { script { buildExample() } } }
|
||||||
stage('relay-angular-chat') { steps { script { buildExample() } } }
|
|
||||||
stage('relay-reactjs-chat') { steps { script { buildExample() } } }
|
|
||||||
stage('store-reactjs-chat') { steps { script { buildExample() } } }
|
stage('store-reactjs-chat') { steps { script { buildExample() } } }
|
||||||
stage('web-chat') { steps { script { buildExample() } } }
|
|
||||||
stage('noise-js') { steps { script { buildExample() } } }
|
|
||||||
stage('noise-rtc') { steps { script { buildExample() } } }
|
|
||||||
stage('relay-direct-rtc') { steps { script { buildExample() } } }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('HTML Examples') {
|
stage('HTML Examples') {
|
||||||
parallel {
|
parallel {
|
||||||
stage('relay-js') { steps { script { copyExample() } } }
|
|
||||||
stage('store-js') { steps { script { copyExample() } } }
|
stage('store-js') { steps { script { copyExample() } } }
|
||||||
stage('light-js') { steps { script { copyExample() } } }
|
|
||||||
stage('light-chat') { steps { script { copyExample() } } }
|
stage('light-chat') { steps { script { copyExample() } } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Using Waku Light Push and Filter in JavaScript
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Waku Light node: Waku Filter + Waku Light Push
|
|
||||||
- Pure Javascript/HTML.
|
|
||||||
- Use minified bundle of js from unpkg.com, no import, no package manager.
|
|
||||||
|
|
||||||
This example uses Waku Filter to listen to messages and Waku Light Push to send messages.
|
|
||||||
|
|
||||||
To test the example, simply download the `index.html` file from this folder and open it in a browser.
|
|
||||||
|
|
||||||
The `master` branch's HEAD is deployed at https://examples.waku.org/light-js/.
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,280 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
|
||||||
<title>JS-Waku light node example</title>
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<h2>Status</h2>
|
|
||||||
</div>
|
|
||||||
<div id="status"></div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Local Peer Id</h2>
|
|
||||||
</div>
|
|
||||||
<div id="peer-id"></div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Select Peer</h2>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="getPeersButton" type="button">Refresh Peers</button>
|
|
||||||
<select id="peer-select">
|
|
||||||
<option value=""></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="remote-multiaddr">Remote peer's multiaddr</label>
|
|
||||||
</div>
|
|
||||||
<div style="margin-right: 1em">
|
|
||||||
<input
|
|
||||||
id="remote-multiaddr"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
style="width: 100%; max-width: 900px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button disabled id="dial" type="button">Dial</button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Remote Peers</h2>
|
|
||||||
</div>
|
|
||||||
<div id="remote-peer-id"></div>
|
|
||||||
<br />
|
|
||||||
<button disabled id="subscribe" type="button">Subscribe with Filter</button>
|
|
||||||
<button disabled id="unsubscribe" type="button">
|
|
||||||
Unsubscribe with Filter
|
|
||||||
</button>
|
|
||||||
<br />
|
|
||||||
<label for="textInput">Message text</label>
|
|
||||||
<input id="textInput" placeholder="Type your message here" type="text" />
|
|
||||||
<button disabled id="sendButton" type="button">
|
|
||||||
Send message using Light Push
|
|
||||||
</button>
|
|
||||||
<br />
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Store</h2>
|
|
||||||
</div>
|
|
||||||
<button disabled id="queryStoreButton" type="button">
|
|
||||||
Retrieve past messages
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/@multiformats/multiaddr@12.1.1/dist/index.min.js"></script>
|
|
||||||
<script type="module">
|
|
||||||
import {
|
|
||||||
createLightNode,
|
|
||||||
waitForRemotePeer,
|
|
||||||
createEncoder,
|
|
||||||
createDecoder,
|
|
||||||
utf8ToBytes,
|
|
||||||
bytesToUtf8,
|
|
||||||
} from "https://unpkg.com/@waku/sdk@0.0.20/bundle/index.js";
|
|
||||||
import {
|
|
||||||
enrTree,
|
|
||||||
DnsNodeDiscovery,
|
|
||||||
} from "https://unpkg.com/@waku/dns-discovery@0.0.16/bundle/index.js";
|
|
||||||
import { messageHash } from "https://unpkg.com/@waku/message-hash@0.1.8/bundle/index.js";
|
|
||||||
|
|
||||||
const peerIdDiv = document.getElementById("peer-id");
|
|
||||||
const remotePeerIdDiv = document.getElementById("remote-peer-id");
|
|
||||||
const statusDiv = document.getElementById("status");
|
|
||||||
const remoteMultiAddrDiv = document.getElementById("remote-multiaddr");
|
|
||||||
const dialButton = document.getElementById("dial");
|
|
||||||
const subscribeButton = document.getElementById("subscribe");
|
|
||||||
const unsubscribeButton = document.getElementById("unsubscribe");
|
|
||||||
const queryStoreButton = document.getElementById("queryStoreButton");
|
|
||||||
const messagesDiv = document.getElementById("messages");
|
|
||||||
const textInput = document.getElementById("textInput");
|
|
||||||
const sendButton = document.getElementById("sendButton");
|
|
||||||
const getPeersButton = document.getElementById("getPeersButton");
|
|
||||||
const peersSelector = document.getElementById("peer-select");
|
|
||||||
|
|
||||||
const ContentTopic = "/js-waku-examples/1/chat/utf8";
|
|
||||||
const decoder = createDecoder(ContentTopic);
|
|
||||||
const encoder = createEncoder({ contentTopic: ContentTopic });
|
|
||||||
// Each key is a unique identifier for the message. Each value is an obj { text, timestamp }
|
|
||||||
let messages = {};
|
|
||||||
let unsubscribe;
|
|
||||||
|
|
||||||
const updateMessages = (msgs, div) => {
|
|
||||||
div.innerHTML = "<ul>";
|
|
||||||
Object.values(msgs)
|
|
||||||
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
|
|
||||||
.forEach(
|
|
||||||
(msg) =>
|
|
||||||
(div.innerHTML +=
|
|
||||||
"<li>" + `${msg.text} - ${msg.timestamp}` + "</li>")
|
|
||||||
);
|
|
||||||
div.innerHTML += "</ul>";
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await getPeers();
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Failed to find a peer", e);
|
|
||||||
remoteMultiAddrDiv.value =
|
|
||||||
"/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm";
|
|
||||||
}
|
|
||||||
|
|
||||||
statusDiv.innerHTML = "<p>Creating Waku node.</p>";
|
|
||||||
const node = await createLightNode();
|
|
||||||
|
|
||||||
statusDiv.innerHTML = "<p>Starting Waku node.</p>";
|
|
||||||
await node.start();
|
|
||||||
|
|
||||||
window.waku = node;
|
|
||||||
console.info(
|
|
||||||
"Use window.waku to access the waku node running in the browser directly through the console."
|
|
||||||
);
|
|
||||||
|
|
||||||
// Queries all peers from libp2p peer store and updates list of connected peers
|
|
||||||
const updatePeersList = async () => {
|
|
||||||
// Generate <p> element with connection string from each peer
|
|
||||||
const peers = await node.libp2p.peerStore.all();
|
|
||||||
const peerIdElements = peers.map((peer) => {
|
|
||||||
const element = document.createElement("p");
|
|
||||||
element.textContent = `${peer.addresses[1].multiaddr}/p2p/${peer.id}`;
|
|
||||||
return element;
|
|
||||||
});
|
|
||||||
// Update elements displaying list of peers
|
|
||||||
remotePeerIdDiv.replaceChildren(...peerIdElements);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Refreshes list of connected peers each time a new one is detected
|
|
||||||
node.store.addLibp2pEventListener("peer:connect", async (event) => {
|
|
||||||
const peerId = event.detail;
|
|
||||||
console.log(`Peer connected with peer id: ${peerId}`);
|
|
||||||
// Wait half a second after receiving event for peer to show up in peer store
|
|
||||||
setTimeout(async () => {
|
|
||||||
updatePeersList();
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// Update status
|
|
||||||
statusDiv.innerHTML = `<p>Peer dialed: ${peerId}</p>`;
|
|
||||||
// Enable send and subscribe inputs as we are now connected to a peer
|
|
||||||
textInput.disabled = false;
|
|
||||||
sendButton.disabled = false;
|
|
||||||
subscribeButton.disabled = false;
|
|
||||||
queryStoreButton.disabled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
statusDiv.innerHTML = "<p>Waku node started.</p>";
|
|
||||||
peerIdDiv.innerHTML = "<p>" + node.libp2p.peerId.toString() + "</p>";
|
|
||||||
dialButton.disabled = false;
|
|
||||||
|
|
||||||
dialButton.onclick = async () => {
|
|
||||||
const ma = remoteMultiAddrDiv.value;
|
|
||||||
if (!ma) {
|
|
||||||
statusDiv.innerHTML = "<p>Error: No multiaddr provided.</p>";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
statusDiv.innerHTML = "<p>Dialing peer.</p>";
|
|
||||||
let multiaddr;
|
|
||||||
try {
|
|
||||||
multiaddr = MultiformatsMultiaddr.multiaddr(ma);
|
|
||||||
} catch (err) {
|
|
||||||
statusDiv.innerHTML = "<p>Error: invalid multiaddr provided</p>";
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
await node.dial(multiaddr, ["filter", "lightpush", "store"]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageReceivedCallback = (wakuMessage) => {
|
|
||||||
// create a unique key for the message
|
|
||||||
const msgHash =
|
|
||||||
bytesToUtf8(messageHash(ContentTopic, wakuMessage)) +
|
|
||||||
wakuMessage.proto.timestamp;
|
|
||||||
const text = bytesToUtf8(wakuMessage.payload);
|
|
||||||
// store message by its key
|
|
||||||
messages[msgHash + wakuMessage.proto.timestamp] = {
|
|
||||||
text,
|
|
||||||
timestamp: wakuMessage.timestamp,
|
|
||||||
};
|
|
||||||
// call function to refresh display of messages
|
|
||||||
updateMessages(messages, messagesDiv);
|
|
||||||
};
|
|
||||||
|
|
||||||
subscribeButton.onclick = async () => {
|
|
||||||
unsubscribe = await node.filter.subscribe(
|
|
||||||
[decoder],
|
|
||||||
messageReceivedCallback
|
|
||||||
);
|
|
||||||
unsubscribeButton.disabled = false;
|
|
||||||
subscribeButton.disabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
queryStoreButton.onclick = async () => {
|
|
||||||
await node.store.queryWithOrderedCallback(
|
|
||||||
[decoder],
|
|
||||||
messageReceivedCallback
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
unsubscribeButton.onclick = async () => {
|
|
||||||
await unsubscribe();
|
|
||||||
unsubscribe = undefined;
|
|
||||||
unsubscribeButton.disabled = true;
|
|
||||||
subscribeButton.disabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
sendButton.onclick = async () => {
|
|
||||||
const text = textInput.value;
|
|
||||||
|
|
||||||
await node.lightPush.send(encoder, {
|
|
||||||
payload: utf8ToBytes(text),
|
|
||||||
});
|
|
||||||
console.log("Message sent!");
|
|
||||||
textInput.value = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
getPeersButton.onclick = async () => {
|
|
||||||
await getPeers(statusDiv, remoteMultiAddrDiv);
|
|
||||||
};
|
|
||||||
|
|
||||||
peersSelector.addEventListener("change", function (event) {
|
|
||||||
remoteMultiAddrDiv.value = event.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getPeers() {
|
|
||||||
// Display status
|
|
||||||
statusDiv.innerHTML = "<p>Discovering peers</p>";
|
|
||||||
|
|
||||||
// Clear all options in select element
|
|
||||||
peersSelector.innerHTML = "";
|
|
||||||
|
|
||||||
// Get peers using DNS discovery
|
|
||||||
const defaultNodeCount = 5;
|
|
||||||
const dnsDiscovery = await DnsNodeDiscovery.dnsOverHttp();
|
|
||||||
const peers = await dnsDiscovery.getPeers([enrTree["TEST"]], {
|
|
||||||
relay: defaultNodeCount,
|
|
||||||
store: defaultNodeCount,
|
|
||||||
filter: defaultNodeCount,
|
|
||||||
lightPush: defaultNodeCount,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create an option element for each peer's multiaddr and append to select element
|
|
||||||
const optionElements = peers.map((peer) => {
|
|
||||||
const optionElement = document.createElement("option");
|
|
||||||
optionElement.value = `${peer.multiaddrs[1]}/p2p/${peer.peerId}`;
|
|
||||||
optionElement.text = `${peer.multiaddrs[1]}/p2p/${peer.peerId}`;
|
|
||||||
return optionElement;
|
|
||||||
});
|
|
||||||
peersSelector.append(...optionElements);
|
|
||||||
|
|
||||||
// Set first peer as selected
|
|
||||||
peersSelector.options[0].selected = true;
|
|
||||||
remoteMultiAddrDiv.value = peersSelector.options[0].value;
|
|
||||||
|
|
||||||
statusDiv.innerHTML = "<p>Peers discovered</p>";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Light JS",
|
|
||||||
"description": "Send messages between several users (or just one) using light client targeted protocols.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
# Waku Noise Pairing Example App
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
Browse to http://localhost:8080
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,200 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
|
||||||
<title>Waku Noise</title>
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
word-wrap: break-word;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 800px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3:last-of-type {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 span,
|
|
||||||
h3 span {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
color: #9ea13b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.progress {
|
|
||||||
color: white;
|
|
||||||
background-color: #9ea13b;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.success {
|
|
||||||
color: white;
|
|
||||||
background-color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.error {
|
|
||||||
color: white;
|
|
||||||
background-color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo input {
|
|
||||||
display: block;
|
|
||||||
min-width: 250px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo button {
|
|
||||||
flex-grow: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo button + button {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea {
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea ul {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea ul li + li {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea div > * {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="status">
|
|
||||||
<h3>
|
|
||||||
<b>Waku Node Status:</b> <span id="waku-status">connecting...</span>
|
|
||||||
</h3>
|
|
||||||
<h3 id="handshake-span">
|
|
||||||
<b>Handshake Status:</b>
|
|
||||||
<span id="handshake-status" class="progress">waiting for waku</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pairingInfo" id="qr-url-container" style="display: none">
|
|
||||||
<h2>Pairing information</h2>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="qr-url"
|
|
||||||
readonly
|
|
||||||
placeholder="generating URL..."
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<button id="copy-url" style="width: 100px">Copy URL</button>
|
|
||||||
<button id="open-tab" style="width: 150px">Open in new tab</button>
|
|
||||||
</div>
|
|
||||||
<canvas id="qr-canvas"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chatArea" id="chat-area" style="display: none">
|
|
||||||
<h2>Chat</h2>
|
|
||||||
<ul id="messages"></ul>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<input id="nick-input" placeholder="Choose a nickname" type="text" />
|
|
||||||
<textarea
|
|
||||||
id="text-input"
|
|
||||||
placeholder="Type your message here"
|
|
||||||
type="text"
|
|
||||||
></textarea>
|
|
||||||
<button id="send-btn" type="button" disabled>Send message</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
NOTICE: you can try also try to pair your browser with a
|
|
||||||
<a href="https://github.com/waku-org/go-waku/tree/master/examples/noise"
|
|
||||||
>go-waku based node</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,274 +0,0 @@
|
||||||
import { createLightNode, waitForRemotePeer } from "@waku/sdk";
|
|
||||||
import * as utils from "@waku/utils/bytes";
|
|
||||||
import * as noise from "@waku/noise";
|
|
||||||
import protobuf from "protobufjs";
|
|
||||||
import QRCode from "qrcode";
|
|
||||||
|
|
||||||
// Protobuf
|
|
||||||
const ProtoChatMessage = new protobuf.Type("ChatMessage")
|
|
||||||
.add(new protobuf.Field("timestamp", 1, "uint64"))
|
|
||||||
.add(new protobuf.Field("nick", 2, "string"))
|
|
||||||
.add(new protobuf.Field("text", 3, "bytes"));
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const ui = initUI();
|
|
||||||
ui.waku.connecting();
|
|
||||||
|
|
||||||
// Starting the node
|
|
||||||
const node = await createLightNode({
|
|
||||||
defaultBootstrap: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await node.start();
|
|
||||||
await waitForRemotePeer(node, ["filter", "lightpush"]);
|
|
||||||
|
|
||||||
ui.waku.connected();
|
|
||||||
|
|
||||||
const myStaticKey = noise.generateX25519KeyPair();
|
|
||||||
const urlPairingInfo = getPairingInfoFromURL();
|
|
||||||
|
|
||||||
const pairingObj = new noise.WakuPairing(
|
|
||||||
node.lightPush,
|
|
||||||
node.filter,
|
|
||||||
myStaticKey,
|
|
||||||
urlPairingInfo || new noise.ResponderParameters()
|
|
||||||
);
|
|
||||||
const pExecute = pairingObj.execute(120000); // timeout after 2m
|
|
||||||
|
|
||||||
scheduleHandshakeAuthConfirmation(pairingObj, ui);
|
|
||||||
|
|
||||||
let encoder;
|
|
||||||
let decoder;
|
|
||||||
|
|
||||||
try {
|
|
||||||
ui.handshake.waiting();
|
|
||||||
|
|
||||||
if (!urlPairingInfo) {
|
|
||||||
const pairingURL = buildPairingURLFromObj(pairingObj);
|
|
||||||
ui.shareInfo.setURL(pairingURL);
|
|
||||||
ui.shareInfo.renderQR(pairingURL);
|
|
||||||
ui.shareInfo.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
[encoder, decoder] = await pExecute;
|
|
||||||
|
|
||||||
ui.handshake.connected();
|
|
||||||
ui.shareInfo.hide();
|
|
||||||
} catch (err) {
|
|
||||||
ui.handshake.error(err.message);
|
|
||||||
ui.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.message.display();
|
|
||||||
|
|
||||||
await node.filter.subscribe(
|
|
||||||
[decoder],
|
|
||||||
ui.message.onReceive.bind(ui.message)
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.message.onSend(async (text, nick) => {
|
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
|
||||||
const message = ProtoChatMessage.create({
|
|
||||||
nick,
|
|
||||||
timestamp,
|
|
||||||
text: utils.utf8ToBytes(text),
|
|
||||||
});
|
|
||||||
const payload = ProtoChatMessage.encode(message).finish();
|
|
||||||
|
|
||||||
await node.lightPush.send(encoder, { payload, timestamp });
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
ui.waku.error(err.message);
|
|
||||||
ui.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPairingURLFromObj(pairingObj) {
|
|
||||||
const pInfo = pairingObj.getPairingInfo();
|
|
||||||
|
|
||||||
// Data to encode in the QR code. The qrMessageNametag too to the QR string (separated by )
|
|
||||||
const messageNameTagParam = `messageNameTag=${utils.bytesToHex(
|
|
||||||
pInfo.qrMessageNameTag
|
|
||||||
)}`;
|
|
||||||
const qrCodeParam = `qrCode=${encodeURIComponent(pInfo.qrCode)}`;
|
|
||||||
|
|
||||||
return `${window.location.href}?${messageNameTagParam}&${qrCodeParam}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPairingInfoFromURL() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
const messageNameTag = urlParams.get("messageNameTag");
|
|
||||||
const qrCodeString = urlParams.get("qrCode");
|
|
||||||
|
|
||||||
if (!(messageNameTag && qrCodeString)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new noise.InitiatorParameters(
|
|
||||||
decodeURIComponent(qrCodeString),
|
|
||||||
utils.hexToBytes(messageNameTag)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function scheduleHandshakeAuthConfirmation(pairingObj, ui) {
|
|
||||||
const authCode = await pairingObj.getAuthCode();
|
|
||||||
ui.handshake.connecting();
|
|
||||||
pairingObj.validateAuthCode(confirm("Confirm that authcode is: " + authCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
function initUI() {
|
|
||||||
const messagesList = document.getElementById("messages");
|
|
||||||
const nicknameInput = document.getElementById("nick-input");
|
|
||||||
const textInput = document.getElementById("text-input");
|
|
||||||
const sendButton = document.getElementById("send-btn");
|
|
||||||
const chatArea = document.getElementById("chat-area");
|
|
||||||
const wakuStatusSpan = document.getElementById("waku-status");
|
|
||||||
const handshakeStatusSpan = document.getElementById("handshake-status");
|
|
||||||
|
|
||||||
const qrCanvas = document.getElementById("qr-canvas");
|
|
||||||
const qrUrlContainer = document.getElementById("qr-url-container");
|
|
||||||
const qrUrl = document.getElementById("qr-url");
|
|
||||||
const copyURLButton = document.getElementById("copy-url");
|
|
||||||
const openTabButton = document.getElementById("open-tab");
|
|
||||||
|
|
||||||
copyURLButton.onclick = () => {
|
|
||||||
const copyText = document.getElementById("qr-url"); // need to get it each time otherwise copying does not work
|
|
||||||
copyText.select();
|
|
||||||
copyText.setSelectionRange(0, 99999);
|
|
||||||
navigator.clipboard.writeText(copyText.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
openTabButton.onclick = () => {
|
|
||||||
window.open(qrUrl.value, "_blank");
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableChatUIStateIfNeeded = () => {
|
|
||||||
const readyToSend = nicknameInput.value !== "";
|
|
||||||
textInput.disabled = !readyToSend;
|
|
||||||
sendButton.disabled = !readyToSend;
|
|
||||||
};
|
|
||||||
nicknameInput.onchange = disableChatUIStateIfNeeded;
|
|
||||||
nicknameInput.onblur = disableChatUIStateIfNeeded;
|
|
||||||
|
|
||||||
return {
|
|
||||||
shareInfo: {
|
|
||||||
setURL(url) {
|
|
||||||
qrUrl.value = url;
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
qrUrlContainer.style.display = "none";
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
qrUrlContainer.style.display = "flex";
|
|
||||||
},
|
|
||||||
renderQR(url) {
|
|
||||||
QRCode.toCanvas(qrCanvas, url, (err) => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
waku: {
|
|
||||||
_val(msg) {
|
|
||||||
wakuStatusSpan.innerText = msg;
|
|
||||||
},
|
|
||||||
_class(name) {
|
|
||||||
wakuStatusSpan.className = name;
|
|
||||||
},
|
|
||||||
connecting() {
|
|
||||||
this._val("connecting...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connected() {
|
|
||||||
this._val("connected");
|
|
||||||
this._class("success");
|
|
||||||
},
|
|
||||||
error(msg) {
|
|
||||||
this._val(msg);
|
|
||||||
this._class("error");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handshake: {
|
|
||||||
_val(val) {
|
|
||||||
handshakeStatusSpan.innerText = val;
|
|
||||||
},
|
|
||||||
_class(name) {
|
|
||||||
handshakeStatusSpan.className = name;
|
|
||||||
},
|
|
||||||
error(msg) {
|
|
||||||
this._val(msg);
|
|
||||||
this._class("error");
|
|
||||||
},
|
|
||||||
waiting() {
|
|
||||||
this._val("waiting for handshake...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
generating() {
|
|
||||||
this._val("generating QR code...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connecting() {
|
|
||||||
this._val("executing handshake...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connected() {
|
|
||||||
this._val("handshake completed!");
|
|
||||||
this._class("success");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
_render({ time, text, nick }) {
|
|
||||||
messagesList.innerHTML += `
|
|
||||||
<li>
|
|
||||||
(${nick})
|
|
||||||
<strong>${text}</strong>
|
|
||||||
<i>[${new Date(time).toISOString()}]</i>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
_status(text, className) {
|
|
||||||
sendButton.className = className;
|
|
||||||
},
|
|
||||||
onReceive({ payload }) {
|
|
||||||
const { timestamp, nick, text } = ProtoChatMessage.decode(payload);
|
|
||||||
|
|
||||||
this._render({
|
|
||||||
nick,
|
|
||||||
time: timestamp * 1000,
|
|
||||||
text: utils.bytesToUtf8(text),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSend(cb) {
|
|
||||||
sendButton.addEventListener("click", async () => {
|
|
||||||
try {
|
|
||||||
this._status("sending...", "progress");
|
|
||||||
await cb(textInput.value, nicknameInput.value);
|
|
||||||
this._status("sent", "success");
|
|
||||||
|
|
||||||
this._render({
|
|
||||||
time: Date.now(), // a bit different from what receiver will see but for the matter of example is good enough
|
|
||||||
text: textInput.value,
|
|
||||||
nick: nicknameInput.value,
|
|
||||||
});
|
|
||||||
textInput.value = "";
|
|
||||||
} catch (e) {
|
|
||||||
this._status(`error: ${e.message}`, "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
display() {
|
|
||||||
chatArea.style.display = "block";
|
|
||||||
this._status("waiting for input", "progress");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.shareInfo.hide();
|
|
||||||
chatArea.style.display = "none";
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Waku Noise",
|
|
||||||
"description": "Example showing Waku noise capabilities.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@waku/noise-example",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"start": "webpack-dev-server"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@waku/sdk": "0.0.18",
|
|
||||||
"@waku/noise": "0.0.3-31510da",
|
|
||||||
"@waku/utils": "0.0.10",
|
|
||||||
"protobufjs": "^7.1.2",
|
|
||||||
"qrcode": "^1.5.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"webpack": "^5.74.0",
|
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-dev-server": "^4.11.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: "./index.js",
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, "build"),
|
|
||||||
filename: "index.js",
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true,
|
|
||||||
},
|
|
||||||
mode: "development",
|
|
||||||
plugins: [
|
|
||||||
new CopyWebpackPlugin({
|
|
||||||
patterns: ["index.html", "favicon.ico", "favicon.png", "manifest.json"],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
State of this example: **work in progress**
|
|
||||||
|
|
||||||
What's done:
|
|
||||||
- By using `js-noise` users can establish secure communication channel.
|
|
||||||
- This channel is used to exchange `offer/answer` to initiate direct WebRTC connection.
|
|
||||||
|
|
||||||
What should be done:
|
|
||||||
- `STUN` server: in order not to loose benefits of peer-to-peer protocols used underneath we should find a way to be able to retrieve coordinates of a user to build `offer/answer` by not involving one `STUN` server for it;
|
|
||||||
- `TURN` server: similarly to prev point we should be able to cover a need to create WebRTC connection for users who are behind secure `NAT` and are not able to communicated just as it is.
|
|
||||||
|
|
||||||
Additional reading that explains why `STUN/TURN` is not easily removable from the equation: https://github.com/libp2p/specs/pull/497/files#diff-2cb0b0dcc282bd123b21f5a0610e8a01b516fc453b95c655cf7e16734f2f7b11R48-R53
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,198 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
|
||||||
<title>Waku NoiseRTC</title>
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
word-wrap: break-word;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 800px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3:last-of-type {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 span,
|
|
||||||
h3 span {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
color: #9ea13b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.progress {
|
|
||||||
color: white;
|
|
||||||
background-color: #9ea13b;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.success {
|
|
||||||
color: white;
|
|
||||||
background-color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.error {
|
|
||||||
color: white;
|
|
||||||
background-color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo input {
|
|
||||||
display: block;
|
|
||||||
min-width: 250px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo button {
|
|
||||||
flex-grow: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pairingInfo button + button {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea {
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea ul {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea ul li + li {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatArea div > * {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="status">
|
|
||||||
<h3>
|
|
||||||
<b>Waku Node Status:</b>
|
|
||||||
<span id="waku-status" class="progress">connecting...</span>
|
|
||||||
</h3>
|
|
||||||
<h3 id="handshake-span">
|
|
||||||
<b>Handshake Status:</b>
|
|
||||||
<span id="handshake-status" class="progress">waiting for waku</span>
|
|
||||||
</h3>
|
|
||||||
<h3>
|
|
||||||
<b>RTC Status:</b>
|
|
||||||
<span id="rtc-status" class="progress"
|
|
||||||
>waiting for noise to be established</span
|
|
||||||
>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pairingInfo" id="qr-url-container" style="display: none">
|
|
||||||
<h2>Pairing information</h2>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="qr-url"
|
|
||||||
readonly
|
|
||||||
placeholder="generating URL..."
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<button id="copy-url" style="width: 100px">Copy URL</button>
|
|
||||||
<button id="open-tab" style="width: 100px">Open in new</button>
|
|
||||||
</div>
|
|
||||||
<canvas id="qr-canvas"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chatArea" id="chat-area" style="display: none">
|
|
||||||
<h2>Chat</h2>
|
|
||||||
<ul id="messages"></ul>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<input id="nick-input" placeholder="Choose a nickname" type="text" />
|
|
||||||
<textarea
|
|
||||||
id="text-input"
|
|
||||||
placeholder="Type your message here"
|
|
||||||
type="text"
|
|
||||||
></textarea>
|
|
||||||
<button id="send-btn" type="button" disabled>Send message</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,464 +0,0 @@
|
||||||
import { createLightNode, waitForRemotePeer } from "@waku/sdk";
|
|
||||||
import * as utils from "@waku/utils/bytes";
|
|
||||||
import * as noise from "@waku/noise";
|
|
||||||
import protobuf from "protobufjs";
|
|
||||||
import QRCode from "qrcode";
|
|
||||||
|
|
||||||
// Protobuf
|
|
||||||
const ProtoMessage = new protobuf.Type("Message").add(
|
|
||||||
new protobuf.Field("data", 3, "string")
|
|
||||||
);
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const ui = initUI();
|
|
||||||
ui.waku.connecting();
|
|
||||||
|
|
||||||
// Starting the node
|
|
||||||
const node = await createLightNode({ defaultBootstrap: true });
|
|
||||||
|
|
||||||
try {
|
|
||||||
await node.start();
|
|
||||||
await waitForRemotePeer(node, ["filter", "lightpush"]);
|
|
||||||
|
|
||||||
ui.waku.connected();
|
|
||||||
|
|
||||||
const myStaticKey = noise.generateX25519KeyPair();
|
|
||||||
const urlPairingInfo = getPairingInfoFromURL();
|
|
||||||
|
|
||||||
const pairingObj = new noise.WakuPairing(
|
|
||||||
node.lightPush,
|
|
||||||
node.filter,
|
|
||||||
myStaticKey,
|
|
||||||
urlPairingInfo || new noise.ResponderParameters()
|
|
||||||
);
|
|
||||||
const pExecute = pairingObj.execute(120000); // timeout after 2m
|
|
||||||
|
|
||||||
scheduleHandshakeAuthConfirmation(pairingObj, ui);
|
|
||||||
|
|
||||||
let sendWakuMessage;
|
|
||||||
let listenToWakuMessages;
|
|
||||||
|
|
||||||
try {
|
|
||||||
ui.handshake.waiting();
|
|
||||||
|
|
||||||
if (!urlPairingInfo) {
|
|
||||||
const pairingURL = buildPairingURLFromObj(pairingObj);
|
|
||||||
ui.shareInfo.setURL(pairingURL);
|
|
||||||
ui.shareInfo.renderQR(pairingURL);
|
|
||||||
ui.shareInfo.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
[sendWakuMessage, listenToWakuMessages] = await buildWakuMessage(
|
|
||||||
node,
|
|
||||||
pExecute
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.handshake.connected();
|
|
||||||
ui.shareInfo.hide();
|
|
||||||
} catch (err) {
|
|
||||||
ui.handshake.error(err.message);
|
|
||||||
ui.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.message.display();
|
|
||||||
|
|
||||||
const { peerConnection, sendMessage: sendRTCMessage } = initRTC({
|
|
||||||
ui,
|
|
||||||
onReceive: ui.message.onReceive.bind(ui.message),
|
|
||||||
});
|
|
||||||
|
|
||||||
peerConnection.onicecandidate = async (event) => {
|
|
||||||
if (event.candidate) {
|
|
||||||
console.log("candidate sent");
|
|
||||||
try {
|
|
||||||
ui.rtc.sendingCandidate();
|
|
||||||
await sendWakuMessage({
|
|
||||||
type: "candidate",
|
|
||||||
candidate: event.candidate,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
ui.rtc.error(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendOffer = async () => {
|
|
||||||
console.log("offer sent");
|
|
||||||
ui.rtc.sendingOffer();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const offer = await peerConnection.createOffer();
|
|
||||||
await peerConnection.setLocalDescription(offer);
|
|
||||||
|
|
||||||
await sendWakuMessage({
|
|
||||||
type: "offer",
|
|
||||||
offer,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
ui.rtc.error(error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendAnswer = async (data) => {
|
|
||||||
console.log("answer sent");
|
|
||||||
ui.rtc.sendingAnswer();
|
|
||||||
try {
|
|
||||||
await peerConnection.setRemoteDescription(
|
|
||||||
new RTCSessionDescription(data.offer)
|
|
||||||
);
|
|
||||||
|
|
||||||
const answer = await peerConnection.createAnswer();
|
|
||||||
peerConnection.setLocalDescription(answer);
|
|
||||||
|
|
||||||
await sendWakuMessage({
|
|
||||||
type: "answer",
|
|
||||||
answer,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
ui.rtc.error(error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const receiveAnswer = async (data) => {
|
|
||||||
try {
|
|
||||||
console.log("answer received");
|
|
||||||
await peerConnection.setRemoteDescription(
|
|
||||||
new RTCSessionDescription(data.answer)
|
|
||||||
);
|
|
||||||
console.log("answer saved");
|
|
||||||
|
|
||||||
await sendWakuMessage({
|
|
||||||
type: "ready",
|
|
||||||
text: "received answer",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
ui.rtc.error(error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const receiveCandidate = async (data) => {
|
|
||||||
try {
|
|
||||||
console.log("candidate saved");
|
|
||||||
await peerConnection.addIceCandidate(
|
|
||||||
new RTCIceCandidate(data.candidate)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
ui.rtc.error(error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleWakuMessages = async (data) => {
|
|
||||||
if (data.type === "offer") {
|
|
||||||
await sendAnswer(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === "answer") {
|
|
||||||
await receiveAnswer(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === "ready") {
|
|
||||||
console.log("RTC: partner is", data.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === "candidate") {
|
|
||||||
await receiveCandidate(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await listenToWakuMessages(handleWakuMessages);
|
|
||||||
ui.message.onSend(sendRTCMessage);
|
|
||||||
|
|
||||||
// if we are initiator of Noise handshake
|
|
||||||
// let's initiate Web RTC as well
|
|
||||||
if (!urlPairingInfo) {
|
|
||||||
await sendOffer();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ui.waku.error(err.message);
|
|
||||||
ui.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPairingURLFromObj(pairingObj) {
|
|
||||||
const pInfo = pairingObj.getPairingInfo();
|
|
||||||
|
|
||||||
// Data to encode in the QR code. The qrMessageNametag too to the QR string (separated by )
|
|
||||||
const messageNameTagParam = `messageNameTag=${utils.bytesToHex(
|
|
||||||
pInfo.qrMessageNameTag
|
|
||||||
)}`;
|
|
||||||
const qrCodeParam = `qrCode=${encodeURIComponent(pInfo.qrCode)}`;
|
|
||||||
|
|
||||||
return `${window.location.href}?${messageNameTagParam}&${qrCodeParam}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPairingInfoFromURL() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
const messageNameTag = urlParams.get("messageNameTag");
|
|
||||||
const qrCodeString = urlParams.get("qrCode");
|
|
||||||
|
|
||||||
if (!(messageNameTag && qrCodeString)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new noise.InitiatorParameters(
|
|
||||||
decodeURIComponent(qrCodeString),
|
|
||||||
utils.hexToBytes(messageNameTag)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function scheduleHandshakeAuthConfirmation(pairingObj, ui) {
|
|
||||||
const authCode = await pairingObj.getAuthCode();
|
|
||||||
ui.handshake.connecting();
|
|
||||||
pairingObj.validateAuthCode(confirm("Confirm that authcode is: " + authCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildWakuMessage(node, noiseExecute) {
|
|
||||||
const [encoder, decoder] = await noiseExecute;
|
|
||||||
|
|
||||||
const sendMessage = async (message) => {
|
|
||||||
let payload = ProtoMessage.create({
|
|
||||||
data: JSON.stringify(message),
|
|
||||||
});
|
|
||||||
payload = ProtoMessage.encode(payload).finish();
|
|
||||||
|
|
||||||
return node.lightPush.send(encoder, { payload });
|
|
||||||
};
|
|
||||||
|
|
||||||
const listenToMessages = async (fn) => {
|
|
||||||
return node.filter.subscribe([decoder], ({ payload }) => {
|
|
||||||
const { data } = ProtoMessage.decode(payload);
|
|
||||||
fn(JSON.parse(data));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return [sendMessage, listenToMessages];
|
|
||||||
}
|
|
||||||
|
|
||||||
function initRTC({ ui, onReceive }) {
|
|
||||||
const configuration = {
|
|
||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
||||||
};
|
|
||||||
const peerConnection = new RTCPeerConnection(configuration);
|
|
||||||
const sendChannel = peerConnection.createDataChannel("chat");
|
|
||||||
|
|
||||||
let receiveChannel;
|
|
||||||
|
|
||||||
sendChannel.onopen = (event) => {
|
|
||||||
ui.rtc.ready();
|
|
||||||
console.log("onopen send", event);
|
|
||||||
};
|
|
||||||
|
|
||||||
peerConnection.ondatachannel = (event) => {
|
|
||||||
receiveChannel = event.channel;
|
|
||||||
|
|
||||||
receiveChannel.onmessage = (event) => {
|
|
||||||
onReceive(JSON.parse(event.data));
|
|
||||||
};
|
|
||||||
|
|
||||||
receiveChannel.onopen = (event) => {
|
|
||||||
ui.rtc.ready();
|
|
||||||
console.log("onopen receive", event);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendMessage = (text, nick) => {
|
|
||||||
sendChannel.send(JSON.stringify({ text, nick, timestamp: Date.now() }));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
peerConnection,
|
|
||||||
sendChannel,
|
|
||||||
receiveChannel,
|
|
||||||
sendMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initUI() {
|
|
||||||
const messagesList = document.getElementById("messages");
|
|
||||||
const nicknameInput = document.getElementById("nick-input");
|
|
||||||
const textInput = document.getElementById("text-input");
|
|
||||||
const sendButton = document.getElementById("send-btn");
|
|
||||||
const chatArea = document.getElementById("chat-area");
|
|
||||||
const wakuStatusSpan = document.getElementById("waku-status");
|
|
||||||
const handshakeStatusSpan = document.getElementById("handshake-status");
|
|
||||||
|
|
||||||
const qrCanvas = document.getElementById("qr-canvas");
|
|
||||||
const qrUrlContainer = document.getElementById("qr-url-container");
|
|
||||||
const qrUrl = document.getElementById("qr-url");
|
|
||||||
const copyURLButton = document.getElementById("copy-url");
|
|
||||||
const openTabButton = document.getElementById("open-tab");
|
|
||||||
|
|
||||||
const rtcStatus = document.getElementById("rtc-status");
|
|
||||||
const connectChat = document.getElementById("connect-chat-btn");
|
|
||||||
|
|
||||||
copyURLButton.onclick = () => {
|
|
||||||
const copyText = document.getElementById("qr-url"); // need to get it each time otherwise copying does not work
|
|
||||||
copyText.select();
|
|
||||||
copyText.setSelectionRange(0, 99999);
|
|
||||||
navigator.clipboard.writeText(copyText.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
openTabButton.onclick = () => {
|
|
||||||
window.open(qrUrl.value, "_blank");
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableChatUIStateIfNeeded = () => {
|
|
||||||
const readyToSend = nicknameInput.value !== "";
|
|
||||||
textInput.disabled = !readyToSend;
|
|
||||||
sendButton.disabled = !readyToSend;
|
|
||||||
};
|
|
||||||
nicknameInput.onchange = disableChatUIStateIfNeeded;
|
|
||||||
nicknameInput.onblur = disableChatUIStateIfNeeded;
|
|
||||||
|
|
||||||
return {
|
|
||||||
shareInfo: {
|
|
||||||
setURL(url) {
|
|
||||||
qrUrl.value = url;
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
qrUrlContainer.style.display = "none";
|
|
||||||
},
|
|
||||||
show() {
|
|
||||||
qrUrlContainer.style.display = "flex";
|
|
||||||
},
|
|
||||||
renderQR(url) {
|
|
||||||
QRCode.toCanvas(qrCanvas, url, (err) => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
waku: {
|
|
||||||
_val(msg) {
|
|
||||||
wakuStatusSpan.innerText = msg;
|
|
||||||
},
|
|
||||||
_class(name) {
|
|
||||||
wakuStatusSpan.className = name;
|
|
||||||
},
|
|
||||||
connecting() {
|
|
||||||
this._val("connecting...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connected() {
|
|
||||||
this._val("connected");
|
|
||||||
this._class("success");
|
|
||||||
},
|
|
||||||
error(msg) {
|
|
||||||
this._val(msg);
|
|
||||||
this._class("error");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handshake: {
|
|
||||||
_val(val) {
|
|
||||||
handshakeStatusSpan.innerText = val;
|
|
||||||
},
|
|
||||||
_class(name) {
|
|
||||||
handshakeStatusSpan.className = name;
|
|
||||||
},
|
|
||||||
error(msg) {
|
|
||||||
this._val(msg);
|
|
||||||
this._class("error");
|
|
||||||
},
|
|
||||||
waiting() {
|
|
||||||
this._val("waiting for handshake...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
generating() {
|
|
||||||
this._val("generating QR code...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connecting() {
|
|
||||||
this._val("executing handshake...");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
connected() {
|
|
||||||
this._val("handshake completed!");
|
|
||||||
this._class("success");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
_render({ time, text, nick }) {
|
|
||||||
messagesList.innerHTML += `
|
|
||||||
<li>
|
|
||||||
(${nick})
|
|
||||||
<strong>${text}</strong>
|
|
||||||
<i>[${new Date(time).toISOString()}]</i>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
_status(text, className) {
|
|
||||||
sendButton.className = className;
|
|
||||||
},
|
|
||||||
onReceive(data) {
|
|
||||||
const { timestamp, nick, text } = data;
|
|
||||||
|
|
||||||
this._render({
|
|
||||||
nick,
|
|
||||||
time: timestamp,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSend(cb) {
|
|
||||||
sendButton.addEventListener("click", async () => {
|
|
||||||
try {
|
|
||||||
this._status("sending...", "progress");
|
|
||||||
await cb(textInput.value, nicknameInput.value);
|
|
||||||
this._status("sent", "success");
|
|
||||||
|
|
||||||
this._render({
|
|
||||||
time: Date.now(), // a bit different from what receiver will see but for the matter of example is good enough
|
|
||||||
text: textInput.value,
|
|
||||||
nick: nicknameInput.value,
|
|
||||||
});
|
|
||||||
textInput.value = "";
|
|
||||||
} catch (e) {
|
|
||||||
this._status(`error: ${e.message}`, "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
display() {
|
|
||||||
chatArea.style.display = "block";
|
|
||||||
this._status("waiting for input", "progress");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rtc: {
|
|
||||||
_val(msg) {
|
|
||||||
rtcStatus.innerText = msg;
|
|
||||||
},
|
|
||||||
_class(name) {
|
|
||||||
rtcStatus.className = name;
|
|
||||||
},
|
|
||||||
sendingOffer() {
|
|
||||||
this._val("sending offer");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
sendingAnswer() {
|
|
||||||
this._val("sending answer");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
sendingCandidate() {
|
|
||||||
this._val("sending ice candidate");
|
|
||||||
this._class("progress");
|
|
||||||
},
|
|
||||||
ready() {
|
|
||||||
this._val("ready");
|
|
||||||
this._class("success");
|
|
||||||
},
|
|
||||||
error(msg) {
|
|
||||||
this._val(msg);
|
|
||||||
this._class("error");
|
|
||||||
},
|
|
||||||
onConnect(cb) {
|
|
||||||
connectChat.addEventListener("click", cb);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.shareInfo.hide();
|
|
||||||
chatArea.style.display = "none";
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Waku NoiseRTC",
|
|
||||||
"description": "Example showing WebRTC with Waku Noise.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@waku/noise-rtc",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"start": "webpack-dev-server"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@waku/sdk": "0.0.18",
|
|
||||||
"@waku/noise": "0.0.3-31510da",
|
|
||||||
"@waku/utils": "0.0.10",
|
|
||||||
"protobufjs": "^7.1.2",
|
|
||||||
"qrcode": "^1.5.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"webpack": "^5.74.0",
|
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-dev-server": "^4.11.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: "./index.js",
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, "build"),
|
|
||||||
filename: "index.js",
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true,
|
|
||||||
},
|
|
||||||
mode: "development",
|
|
||||||
plugins: [
|
|
||||||
new CopyWebpackPlugin({
|
|
||||||
patterns: ["index.html", "favicon.ico", "favicon.png", "manifest.json"],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
/.angular/cache
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Minimal Angular (v13) Waku Relay App
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Group messaging
|
|
||||||
- Angular
|
|
||||||
- Waku Relay
|
|
||||||
- Protobuf using `protobufjs`
|
|
||||||
- No async/await syntax
|
|
||||||
|
|
||||||
A barebone messaging app to illustrate the seamless integration of `js-waku` into AngularJS.
|
|
||||||
|
|
||||||
The `master` branch's HEAD is deployed at https://examples.waku.org/relay-angular-chat/.
|
|
||||||
|
|
||||||
To run a development version locally, do:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/waku-org/js-waku-examples
|
|
||||||
cd js-waku-examples/examples/relay-angular-chat
|
|
||||||
npm install
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Known issues
|
|
||||||
|
|
||||||
There is a problem when using `npm` to install/run the Angular app.
|
|
|
@ -1,97 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"version": 1,
|
|
||||||
"newProjectRoot": "projects",
|
|
||||||
"projects": {
|
|
||||||
"relay-angular-chat": {
|
|
||||||
"projectType": "application",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:application": {
|
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"prefix": "app",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
|
||||||
"options": {
|
|
||||||
"outputPath": "build",
|
|
||||||
"index": "src/index.html",
|
|
||||||
"main": "src/main.ts",
|
|
||||||
"tsConfig": "tsconfig.app.json",
|
|
||||||
"assets": ["src/favicon.ico", "src/assets"],
|
|
||||||
"styles": ["src/styles.css"],
|
|
||||||
"scripts": []
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"budgets": [
|
|
||||||
{
|
|
||||||
"type": "initial",
|
|
||||||
"maximumWarning": "500kb",
|
|
||||||
"maximumError": "2mb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "anyComponentStyle",
|
|
||||||
"maximumWarning": "2kb",
|
|
||||||
"maximumError": "4kb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fileReplacements": [
|
|
||||||
{
|
|
||||||
"replace": "src/environments/environment.ts",
|
|
||||||
"with": "src/environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputHashing": "all"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"buildOptimizer": false,
|
|
||||||
"optimization": false,
|
|
||||||
"vendorChunk": true,
|
|
||||||
"extractLicenses": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"namedChunks": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"browserTarget": "relay-angular-chat:build:production"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"browserTarget": "relay-angular-chat:build:development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "development"
|
|
||||||
},
|
|
||||||
"extract-i18n": {
|
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "relay-angular-chat:build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "src/test.ts",
|
|
||||||
"tsConfig": "tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"assets": ["src/favicon.ico", "src/assets"],
|
|
||||||
"styles": ["src/styles.css"],
|
|
||||||
"scripts": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultProject": "relay-angular-chat",
|
|
||||||
"cli": {
|
|
||||||
"analytics": false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
// Karma configuration file, see link for more information
|
|
||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
config.set({
|
|
||||||
basePath: "",
|
|
||||||
frameworks: ["jasmine", "@angular-devkit/build-angular"],
|
|
||||||
plugins: [
|
|
||||||
require("karma-jasmine"),
|
|
||||||
require("karma-chrome-launcher"),
|
|
||||||
require("karma-jasmine-html-reporter"),
|
|
||||||
require("karma-coverage"),
|
|
||||||
require("@angular-devkit/build-angular/plugins/karma"),
|
|
||||||
],
|
|
||||||
client: {
|
|
||||||
jasmine: {
|
|
||||||
// you can add configuration options for Jasmine here
|
|
||||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
|
||||||
// for example, you can disable the random execution with `random: false`
|
|
||||||
// or set a specific seed with `seed: 4321`
|
|
||||||
},
|
|
||||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
|
||||||
},
|
|
||||||
jasmineHtmlReporter: {
|
|
||||||
suppressAll: true, // removes the duplicated traces
|
|
||||||
},
|
|
||||||
coverageReporter: {
|
|
||||||
dir: require("path").join(__dirname, "./coverage/relay-angular-chat"),
|
|
||||||
subdir: ".",
|
|
||||||
reporters: [{ type: "html" }, { type: "text-summary" }],
|
|
||||||
},
|
|
||||||
reporters: ["progress", "kjhtml"],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ["Chrome"],
|
|
||||||
singleRun: false,
|
|
||||||
restartOnFileChange: true,
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,50 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@waku/relay-angular-chat",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"homepage": "/relay-angular-chat",
|
|
||||||
"scripts": {
|
|
||||||
"ng": "ng",
|
|
||||||
"start": "ng serve",
|
|
||||||
"build": "ng build --base-href /relay-angular-chat --deploy-url /relay-angular-chat/",
|
|
||||||
"watch": "ng build --watch --configuration development",
|
|
||||||
"test": "exit 0",
|
|
||||||
"test:local": "ng test",
|
|
||||||
"test:ci": "ng test --watch=false --browsers=ChromeHeadless"
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@angular/animations": "~14.2.11",
|
|
||||||
"@angular/common": "~14.2.11",
|
|
||||||
"@angular/compiler": "~14.2.11",
|
|
||||||
"@angular/core": "~14.2.11",
|
|
||||||
"@angular/forms": "~14.2.11",
|
|
||||||
"@angular/platform-browser": "~14.2.11",
|
|
||||||
"@angular/platform-browser-dynamic": "~14.2.11",
|
|
||||||
"@angular/router": "~14.2.11",
|
|
||||||
"@waku/core": "^0.0.6",
|
|
||||||
"@waku/create": "^0.0.4",
|
|
||||||
"@waku/interfaces": "^0.0.5",
|
|
||||||
"protobufjs": "^7.1.2",
|
|
||||||
"rxjs": "~7.5.7",
|
|
||||||
"tslib": "^2.4.1",
|
|
||||||
"zone.js": "~0.11.8"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"util": false
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@angular-devkit/build-angular": "~14.2.10",
|
|
||||||
"@angular/cli": "~14.2.10",
|
|
||||||
"@angular/compiler-cli": "~14.2.11",
|
|
||||||
"@types/jasmine": "~4.3.0",
|
|
||||||
"@types/node": "^17.0.45",
|
|
||||||
"is-ci-cli": "^2.2.0",
|
|
||||||
"jasmine-core": "~4.3.0",
|
|
||||||
"karma": "~6.4.1",
|
|
||||||
"karma-chrome-launcher": "~3.1.1",
|
|
||||||
"karma-coverage": "~2.2.0",
|
|
||||||
"karma-jasmine": "~5.1.0",
|
|
||||||
"karma-jasmine-html-reporter": "~2.0.0",
|
|
||||||
"typescript": "~4.7.4"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
declare module "protons";
|
|
|
@ -1,13 +0,0 @@
|
||||||
declare module "time-cache" {
|
|
||||||
interface ITimeCache {
|
|
||||||
put(key: string, value: any, validity: number): void;
|
|
||||||
get(key: string): any;
|
|
||||||
has(key: string): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type TimeCache = ITimeCache;
|
|
||||||
|
|
||||||
function TimeCache(options: object): TimeCache;
|
|
||||||
|
|
||||||
export = TimeCache;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
/* Application-wide Styles */
|
|
||||||
h1 {
|
|
||||||
color: #369;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-size: 250%;
|
|
||||||
}
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
color: #444;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-weight: lighter;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 2em;
|
|
||||||
}
|
|
||||||
body,
|
|
||||||
input[type="text"],
|
|
||||||
button {
|
|
||||||
color: #333;
|
|
||||||
font-family: Cambria, Georgia, serif;
|
|
||||||
}
|
|
||||||
/* everywhere else */
|
|
||||||
* {
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
<p>Waku node's status: {{ wakuStatus }}</p>
|
|
||||||
<app-messages></app-messages>
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { TestBed } from "@angular/core/testing";
|
|
||||||
import { AppComponent } from "./app.component";
|
|
||||||
import { MessagesComponent } from "./messages/messages.component";
|
|
||||||
|
|
||||||
describe("AppComponent", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [AppComponent, MessagesComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
xit("should create the app", () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
xit(`should have as title 'relay-angular-chat'`, () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app.title).toEqual("relay-angular-chat");
|
|
||||||
});
|
|
||||||
|
|
||||||
xit("should render title", () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
|
||||||
expect(compiled.querySelector(".h1")?.textContent).toContain(
|
|
||||||
"relay-angular-chat"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { Component } from "@angular/core";
|
|
||||||
import { WakuService } from "./waku.service";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-root",
|
|
||||||
templateUrl: "./app.component.html",
|
|
||||||
styleUrls: ["./app.component.css"],
|
|
||||||
})
|
|
||||||
export class AppComponent {
|
|
||||||
title: string = "relay-angular-chat";
|
|
||||||
wakuStatus!: string;
|
|
||||||
|
|
||||||
constructor(private wakuService: WakuService) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.wakuService.init();
|
|
||||||
this.wakuService.wakuStatus.subscribe((wakuStatus) => {
|
|
||||||
this.wakuStatus = wakuStatus;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { NgModule } from "@angular/core";
|
|
||||||
import { BrowserModule } from "@angular/platform-browser";
|
|
||||||
import { AppComponent } from "./app.component";
|
|
||||||
import { MessagesComponent } from "./messages/messages.component";
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppComponent, MessagesComponent],
|
|
||||||
imports: [BrowserModule],
|
|
||||||
providers: [],
|
|
||||||
bootstrap: [AppComponent],
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<button (click)="sendMessage()" [disabled]="wakuStatus !== 'Connected'">
|
|
||||||
Send Message
|
|
||||||
</button>
|
|
||||||
<h2>Messages</h2>
|
|
||||||
<ul class="messages">
|
|
||||||
<li *ngFor="let message of messages">
|
|
||||||
<span>{{ message.timestamp }} {{ message.text }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
||||||
import { MessagesComponent } from "./messages.component";
|
|
||||||
|
|
||||||
describe("MessagesComponent", () => {
|
|
||||||
let component: MessagesComponent;
|
|
||||||
let fixture: ComponentFixture<MessagesComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [MessagesComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(MessagesComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
xit("should create", () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
import { WakuService } from "../waku.service";
|
|
||||||
import type { WakuPrivacy } from "@waku/interfaces";
|
|
||||||
import protobuf from "protobufjs";
|
|
||||||
import { DecoderV0, EncoderV0 } from "@waku/core/lib/waku_message/version_0";
|
|
||||||
import type { MessageV0 } from "@waku/core/lib/waku_message/version_0";
|
|
||||||
|
|
||||||
const ProtoChatMessage = new protobuf.Type("ChatMessage")
|
|
||||||
.add(new protobuf.Field("timestamp", 1, "uint32"))
|
|
||||||
.add(new protobuf.Field("text", 2, "string"));
|
|
||||||
|
|
||||||
interface MessageInterface {
|
|
||||||
timestamp: Date;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-messages",
|
|
||||||
templateUrl: "./messages.component.html",
|
|
||||||
styleUrls: ["./messages.component.css"],
|
|
||||||
})
|
|
||||||
export class MessagesComponent implements OnInit {
|
|
||||||
contentTopic: string = `/js-waku-examples/1/chat/proto`;
|
|
||||||
decoder: DecoderV0;
|
|
||||||
encoder: EncoderV0;
|
|
||||||
messages: MessageInterface[] = [];
|
|
||||||
messageCount: number = 0;
|
|
||||||
waku!: WakuPrivacy;
|
|
||||||
wakuStatus!: string;
|
|
||||||
deleteObserver?: () => void;
|
|
||||||
|
|
||||||
constructor(private wakuService: WakuService) {
|
|
||||||
this.decoder = new DecoderV0(this.contentTopic);
|
|
||||||
this.encoder = new EncoderV0(this.contentTopic);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.wakuService.wakuStatus.subscribe((wakuStatus) => {
|
|
||||||
this.wakuStatus = wakuStatus;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.wakuService.waku.subscribe((waku) => {
|
|
||||||
this.waku = waku;
|
|
||||||
this.deleteObserver = this.waku.relay.addObserver(
|
|
||||||
this.decoder,
|
|
||||||
this.processIncomingMessages
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.onbeforeunload = () => this.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.deleteObserver) this.deleteObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage(): void {
|
|
||||||
const time = new Date().getTime();
|
|
||||||
|
|
||||||
const protoMsg = ProtoChatMessage.create({
|
|
||||||
timestamp: time,
|
|
||||||
text: `Here is a message #${this.messageCount}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload = ProtoChatMessage.encode(protoMsg).finish();
|
|
||||||
this.waku.relay.send(this.encoder, { payload }).then(() => {
|
|
||||||
console.log(`Message #${this.messageCount} sent`);
|
|
||||||
this.messageCount += 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
processIncomingMessages = (wakuMessage: MessageV0) => {
|
|
||||||
if (!wakuMessage.payload) return;
|
|
||||||
|
|
||||||
const { text, timestamp } = ProtoChatMessage.decode(
|
|
||||||
wakuMessage.payload
|
|
||||||
) as unknown as { text: string; timestamp: bigint };
|
|
||||||
const time = new Date();
|
|
||||||
time.setTime(Number(timestamp));
|
|
||||||
const message = { text, timestamp: time };
|
|
||||||
|
|
||||||
this.messages.push(message);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { TestBed } from "@angular/core/testing";
|
|
||||||
|
|
||||||
import { WakuService } from "./waku.service";
|
|
||||||
|
|
||||||
describe("WakuService", () => {
|
|
||||||
let service: WakuService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
service = TestBed.inject(WakuService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be created", () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { Injectable } from "@angular/core";
|
|
||||||
import { BehaviorSubject, Subject } from "rxjs";
|
|
||||||
import { createPrivacyNode } from "@waku/create";
|
|
||||||
import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer";
|
|
||||||
import type { WakuPrivacy } from "@waku/interfaces";
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: "root",
|
|
||||||
})
|
|
||||||
export class WakuService {
|
|
||||||
private wakuSubject = new Subject<WakuPrivacy>();
|
|
||||||
public waku = this.wakuSubject.asObservable();
|
|
||||||
|
|
||||||
private wakuStatusSubject = new BehaviorSubject("");
|
|
||||||
public wakuStatus = this.wakuStatusSubject.asObservable();
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
createPrivacyNode({ defaultBootstrap: true }).then((waku) => {
|
|
||||||
waku.start().then(() => {
|
|
||||||
this.wakuSubject.next(waku);
|
|
||||||
this.wakuStatusSubject.next("Connecting...");
|
|
||||||
|
|
||||||
waitForRemotePeer(waku).then(() => {
|
|
||||||
this.wakuStatusSubject.next("Connected");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const environment = {
|
|
||||||
production: true,
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
// This file can be replaced during build by using the `fileReplacements` array.
|
|
||||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
|
||||||
// The list of file replacements can be found in `angular.json`.
|
|
||||||
|
|
||||||
export const environment = {
|
|
||||||
production: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For easier debugging in development mode, you can import the following file
|
|
||||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
|
||||||
*
|
|
||||||
* This import should be commented out in production mode because it will have a negative impact
|
|
||||||
* on performance if an error is thrown.
|
|
||||||
*/
|
|
||||||
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,15 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>RelayAngularChat</title>
|
|
||||||
<base href="/" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { enableProdMode } from "@angular/core";
|
|
||||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
|
||||||
|
|
||||||
import { AppModule } from "./app/app.module";
|
|
||||||
import { environment } from "./environments/environment";
|
|
||||||
|
|
||||||
import "zone.js";
|
|
||||||
|
|
||||||
if (environment.production) {
|
|
||||||
enableProdMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
platformBrowserDynamic()
|
|
||||||
.bootstrapModule(AppModule)
|
|
||||||
.catch((err) => console.error(err));
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Relay Chat",
|
|
||||||
"description": "A barebone messaging app to illustrate the Angular Relay guide.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
|
|
@ -1,30 +0,0 @@
|
||||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|
||||||
|
|
||||||
import "zone.js/testing";
|
|
||||||
import { getTestBed } from "@angular/core/testing";
|
|
||||||
import {
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting,
|
|
||||||
} from "@angular/platform-browser-dynamic/testing";
|
|
||||||
|
|
||||||
declare const require: {
|
|
||||||
context(
|
|
||||||
path: string,
|
|
||||||
deep?: boolean,
|
|
||||||
filter?: RegExp
|
|
||||||
): {
|
|
||||||
<T>(id: string): T;
|
|
||||||
keys(): string[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// First, initialize the Angular testing environment.
|
|
||||||
getTestBed().initTestEnvironment(
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then we find all the tests.
|
|
||||||
const context = require.context("./", true, /\.spec\.ts$/);
|
|
||||||
// And load the modules.
|
|
||||||
context.keys().map(context);
|
|
|
@ -1,10 +0,0 @@
|
||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/app",
|
|
||||||
"types": []
|
|
||||||
},
|
|
||||||
"files": ["src/main.ts"],
|
|
||||||
"include": ["src/**/*.d.ts"]
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
||||||
{
|
|
||||||
"compileOnSave": false,
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "./",
|
|
||||||
"outDir": "./dist/out-tsc",
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"downlevelIteration": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"importHelpers": true,
|
|
||||||
"target": "es2020",
|
|
||||||
"module": "es2020",
|
|
||||||
"lib": ["es2020", "dom"],
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"enableI18nLegacyMessageIdFormat": false,
|
|
||||||
"strictInjectionParameters": true,
|
|
||||||
"strictInputAccessModifiers": true,
|
|
||||||
"strictTemplates": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./out-tsc/spec",
|
|
||||||
"types": ["jasmine"]
|
|
||||||
},
|
|
||||||
"files": ["src/test.ts"],
|
|
||||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Direct WebRTC connection for Waku Relay
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Waku Relay node with direct WebRTC connection
|
|
||||||
- Pure Javascript/HTML.
|
|
||||||
|
|
||||||
This example uses WebRTC transport and Waku Relay to exchange messages.
|
|
||||||
|
|
||||||
To test the example run `npm install` and then `npm start`.
|
|
||||||
|
|
||||||
The `master` branch's HEAD is deployed at https://examples.waku.org/relay-direct-chat/.
|
|
||||||
|
|
||||||
### Steps to run an example:
|
|
||||||
1. Get a Waku node that implements `/libp2p/circuit/relay/0.2.0/hop` and `/libp2p/circuit/relay/0.2.0/stop`
|
|
||||||
1.1. Find `go-waku` node or
|
|
||||||
1.2. Build and then run `go-waku` node with following command: `./build/waku --ws true --relay true --circuit-relay true`
|
|
||||||
2. Copy node's multiaddr (e.g `/ip4/192.168.0.101/tcp/60001/ws/p2p/16Uiu2HAm9w2xeDWFJm5eeGLZfJdaPtkNatQD1xrzK5EFWSeXdFvu`)
|
|
||||||
3. In `relay-chat` example's folder run `npm install` and then `npm start`
|
|
||||||
4. Use `go-waku`'s multiaddr for **Remote node multiaddr** and press dial. Repeat in two more tabs.
|
|
||||||
5. In `tab2` copy **Local Peer Id** and use as **WebRTC Peer** in `tab1` and press dial.
|
|
||||||
6. In `tab1` or `tab2` press **Ensure WebRTC Relay connection**
|
|
||||||
7. In `tab1` press **Drop non WebRTC connections**
|
|
||||||
8. In `tab1` enter **Nickname** and **Message** and send.
|
|
||||||
9. See the message in `tab3` which was connected only to `go-waku` node.
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,73 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
|
||||||
<title>Relay direct chat</title>
|
|
||||||
<link rel="stylesheet" href="./style.css" />
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">
|
|
||||||
<h3>Status: <span id="status"></span></h3>
|
|
||||||
|
|
||||||
<h4><label for="remoteNode">Remote node multiaddr</label></h4>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
id="remoteNode"
|
|
||||||
value="/dns4/node-01.ac-cn-hongkong-c.go-waku.prod.statusim.net/tcp/443/wss/p2p/16Uiu2HAm1fVVprL9EaDpDw4frNX3CPfZu5cBqhtk9Ncm6WCvprpv"
|
|
||||||
/>
|
|
||||||
<button id="connectRemoteNode">Dial</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4><label for="webrtcPeer">WebRTC Peer</label></h4>
|
|
||||||
<div>
|
|
||||||
<input id="webrtcPeer" />
|
|
||||||
<button id="connectWebrtcPeer">Dial</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button id="relayWebRTC">Ensure WebRTC Relay connection</button>
|
|
||||||
<button id="dropNonWebRTC">Drop non WebRTC connections</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details open>
|
|
||||||
<summary>Peer's information</summary>
|
|
||||||
|
|
||||||
<h4>Content topic</h4>
|
|
||||||
<p id="contentTopic"></p>
|
|
||||||
|
|
||||||
<h4>Local Peer Id</h4>
|
|
||||||
<p id="localPeerId"></p>
|
|
||||||
|
|
||||||
<h4>Remote Peer Id</h4>
|
|
||||||
<p id="remotePeerId"></p>
|
|
||||||
|
|
||||||
<h4>Relay mesh's protocols</h4>
|
|
||||||
<p id="relayMeshInfo"></p>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<div class="inputArea">
|
|
||||||
<input type="text" id="nickText" placeholder="Nickname" />
|
|
||||||
<textarea id="messageText" placeholder="Message"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<button id="send">Send</button>
|
|
||||||
<button id="exit">Exit chat</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/protobufjs@7.X.X/dist/protobuf.min.js"></script>
|
|
||||||
<script type="module" src="./index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,340 +0,0 @@
|
||||||
import {
|
|
||||||
createRelayNode,
|
|
||||||
bytesToUtf8,
|
|
||||||
utf8ToBytes,
|
|
||||||
createDecoder,
|
|
||||||
createEncoder,
|
|
||||||
} from "@waku/sdk";
|
|
||||||
|
|
||||||
import { webSockets } from "@libp2p/websockets";
|
|
||||||
import { all as filterAll } from "@libp2p/websockets/filters";
|
|
||||||
|
|
||||||
import { webRTC } from "@libp2p/webrtc";
|
|
||||||
import { circuitRelayTransport } from "libp2p/circuit-relay";
|
|
||||||
|
|
||||||
const CONTENT_TOPIC = "/toy-chat/2/huilong/proto";
|
|
||||||
|
|
||||||
const ui = initUI();
|
|
||||||
runApp(ui).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
ui.setStatus(`error: ${err.message}`, "error");
|
|
||||||
});
|
|
||||||
|
|
||||||
async function runApp(ui) {
|
|
||||||
const {
|
|
||||||
info,
|
|
||||||
sendMessage,
|
|
||||||
unsubscribeFromMessages,
|
|
||||||
dial,
|
|
||||||
dialWebRTCpeer,
|
|
||||||
dropNetworkConnections,
|
|
||||||
ensureWebRTCconnectionInRelayMesh,
|
|
||||||
} = await initWakuContext({
|
|
||||||
ui,
|
|
||||||
contentTopic: CONTENT_TOPIC,
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.setLocalPeer(info.localPeerId);
|
|
||||||
ui.setContentTopic(info.contentTopic);
|
|
||||||
|
|
||||||
ui.onSendMessage(sendMessage);
|
|
||||||
ui.onRemoteNodeConnect(dial);
|
|
||||||
ui.onWebrtcConnect(dialWebRTCpeer);
|
|
||||||
ui.onRelayWebRTC(ensureWebRTCconnectionInRelayMesh);
|
|
||||||
ui.onDropNonWebRTC(dropNetworkConnections);
|
|
||||||
|
|
||||||
ui.onExit(async () => {
|
|
||||||
ui.setStatus("disconnecting...", "progress");
|
|
||||||
await unsubscribeFromMessages();
|
|
||||||
ui.setStatus("disconnected", "terminated");
|
|
||||||
ui.resetMessages();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initWakuContext({ ui, contentTopic }) {
|
|
||||||
const Decoder = createDecoder(contentTopic);
|
|
||||||
const Encoder = createEncoder({ contentTopic });
|
|
||||||
|
|
||||||
const ChatMessage = new protobuf.Type("ChatMessage")
|
|
||||||
.add(new protobuf.Field("timestamp", 1, "uint64"))
|
|
||||||
.add(new protobuf.Field("nick", 2, "string"))
|
|
||||||
.add(new protobuf.Field("text", 3, "bytes"));
|
|
||||||
|
|
||||||
ui.setStatus("starting...", "progress");
|
|
||||||
|
|
||||||
const node = await createRelayNode({
|
|
||||||
libp2p: {
|
|
||||||
addresses: {
|
|
||||||
listen: ["/webrtc"],
|
|
||||||
},
|
|
||||||
connectionGater: {
|
|
||||||
denyDialMultiaddr: () => {
|
|
||||||
// refuse to deny localhost addresses
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
transports: [
|
|
||||||
webRTC({}),
|
|
||||||
circuitRelayTransport({
|
|
||||||
discoverRelays: 1,
|
|
||||||
}),
|
|
||||||
webSockets({ filter: filterAll }),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await node.start();
|
|
||||||
|
|
||||||
// Set a filter by using Decoder for a given ContentTopic
|
|
||||||
const unsubscribeFromMessages = await node.relay.subscribe(
|
|
||||||
[Decoder],
|
|
||||||
(wakuMessage) => {
|
|
||||||
const messageObj = ChatMessage.decode(wakuMessage.payload);
|
|
||||||
ui.renderMessage({
|
|
||||||
...messageObj,
|
|
||||||
text: bytesToUtf8(messageObj.text),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.setStatus("started", "success");
|
|
||||||
|
|
||||||
const localPeerId = node.libp2p.peerId.toString();
|
|
||||||
|
|
||||||
const remotePeers = await node.libp2p.peerStore.all();
|
|
||||||
const remotePeerIds = new Set(remotePeers.map((peer) => peer.id.toString()));
|
|
||||||
|
|
||||||
ui.setRemotePeer(Array.from(remotePeerIds.keys()));
|
|
||||||
|
|
||||||
node.libp2p.addEventListener("peer:connect", async (event) => {
|
|
||||||
remotePeerIds.add(event.detail.toString());
|
|
||||||
ui.setRemotePeer(Array.from(remotePeerIds.keys()));
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.libp2p.addEventListener("peer:disconnect", (event) => {
|
|
||||||
remotePeerIds.delete(event.detail.toString());
|
|
||||||
ui.setRemotePeer(Array.from(remotePeerIds.keys()));
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.libp2p.addEventListener("peer:identify", (event) => {
|
|
||||||
const peer = event.detail;
|
|
||||||
|
|
||||||
if (!peer.protocols.includes("/webrtc-signaling/0.0.1")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.setWebrtcPeer(peer.peerId.toString());
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.node = node;
|
|
||||||
|
|
||||||
return {
|
|
||||||
unsubscribeFromMessages,
|
|
||||||
info: {
|
|
||||||
contentTopic,
|
|
||||||
localPeerId,
|
|
||||||
},
|
|
||||||
sendMessage: async ({ text, nick }) => {
|
|
||||||
if (!text || !nick) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const protoMessage = ChatMessage.create({
|
|
||||||
nick,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
text: utf8ToBytes(text),
|
|
||||||
});
|
|
||||||
|
|
||||||
await node.relay.send(Encoder, {
|
|
||||||
payload: ChatMessage.encode(protoMessage).finish(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
dial: async (multiaddr) => {
|
|
||||||
ui.setStatus("connecting...", "progress");
|
|
||||||
await node.dial(multiaddr);
|
|
||||||
ui.setStatus("connected", "success");
|
|
||||||
},
|
|
||||||
dialWebRTCpeer: async (peerId) => {
|
|
||||||
const peers = await node.libp2p.peerStore.all();
|
|
||||||
const circuitPeer = peers.filter(
|
|
||||||
(p) =>
|
|
||||||
p.protocols.includes("/libp2p/circuit/relay/0.2.0/hop") &&
|
|
||||||
p.protocols.includes("/libp2p/circuit/relay/0.2.0/stop")
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
if (!circuitPeer) {
|
|
||||||
throw Error("No Circuit peer is found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let multiaddr = circuitPeer.addresses.pop().multiaddr;
|
|
||||||
multiaddr = `${multiaddr}/p2p/${circuitPeer.id.toString()}/p2p-circuit/webrtc/p2p/${peerId}`;
|
|
||||||
|
|
||||||
await node.dial(multiaddr);
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
},
|
|
||||||
ensureWebRTCconnectionInRelayMesh: async () => {
|
|
||||||
const promises = node.libp2p
|
|
||||||
.getConnections()
|
|
||||||
.filter((c) => c.stat.multiplexer === "/webrtc")
|
|
||||||
.map(async (c) => {
|
|
||||||
const outboundStream = node.relay.gossipSub.streamsOutbound.get(
|
|
||||||
c.remotePeer.toString()
|
|
||||||
);
|
|
||||||
const isWebRTCOutbound =
|
|
||||||
outboundStream.rawStream.constructor.name === "WebRTCStream";
|
|
||||||
|
|
||||||
if (isWebRTCOutbound) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.relay.gossipSub.streamsOutbound.delete(c.remotePeer.toString());
|
|
||||||
await node.relay.gossipSub.createOutboundStream(
|
|
||||||
c.remotePeer.toString(),
|
|
||||||
c
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await Promise.all(promises);
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
},
|
|
||||||
dropNetworkConnections: async () => {
|
|
||||||
const promises = node.libp2p
|
|
||||||
.getConnections()
|
|
||||||
.filter((c) => c.stat.multiplexer !== "/webrtc")
|
|
||||||
.map(async (c) => {
|
|
||||||
const peerId = c.remotePeer.toString();
|
|
||||||
|
|
||||||
node.relay.gossipSub.peers.delete(peerId);
|
|
||||||
node.relay.gossipSub.streamsInbound.delete(peerId);
|
|
||||||
node.relay.gossipSub.streamsOutbound.delete(peerId);
|
|
||||||
|
|
||||||
await node.libp2p.peerStore.delete(c.remotePeer);
|
|
||||||
await c.close();
|
|
||||||
});
|
|
||||||
await Promise.all(promises);
|
|
||||||
ui.setRelayMeshInfo(node.relay.gossipSub);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI adapter
|
|
||||||
function initUI() {
|
|
||||||
const exitButton = document.getElementById("exit");
|
|
||||||
const sendButton = document.getElementById("send");
|
|
||||||
|
|
||||||
const statusBlock = document.getElementById("status");
|
|
||||||
const localPeerBlock = document.getElementById("localPeerId");
|
|
||||||
const remotePeerId = document.getElementById("remotePeerId");
|
|
||||||
const contentTopicBlock = document.getElementById("contentTopic");
|
|
||||||
|
|
||||||
const messagesBlock = document.getElementById("messages");
|
|
||||||
|
|
||||||
const nickText = document.getElementById("nickText");
|
|
||||||
const messageText = document.getElementById("messageText");
|
|
||||||
|
|
||||||
const remoteNode = document.getElementById("remoteNode");
|
|
||||||
const connectRemoteNode = document.getElementById("connectRemoteNode");
|
|
||||||
|
|
||||||
const webrtcPeer = document.getElementById("webrtcPeer");
|
|
||||||
const connectWebrtcPeer = document.getElementById("connectWebrtcPeer");
|
|
||||||
|
|
||||||
const relayWebRTCbutton = document.getElementById("relayWebRTC");
|
|
||||||
const dropNonWebRTCbutton = document.getElementById("dropNonWebRTC");
|
|
||||||
|
|
||||||
const relayMeshInfo = document.getElementById("relayMeshInfo");
|
|
||||||
|
|
||||||
return {
|
|
||||||
// UI events
|
|
||||||
onExit: (cb) => {
|
|
||||||
exitButton.addEventListener("click", cb);
|
|
||||||
},
|
|
||||||
onSendMessage: (cb) => {
|
|
||||||
sendButton.addEventListener("click", async () => {
|
|
||||||
await cb({
|
|
||||||
nick: nickText.value,
|
|
||||||
text: messageText.value,
|
|
||||||
});
|
|
||||||
messageText.value = "";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// UI renderers
|
|
||||||
setStatus: (value, className) => {
|
|
||||||
statusBlock.innerHTML = `<span class=${className || ""}>${value}</span>`;
|
|
||||||
},
|
|
||||||
setLocalPeer: (id) => {
|
|
||||||
localPeerBlock.innerText = id.toString();
|
|
||||||
},
|
|
||||||
setRemotePeer: (ids) => {
|
|
||||||
remotePeerId.innerText = ids.join("\n");
|
|
||||||
},
|
|
||||||
setContentTopic: (topic) => {
|
|
||||||
contentTopicBlock.innerText = topic.toString();
|
|
||||||
},
|
|
||||||
renderMessage: (messageObj) => {
|
|
||||||
const { nick, text, timestamp } = messageObj;
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
|
|
||||||
// WARNING: XSS vulnerable
|
|
||||||
messagesBlock.innerHTML += `
|
|
||||||
<div class="message">
|
|
||||||
<p>${nick} <span>(${date.toDateString()})</span>:</p>
|
|
||||||
<p>${text}</p>
|
|
||||||
<div>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
resetMessages: () => {
|
|
||||||
messagesBlock.innerHTML = "";
|
|
||||||
},
|
|
||||||
setWebrtcPeer: (peerId) => {
|
|
||||||
webrtcPeer.value = peerId;
|
|
||||||
},
|
|
||||||
onRemoteNodeConnect: (cb) => {
|
|
||||||
connectRemoteNode.addEventListener("click", () => {
|
|
||||||
const multiaddr = remoteNode.value;
|
|
||||||
|
|
||||||
if (!multiaddr) {
|
|
||||||
throw Error("No multiaddr set to dial");
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(multiaddr);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onWebrtcConnect: (cb) => {
|
|
||||||
connectWebrtcPeer.addEventListener("click", () => {
|
|
||||||
const multiaddr = webrtcPeer.value;
|
|
||||||
|
|
||||||
if (!multiaddr) {
|
|
||||||
throw Error("No multiaddr to dial webrtc");
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(multiaddr);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onRelayWebRTC: (cb) => {
|
|
||||||
relayWebRTCbutton.addEventListener("click", cb);
|
|
||||||
},
|
|
||||||
onDropNonWebRTC: (cb) => {
|
|
||||||
dropNonWebRTCbutton.addEventListener("click", cb);
|
|
||||||
},
|
|
||||||
setRelayMeshInfo: (gossipSub) => {
|
|
||||||
relayMeshInfo.innerHTML = "";
|
|
||||||
|
|
||||||
Array.from(gossipSub.peers)
|
|
||||||
.map((peerId) => {
|
|
||||||
let inbound = gossipSub.streamsInbound.get(peerId);
|
|
||||||
inbound = inbound ? inbound.rawStream.constructor.name : "none";
|
|
||||||
|
|
||||||
let outbound = gossipSub.streamsOutbound.get(peerId);
|
|
||||||
outbound = outbound ? outbound.rawStream.constructor.name : "none";
|
|
||||||
|
|
||||||
return [peerId, inbound, outbound];
|
|
||||||
})
|
|
||||||
.map(([peerId, inbound, outbound]) => {
|
|
||||||
relayMeshInfo.innerHTML += `${peerId}<br /><b>inbound</b>: ${inbound}\t<b>outbound</b>: ${outbound}`;
|
|
||||||
relayMeshInfo.innerHTML += "<br /><br />";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Relay direct chat",
|
|
||||||
"description": "Send messages between several users (or just one) using Relay with direct WebRTC connection.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"name": "light",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "**Demonstrates**:",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"start": "webpack-dev-server"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@libp2p/webrtc": "^2.0.11",
|
|
||||||
"@libp2p/websockets": "^6.0.3",
|
|
||||||
"@waku/dns-discovery": "^0.0.15",
|
|
||||||
"@waku/sdk": "^0.0.17",
|
|
||||||
"libp2p": "^0.45.9"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"webpack": "^5.74.0",
|
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-dev-server": "^4.11.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
word-wrap: break-word;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 + h4,
|
|
||||||
div + h4,
|
|
||||||
div + details,
|
|
||||||
div + div {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div input {
|
|
||||||
min-width: 300px;
|
|
||||||
min-height: 30px;
|
|
||||||
width: 80%;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div button {
|
|
||||||
min-width: 50px;
|
|
||||||
min-height: 30px;
|
|
||||||
width: 10%;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header div button + button {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
details {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
details p {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
textarea {
|
|
||||||
line-height: 1rem;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
min-height: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
width: 800px;
|
|
||||||
min-width: 300px;
|
|
||||||
max-width: 800px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
#messages {
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message + .message {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message :first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message p + p {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message span {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputArea {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
margin-top: 10px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls button {
|
|
||||||
flex-grow: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#send {
|
|
||||||
background-color: #32d1a0;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#send:hover {
|
|
||||||
background-color: #3abd96;
|
|
||||||
}
|
|
||||||
#send:active {
|
|
||||||
background-color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
#exit {
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
background-color: #ff3a31;
|
|
||||||
}
|
|
||||||
#exit:hover {
|
|
||||||
background-color: #e4423a;
|
|
||||||
}
|
|
||||||
#exit:active {
|
|
||||||
background-color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #3ba183;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
color: #9ea13b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminated {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #c84740;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: "./index.js",
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, "build"),
|
|
||||||
filename: "index.js",
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true,
|
|
||||||
},
|
|
||||||
mode: "development",
|
|
||||||
plugins: [
|
|
||||||
new CopyWebpackPlugin({
|
|
||||||
patterns: [
|
|
||||||
"index.html",
|
|
||||||
"favicon.ico",
|
|
||||||
"favicon.png",
|
|
||||||
"manifest.json",
|
|
||||||
"style.css",
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Using Waku Relay in JavaScript
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Waku Relay: Send and receive messages using Waku Relay.
|
|
||||||
- Pure Javascript/HTML.
|
|
||||||
- Use minified bundle of js from unpkg.com, no import, no package manager.
|
|
||||||
|
|
||||||
This example uses Waku Relay to send and receive simple text messages.
|
|
||||||
|
|
||||||
To test the example, simply download the `index.html` file from this folder and open it in a browser.
|
|
||||||
|
|
||||||
The `master` branch's HEAD is deployed at https://examples.waku.org/relay-js/.
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,131 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
|
||||||
<title>JS-Waku Chat</title>
|
|
||||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
|
||||||
<link rel="manifest" href="./manifest.json" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div><h1>Waku Node Status</h1></div>
|
|
||||||
<div id="status"></div>
|
|
||||||
|
|
||||||
<label for="textInput">Message text</label>
|
|
||||||
<input
|
|
||||||
disabled
|
|
||||||
id="textInput"
|
|
||||||
placeholder="Type your message here"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<button disabled id="sendButton" type="button">
|
|
||||||
Send Message using Relay
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div><h1>Messages</h1></div>
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
/**
|
|
||||||
* Demonstrate usage of js-waku in the browser. Use relay, gossip sub protocol to send and receive messages.
|
|
||||||
* Recommended payload is protobuf. Using simple utf-8 string for demo purposes only.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
waitForRemotePeer,
|
|
||||||
createDecoder,
|
|
||||||
createEncoder,
|
|
||||||
bytesToUtf8,
|
|
||||||
utf8ToBytes,
|
|
||||||
createRelayNode,
|
|
||||||
} from "https://unpkg.com/@waku/sdk@0.0.18/bundle/index.js";
|
|
||||||
|
|
||||||
const statusDiv = document.getElementById("status");
|
|
||||||
const messagesDiv = document.getElementById("messages");
|
|
||||||
const textInput = document.getElementById("textInput");
|
|
||||||
const sendButton = document.getElementById("sendButton");
|
|
||||||
|
|
||||||
// Every Waku Message has a content topic that categorizes it.
|
|
||||||
// It is always encoded in clear text.
|
|
||||||
// Recommendation: `/dapp-name/version/functionality/codec`
|
|
||||||
// We recommend to use protobuf as codec (`proto`), this demo uses utf-8
|
|
||||||
// for simplicity's sake.
|
|
||||||
const contentTopic = "/js-waku-examples/1/chat/utf8";
|
|
||||||
|
|
||||||
// Prepare encoder and decoder, `V0` for clear text messages.
|
|
||||||
|
|
||||||
const encoder = createEncoder({ contentTopic });
|
|
||||||
const decoder = createDecoder(contentTopic);
|
|
||||||
|
|
||||||
try {
|
|
||||||
statusDiv.innerHTML = "<p>Starting</p>";
|
|
||||||
|
|
||||||
// Create and starts a Waku node.
|
|
||||||
// `defaultBootstrap: true` bootstraps by connecting to pre-defined/hardcoded Waku nodes.
|
|
||||||
// `emitSelf`: emits event of sent message to itself and invokes subscribers by it
|
|
||||||
// We are currently working on migrating this method to DNS Discovery.
|
|
||||||
//
|
|
||||||
// https://js.waku.org/functions/lib_create_waku.createPrivacyNode.html
|
|
||||||
const waku = await createRelayNode({
|
|
||||||
emitSelf: true,
|
|
||||||
defaultBootstrap: true,
|
|
||||||
});
|
|
||||||
await waku.start();
|
|
||||||
|
|
||||||
// Add a hook to process all incoming messages on a specified content topic.
|
|
||||||
//
|
|
||||||
// https://js.waku.org/classes/index.waku_relay.WakuRelay.html#addObserver
|
|
||||||
waku.relay.subscribe(
|
|
||||||
decoder,
|
|
||||||
(message) => {
|
|
||||||
// Checks there is a payload on the message.
|
|
||||||
// Waku Message is encoded in protobuf, in proto v3 fields are always optional.
|
|
||||||
//
|
|
||||||
// https://js.waku.org/interfaces/index.proto_message.WakuMessage-1.html#payload
|
|
||||||
if (!message.payload) return;
|
|
||||||
|
|
||||||
// Helper method to decode the payload to utf-8. A production dApp should
|
|
||||||
// use `wakuMessage.payload` (Uint8Array) which enables encoding a data
|
|
||||||
// structure of their choice.
|
|
||||||
//
|
|
||||||
// https://js.waku.org/functions/index.utils.bytesToUtf8.html
|
|
||||||
const text = bytesToUtf8(message.payload);
|
|
||||||
messagesDiv.innerHTML =
|
|
||||||
`<p>${text}</p><br />` + messagesDiv.innerHTML;
|
|
||||||
},
|
|
||||||
[contentTopic]
|
|
||||||
);
|
|
||||||
|
|
||||||
statusDiv.innerHTML = "<p>Connecting to a peer</p>";
|
|
||||||
|
|
||||||
// Best effort method that waits for the Waku node to be connected to remote
|
|
||||||
// waku nodes (peers) and for appropriate handshakes to be done.
|
|
||||||
//
|
|
||||||
// https://js.waku.org/functions/lib_wait_for_remote_peer.waitForRemotePeer.html
|
|
||||||
await waitForRemotePeer(waku);
|
|
||||||
|
|
||||||
// We are now connected to a remote peer, let's define the `sendMessage`
|
|
||||||
// function that sends the text input over Waku Relay, the gossipsub
|
|
||||||
// protocol.
|
|
||||||
sendButton.onclick = async () => {
|
|
||||||
const payload = utf8ToBytes(textInput.value);
|
|
||||||
await waku.relay.send(encoder, { payload });
|
|
||||||
console.log("Message sent!");
|
|
||||||
|
|
||||||
// Reset the text input.
|
|
||||||
textInput.value = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ready to send & receive messages, enable text input.
|
|
||||||
textInput.disabled = false;
|
|
||||||
sendButton.disabled = false;
|
|
||||||
statusDiv.innerHTML = "<p>Ready!</p>";
|
|
||||||
} catch (e) {
|
|
||||||
statusDiv.innerHTML = "Failed to start application";
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Relay JS",
|
|
||||||
"description": "This example uses Waku Relay to send and receive simple text messages.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
|
@ -1 +0,0 @@
|
||||||
auto-install-peers=true
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Minimal ReactJS Waku Relay App
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Group chat
|
|
||||||
- React/JavaScript
|
|
||||||
- `create-react-app`/`react-scripts` 5.0.0
|
|
||||||
- Waku Relay
|
|
||||||
- Protobuf using `protobufjs`.
|
|
||||||
|
|
||||||
A barebone chat app to illustrate the seamless integration of `js-waku` into ReactJS.
|
|
||||||
|
|
||||||
The `master` branch's HEAD is deployed at https://examples.waku.org/relay-reactjs-chat/.
|
|
||||||
|
|
||||||
To run a development version locally, do:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/waku-org/js-waku-examples
|
|
||||||
cd js-waku-examples/examples/relay-reactjs-chat
|
|
||||||
npm install
|
|
||||||
npm run start
|
|
||||||
```
|
|
|
@ -1,28 +0,0 @@
|
||||||
const { getLoaders, loaderByName } = require("@craco/craco");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
webpack: {
|
|
||||||
configure: (webpackConfig) => {
|
|
||||||
const { hasFoundAny, matches } = getLoaders(
|
|
||||||
webpackConfig,
|
|
||||||
loaderByName("babel-loader")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasFoundAny) {
|
|
||||||
matches.forEach((c) => {
|
|
||||||
// Modify test to include cjs for @chainsafe/libp2p-gossipsub rpc module
|
|
||||||
if (c.loader.test.toString().includes("mjs")) {
|
|
||||||
// If your project uses typescript then do not forget to include `ts`/`tsx`
|
|
||||||
if (c.loader.test.toString().includes("jsx")) {
|
|
||||||
c.loader.test = /\.(js|cjs|mjs|jsx)$/;
|
|
||||||
} else {
|
|
||||||
c.loader.test = /\.(js|cjs|mjs)$/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return webpackConfig;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"name": "relay-reactjs-chat",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"homepage": "/relay-reactjs-chat",
|
|
||||||
"dependencies": {
|
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"@waku/sdk": "^0.0.18",
|
|
||||||
"protobufjs": "^7.1.2",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-scripts": "5.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "GENERATE_SOURCEMAP=false PORT=3001 craco start",
|
|
||||||
"build": "GENERATE_SOURCEMAP=false craco build",
|
|
||||||
"test": "exit 0",
|
|
||||||
"eject": "craco eject"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"crypto": false
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not ie <= 99",
|
|
||||||
"not android <= 4.4.4",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@craco/craco": "7.0.0",
|
|
||||||
"eslint": "^8.28.0"
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,43 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="A barebone chat app to illustrate the ReactJS Relay guide."
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.png" />
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React Relay</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Waku Relay",
|
|
||||||
"description": "A barebone chat app to illustrate the ReactJS Relay guide.",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "favicon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
|
@ -1,38 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import protobuf from "protobufjs";
|
|
||||||
import {
|
|
||||||
createRelayNode,
|
|
||||||
createDecoder,
|
|
||||||
createEncoder,
|
|
||||||
waitForRemotePeer,
|
|
||||||
} from "@waku/sdk";
|
|
||||||
|
|
||||||
const ContentTopic = `/js-waku-examples/1/chat/proto`;
|
|
||||||
const Encoder = createEncoder({ contentTopic: ContentTopic });
|
|
||||||
const Decoder = createDecoder(ContentTopic);
|
|
||||||
|
|
||||||
const SimpleChatMessage = new protobuf.Type("SimpleChatMessage")
|
|
||||||
.add(new protobuf.Field("timestamp", 1, "uint32"))
|
|
||||||
.add(new protobuf.Field("text", 2, "string"));
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [waku, setWaku] = React.useState(undefined);
|
|
||||||
const [wakuStatus, setWakuStatus] = React.useState("None");
|
|
||||||
// Using a counter just for the messages to be different
|
|
||||||
const [sendCounter, setSendCounter] = React.useState(0);
|
|
||||||
const [messages, setMessages] = React.useState([]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!!waku) return;
|
|
||||||
if (wakuStatus !== "None") return;
|
|
||||||
|
|
||||||
setWakuStatus("Starting");
|
|
||||||
(async () => {
|
|
||||||
const waku = await createRelayNode({ defaultBootstrap: true });
|
|
||||||
|
|
||||||
setWaku(waku);
|
|
||||||
await waku.start();
|
|
||||||
setWakuStatus("Connecting");
|
|
||||||
await waitForRemotePeer(waku, ["relay"]);
|
|
||||||
setWakuStatus("Ready");
|
|
||||||
})();
|
|
||||||
}, [waku, wakuStatus]);
|
|
||||||
|
|
||||||
const processIncomingMessage = React.useCallback((wakuMessage) => {
|
|
||||||
console.log("Message received", wakuMessage);
|
|
||||||
if (!wakuMessage.payload) return;
|
|
||||||
|
|
||||||
const { text, timestamp } = SimpleChatMessage.decode(wakuMessage.payload);
|
|
||||||
|
|
||||||
const time = new Date();
|
|
||||||
|
|
||||||
time.setTime(timestamp);
|
|
||||||
const message = { text, timestamp: time };
|
|
||||||
|
|
||||||
setMessages((messages) => {
|
|
||||||
return [message].concat(messages);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!waku) return;
|
|
||||||
|
|
||||||
// Pass the content topic to only process messages related to your dApp
|
|
||||||
const deleteObserver = waku.relay.subscribe(
|
|
||||||
Decoder,
|
|
||||||
processIncomingMessage
|
|
||||||
);
|
|
||||||
|
|
||||||
// Called when the component is unmounted, see ReactJS doc.
|
|
||||||
return deleteObserver;
|
|
||||||
}, [waku, wakuStatus, processIncomingMessage]);
|
|
||||||
|
|
||||||
const sendMessageOnClick = () => {
|
|
||||||
// Check Waku is started and connected first.
|
|
||||||
if (wakuStatus !== "Ready") return;
|
|
||||||
|
|
||||||
sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() =>
|
|
||||||
console.log("Message sent")
|
|
||||||
);
|
|
||||||
|
|
||||||
// For demonstration purposes.
|
|
||||||
setSendCounter(sendCounter + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="App">
|
|
||||||
<header className="App-header">
|
|
||||||
<p>{wakuStatus}</p>
|
|
||||||
<button onClick={sendMessageOnClick} disabled={wakuStatus !== "Ready"}>
|
|
||||||
Send Message
|
|
||||||
</button>
|
|
||||||
<ul>
|
|
||||||
{messages.map((msg) => {
|
|
||||||
return (
|
|
||||||
<li key={msg.timestamp.valueOf()}>
|
|
||||||
<p>
|
|
||||||
{msg.timestamp.toString()}: {msg.text}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessage(message, waku, timestamp) {
|
|
||||||
const time = timestamp.getTime();
|
|
||||||
|
|
||||||
// Encode to protobuf
|
|
||||||
const protoMsg = SimpleChatMessage.create({
|
|
||||||
timestamp: time,
|
|
||||||
text: message,
|
|
||||||
});
|
|
||||||
const payload = SimpleChatMessage.encode(protoMsg).finish();
|
|
||||||
|
|
||||||
// Send over Waku Relay
|
|
||||||
return waku.relay.send(Encoder, { payload });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import App from "./App";
|
|
||||||
|
|
||||||
test("renders learn react link", () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
|
||||||
monospace;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,5 +0,0 @@
|
||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import "@testing-library/jest-dom";
|
|
|
@ -1,135 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.1",
|
|
||||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
|
|
||||||
"language": "en",
|
|
||||||
"words": [
|
|
||||||
"Alives",
|
|
||||||
"asym",
|
|
||||||
"backoff",
|
|
||||||
"backoffs",
|
|
||||||
"bitauth",
|
|
||||||
"bitjson",
|
|
||||||
"bufbuild",
|
|
||||||
"chainsafe",
|
|
||||||
"cimg",
|
|
||||||
"ciphertext",
|
|
||||||
"circleci",
|
|
||||||
"codecov",
|
|
||||||
"commitlint",
|
|
||||||
"dependabot",
|
|
||||||
"Dialable",
|
|
||||||
"dingpu",
|
|
||||||
"Dlazy",
|
|
||||||
"dnsaddr",
|
|
||||||
"Dout",
|
|
||||||
"Dscore",
|
|
||||||
"ecies",
|
|
||||||
"editorconfig",
|
|
||||||
"enr",
|
|
||||||
"enrs",
|
|
||||||
"enrtree",
|
|
||||||
"ephem",
|
|
||||||
"esnext",
|
|
||||||
"ethersproject",
|
|
||||||
"execa",
|
|
||||||
"exponentiate",
|
|
||||||
"fanout",
|
|
||||||
"floodsub",
|
|
||||||
"fontsource",
|
|
||||||
"globby",
|
|
||||||
"gossipsub",
|
|
||||||
"huilong",
|
|
||||||
"iasked",
|
|
||||||
"ihave",
|
|
||||||
"ihaves",
|
|
||||||
"ineed",
|
|
||||||
"ipfs",
|
|
||||||
"iwant",
|
|
||||||
"jdev",
|
|
||||||
"jswaku",
|
|
||||||
"keccak",
|
|
||||||
"lastpub",
|
|
||||||
"libauth",
|
|
||||||
"libp",
|
|
||||||
"lightpush",
|
|
||||||
"livechat",
|
|
||||||
"mkdir",
|
|
||||||
"mplex",
|
|
||||||
"multiaddr",
|
|
||||||
"multiaddresses",
|
|
||||||
"multiaddrs",
|
|
||||||
"multicodec",
|
|
||||||
"multicodecs",
|
|
||||||
"multiformats",
|
|
||||||
"multihashes",
|
|
||||||
"muxed",
|
|
||||||
"muxer",
|
|
||||||
"muxers",
|
|
||||||
"mvps",
|
|
||||||
"nodekey",
|
|
||||||
"nwaku",
|
|
||||||
"opendns",
|
|
||||||
"peerhave",
|
|
||||||
"portfinder",
|
|
||||||
"prettierignore",
|
|
||||||
"proto",
|
|
||||||
"protobuf",
|
|
||||||
"protoc",
|
|
||||||
"reactjs",
|
|
||||||
"recid",
|
|
||||||
"rlnrelay",
|
|
||||||
"roadmap",
|
|
||||||
"sandboxed",
|
|
||||||
"scanf",
|
|
||||||
"secio",
|
|
||||||
"seckey",
|
|
||||||
"secp",
|
|
||||||
"sscanf",
|
|
||||||
"staticnode",
|
|
||||||
"statusim",
|
|
||||||
"submodule",
|
|
||||||
"submodules",
|
|
||||||
"supercrypto",
|
|
||||||
"transpiled",
|
|
||||||
"typedoc",
|
|
||||||
"unencrypted",
|
|
||||||
"unmarshal",
|
|
||||||
"unmount",
|
|
||||||
"unmounts",
|
|
||||||
"untracked",
|
|
||||||
"upgrader",
|
|
||||||
"vacp",
|
|
||||||
"varint",
|
|
||||||
"waku",
|
|
||||||
"wakuconnect",
|
|
||||||
"wakunode",
|
|
||||||
"wakuv",
|
|
||||||
"webfonts",
|
|
||||||
"websockets",
|
|
||||||
"wifi",
|
|
||||||
"xsalsa20"
|
|
||||||
],
|
|
||||||
"flagWords": [],
|
|
||||||
"ignorePaths": [
|
|
||||||
"package.json",
|
|
||||||
"package-lock.json",
|
|
||||||
"yarn.lock",
|
|
||||||
"tsconfig.json",
|
|
||||||
"node_modules/**",
|
|
||||||
"build",
|
|
||||||
"gen",
|
|
||||||
"proto",
|
|
||||||
"*.spec.ts"
|
|
||||||
],
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "import",
|
|
||||||
"pattern": "/import .*/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "enrtree",
|
|
||||||
"pattern": "/enrtree://.*/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignoreRegExpList": ["import", "enrtree"]
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Remove ReactJS warning about webpack
|
|
||||||
# because this is not a monorepo, ReactJS projects are examples
|
|
||||||
SKIP_PREFLIGHT_CHECK=true
|
|
|
@ -1,23 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
|
@ -1,3 +0,0 @@
|
||||||
# package.json is formatted by package managers, so we ignore it here
|
|
||||||
package.json
|
|
||||||
gen
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Web Chat App
|
|
||||||
|
|
||||||
**Demonstrates**:
|
|
||||||
|
|
||||||
- Group chat
|
|
||||||
- React/TypeScript
|
|
||||||
- Waku Filter
|
|
||||||
- Waku Light Push
|
|
||||||
- Waku Store
|
|
||||||
- Protobuf using `protons`
|
|
||||||
|
|
||||||
A ReactJS chat app is provided as a showcase of the library used in the browser.
|
|
||||||
It implements [Waku v2 Toy Chat](https://rfc.vac.dev/spec/22/) protocol.
|
|
||||||
A deployed version is available at https://examples.waku.org/web-chat/.
|
|
||||||
|
|
||||||
To run a development version locally, do:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/waku-org/js-waku-examples
|
|
||||||
cd js-waku-examples/examples/web-chat
|
|
||||||
npm install
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `/help` to see the available commands.
|
|
|
@ -1,6 +0,0 @@
|
||||||
version: v1beta1
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- name: ts_proto
|
|
||||||
out: ./src/proto
|
|
||||||
opt: grpc_js,esModuleInterop=true
|
|
|
@ -1,5 +0,0 @@
|
||||||
version: v1beta1
|
|
||||||
|
|
||||||
build:
|
|
||||||
roots:
|
|
||||||
- ./src/proto
|
|