From dc935bc3226b92947a4b41f869ebe9c896f59106 Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:39:36 +0200 Subject: [PATCH] feat: re-create dogfooding app with better UI and new Filter API (#131) --- examples/dogfooding/index.html | 93 ++++---- examples/dogfooding/package-lock.json | 131 ++++++----- examples/dogfooding/package.json | 2 +- examples/dogfooding/public/style.css | 187 +++++++++++++++ examples/dogfooding/src/index.ts | 258 ++++++++++----------- examples/dogfooding/src/message-service.ts | 42 ++++ examples/dogfooding/src/ui-manager.ts | 105 +++++++++ examples/dogfooding/src/util.ts | 14 +- examples/dogfooding/src/utils.ts | 12 + examples/dogfooding/src/waku-service.ts | 64 +++++ examples/dogfooding/webpack.config.js | 23 +- 11 files changed, 673 insertions(+), 258 deletions(-) create mode 100644 examples/dogfooding/public/style.css create mode 100644 examples/dogfooding/src/message-service.ts create mode 100644 examples/dogfooding/src/ui-manager.ts create mode 100644 examples/dogfooding/src/utils.ts create mode 100644 examples/dogfooding/src/waku-service.ts diff --git a/examples/dogfooding/index.html b/examples/dogfooding/index.html index 4ca2627..3e9337a 100644 --- a/examples/dogfooding/index.html +++ b/examples/dogfooding/index.html @@ -1,51 +1,62 @@ - + - Sent Received Message Ratio + Waku Dogfooding - Refactored - - - -
-

Waku Dogfooding App

- -
-
-
-

Sent

-
-
-
-

Received

-
-
+ + + +
+
+

Waku Message Center

+
+ Your Peer ID: Connecting... +
+
+ +
+
+

Message Statistics

+
+
+ 0 + Sent by Me +
+
+ 0 + Received (Mine) +
+
+ 0 + Received (Others) +
+
+
+ +
+

Controls

+ +
+ + +
+
+ +
+

Message Log

+
+
+
+
+ +
+

Waku Dogfooding App - Modern UI

+
- + \ No newline at end of file diff --git a/examples/dogfooding/package-lock.json b/examples/dogfooding/package-lock.json index a7515c3..04bd2c2 100644 --- a/examples/dogfooding/package-lock.json +++ b/examples/dogfooding/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@libp2p/crypto": "^5.0.5", "@multiformats/multiaddr": "^12.3.1", - "@waku/sdk": "0.0.31-3038c48.0", + "@waku/sdk": "0.0.32-16328a3.0", "libp2p": "^2.1.10", "protobufjs": "^7.3.0", "uint8arrays": "^5.1.0" @@ -72,6 +72,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", @@ -1586,16 +1603,16 @@ ] }, "node_modules/@waku/discovery": { - "version": "0.0.8-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.8-3038c48.0.tgz", - "integrity": "sha512-+FnEs0nWkYtCGU4rfAmXQwwxH09WgzTLkM6t8tgplkjNNQ5TLyqptA1yt9gQKXL5ZEWVMbR8Ydzv86jS15Hizw==", + "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.35-3038c48.0", - "@waku/enr": "0.0.29-3038c48.0", - "@waku/interfaces": "0.0.30-3038c48.0", - "@waku/proto": "0.0.10-3038c48.0", - "@waku/utils": "0.0.23-3038c48.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", @@ -1606,16 +1623,16 @@ } }, "node_modules/@waku/discovery/node_modules/@waku/core": { - "version": "0.0.35-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.35-3038c48.0.tgz", - "integrity": "sha512-N2YTKAL4ovl0pbiDr/FqVGtM41Lm+a5+Obk+FKUws/QlCHQK0RG8Qiders7UO5kWpuStm7Cynp8ngPp91U2nBQ==", + "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.29-3038c48.0", - "@waku/interfaces": "0.0.30-3038c48.0", - "@waku/proto": "0.0.10-3038c48.0", - "@waku/utils": "0.0.23-3038c48.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", @@ -1689,9 +1706,9 @@ } }, "node_modules/@waku/enr": { - "version": "0.0.29-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.29-3038c48.0.tgz", - "integrity": "sha512-ZT+8Lh5hmeCDuQ3gQQ9ve5pPkR/sZs1/g7IM8hQTPxbP663/GVrJ6d6jfHCAWmEFauP4FfVdsDl3UfphU7RD+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", @@ -1699,7 +1716,7 @@ "@libp2p/peer-id": "^5.0.1", "@multiformats/multiaddr": "^12.0.0", "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.23-3038c48.0", + "@waku/utils": "0.0.24-16328a3.0", "debug": "^4.3.4", "js-sha3": "^0.9.2" }, @@ -1716,34 +1733,34 @@ } }, "node_modules/@waku/interfaces": { - "version": "0.0.30-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.30-3038c48.0.tgz", - "integrity": "sha512-QsXga9S6cUh8culnWomZcRBRZZdkPDT5PNinKGcjqQAMpueyuXqeSCVVH41JUkuTKel4w7fXOP9qcr+KLn+I4Q==", + "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.10-3038c48.0" + "@waku/proto": "0.0.11-16328a3.0" }, "engines": { "node": ">=20" } }, "node_modules/@waku/message-hash": { - "version": "0.1.19-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/message-hash/-/message-hash-0.1.19-3038c48.0.tgz", - "integrity": "sha512-u3THyufk2yLnLNyfLbyh3iGls2EgR3TdpQ17rZz4lK73qYyMbT9Rw/bwFL+W+FVg8eKCeBfgXjou2NuqNPLvdA==", + "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.23-3038c48.0" + "@waku/utils": "0.0.24-16328a3.0" }, "engines": { "node": ">=20" } }, "node_modules/@waku/proto": { - "version": "0.0.10-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.10-3038c48.0.tgz", - "integrity": "sha512-xwStT7V2BsP9i4y0+sV5jCqBAuiuqPgHIz2vO4bGClVFwe5HCMjHbkWNZp0MVj4OWI3EqXmxakcirtBzT3gbOw==", + "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" @@ -1753,9 +1770,9 @@ } }, "node_modules/@waku/sdk": { - "version": "0.0.31-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.31-3038c48.0.tgz", - "integrity": "sha512-+ZkejfevYPQpIyxiUGeGxOYZHnl0VgwvWzJ/SQiBXP4YusLTRZJgulFwTcWzFlL8iBwdEptg9A2Ju/BC6IxVQA==", + "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", @@ -1765,12 +1782,12 @@ "@libp2p/ping": "2.0.1", "@libp2p/websockets": "^9.0.1", "@noble/hashes": "^1.3.3", - "@waku/core": "0.0.35-3038c48.0", - "@waku/discovery": "0.0.8-3038c48.0", - "@waku/interfaces": "0.0.30-3038c48.0", - "@waku/message-hash": "0.1.19-3038c48.0", - "@waku/proto": "0.0.10-3038c48.0", - "@waku/utils": "0.0.23-3038c48.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": { @@ -1778,16 +1795,16 @@ } }, "node_modules/@waku/sdk/node_modules/@waku/core": { - "version": "0.0.35-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.35-3038c48.0.tgz", - "integrity": "sha512-N2YTKAL4ovl0pbiDr/FqVGtM41Lm+a5+Obk+FKUws/QlCHQK0RG8Qiders7UO5kWpuStm7Cynp8ngPp91U2nBQ==", + "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.29-3038c48.0", - "@waku/interfaces": "0.0.30-3038c48.0", - "@waku/proto": "0.0.10-3038c48.0", - "@waku/utils": "0.0.23-3038c48.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", @@ -1859,13 +1876,13 @@ } }, "node_modules/@waku/utils": { - "version": "0.0.23-3038c48.0", - "resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.23-3038c48.0.tgz", - "integrity": "sha512-vLoMRH6/wOx+Jx4522D6zeuf4v7CRzaOxhZYFv+lxJFEdTZr7C4ruuHVSdo5VdMx/u6Ad9oNtfqhygppcWyxpw==", + "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.30-3038c48.0", + "@waku/interfaces": "0.0.31-16328a3.0", "chai": "^4.3.10", "debug": "^4.3.4", "uint8arrays": "^5.0.1" @@ -3393,9 +3410,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", @@ -6022,9 +6039,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": { diff --git a/examples/dogfooding/package.json b/examples/dogfooding/package.json index 248cf23..e30e9f7 100644 --- a/examples/dogfooding/package.json +++ b/examples/dogfooding/package.json @@ -9,7 +9,7 @@ "dependencies": { "@libp2p/crypto": "^5.0.5", "@multiformats/multiaddr": "^12.3.1", - "@waku/sdk": "0.0.31-3038c48.0", + "@waku/sdk": "0.0.32-16328a3.0", "libp2p": "^2.1.10", "protobufjs": "^7.3.0", "uint8arrays": "^5.1.0" diff --git a/examples/dogfooding/public/style.css b/examples/dogfooding/public/style.css new file mode 100644 index 0000000..d10d7e8 --- /dev/null +++ b/examples/dogfooding/public/style.css @@ -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; + } +} diff --git a/examples/dogfooding/src/index.ts b/examples/dogfooding/src/index.ts index 3677558..e850495 100644 --- a/examples/dogfooding/src/index.ts +++ b/examples/dogfooding/src/index.ts @@ -1,161 +1,139 @@ import { - createLightNode, - DecodedMessage, - LightNode, -} 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"); -const messageReceivedEvent = new CustomEvent("messageReceived"); + if (currentPeerId) { + updatePeerIdDisplay(currentPeerId); + } -async function wakuNode(): Promise { - let seed = localStorage.getItem("seed"); + 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); - if (!seed) { - seed = (await sha256(generateRandomNumber())).slice(0, 32); - localStorage.setItem("seed", seed); - } + const tempDecodedMessage = ProtoChatMessage.decode(payload); + const messageId = (tempDecodedMessage as any).id || `temp-id-${Date.now()}`; - const privateKey = await generateKeyPairFromSeed("Ed25519", fromString(seed)); + const chatMessage: ChatMessage = { + id: messageId, + timestamp: Date.now(), + senderPeerId: currentPeerId || "unknown", + content: messageContent + }; - const node = await createLightNode({ - defaultBootstrap: false, - 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, - { + 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 - ); - - // Increment sequence - sequenceIndex++; - - if (sequenceIndex < sequenceTotal) { - setTimeout(sendMessage, period); // Schedule the next send - } else { - document.dispatchEvent(sequenceCompletedEvent); + 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); } - } catch (error) { - console.error("DEBUG: Error sending message", error); + await new Promise(resolve => setTimeout(resolve, 100)); } + 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 () => { - const decoder = node.createDecoder({ contentTopic: DEFAULT_CONTENT_TOPIC }); - - const subscriptionCallback = async (message: DecodedMessage) => { - const decodedMessage: any = ProtoSequencedMessage.decode( - message.payload - ); - - if (decodedMessage.sender === peerId) { - return; - } - - const messageElement = document.createElement("div"); - messageElement.textContent = `Message: ${decodedMessage.hash}`; - document.dispatchEvent(messageReceivedEvent); + 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 { + console.warn("Could not decode received Waku message. Payload might be malformed or not a ChatMessage."); + } + }); + 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 { - node, - startLightPushSequence, - startFilterSubscription, - }; + 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()); + }); + } + + 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 () => { - const { startLightPushSequence, startFilterSubscription } = await app(); - - startFilterSubscription(); - - document.addEventListener(sequenceCompletedEvent.type, () => - startLightPushSequence(10, 3000) - ); - - startLightPushSequence(10, 3000); -})(); \ No newline at end of file +document.addEventListener("DOMContentLoaded", () => { + console.log("DOM fully loaded and parsed. Starting app initialization."); + initializeApp(); +}); diff --git a/examples/dogfooding/src/message-service.ts b/examples/dogfooding/src/message-service.ts new file mode 100644 index 0000000..bc1539e --- /dev/null +++ b/examples/dogfooding/src/message-service.ts @@ -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; + } +} diff --git a/examples/dogfooding/src/ui-manager.ts b/examples/dogfooding/src/ui-manager.ts new file mode 100644 index 0000000..4c0dcc0 --- /dev/null +++ b/examples/dogfooding/src/ui-manager.ts @@ -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 : ""; +} diff --git a/examples/dogfooding/src/util.ts b/examples/dogfooding/src/util.ts index ed17548..de5580c 100644 --- a/examples/dogfooding/src/util.ts +++ b/examples/dogfooding/src/util.ts @@ -3,10 +3,10 @@ export const generateRandomNumber = (): number => { }; export const sha256 = async (number: number | string ): Promise => { - const encoder = new TextEncoder(); - const data = encoder.encode(number.toString()); - const buffer = await crypto.subtle.digest("SHA-256", data); - return Array.from(new Uint8Array(buffer)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -}; \ No newline at end of file + const encoder = new TextEncoder(); + const data = encoder.encode(number.toString()); + const buffer = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(buffer)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + }; diff --git a/examples/dogfooding/src/utils.ts b/examples/dogfooding/src/utils.ts new file mode 100644 index 0000000..58c3e91 --- /dev/null +++ b/examples/dogfooding/src/utils.ts @@ -0,0 +1,12 @@ +export async function sha256(text: string): Promise { + 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(); +} diff --git a/examples/dogfooding/src/waku-service.ts b/examples/dogfooding/src/waku-service.ts new file mode 100644 index 0000000..f2bf83a --- /dev/null +++ b/examples/dogfooding/src/waku-service.ts @@ -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 { + 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 }); +} diff --git a/examples/dogfooding/webpack.config.js b/examples/dogfooding/webpack.config.js index b5fe261..6a4d646 100644 --- a/examples/dogfooding/webpack.config.js +++ b/examples/dogfooding/webpack.config.js @@ -1,25 +1,22 @@ -const path = require("path"); -const webpack = require("webpack"); const CopyWebpackPlugin = require("copy-webpack-plugin"); -const HtmlWebpackPlugin = require('html-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", - publicPath: process.env.NODE_ENV === 'production' ? '/dogfooding/' : '/' }, experiments: { 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/, }, @@ -32,11 +29,13 @@ module.exports = { }, plugins: [ 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/' : '/', - }) ], }; \ No newline at end of file