implement new ui

This commit is contained in:
Sasha 2025-05-31 17:52:48 +02:00
parent 549eb9963a
commit 4256472ac6
No known key found for this signature in database
10 changed files with 682 additions and 278 deletions

View File

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

View File

@ -10,7 +10,7 @@
"dependencies": {
"@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.30-0e49a1e.0",
"@waku/sdk": "0.0.32-16328a3.0",
"libp2p": "^2.1.10",
"protobufjs": "^7.3.0",
"uint8arrays": "^5.1.0"
@ -35,9 +35,9 @@
"license": "Apache-2.0"
},
"node_modules/@chainsafe/as-sha256": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.0.1.tgz",
"integrity": "sha512-4Y/kQm0LsJ6QRtGcMq6gOdQP+fZhWDfIV2eIqP6oFJZBWYGmdh3wm8YbrXDPLJO87X2Fu6koRLdUS00O3k14Hw==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.4.2.tgz",
"integrity": "sha512-HJ8GZBRjLeWtRsAXf3EbNsNzmTGpzTFjfpSf4yHkLYC+E52DhT6hwz+7qpj6I/EmFzSUm5tYYvT9K8GZokLQCQ==",
"license": "Apache-2.0"
},
"node_modules/@chainsafe/is-ip": {
@ -47,20 +47,20 @@
"license": "MIT"
},
"node_modules/@chainsafe/libp2p-noise": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-16.1.0.tgz",
"integrity": "sha512-GJA/i5pd6VmetxokvnPlEbVCeL7SfLHkSuUHwbJ4w0u7dZUbse4Hr8SA8RYGwNHbZr2TEKFC9WerhvMWbciIrQ==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-16.0.0.tgz",
"integrity": "sha512-8rqr8V1RD2/lVbfL0Bb//N8iPOFof11cUe8v8z8xJT7fUhCAbtCCSM4jbwI4HCnw0MvHLmcpmAfDCFRwcWzoeA==",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"@chainsafe/as-chacha20poly1305": "^0.1.0",
"@chainsafe/as-sha256": "^1.0.0",
"@chainsafe/as-sha256": "^0.4.1",
"@libp2p/crypto": "^5.0.0",
"@libp2p/interface": "^2.0.0",
"@libp2p/peer-id": "^5.0.0",
"@noble/ciphers": "^1.1.3",
"@noble/ciphers": "^0.6.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"it-length-prefixed": "^10.0.1",
"it-length-prefixed": "^9.0.1",
"it-length-prefixed-stream": "^1.0.0",
"it-pair": "^2.0.6",
"it-pipe": "^3.0.1",
@ -71,6 +71,23 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz",
@ -680,13 +697,10 @@
}
},
"node_modules/@noble/ciphers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
@ -719,9 +733,9 @@
}
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz",
"integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==",
"funding": [
{
"type": "individual",
@ -1513,16 +1527,16 @@
]
},
"node_modules/@waku/discovery": {
"version": "0.0.7-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.7-0e49a1e.0.tgz",
"integrity": "sha512-dHj/0w11V+Fkfl7T0MqJ9VEINAvuleU8vJ1hLifBbg7lbUWFe8b24qzPNzhqsxV93cLR+okhM7gj0N2yeVpaOQ==",
"version": "0.0.9-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.9-16328a3.0.tgz",
"integrity": "sha512-s3tQpF4t0G/Fa5BCwO+92vf9gPczaz3YIGhrctdU1SYoS5CHhQ0JZ44HKHv/nL+eIzRKwimiGAk3RjLW7Z9pIw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@waku/core": "0.0.34-0e49a1e.0",
"@waku/enr": "0.0.28-0e49a1e.0",
"@waku/interfaces": "0.0.29-0e49a1e.0",
"@waku/proto": "0.0.9-0e49a1e.0",
"@waku/utils": "0.0.22-0e49a1e.0",
"@waku/core": "0.0.36-16328a3.0",
"@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4",
"dns-over-http-resolver": "^3.0.8",
"hi-base32": "^0.5.1",
@ -1533,16 +1547,16 @@
}
},
"node_modules/@waku/discovery/node_modules/@waku/core": {
"version": "0.0.34-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.34-0e49a1e.0.tgz",
"integrity": "sha512-kihMebnMzUnZXAwkAiya34jkcvogkRcszCzaIKO0m4StytKqLBmR1I5MmN9OcsFbygVeGMnax+8K+EHlemSOfg==",
"version": "0.0.36-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-16328a3.0.tgz",
"integrity": "sha512-+5GNNcy3FYnPKaL0RuJ01oo3+wMqkVhHPKdpRc3T0Rodi6IZsFCFdT1ob3A5rpFo5IURXqNjElUVBItIGrtupw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@libp2p/ping": "2.0.1",
"@waku/enr": "0.0.28-0e49a1e.0",
"@waku/interfaces": "0.0.29-0e49a1e.0",
"@waku/proto": "0.0.9-0e49a1e.0",
"@waku/utils": "0.0.22-0e49a1e.0",
"@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4",
"it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4",
@ -1633,9 +1647,9 @@
}
},
"node_modules/@waku/enr": {
"version": "0.0.28-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.28-0e49a1e.0.tgz",
"integrity": "sha512-K9LcAf3c0pSfmVgx0I17V9wM7idAqu6VdlICrVcxL7ZVnIUPH+Skmk/S15LS9YigDwl283+hmwL8zO8P8inM+g==",
"version": "0.0.30-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.30-16328a3.0.tgz",
"integrity": "sha512-9D4WVJcuL/ncVsHb7KyeIkkDJia6UVdNK9Kf1u9BhvJBtzioPGgXrmp1O7CsXpIKVu4f8IVCu1Lc6hUVG84Chw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@ethersproject/rlp": "^5.7.0",
@ -1643,7 +1657,7 @@
"@libp2p/peer-id": "^5.0.1",
"@multiformats/multiaddr": "^12.0.0",
"@noble/secp256k1": "^1.7.1",
"@waku/utils": "0.0.22-0e49a1e.0",
"@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4",
"js-sha3": "^0.9.2"
},
@ -1660,34 +1674,34 @@
}
},
"node_modules/@waku/interfaces": {
"version": "0.0.29-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.29-0e49a1e.0.tgz",
"integrity": "sha512-TaGr2cshZ2qmZsix+QNWwKFjefcOgXnBQk0P1RGE7lGo22KmxLqwwjeEMkmyud3El826Ukg6XprKliVGrceBzA==",
"version": "0.0.31-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.31-16328a3.0.tgz",
"integrity": "sha512-2jZCwBSBKd7PX12xJadMmxMpIKGR9SrQTnTqUfDD4aT72GQ9SpaqQDY5h+svMt/qjriZMBSUpITQQF04w0GIaw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@waku/proto": "0.0.9-0e49a1e.0"
"@waku/proto": "0.0.11-16328a3.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@waku/message-hash": {
"version": "0.1.18-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/message-hash/-/message-hash-0.1.18-0e49a1e.0.tgz",
"integrity": "sha512-n3S8WOQhp8jj2hLUmRQ2LnJMpJb4E04ZrmknpJ6XIQru0pWq2LV2qqJe3+5RpvrM+JADc6HggmCjxYojW0EkZA==",
"version": "0.1.20-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/message-hash/-/message-hash-0.1.20-16328a3.0.tgz",
"integrity": "sha512-j1FHSgeJeKnc9Tet3/NcrNjOV1gKa6U3WXNe/uBRZsvn2P30Qg6a1Am2eMfxpmO3ggXz25ywe2WDNA/K4vk/RQ==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@noble/hashes": "^1.3.2",
"@waku/utils": "0.0.22-0e49a1e.0"
"@waku/utils": "0.0.24-16328a3.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@waku/proto": {
"version": "0.0.9-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.9-0e49a1e.0.tgz",
"integrity": "sha512-rf82UD5KFobmGrsd/l5AFNACPcP9k0yEfq5eNgoOH5rGo/BFs9N5L7Wi9E+56b5hxPcA8b+2bpu1EGgQuDq2gw==",
"version": "0.0.11-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.11-16328a3.0.tgz",
"integrity": "sha512-yFGW6UaQC0MrffZV25uVDYC8sUxI6OpnizTfSyebLg84eihYHJyxTed7pD/EyjSl8vL9GFX8nDuksDbkIQ2OQw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"protons-runtime": "^5.4.0"
@ -1697,24 +1711,24 @@
}
},
"node_modules/@waku/sdk": {
"version": "0.0.30-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.30-0e49a1e.0.tgz",
"integrity": "sha512-j6L+2idHmx0f5HUHtLW6uqc4F1/JSQKVsqL3Mqyh1OrwaYqKAfDqvOyJq8hb9auWRCOTHZs0Bs5g7iA99kti2Q==",
"version": "0.0.32-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.32-16328a3.0.tgz",
"integrity": "sha512-tHS2+lf7NMQekuxfrZOGsMWya35W8saxjMjPnIRE6FOq0Mlv1ADwso05W7iy9ExNJoFD3+4XVemDvc5RfIM75w==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@chainsafe/libp2p-noise": "^16.0.0",
"@chainsafe/libp2p-noise": "16.0.0",
"@libp2p/bootstrap": "^11.0.1",
"@libp2p/identify": "^3.0.1",
"@libp2p/mplex": "^11.0.1",
"@libp2p/ping": "2.0.1",
"@libp2p/websockets": "^9.0.1",
"@noble/hashes": "^1.3.3",
"@waku/core": "0.0.34-0e49a1e.0",
"@waku/discovery": "0.0.7-0e49a1e.0",
"@waku/interfaces": "0.0.29-0e49a1e.0",
"@waku/message-hash": "0.1.18-0e49a1e.0",
"@waku/proto": "0.0.9-0e49a1e.0",
"@waku/utils": "0.0.22-0e49a1e.0",
"@waku/core": "0.0.36-16328a3.0",
"@waku/discovery": "0.0.9-16328a3.0",
"@waku/interfaces": "0.0.31-16328a3.0",
"@waku/message-hash": "0.1.20-16328a3.0",
"@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.24-16328a3.0",
"libp2p": "2.1.8"
},
"engines": {
@ -1722,16 +1736,16 @@
}
},
"node_modules/@waku/sdk/node_modules/@waku/core": {
"version": "0.0.34-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.34-0e49a1e.0.tgz",
"integrity": "sha512-kihMebnMzUnZXAwkAiya34jkcvogkRcszCzaIKO0m4StytKqLBmR1I5MmN9OcsFbygVeGMnax+8K+EHlemSOfg==",
"version": "0.0.36-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-16328a3.0.tgz",
"integrity": "sha512-+5GNNcy3FYnPKaL0RuJ01oo3+wMqkVhHPKdpRc3T0Rodi6IZsFCFdT1ob3A5rpFo5IURXqNjElUVBItIGrtupw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@libp2p/ping": "2.0.1",
"@waku/enr": "0.0.28-0e49a1e.0",
"@waku/interfaces": "0.0.29-0e49a1e.0",
"@waku/proto": "0.0.9-0e49a1e.0",
"@waku/utils": "0.0.22-0e49a1e.0",
"@waku/enr": "0.0.30-16328a3.0",
"@waku/interfaces": "0.0.31-16328a3.0",
"@waku/proto": "0.0.11-16328a3.0",
"@waku/utils": "0.0.24-16328a3.0",
"debug": "^4.3.4",
"it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4",
@ -1820,13 +1834,13 @@
}
},
"node_modules/@waku/utils": {
"version": "0.0.22-0e49a1e.0",
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.22-0e49a1e.0.tgz",
"integrity": "sha512-4aHZK5w4kGN1GgJMQOTwplwPIwiRefsDn2/bfcP87+EPGPw3toaDSWfOhWLvh2ZG1Zsk6k5eZmN4WF/oHx9NdA==",
"version": "0.0.24-16328a3.0",
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.24-16328a3.0.tgz",
"integrity": "sha512-zYNeWtRAYGJLVvE/8xnKmO5ctxuPD6/k9NAS/g3ES0o++kBrXNRFy3d1V1nKlnerwJuwYmB0RhfwFmz7ehtwWA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@noble/hashes": "^1.3.2",
"@waku/interfaces": "0.0.29-0e49a1e.0",
"@waku/interfaces": "0.0.31-16328a3.0",
"chai": "^4.3.10",
"debug": "^4.3.4",
"uint8arrays": "^5.0.1"
@ -3283,9 +3297,9 @@
}
},
"node_modules/dns-over-http-resolver": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.10.tgz",
"integrity": "sha512-l2kMOLxK6f9ll+5sf2Ndl8WS/2eXhOf9ZSXZMPnTVyHsv1ktN1WX3FwcyYklMq3ORv2N1nhf0TsGKWBjhgn0ug==",
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.15.tgz",
"integrity": "sha512-h2Ldu6b8LjW725Q5zjjv7T5s1K3dPjlU3DWvcEFqB3Ksb3QmqC4dHhPKlGlBS/1P47D4T5arZMiE4dD4OIfO6A==",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"quick-lru": "^7.0.0",
@ -5727,9 +5741,9 @@
}
},
"node_modules/it-first": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.7.tgz",
"integrity": "sha512-e2dVSlOP+pAxPYPVJBF4fX7au8cvGfvLhIrGCMc5aWDnCvwgOo94xHbi3Da6eXQ2jPL5FGEM8sJMn5uE8Seu+g==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.8.tgz",
"integrity": "sha512-neaRRwOMCmMKkXJVZ4bvUDVlde+Xh0aTWr7hFaOZeDXzbctGVV/WHmPVqBqy3RjlsP7eRM0vcqNtlM8hivcmGw==",
"license": "Apache-2.0 OR MIT"
},
"node_modules/it-foreach": {
@ -7185,9 +7199,9 @@
"license": "MIT"
},
"node_modules/quick-lru": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz",
"integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.1.tgz",
"integrity": "sha512-kLjThirJMkWKutUKbZ8ViqFc09tDQhlbQo2MNuVeLWbRauqYP96Sm6nzlQ24F0HFjUNZ4i9+AgldJ9H6DZXi7g==",
"license": "MIT",
"engines": {
"node": ">=18"

View File

@ -9,7 +9,7 @@
"dependencies": {
"@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.30-0e49a1e.0",
"@waku/sdk": "0.0.32-16328a3.0",
"libp2p": "^2.1.10",
"protobufjs": "^7.3.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,176 +1,139 @@
import {
createLightNode,
createEncoder,
createDecoder,
DecodedMessage,
LightNode,
utils,
} from "@waku/sdk";
import { generateKeyPairFromSeed } from "@libp2p/crypto/keys";
import { fromString } from "uint8arrays";
import { Type, Field } from "protobufjs";
getWakuNode,
createWakuEncoder,
createWakuDecoder,
getPeerId
} from "./waku-service";
import { DecodedMessage } from "@waku/sdk";
import {
generateRandomNumber,
sha256,
} from "./util";
encodeMessage,
decodeMessage,
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")
.add(new Field("hash", 1, "string"))
.add(new Field("total", 2, "uint64"))
.add(new Field("index", 3, "uint64"))
.add(new Field("sender", 4, "string"));
async function initializeApp() {
try {
console.log("Initializing Waku node...");
const node = await getWakuNode();
const currentPeerId = getPeerId();
console.log("Waku node initialized. Peer ID:", currentPeerId);
const sequenceCompletedEvent = new CustomEvent("sequenceCompleted");
async function wakuNode(): Promise<LightNode> {
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,
},
});
(window as any).waku = node;
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")
]);
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 = createEncoder({
contentTopic: DEFAULT_CONTENT_TOPIC,
pubsubTopicShardInfo: {
clusterId: 42,
shard: 0,
if (currentPeerId) {
updatePeerIdDisplay(currentPeerId);
}
});
const startLightPushSequence = async (
numMessages: number,
period: number = 3000
) => {
const sequenceHash = await sha256(generateRandomNumber());
const sequenceTotal = numMessages;
let sequenceIndex = 0;
const sendMessageBatch = async () => {
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);
const sendMessage = async () => {
try {
const messageHash = await sha256(
`${sequenceHash}-${sequenceIndex}-${sequenceTotal}`
);
const tempDecodedMessage = ProtoChatMessage.decode(payload);
const messageId = (tempDecodedMessage as any).id || `temp-id-${Date.now()}`;
const message = ProtoSequencedMessage.create({
hash: messageHash,
total: sequenceTotal,
index: sequenceIndex,
sender: peerId,
});
const payload = ProtoSequencedMessage.encode(message).finish();
const chatMessage: ChatMessage = {
id: messageId,
timestamp: Date.now(),
senderPeerId: currentPeerId || "unknown",
content: messageContent
};
const result = await node.lightPush.send(
encoder,
{
try {
const result = await node.lightPush.send(encoder, {
payload,
timestamp: new Date(),
},
{ autoRetry: true }
);
timestamp: new Date(chatMessage.timestamp),
}, { autoRetry: true });
console.log("DEBUG: light push successes: ", result.successes.length);
console.log(
"DEBUG: light push failures: ",
result.failures.length
);
if (result.successes.length > 0) {
console.log(`Message ${i + 1} (ID: ${chatMessage.id}) sent successfully.`);
incrementSentByMe();
addMessageToLog(chatMessage, 'sent');
} else {
console.warn(`Failed to send message ${i + 1} (ID: ${chatMessage.id}):`, result.failures);
}
} catch (error) {
console.error(`Error sending message ${i + 1} (ID: ${chatMessage.id}):`, error);
}
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log("Message batch sending complete.");
};
// Increment sequence
sequenceIndex++;
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);
if (sequenceIndex < sequenceTotal) {
setTimeout(sendMessage, period); // Schedule the next send
if (chatMessage) {
console.log("Decoded chat message:", chatMessage);
if (chatMessage.senderPeerId === currentPeerId) {
incrementReceivedMine();
console.log("Received own message (loopback):", chatMessage.id);
} else {
incrementReceivedOthers();
addMessageToLog(chatMessage, 'received-other');
console.log("Received message from other peer:", chatMessage.id);
}
} else {
document.dispatchEvent(sequenceCompletedEvent);
console.warn("Could not decode received Waku message. Payload might be malformed or not a ChatMessage.");
}
if ( result.successes.length > 0) {
const messageElement = document.createElement("div");
messageElement.textContent = messageHash;
document.getElementById("messagesSent")?.appendChild(messageElement);
}
} catch (error) {
console.error("DEBUG: Error sending message", error);
}
});
console.log("Subscription active.");
};
sendMessage(); // Start the recursive sending
};
const sendMessageButton = document.getElementById("sendMessageButton");
if (sendMessageButton) {
sendMessageButton.addEventListener("click", () => {
console.log("Send Message Button clicked");
sendMessageBatch();
});
}
const startFilterSubscription = async () => {
const decoder = createDecoder(DEFAULT_CONTENT_TOPIC, { clusterId: 42, shard: 0 });
const searchButton = document.getElementById("searchButton");
if (searchButton) {
searchButton.addEventListener("click", () => {
console.log("Search button clicked");
renderMessages(getSearchTerm());
});
}
const searchInput = document.getElementById("searchInput");
if(searchInput) {
searchInput.addEventListener("input", () => {
console.log("Search input changed");
renderMessages(getSearchTerm());
});
}
const subscriptionCallback = async (message: DecodedMessage) => {
const decodedMessage: any = ProtoSequencedMessage.decode(
message.payload
);
await subscribeToMessages();
console.log("Application setup complete. Click 'Send New Message Batch' to send messages.");
if (decodedMessage.sender === peerId) {
return;
}
const messageElement = document.createElement("div");
messageElement.textContent = decodedMessage.hash;
document.getElementById("messagesReceived")?.appendChild(messageElement);
};
await node.nextFilter.subscribe(decoder, subscriptionCallback);
await node.nextFilter.subscribe(decoder, subscriptionCallback);
};
return {
node,
startLightPushSequence,
startFilterSubscription,
};
} 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 () => {
const { startLightPushSequence, startFilterSubscription } = await app();
startFilterSubscription();
document.addEventListener(sequenceCompletedEvent.type, () =>
startLightPushSequence(10, 3000)
);
startLightPushSequence(10, 3000);
})();
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM fully loaded and parsed. Starting app initialization.");
initializeApp();
});

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

@ -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

@ -2,7 +2,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
module.exports = {
entry: "./src/index.ts", // Changed from index.js to index.ts
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "build"),
filename: "index.js",
@ -11,12 +11,12 @@ module.exports = {
asyncWebAssembly: true,
},
resolve: {
extensions: ['.ts', '.js'], // Add .ts to the extensions
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/, // Add a rule for TypeScript files
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
@ -29,7 +29,13 @@ module.exports = {
},
plugins: [
new CopyWebpackPlugin({
patterns: ["index.html", "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" },
],
}),
],
};