diff --git a/light-chat/index.html b/light-chat/index.html
new file mode 100644
index 0000000..ead9006
--- /dev/null
+++ b/light-chat/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ JS-Waku light chat
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/light-chat/index.js b/light-chat/index.js
new file mode 100644
index 0000000..693e20d
--- /dev/null
+++ b/light-chat/index.js
@@ -0,0 +1,171 @@
+import * as utils from "https://unpkg.com/@waku/byte-utils@0.0.2/bundle/index.js";
+import * as wakuCreate from "https://unpkg.com/@waku/create@0.0.5/bundle/index.js";
+import {
+ waitForRemotePeer,
+ createDecoder,
+ createEncoder,
+} from "https://unpkg.com/@waku/core@0.0.7/bundle/index.js";
+
+const MULTI_ADDR =
+ "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm";
+const CONTENT_TOPIC = "/toy-chat/2/huilong/proto";
+const PROTOCOLS = ["filter", "lightpush"];
+
+const ui = initUI();
+runApp(ui).catch((err) => {
+ console.error(err);
+ ui.setStatus(`error: ${err.message}`, "error");
+});
+
+async function runApp(ui) {
+ ui.setStatus("connecting...", "progress");
+
+ const { info, sendMessage, unsubscribeFromMessages } = await initWakuContext({
+ protocols: PROTOCOLS,
+ multiAddr: MULTI_ADDR,
+ contentTopic: CONTENT_TOPIC,
+ onMessageReceived: ui.renderMessage,
+ });
+
+ ui.setStatus("connected", "success");
+
+ ui.setLocalPeer(info.localPeerId);
+ ui.setRemotePeer(info.remotePeerIds);
+ ui.setRemoteMultiAddr(info.multiAddr);
+ ui.setContentTopic(info.contentTopic);
+
+ ui.onSendMessage(sendMessage);
+
+ ui.onExit(async () => {
+ ui.setStatus("disconnecting...", "progress");
+ await unsubscribeFromMessages();
+ ui.setStatus("disconnected", "terminated");
+ ui.resetMessages();
+ });
+}
+
+async function initWakuContext({
+ multiAddr,
+ protocols,
+ contentTopic,
+ onMessageReceived,
+}) {
+ const Decoder = createDecoder(contentTopic);
+ const Encoder = createEncoder(contentTopic);
+
+ const ChatMessage = new protobuf.Type("ChatMessage")
+ .add(new protobuf.Field("timestamp", 1, "uint64"))
+ .add(new protobuf.Field("nick", 2, "string"))
+ .add(new protobuf.Field("text", 3, "bytes"));
+
+ const node = await wakuCreate.createLightNode({ defaultBootstrap: true });
+
+ await node.start();
+ await waitForRemotePeer(node, protocols);
+
+ // Set a filter by using Decoder for a given ContentTopic
+ const unsubscribeFromMessages = await node.filter.subscribe(
+ [Decoder],
+ (wakuMessage) => {
+ const messageObj = ChatMessage.decode(wakuMessage.payload);
+ onMessageReceived({
+ ...messageObj,
+ text: utils.bytesToUtf8(messageObj.text),
+ });
+ }
+ );
+
+ const localPeerId = node.libp2p.peerId.toString();
+
+ const remotePeers = await node.libp2p.peerStore.all();
+ const remotePeerIds = remotePeers.map((peer) => peer.id.toString());
+
+ return {
+ unsubscribeFromMessages,
+ info: {
+ multiAddr,
+ contentTopic,
+ localPeerId,
+ remotePeerIds,
+ },
+ sendMessage: async ({ text, nick }) => {
+ if (!text || !nick) {
+ return;
+ }
+
+ const protoMessage = ChatMessage.create({
+ nick,
+ timestamp: Date.now(),
+ text: utils.utf8ToBytes(text),
+ });
+
+ await node.lightPush.push(Encoder, {
+ payload: ChatMessage.encode(protoMessage).finish(),
+ });
+ },
+ };
+}
+
+// UI adapter
+function initUI() {
+ const exitButton = document.getElementById("exit");
+ const sendButton = document.getElementById("send");
+
+ const statusBlock = document.getElementById("status");
+ const localPeerBlock = document.getElementById("localPeerId");
+ const remotePeerId = document.getElementById("remotePeerId");
+ const remoteMultiAddr = document.getElementById("remoteMultiAddr");
+ const contentTopicBlock = document.getElementById("contentTopic");
+
+ const messagesBlock = document.getElementById("messages");
+
+ const nickText = document.getElementById("nickText");
+ const messageText = document.getElementById("messageText");
+
+ return {
+ // UI events
+ onExit: (cb) => {
+ exitButton.addEventListener("click", cb);
+ },
+ onSendMessage: (cb) => {
+ sendButton.addEventListener("click", async () => {
+ await cb({
+ nick: nickText.value,
+ text: messageText.value,
+ });
+ messageText.value = "";
+ });
+ },
+ // UI renderers
+ setStatus: (value, className) => {
+ statusBlock.innerHTML = `${value}`;
+ },
+ setLocalPeer: (id) => {
+ localPeerBlock.innerText = id.toString();
+ },
+ setRemotePeer: (ids) => {
+ remotePeerId.innerText = ids.join("\n");
+ },
+ setRemoteMultiAddr: (multiAddr) => {
+ remoteMultiAddr.innerText = multiAddr.toString();
+ },
+ setContentTopic: (topic) => {
+ contentTopicBlock.innerText = topic.toString();
+ },
+ renderMessage: (messageObj) => {
+ const { nick, text, timestamp } = messageObj;
+ const date = new Date(timestamp);
+
+ // WARNING: XSS vulnerable
+ messagesBlock.innerHTML += `
+
+
${nick} (${date.toDateString()}):
+
${text}
+
+ `;
+ },
+ resetMessages: () => {
+ messagesBlock.innerHTML = "";
+ },
+ };
+}
diff --git a/rln-js/style.css b/rln-js/style.css
deleted file mode 100644
index a09d86a..0000000
--- a/rln-js/style.css
+++ /dev/null
@@ -1,51 +0,0 @@
-html {
- scroll-behavior: smooth;
-}
-
-h1 {
- margin: 1rem 2.5rem 1rem 0;
-}
-
-.value {
- font-size: 1.3em;
-}
-
-body > footer {
- text-align: right;
-}
-
-.row {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
-}
-
-.rcenter {
- align-items: center;
-}
-
-.mu1 {
- margin-top: 4em;
-}
-
-.w50 {
- width: 46%;
-}
-.w30 {
- width: 35%;
-}
-.w70 {
- width: 60%;
-}
-
-.mf label {
- width: 100%;
-}
-
-.mf input {
- width: 80%;
-}
-
-.mf input.p100 {
- width: 100%;
-}