feat: remove lab.waku.org related examples (#295)
|
@ -14,13 +14,7 @@ jobs:
|
|||
example:
|
||||
[
|
||||
eth-pm,
|
||||
relay-angular-chat,
|
||||
relay-reactjs-chat,
|
||||
store-reactjs-chat,
|
||||
web-chat,
|
||||
noise-js,
|
||||
noise-rtc,
|
||||
relay-direct-rtc
|
||||
store-reactjs-chat
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
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.
|
||||
|
||||
### Web Chat App
|
||||
|
||||
- [code](examples/web-chat)
|
||||
- [website](https://examples.waku.org/web-chat)
|
||||
- Demonstrates: Group chat, React/TypeScript, Relay, Store.
|
||||
|
||||
### Ethereum Private Messaging
|
||||
|
||||
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)
|
||||
- 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
|
||||
|
||||
> Rate limiting nullifier (RLN) is a construct based on zero-knowledge proofs
|
||||
|
|
|
@ -36,21 +36,13 @@ pipeline {
|
|||
stage('Examples') {
|
||||
parallel {
|
||||
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('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') {
|
||||
parallel {
|
||||
stage('relay-js') { steps { script { copyExample() } } }
|
||||
stage('store-js') { steps { script { copyExample() } } }
|
||||
stage('light-js') { 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
|