@@ -70,36 +78,40 @@ export const Waku: React.FunctionComponent<{}> = () => {
function useMessage() {
const [nick, setNick] = React.useState
("");
- const [message, setMessage] = React.useState("");
+ const [text, setText] = React.useState("");
const onNickChange = (e: React.SyntheticEvent) => {
setNick(e.currentTarget.value || "");
};
const onMessageChange = (e: React.SyntheticEvent) => {
- setMessage(e.currentTarget.value || "");
+ setText(e.currentTarget.value || "");
+ };
+
+ const resetText = () => {
+ setText("");
};
return {
nick,
- message,
+ text,
+ resetText,
onNickChange,
onMessageChange,
};
}
-function renderMessage() {
- /*
- wakuMessage.proofState = !!wakuMessage.rateLimitProof
- ? "verifying..."
- : "no proof attached";
-
- wakuMessage.msg = `
- (${nick})
- ${utils.bytesToUtf8(text)}
- [${time.toISOString()}]
- `;
- messagesList.innerHTML += `${message.msg} - [epoch: ${message.epoch}, proof: ${message.proofState} ]`;
- */
- return <>>;
+function renderMessage(content: MessageContent) {
+ return (
+
+
+ {content.nick}
+
+ ({content.proofStatus}, {content.time})
+
+ :
+
+ {content.text}
+
+ );
}
diff --git a/examples/rln-js/src/constants.ts b/examples/rln-js/src/constants.ts
index eb9a242..8382b98 100644
--- a/examples/rln-js/src/constants.ts
+++ b/examples/rln-js/src/constants.ts
@@ -1,9 +1,15 @@
import protobuf from "protobufjs";
+export type ProtoChatMessageType = {
+ timestamp: number;
+ nick: string;
+ text: string;
+};
+
export const ProtoChatMessage = new protobuf.Type("ChatMessage")
.add(new protobuf.Field("timestamp", 1, "uint64"))
.add(new protobuf.Field("nick", 2, "string"))
- .add(new protobuf.Field("text", 3, "bytes"));
+ .add(new protobuf.Field("text", 3, "string"));
export const CONTENT_TOPIC = "/toy-chat/2/luzhou/proto";
diff --git a/examples/rln-js/src/hooks/useWaku.ts b/examples/rln-js/src/hooks/useWaku.ts
index 7cddb2f..4823b5e 100644
--- a/examples/rln-js/src/hooks/useWaku.ts
+++ b/examples/rln-js/src/hooks/useWaku.ts
@@ -1,11 +1,11 @@
import React from "react";
-import { waku, Waku, WakuEventsNames } from "@/services/waku";
+import { waku, Waku, WakuEventsNames, MessageContent } from "@/services/waku";
import { useStore } from "./useStore";
import { useRLN } from "./useRLN";
export const useWaku = () => {
- const messages: string[] = [];
const wakuRef = React.useRef();
+ const [messages, setMessages] = React.useState([]);
const { rln } = useRLN();
const { activeMembershipID, credentials, setWakuStatus } = useStore();
@@ -20,6 +20,11 @@ export const useWaku = () => {
};
waku.addEventListener(WakuEventsNames.Status, statusListener);
+ const messagesListener = (event: CustomEvent) => {
+ setMessages((prev) => [...prev, event.detail as MessageContent]);
+ };
+ waku.addEventListener(WakuEventsNames.Message, messagesListener);
+
let terminated = false;
const run = async () => {
if (terminated) {
@@ -44,15 +49,16 @@ export const useWaku = () => {
return () => {
terminated = true;
waku.removeEventListener(WakuEventsNames.Status, statusListener);
+ waku.removeEventListener(WakuEventsNames.Message, messagesListener);
};
}, [activeMembershipID, credentials, rln, setWakuStatus]);
const onSend = React.useCallback(
- async (nick: string, message: string) => {
+ async (nick: string, text: string) => {
if (!wakuRef.current) {
return;
}
- // await wakuRef.current.node?.lightPush.send()
+ await wakuRef.current.sendMessage(nick, text);
},
[wakuRef]
);
diff --git a/examples/rln-js/src/services/waku.ts b/examples/rln-js/src/services/waku.ts
index b7a640c..4d1a13c 100644
--- a/examples/rln-js/src/services/waku.ts
+++ b/examples/rln-js/src/services/waku.ts
@@ -6,8 +6,18 @@ import {
LightNode,
waitForRemotePeer,
} from "@waku/sdk";
-import { CONTENT_TOPIC } from "@/constants";
-import { RLNDecoder, RLNEncoder, IdentityCredential } from "@waku/rln";
+import {
+ CONTENT_TOPIC,
+ ProtoChatMessage,
+ ProtoChatMessageType,
+} from "@/constants";
+import {
+ RLNDecoder,
+ RLNEncoder,
+ IdentityCredential,
+ RLNInstance,
+ RLNContract,
+} from "@waku/rln";
import { RLN } from "@/services/rln";
type InitOptions = {
@@ -16,8 +26,22 @@ type InitOptions = {
rln: RLN;
};
+export type MessageContent = {
+ nick: string;
+ text: string;
+ time: string;
+ proofStatus: string;
+};
+
+type SubscribeOptions = {
+ rlnContract: RLNContract;
+ node: LightNode;
+ decoder: RLNDecoder;
+};
+
export enum WakuEventsNames {
Status = "status",
+ Message = "message",
}
export enum WakuStatusEventPayload {
@@ -51,7 +75,6 @@ export class Waku implements IWaku {
constructor() {}
public async init(options: InitOptions) {
- const { rln } = options;
if (this.initialized || this.initializing || !options.rln.rlnInstance) {
return;
}
@@ -72,6 +95,14 @@ export class Waku implements IWaku {
this.emitStatusEvent(WakuStatusEventPayload.WAITING_FOR_PEERS);
await waitForRemotePeer(this.node);
this.emitStatusEvent(WakuStatusEventPayload.READY);
+
+ if (options.rln.rlnContract) {
+ await this.subscribeToMessages({
+ node: this.node,
+ decoder: this.decoder,
+ rlnContract: options.rln.rlnContract,
+ });
+ }
}
this.initialized = true;
@@ -95,6 +126,58 @@ export class Waku implements IWaku {
);
}
+ public async sendMessage(nick: string, text: string): Promise {
+ if (!this.node || !this.encoder) {
+ return;
+ }
+
+ const timestamp = new Date();
+ const msg = ProtoChatMessage.create({
+ text,
+ nick,
+ timestamp: Math.floor(timestamp.valueOf() / 1000),
+ });
+ const payload = ProtoChatMessage.encode(msg).finish();
+ console.log("Sending message with proof...");
+
+ await this.node.lightPush.send(this.encoder, { payload, timestamp });
+ console.log("Message sent!");
+ }
+
+ private async subscribeToMessages(options: SubscribeOptions) {
+ await options.node.filter.subscribe(options.decoder, (message) => {
+ try {
+ const { timestamp, nick, text } = ProtoChatMessage.decode(
+ message.payload
+ ) as unknown as ProtoChatMessageType;
+
+ let proofStatus = "no proof";
+ if (message.rateLimitProof) {
+ console.log("Proof received: ", message.rateLimitProof);
+
+ try {
+ console.time("Proof verification took:");
+ const res = message.verify(options.rlnContract.roots());
+ console.timeEnd("Proof verification took:");
+ proofStatus = res ? "verified" : "not verified";
+ } catch (error) {
+ proofStatus = "invalid";
+ console.error("Failed to verify proof: ", error);
+ }
+ }
+
+ this.emitMessageEvent({
+ nick,
+ text,
+ proofStatus,
+ time: new Date(timestamp).toDateString(),
+ });
+ } catch (error) {
+ console.error("Failed in subscription listener: ", error);
+ }
+ });
+ }
+
public addEventListener(name: WakuEventsNames, fn: EventListener) {
return this.emitter.addEventListener(name, fn as any);
}
@@ -108,6 +191,12 @@ export class Waku implements IWaku {
new CustomEvent(WakuEventsNames.Status, { detail: payload })
);
}
+
+ private emitMessageEvent(payload: MessageContent) {
+ this.emitter.dispatchEvent(
+ new CustomEvent(WakuEventsNames.Message, { detail: payload })
+ );
+ }
}
export const waku = new Waku();