feat: re-create dogfooding app with better UI and new Filter API (#131)

This commit is contained in:
Sasha 2025-06-02 18:39:36 +02:00 committed by GitHub
parent 90701b121b
commit dc935bc322
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 673 additions and 258 deletions

View File

@ -1,51 +1,62 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Sent Received Message Ratio</title> <title>Waku Dogfooding - Refactored</title>
<link rel="apple-touch-icon" href="./favicon.png" /> <link rel="apple-touch-icon" href="./favicon.png" />
<link rel="manifest" href="./manifest.json" /> <link rel="manifest" href="./manifest.json" />
<link rel="icon" href="./favicon.ico" /> <link rel="icon" href="./favicon.ico" />
<style> <link rel="stylesheet" href="./style.css" />
#container { </head>
display: flex; <body>
height: 100vh; <div class="app-container">
} <header>
#sender, <h1>Waku Message Center</h1>
#receiver { <div class="connection-status">
flex: 1; <span>Your Peer ID: <span id="peerIdDisplay">Connecting...</span></span>
display: flex; </div>
flex-direction: column; </header>
align-items: center;
justify-content: flex-start; <main>
} <section class="message-stats">
</style> <h2>Message Statistics</h2>
</head> <div class="stats-counters">
<body> <div>
<div> <span class="counter-value" id="sentByMeCount">0</span>
<h3>Waku Dogfooding App</h3> <span class="counter-label">Sent by Me</span>
<div id="runningScreen" style="display: none"> </div>
<label for="peerID">Your peer ID:</label> <div>
<span id="peerID"></span> <span class="counter-value" id="receivedMineCount">0</span>
<br /> <span class="counter-label">Received (Mine)</span>
<label for="numSent">Messages Sent:</label> </div>
<span id="numSent">0</span> <div>
<br /> <span class="counter-value" id="receivedOthersCount">0</span>
<label for="numReceived">Messages Received:</label> <span class="counter-label">Received (Others)</span>
<span id="numReceived">0</span> </div>
</div> </div>
</div> </section>
<div id="container">
<div id="sender"> <section class="message-controls">
<h3>Sent</h3> <h2>Controls</h2>
<div id="messagesSent"></div> <button id="sendMessageButton" class="btn btn-primary">Send New Message Batch</button>
</div> <div class="search-container">
<div id="receiver"> <input type="text" id="searchInput" placeholder="Search messages by content..." />
<h3>Received</h3> <button id="searchButton" class="btn">Search</button>
<div id="messagesReceived"></div> </div>
</div> </section>
<section class="message-display">
<h2>Message Log</h2>
<div id="messageList" class="message-list">
</div>
</section>
</main>
<footer>
<p>Waku Dogfooding App - Modern UI</p>
</footer>
</div> </div>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@libp2p/crypto": "^5.0.5", "@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1", "@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.31-3038c48.0", "@waku/sdk": "0.0.32-16328a3.0",
"libp2p": "^2.1.10", "libp2p": "^2.1.10",
"protobufjs": "^7.3.0", "protobufjs": "^7.3.0",
"uint8arrays": "^5.1.0" "uint8arrays": "^5.1.0"
@ -72,6 +72,23 @@
"wherearewe": "^2.0.1" "wherearewe": "^2.0.1"
} }
}, },
"node_modules/@chainsafe/libp2p-noise/node_modules/it-length-prefixed": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.1.1.tgz",
"integrity": "sha512-O88nBweT6M9ozsmok68/auKH7ik/slNM4pYbM9lrfy2z5QnpokW5SlrepHZDKtN71llhG2sZvd6uY4SAl+lAQg==",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"it-reader": "^6.0.1",
"it-stream-types": "^2.0.1",
"uint8-varint": "^2.0.1",
"uint8arraylist": "^2.0.0",
"uint8arrays": "^5.0.1"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@chainsafe/netmask": { "node_modules/@chainsafe/netmask": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz",
@ -1586,16 +1603,16 @@
] ]
}, },
"node_modules/@waku/discovery": { "node_modules/@waku/discovery": {
"version": "0.0.8-3038c48.0", "version": "0.0.9-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.8-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.9-16328a3.0.tgz",
"integrity": "sha512-+FnEs0nWkYtCGU4rfAmXQwwxH09WgzTLkM6t8tgplkjNNQ5TLyqptA1yt9gQKXL5ZEWVMbR8Ydzv86jS15Hizw==", "integrity": "sha512-s3tQpF4t0G/Fa5BCwO+92vf9gPczaz3YIGhrctdU1SYoS5CHhQ0JZ44HKHv/nL+eIzRKwimiGAk3RjLW7Z9pIw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@waku/core": "0.0.35-3038c48.0", "@waku/core": "0.0.36-16328a3.0",
"@waku/enr": "0.0.29-3038c48.0", "@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.30-3038c48.0", "@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.10-3038c48.0", "@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.23-3038c48.0", "@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"dns-over-http-resolver": "^3.0.8", "dns-over-http-resolver": "^3.0.8",
"hi-base32": "^0.5.1", "hi-base32": "^0.5.1",
@ -1606,16 +1623,16 @@
} }
}, },
"node_modules/@waku/discovery/node_modules/@waku/core": { "node_modules/@waku/discovery/node_modules/@waku/core": {
"version": "0.0.35-3038c48.0", "version": "0.0.36-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.35-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-16328a3.0.tgz",
"integrity": "sha512-N2YTKAL4ovl0pbiDr/FqVGtM41Lm+a5+Obk+FKUws/QlCHQK0RG8Qiders7UO5kWpuStm7Cynp8ngPp91U2nBQ==", "integrity": "sha512-+5GNNcy3FYnPKaL0RuJ01oo3+wMqkVhHPKdpRc3T0Rodi6IZsFCFdT1ob3A5rpFo5IURXqNjElUVBItIGrtupw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@libp2p/ping": "2.0.1", "@libp2p/ping": "2.0.1",
"@waku/enr": "0.0.29-3038c48.0", "@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.30-3038c48.0", "@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.10-3038c48.0", "@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.23-3038c48.0", "@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"it-all": "^3.0.4", "it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4", "it-length-prefixed": "^9.0.4",
@ -1689,9 +1706,9 @@
} }
}, },
"node_modules/@waku/enr": { "node_modules/@waku/enr": {
"version": "0.0.29-3038c48.0", "version": "0.0.30-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.29-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.30-16328a3.0.tgz",
"integrity": "sha512-ZT+8Lh5hmeCDuQ3gQQ9ve5pPkR/sZs1/g7IM8hQTPxbP663/GVrJ6d6jfHCAWmEFauP4FfVdsDl3UfphU7RD+g==", "integrity": "sha512-9D4WVJcuL/ncVsHb7KyeIkkDJia6UVdNK9Kf1u9BhvJBtzioPGgXrmp1O7CsXpIKVu4f8IVCu1Lc6hUVG84Chw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@ethersproject/rlp": "^5.7.0", "@ethersproject/rlp": "^5.7.0",
@ -1699,7 +1716,7 @@
"@libp2p/peer-id": "^5.0.1", "@libp2p/peer-id": "^5.0.1",
"@multiformats/multiaddr": "^12.0.0", "@multiformats/multiaddr": "^12.0.0",
"@noble/secp256k1": "^1.7.1", "@noble/secp256k1": "^1.7.1",
"@waku/utils": "0.0.23-3038c48.0", "@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"js-sha3": "^0.9.2" "js-sha3": "^0.9.2"
}, },
@ -1716,34 +1733,34 @@
} }
}, },
"node_modules/@waku/interfaces": { "node_modules/@waku/interfaces": {
"version": "0.0.30-3038c48.0", "version": "0.0.31-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.30-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.31-16328a3.0.tgz",
"integrity": "sha512-QsXga9S6cUh8culnWomZcRBRZZdkPDT5PNinKGcjqQAMpueyuXqeSCVVH41JUkuTKel4w7fXOP9qcr+KLn+I4Q==", "integrity": "sha512-2jZCwBSBKd7PX12xJadMmxMpIKGR9SrQTnTqUfDD4aT72GQ9SpaqQDY5h+svMt/qjriZMBSUpITQQF04w0GIaw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@waku/proto": "0.0.10-3038c48.0" "@waku/proto": "0.0.11-16328a3.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@waku/message-hash": { "node_modules/@waku/message-hash": {
"version": "0.1.19-3038c48.0", "version": "0.1.20-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/message-hash/-/message-hash-0.1.19-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/message-hash/-/message-hash-0.1.20-16328a3.0.tgz",
"integrity": "sha512-u3THyufk2yLnLNyfLbyh3iGls2EgR3TdpQ17rZz4lK73qYyMbT9Rw/bwFL+W+FVg8eKCeBfgXjou2NuqNPLvdA==", "integrity": "sha512-j1FHSgeJeKnc9Tet3/NcrNjOV1gKa6U3WXNe/uBRZsvn2P30Qg6a1Am2eMfxpmO3ggXz25ywe2WDNA/K4vk/RQ==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@noble/hashes": "^1.3.2", "@noble/hashes": "^1.3.2",
"@waku/utils": "0.0.23-3038c48.0" "@waku/utils": "0.0.24-16328a3.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@waku/proto": { "node_modules/@waku/proto": {
"version": "0.0.10-3038c48.0", "version": "0.0.11-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.10-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.11-16328a3.0.tgz",
"integrity": "sha512-xwStT7V2BsP9i4y0+sV5jCqBAuiuqPgHIz2vO4bGClVFwe5HCMjHbkWNZp0MVj4OWI3EqXmxakcirtBzT3gbOw==", "integrity": "sha512-yFGW6UaQC0MrffZV25uVDYC8sUxI6OpnizTfSyebLg84eihYHJyxTed7pD/EyjSl8vL9GFX8nDuksDbkIQ2OQw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"protons-runtime": "^5.4.0" "protons-runtime": "^5.4.0"
@ -1753,9 +1770,9 @@
} }
}, },
"node_modules/@waku/sdk": { "node_modules/@waku/sdk": {
"version": "0.0.31-3038c48.0", "version": "0.0.32-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.31-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.32-16328a3.0.tgz",
"integrity": "sha512-+ZkejfevYPQpIyxiUGeGxOYZHnl0VgwvWzJ/SQiBXP4YusLTRZJgulFwTcWzFlL8iBwdEptg9A2Ju/BC6IxVQA==", "integrity": "sha512-tHS2+lf7NMQekuxfrZOGsMWya35W8saxjMjPnIRE6FOq0Mlv1ADwso05W7iy9ExNJoFD3+4XVemDvc5RfIM75w==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@chainsafe/libp2p-noise": "16.0.0", "@chainsafe/libp2p-noise": "16.0.0",
@ -1765,12 +1782,12 @@
"@libp2p/ping": "2.0.1", "@libp2p/ping": "2.0.1",
"@libp2p/websockets": "^9.0.1", "@libp2p/websockets": "^9.0.1",
"@noble/hashes": "^1.3.3", "@noble/hashes": "^1.3.3",
"@waku/core": "0.0.35-3038c48.0", "@waku/core": "0.0.36-16328a3.0",
"@waku/discovery": "0.0.8-3038c48.0", "@waku/discovery": "0.0.9-16328a3.0",
"@waku/interfaces": "0.0.30-3038c48.0", "@waku/interfaces": "0.0.31-16328a3.0",
"@waku/message-hash": "0.1.19-3038c48.0", "@waku/message-hash": "0.1.20-16328a3.0",
"@waku/proto": "0.0.10-3038c48.0", "@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.23-3038c48.0", "@waku/utils": "0.0.24-16328a3.0",
"libp2p": "2.1.8" "libp2p": "2.1.8"
}, },
"engines": { "engines": {
@ -1778,16 +1795,16 @@
} }
}, },
"node_modules/@waku/sdk/node_modules/@waku/core": { "node_modules/@waku/sdk/node_modules/@waku/core": {
"version": "0.0.35-3038c48.0", "version": "0.0.36-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.35-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-16328a3.0.tgz",
"integrity": "sha512-N2YTKAL4ovl0pbiDr/FqVGtM41Lm+a5+Obk+FKUws/QlCHQK0RG8Qiders7UO5kWpuStm7Cynp8ngPp91U2nBQ==", "integrity": "sha512-+5GNNcy3FYnPKaL0RuJ01oo3+wMqkVhHPKdpRc3T0Rodi6IZsFCFdT1ob3A5rpFo5IURXqNjElUVBItIGrtupw==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@libp2p/ping": "2.0.1", "@libp2p/ping": "2.0.1",
"@waku/enr": "0.0.29-3038c48.0", "@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.30-3038c48.0", "@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.10-3038c48.0", "@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.23-3038c48.0", "@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"it-all": "^3.0.4", "it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4", "it-length-prefixed": "^9.0.4",
@ -1859,13 +1876,13 @@
} }
}, },
"node_modules/@waku/utils": { "node_modules/@waku/utils": {
"version": "0.0.23-3038c48.0", "version": "0.0.24-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.23-3038c48.0.tgz", "resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.24-16328a3.0.tgz",
"integrity": "sha512-vLoMRH6/wOx+Jx4522D6zeuf4v7CRzaOxhZYFv+lxJFEdTZr7C4ruuHVSdo5VdMx/u6Ad9oNtfqhygppcWyxpw==", "integrity": "sha512-zYNeWtRAYGJLVvE/8xnKmO5ctxuPD6/k9NAS/g3ES0o++kBrXNRFy3d1V1nKlnerwJuwYmB0RhfwFmz7ehtwWA==",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@noble/hashes": "^1.3.2", "@noble/hashes": "^1.3.2",
"@waku/interfaces": "0.0.30-3038c48.0", "@waku/interfaces": "0.0.31-16328a3.0",
"chai": "^4.3.10", "chai": "^4.3.10",
"debug": "^4.3.4", "debug": "^4.3.4",
"uint8arrays": "^5.0.1" "uint8arrays": "^5.0.1"
@ -3393,9 +3410,9 @@
} }
}, },
"node_modules/dns-over-http-resolver": { "node_modules/dns-over-http-resolver": {
"version": "3.0.10", "version": "3.0.15",
"resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.10.tgz", "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.15.tgz",
"integrity": "sha512-l2kMOLxK6f9ll+5sf2Ndl8WS/2eXhOf9ZSXZMPnTVyHsv1ktN1WX3FwcyYklMq3ORv2N1nhf0TsGKWBjhgn0ug==", "integrity": "sha512-h2Ldu6b8LjW725Q5zjjv7T5s1K3dPjlU3DWvcEFqB3Ksb3QmqC4dHhPKlGlBS/1P47D4T5arZMiE4dD4OIfO6A==",
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"dependencies": { "dependencies": {
"quick-lru": "^7.0.0", "quick-lru": "^7.0.0",
@ -6022,9 +6039,9 @@
} }
}, },
"node_modules/it-first": { "node_modules/it-first": {
"version": "3.0.7", "version": "3.0.8",
"resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.7.tgz", "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.8.tgz",
"integrity": "sha512-e2dVSlOP+pAxPYPVJBF4fX7au8cvGfvLhIrGCMc5aWDnCvwgOo94xHbi3Da6eXQ2jPL5FGEM8sJMn5uE8Seu+g==", "integrity": "sha512-neaRRwOMCmMKkXJVZ4bvUDVlde+Xh0aTWr7hFaOZeDXzbctGVV/WHmPVqBqy3RjlsP7eRM0vcqNtlM8hivcmGw==",
"license": "Apache-2.0 OR MIT" "license": "Apache-2.0 OR MIT"
}, },
"node_modules/it-foreach": { "node_modules/it-foreach": {

View File

@ -9,7 +9,7 @@
"dependencies": { "dependencies": {
"@libp2p/crypto": "^5.0.5", "@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1", "@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.31-3038c48.0", "@waku/sdk": "0.0.32-16328a3.0",
"libp2p": "^2.1.10", "libp2p": "^2.1.10",
"protobufjs": "^7.3.0", "protobufjs": "^7.3.0",
"uint8arrays": "^5.1.0" "uint8arrays": "^5.1.0"

View File

@ -0,0 +1,187 @@
/* General Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
background-color: #f4f7f9;
color: #333;
line-height: 1.6;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #2c3e50;
color: #ecf0f1;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 {
margin: 0;
font-size: 1.8em;
}
.connection-status span {
font-size: 0.9em;
}
main {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
section {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h2 {
margin-top: 0;
color: #3498db;
border-bottom: 2px solid #ecf0f1;
padding-bottom: 10px;
margin-bottom: 15px;
}
/* Message Statistics */
.stats-counters {
display: flex;
justify-content: space-around;
text-align: center;
}
.counter-value {
display: block;
font-size: 2em;
font-weight: bold;
color: #2980b9;
}
.counter-label {
font-size: 0.9em;
color: #555;
}
/* Message Controls */
.btn {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.3s ease;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.search-container {
margin-top: 15px;
display: flex;
gap: 10px;
}
#searchInput {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 0.9em;
}
/* Message Display */
.message-list {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ecf0f1;
border-radius: 5px;
padding: 10px;
}
.message-item {
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
background-color: #ecf0f1;
font-size: 0.9em;
}
.message-item.sent {
background-color: #eafaf1; /* Light green for sent */
}
.message-item.received-mine {
background-color: #e8f6fd; /* Light blue for received (mine) */
}
.message-item.received-other {
background-color: #fdf3e8; /* Light orange for received (other) */
}
.message-item p {
margin: 5px 0;
}
.message-item .timestamp {
font-size: 0.8em;
color: #7f8c8d;
text-align: right;
}
.message-item .content {
font-weight: 500;
}
.message-item .sender-info {
font-size: 0.8em;
color: #34495e;
}
/* Added style for Message ID */
.message-item .message-id {
font-size: 0.75em;
color: #888;
margin-bottom: 3px;
word-break: break-all; /* Ensure long IDs don't break layout */
}
footer {
text-align: center;
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid #ecf0f1;
font-size: 0.9em;
color: #7f8c8d;
}
/* Responsive Design */
@media (min-width: 768px) {
main {
grid-template-columns: repeat(2, 1fr);
}
.message-stats, .message-controls {
grid-column: span 1;
}
.message-display {
grid-column: span 2;
}
}

View File

@ -1,161 +1,139 @@
import { import {
createLightNode, getWakuNode,
DecodedMessage, createWakuEncoder,
LightNode, createWakuDecoder,
} from "@waku/sdk"; getPeerId
import { generateKeyPairFromSeed } from "@libp2p/crypto/keys"; } from "./waku-service";
import { fromString } from "uint8arrays"; import { DecodedMessage } from "@waku/sdk";
import { Type, Field } from "protobufjs";
import { import {
generateRandomNumber, encodeMessage,
sha256, decodeMessage,
} from "./util"; ChatMessage,
ProtoChatMessage,
} from "./message-service";
import {
updatePeerIdDisplay,
incrementSentByMe,
incrementReceivedMine,
incrementReceivedOthers,
addMessageToLog,
renderMessages,
getSearchTerm,
} from "./ui-manager";
const DEFAULT_CONTENT_TOPIC = "/js-waku-examples/1/message-ratio/utf8"; const NUM_MESSAGES_PER_BATCH = 5;
let batchCounter = 0;
const ProtoSequencedMessage = new Type("SequencedMessage") async function initializeApp() {
.add(new Field("hash", 1, "string")) try {
.add(new Field("total", 2, "uint64")) console.log("Initializing Waku node...");
.add(new Field("index", 3, "uint64")) const node = await getWakuNode();
.add(new Field("sender", 4, "string")); const currentPeerId = getPeerId();
console.log("Waku node initialized. Peer ID:", currentPeerId);
const sequenceCompletedEvent = new CustomEvent("sequenceCompleted"); if (currentPeerId) {
const messageReceivedEvent = new CustomEvent("messageReceived"); updatePeerIdDisplay(currentPeerId);
}
async function wakuNode(): Promise<LightNode> { const sendMessageBatch = async () => {
let seed = localStorage.getItem("seed"); const encoder = createWakuEncoder();
batchCounter++;
console.log(`Sending batch C${batchCounter} of ${NUM_MESSAGES_PER_BATCH} messages...`);
for (let i = 0; i < NUM_MESSAGES_PER_BATCH; i++) {
const messageContent = `Batch ${batchCounter} - Msg ${i + 1} @ ${new Date().toLocaleTimeString()}`;
const payload = encodeMessage(messageContent);
if (!seed) { const tempDecodedMessage = ProtoChatMessage.decode(payload);
seed = (await sha256(generateRandomNumber())).slice(0, 32); const messageId = (tempDecodedMessage as any).id || `temp-id-${Date.now()}`;
localStorage.setItem("seed", seed);
}
const privateKey = await generateKeyPairFromSeed("Ed25519", fromString(seed)); const chatMessage: ChatMessage = {
id: messageId,
timestamp: Date.now(),
senderPeerId: currentPeerId || "unknown",
content: messageContent
};
const node = await createLightNode({ try {
defaultBootstrap: false, const result = await node.lightPush.send(encoder, {
numPeersToUse: 2,
networkConfig: {
clusterId: 42,
shards: [0]
},
libp2p: {
privateKey,
},
});
(window as any).waku = node;
await node.dial("/dns4/waku-test.bloxy.one/tcp/8095/wss/p2p/16Uiu2HAmSZbDB7CusdRhgkD81VssRjQV5ZH13FbzCGcdnbbh6VwZ");
await node.dial("/dns4/vps-aaa00d52.vps.ovh.ca/tcp/8000/wss/p2p/16Uiu2HAm9PftGgHZwWE3wzdMde4m3kT2eYJFXLZfGoSED3gysofk");
await node.dial("/dns4/waku.fryorcraken.xyz/tcp/8000/wss/p2p/16Uiu2HAmMRvhDHrtiHft1FTUYnn6cVA8AWVrTyLUayJJ3MWpUZDB");
return node;
}
export async function app() {
const node = await wakuNode();
console.log("DEBUG: your peer ID is:", node.libp2p.peerId.toString());
await node.start();
await node.waitForPeers();
const peerId = node.libp2p.peerId.toString();
const encoder = node.createEncoder({
contentTopic: DEFAULT_CONTENT_TOPIC
});
const startLightPushSequence = async (
numMessages: number,
period: number = 3000
) => {
const sequenceHash = await sha256(generateRandomNumber());
const sequenceTotal = numMessages;
let sequenceIndex = 0;
const sendMessage = async () => {
try {
const messageHash = await sha256(
`${sequenceHash}-${sequenceIndex}-${sequenceTotal}`
);
const timestamp = Math.floor(new Date().getTime() / 1000);
const message = ProtoSequencedMessage.create({
hash: messageHash,
total: sequenceTotal,
index: sequenceIndex,
sender: peerId,
});
const payload = ProtoSequencedMessage.encode(message).finish();
const result = await node.lightPush.send(
encoder,
{
payload, payload,
timestamp: new Date(), timestamp: new Date(chatMessage.timestamp),
}, }, { autoRetry: true });
{ autoRetry: true }
);
console.log("DEBUG: light push successes: ", result.successes.length); if (result.successes.length > 0) {
console.log( console.log(`Message ${i + 1} (ID: ${chatMessage.id}) sent successfully.`);
"DEBUG: light push failures: ", incrementSentByMe();
result.failures.length addMessageToLog(chatMessage, 'sent');
); } else {
console.warn(`Failed to send message ${i + 1} (ID: ${chatMessage.id}):`, result.failures);
// Increment sequence }
sequenceIndex++; } catch (error) {
console.error(`Error sending message ${i + 1} (ID: ${chatMessage.id}):`, error);
if (sequenceIndex < sequenceTotal) {
setTimeout(sendMessage, period); // Schedule the next send
} else {
document.dispatchEvent(sequenceCompletedEvent);
} }
} catch (error) { await new Promise(resolve => setTimeout(resolve, 100));
console.error("DEBUG: Error sending message", error);
} }
console.log("Message batch sending complete.");
}; };
sendMessage(); // Start the recursive sending const subscribeToMessages = async () => {
}; const decoder = createWakuDecoder();
console.log("Subscribing to messages...");
await node.filter.subscribe(decoder, (wakuMessage: DecodedMessage) => {
console.log("Raw Waku message received, payload length:", wakuMessage.payload.length);
const chatMessage = decodeMessage(wakuMessage.payload);
const startFilterSubscription = async () => { if (chatMessage) {
const decoder = node.createDecoder({ contentTopic: DEFAULT_CONTENT_TOPIC }); console.log("Decoded chat message:", chatMessage);
if (chatMessage.senderPeerId === currentPeerId) {
const subscriptionCallback = async (message: DecodedMessage) => { incrementReceivedMine();
const decodedMessage: any = ProtoSequencedMessage.decode( console.log("Received own message (loopback):", chatMessage.id);
message.payload } else {
); incrementReceivedOthers();
addMessageToLog(chatMessage, 'received-other');
if (decodedMessage.sender === peerId) { console.log("Received message from other peer:", chatMessage.id);
return; }
} } else {
console.warn("Could not decode received Waku message. Payload might be malformed or not a ChatMessage.");
const messageElement = document.createElement("div"); }
messageElement.textContent = `Message: ${decodedMessage.hash}`; });
document.dispatchEvent(messageReceivedEvent); console.log("Subscription active.");
}; };
await node.filter.subscribe(decoder, subscriptionCallback); const sendMessageButton = document.getElementById("sendMessageButton");
}; if (sendMessageButton) {
sendMessageButton.addEventListener("click", () => {
console.log("Send Message Button clicked");
sendMessageBatch();
});
}
return { const searchButton = document.getElementById("searchButton");
node, if (searchButton) {
startLightPushSequence, searchButton.addEventListener("click", () => {
startFilterSubscription, console.log("Search button clicked");
}; renderMessages(getSearchTerm());
});
}
const searchInput = document.getElementById("searchInput");
if(searchInput) {
searchInput.addEventListener("input", () => {
console.log("Search input changed");
renderMessages(getSearchTerm());
});
}
await subscribeToMessages();
console.log("Application setup complete. Click 'Send New Message Batch' to send messages.");
} catch (error) {
console.error("Critical error during app initialization:", error);
const peerIdDisplayEl = document.getElementById("peerIdDisplay");
if(peerIdDisplayEl) peerIdDisplayEl.textContent = "Error connecting to Waku Network.";
}
} }
(async () => { document.addEventListener("DOMContentLoaded", () => {
const { startLightPushSequence, startFilterSubscription } = await app(); console.log("DOM fully loaded and parsed. Starting app initialization.");
initializeApp();
startFilterSubscription(); });
document.addEventListener(sequenceCompletedEvent.type, () =>
startLightPushSequence(10, 3000)
);
startLightPushSequence(10, 3000);
})();

View File

@ -0,0 +1,42 @@
import { Type, Field } from "protobufjs";
import { getPeerId } from "./waku-service";
// New message structure with a unique ID and content for searchability
export const ProtoChatMessage = new Type("ChatMessage")
.add(new Field("id", 1, "string")) // Unique message ID (e.g., UUID or timestamp-based)
.add(new Field("timestamp", 2, "uint64"))
.add(new Field("senderPeerId", 3, "string"))
.add(new Field("content", 4, "string")); // Actual message content
export interface ChatMessage {
id: string;
timestamp: number;
senderPeerId: string;
content: string;
}
export function encodeMessage(content: string): Uint8Array {
const id = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const message = ProtoChatMessage.create({
id,
timestamp: Date.now(),
senderPeerId: getPeerId() || "unknown",
content,
});
return ProtoChatMessage.encode(message).finish();
}
export function decodeMessage(payload: Uint8Array): ChatMessage | null {
try {
const decoded = ProtoChatMessage.decode(payload) as any;
return {
id: decoded.id,
timestamp: Number(decoded.timestamp),
senderPeerId: decoded.senderPeerId,
content: decoded.content,
};
} catch (error) {
console.error("Failed to decode message:", error);
return null;
}
}

View File

@ -0,0 +1,105 @@
import { ChatMessage } from "./message-service";
const sentByMeCountEl = document.getElementById("sentByMeCount") as HTMLSpanElement;
const receivedMineCountEl = document.getElementById("receivedMineCount") as HTMLSpanElement;
const receivedOthersCountEl = document.getElementById("receivedOthersCount") as HTMLSpanElement;
const peerIdDisplayEl = document.getElementById("peerIdDisplay") as HTMLSpanElement;
const messageListEl = document.getElementById("messageList") as HTMLDivElement;
const searchInputEl = document.getElementById("searchInput") as HTMLInputElement;
let sentByMe = 0;
let receivedMine = 0;
let receivedOthers = 0;
let currentMessages: ChatMessage[] = [];
let currentPeerId: string | undefined;
export function updatePeerIdDisplay(peerId: string) {
currentPeerId = peerId;
if (peerIdDisplayEl) {
peerIdDisplayEl.textContent = peerId;
}
}
export function incrementSentByMe() {
sentByMe++;
if (sentByMeCountEl) sentByMeCountEl.textContent = sentByMe.toString();
}
export function incrementReceivedMine() {
receivedMine++;
if (receivedMineCountEl) receivedMineCountEl.textContent = receivedMine.toString();
}
export function incrementReceivedOthers() {
receivedOthers++;
if (receivedOthersCountEl) receivedOthersCountEl.textContent = receivedOthers.toString();
}
export function addMessageToLog(message: ChatMessage, type: 'sent' | 'received-mine' | 'received-other') {
currentMessages.push(message);
renderMessages();
}
export function renderMessages(filterText?: string) {
if (!messageListEl) return;
messageListEl.innerHTML = "";
const messagesToRender = filterText
? currentMessages.filter(msg => {
const searchTerm = filterText.toLowerCase();
return (
msg.content.toLowerCase().includes(searchTerm) ||
msg.id.toLowerCase().includes(searchTerm) ||
msg.senderPeerId.toLowerCase().includes(searchTerm)
);
})
: currentMessages;
messagesToRender.sort((a, b) => a.timestamp - b.timestamp);
messagesToRender.forEach(message => {
const item = document.createElement("div");
item.classList.add("message-item");
let typeClass = '';
let senderPrefix = '';
if (message.senderPeerId === currentPeerId) {
typeClass = 'sent';
senderPrefix = 'Me';
} else {
typeClass = 'received-other';
senderPrefix = `Other (${message.senderPeerId.substring(0, 6)}...)`;
}
item.classList.add(typeClass);
const idText = document.createElement("p");
idText.classList.add("message-id");
idText.textContent = `ID: ${message.id}`;
const contentP = document.createElement("p");
contentP.classList.add("content");
contentP.textContent = message.content;
const senderInfoP = document.createElement("p");
senderInfoP.classList.add("sender-info");
senderInfoP.textContent = `From: ${senderPrefix}`;
const timestampP = document.createElement("p");
timestampP.classList.add("timestamp");
timestampP.textContent = new Date(message.timestamp).toLocaleTimeString();
item.appendChild(idText);
item.appendChild(senderInfoP);
item.appendChild(contentP);
item.appendChild(timestampP);
messageListEl.appendChild(item);
});
}
export function getSearchTerm(): string {
return searchInputEl ? searchInputEl.value : "";
}

View File

@ -3,10 +3,10 @@ export const generateRandomNumber = (): number => {
}; };
export const sha256 = async (number: number | string ): Promise<string> => { export const sha256 = async (number: number | string ): Promise<string> => {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const data = encoder.encode(number.toString()); const data = encoder.encode(number.toString());
const buffer = await crypto.subtle.digest("SHA-256", data); const buffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(buffer)) return Array.from(new Uint8Array(buffer))
.map((b) => b.toString(16).padStart(2, "0")) .map((b) => b.toString(16).padStart(2, "0"))
.join(""); .join("");
}; };

View File

@ -0,0 +1,12 @@
export async function sha256(text: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
return hashHex;
}
export function generateRandomNumber(): string {
return Math.random().toString();
}

View File

@ -0,0 +1,64 @@
import { LightNode, createLightNode, createEncoder, createDecoder } from "@waku/sdk";
import { generateKeyPairFromSeed } from "@libp2p/crypto/keys";
import { fromString } from "uint8arrays";
import { sha256, generateRandomNumber } from "./utils";
export const DEFAULT_CONTENT_TOPIC = "/js-waku-examples/1/message-ratio/utf8";
let wakuNodeInstance: LightNode | null = null;
export async function getWakuNode(): Promise<LightNode> {
if (wakuNodeInstance) {
return wakuNodeInstance;
}
let seed = localStorage.getItem("seed");
if (!seed) {
seed = (await sha256(generateRandomNumber())).slice(0, 32);
localStorage.setItem("seed", seed);
}
const privateKey = await generateKeyPairFromSeed("Ed25519", fromString(seed));
const node = await createLightNode({
defaultBootstrap: false,
networkConfig: {
clusterId: 42,
shards: [0]
},
numPeersToUse: 2,
libp2p: {
privateKey,
},
});
await Promise.allSettled([
node.dial("/dns4/waku-test.bloxy.one/tcp/8095/wss/p2p/16Uiu2HAmSZbDB7CusdRhgkD81VssRjQV5ZH13FbzCGcdnbbh6VwZ"),
node.dial("/dns4/vps-aaa00d52.vps.ovh.ca/tcp/8000/wss/p2p/16Uiu2HAm9PftGgHZwWE3wzdMde4m3kT2eYJFXLZfGoSED3gysofk")
]);
await node.start();
await node.waitForPeers();
wakuNodeInstance = node;
(window as any).waku = node;
return node;
}
export function getPeerId(): string | undefined {
return wakuNodeInstance?.libp2p.peerId.toString();
}
export function createWakuEncoder() {
return createEncoder({
contentTopic: DEFAULT_CONTENT_TOPIC,
pubsubTopicShardInfo: {
clusterId: 42,
shard: 0,
}
});
}
export function createWakuDecoder() {
return createDecoder(DEFAULT_CONTENT_TOPIC, { clusterId: 42, shard: 0 });
}

View File

@ -1,25 +1,22 @@
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require("path");
module.exports = { module.exports = {
entry: "./src/index.ts", // Changed from index.js to index.ts entry: "./src/index.ts",
output: { output: {
path: path.resolve(__dirname, "build"), path: path.resolve(__dirname, "build"),
filename: "index.js", filename: "index.js",
publicPath: process.env.NODE_ENV === 'production' ? '/dogfooding/' : '/'
}, },
experiments: { experiments: {
asyncWebAssembly: true, asyncWebAssembly: true,
}, },
resolve: { resolve: {
extensions: ['.ts', '.js'], // Add .ts to the extensions extensions: ['.ts', '.js'],
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, // Add a rule for TypeScript files test: /\.ts$/,
use: 'ts-loader', use: 'ts-loader',
exclude: /node_modules/, exclude: /node_modules/,
}, },
@ -32,11 +29,13 @@ module.exports = {
}, },
plugins: [ plugins: [
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: ["favicon.ico", "favicon.png", "manifest.json"], patterns: [
{ from: "index.html", to: "index.html" },
{ from: "public/style.css", to: "style.css" },
{ from: "manifest.json", to: "manifest.json" },
{ from: "favicon.ico", to: "favicon.ico" },
{ from: "favicon.png", to: "favicon.png" },
],
}), }),
new HtmlWebpackPlugin({
template: 'index.html',
base: process.env.NODE_ENV === 'production' ? '/dogfooding/' : '/',
})
], ],
}; };