mirror of
https://github.com/logos-messaging/examples.waku.org.git
synced 2026-01-04 05:43:07 +00:00
Merge pull request #87 from waku-org/web-chat-bump-js-waku
This commit is contained in:
commit
f698d97013
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
- Group chat
|
- Group chat
|
||||||
- React/TypeScript
|
- React/TypeScript
|
||||||
- Waku Relay
|
- Waku Filter
|
||||||
|
- Waku Light Push
|
||||||
- Waku Store
|
- Waku Store
|
||||||
|
|
||||||
A ReactJS chat app is provided as a showcase of the library used in the browser.
|
A ReactJS chat app is provided as a showcase of the library used in the browser.
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
const webpack = require("webpack");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
dev: (config) => {
|
|
||||||
// Override webpack 5 config from react-scripts to load polyfills
|
|
||||||
if (!config.resolve) config.resolve = {};
|
|
||||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
|
||||||
Object.assign(config.resolve.fallback, {
|
|
||||||
assert: require.resolve("assert"),
|
|
||||||
buffer: require.resolve("buffer"),
|
|
||||||
crypto: false,
|
|
||||||
http: require.resolve("http-browserify"),
|
|
||||||
https: require.resolve("https-browserify"),
|
|
||||||
stream: require.resolve("stream-browserify"),
|
|
||||||
url: require.resolve("url"),
|
|
||||||
zlib: require.resolve("browserify-zlib"),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!config.plugins) config.plugins = [];
|
|
||||||
config.plugins.push(
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
"process.env.ENV": JSON.stringify("dev"),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
config.plugins.push(
|
|
||||||
new webpack.ProvidePlugin({
|
|
||||||
process: "process/browser.js",
|
|
||||||
Buffer: ["buffer", "Buffer"],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
|
||||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
prod: (config) => {
|
|
||||||
// Override webpack 5 config from react-scripts to load polyfills
|
|
||||||
if (!config.resolve) config.resolve = {};
|
|
||||||
if (!config.resolve.fallback) config.resolve.fallback = {};
|
|
||||||
Object.assign(config.resolve.fallback, {
|
|
||||||
assert: require.resolve("assert"),
|
|
||||||
buffer: require.resolve("buffer"),
|
|
||||||
crypto: false,
|
|
||||||
http: require.resolve("http-browserify"),
|
|
||||||
https: require.resolve("https-browserify"),
|
|
||||||
stream: require.resolve("stream-browserify"),
|
|
||||||
url: require.resolve("url"),
|
|
||||||
zlib: require.resolve("browserify-zlib"),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!config.plugins) config.plugins = [];
|
|
||||||
config.plugins.push(
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
"process.env.ENV": JSON.stringify("prod"),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
config.plugins.push(
|
|
||||||
new webpack.ProvidePlugin({
|
|
||||||
process: "process/browser.js",
|
|
||||||
Buffer: ["buffer", "Buffer"],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!config.ignoreWarnings) config.ignoreWarnings = [];
|
|
||||||
config.ignoreWarnings.push(/Failed to parse source map/);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -2,53 +2,44 @@
|
|||||||
"name": "web-chat",
|
"name": "web-chat",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "/examples/web-chat",
|
"homepage": "/web-chat",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livechat/ui-kit": "^0.5.0-20",
|
"@livechat/ui-kit": "^0.5.0-20",
|
||||||
"browserify-zlib": "^0.2.0",
|
"@multiformats/multiaddr": "^10.4.0",
|
||||||
"buffer": "^6.0.3",
|
"js-waku": "0.24.0-f52dd9e",
|
||||||
"http-browserify": "^1.7.0",
|
|
||||||
"https-browserify": "^1.0.0",
|
|
||||||
"js-waku": "^0.24.0",
|
|
||||||
"libp2p-interfaces": "^4.0.6",
|
|
||||||
"long": "^5.2.0",
|
|
||||||
"multiaddr": "^10.0.1",
|
|
||||||
"peer-id": "^0.16.0",
|
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"protobufjs": "^7.0.0",
|
"protons-runtime": "^3.1.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"server-name-generator": "^1.0.5",
|
"server-name-generator": "^1.0.5",
|
||||||
"stream-browserify": "^3.0.0"
|
"uint8arraylist": "^2.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/node": "^17.0.32",
|
"@types/node": "^17.0.32",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"assert": "^2.0.0",
|
|
||||||
"cra-webpack-rewired": "^1.0.1",
|
|
||||||
"cspell": "^6.0.0",
|
"cspell": "^6.0.0",
|
||||||
"gh-pages": "^4.0.0",
|
"gh-pages": "^4.0.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
|
"protons": "^5.1.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cra-webpack-rewired start",
|
"start": "react-scripts start",
|
||||||
"build": "cra-webpack-rewired build",
|
"build": "react-scripts build",
|
||||||
"test:unit": "cra-webpack-rewired test",
|
"test:unit": "exit 0",
|
||||||
"fix": "run-s fix:*",
|
"fix": "run-s fix:*",
|
||||||
"test": "run-s build test:*",
|
"test": "run-s build test:*",
|
||||||
"test:lint": "eslint src --ext .ts --ext .tsx",
|
"test:lint": "eslint src --ext .ts --ext .tsx",
|
||||||
"test:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" \"./config/*.js\" --list-different",
|
"test:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --list-different",
|
||||||
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.{ts,tsx},public/**/*.html}\" -c ../.cspell.json",
|
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.{ts,tsx},public/**/*.html}\" -c ../.cspell.json",
|
||||||
"fix:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" \"./config/*.js\" --write",
|
"fix:prettier": "prettier \"src/**/*.{ts,tsx}\" \"./*.json\" --write",
|
||||||
"fix:lint": "eslint src --ext .ts --ext .tsx --fix",
|
"fix:lint": "eslint src --ext .ts --ext .tsx --fix",
|
||||||
"proto": "run-s proto:*",
|
"proto": "protons src/proto/*.proto",
|
||||||
"proto:build": "buf generate",
|
|
||||||
"js-waku:build": "cd ../; npm run build",
|
"js-waku:build": "cd ../; npm run build",
|
||||||
"predeploy": "run-s js-waku:build build",
|
"predeploy": "run-s js-waku:build build",
|
||||||
"deploy": "gh-pages -d build"
|
"deploy": "gh-pages -d build"
|
||||||
|
|||||||
2905
web-chat/pnpm-lock.yaml
generated
2905
web-chat/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,14 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import {
|
import {
|
||||||
discovery,
|
|
||||||
getPredefinedBootstrapNodes,
|
|
||||||
PageDirection,
|
PageDirection,
|
||||||
|
Protocols,
|
||||||
Waku,
|
Waku,
|
||||||
|
WakuFilter,
|
||||||
|
WakuLightPush,
|
||||||
WakuMessage,
|
WakuMessage,
|
||||||
|
WakuRelay,
|
||||||
|
WakuStore,
|
||||||
} from "js-waku";
|
} from "js-waku";
|
||||||
import handleCommand from "./command";
|
import handleCommand from "./command";
|
||||||
import Room from "./Room";
|
import Room from "./Room";
|
||||||
@ -13,6 +16,14 @@ import { WakuContext } from "./WakuContext";
|
|||||||
import { ThemeProvider } from "@livechat/ui-kit";
|
import { ThemeProvider } from "@livechat/ui-kit";
|
||||||
import { generate } from "server-name-generator";
|
import { generate } from "server-name-generator";
|
||||||
import { Message } from "./Message";
|
import { Message } from "./Message";
|
||||||
|
import {
|
||||||
|
Fleet,
|
||||||
|
getPredefinedBootstrapNodes,
|
||||||
|
} from "js-waku/lib/predefined_bootstrap_nodes";
|
||||||
|
import { waitForRemotePeer } from "js-waku/lib/wait_for_remote_peer";
|
||||||
|
import { PeerDiscoveryStaticPeers } from "js-waku/lib/peer_discovery_static_list";
|
||||||
|
import { defaultLibp2p } from "js-waku/lib/create_waku";
|
||||||
|
import process from "process";
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
AuthorName: {
|
AuthorName: {
|
||||||
@ -110,7 +121,7 @@ export default function App() {
|
|||||||
// Let's retrieve previous messages before listening to new messages
|
// Let's retrieve previous messages before listening to new messages
|
||||||
if (!historicalMessagesRetrieved) return;
|
if (!historicalMessagesRetrieved) return;
|
||||||
|
|
||||||
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
const handleIncomingMessage = (wakuMsg: WakuMessage) => {
|
||||||
console.log("Message received: ", wakuMsg);
|
console.log("Message received: ", wakuMsg);
|
||||||
const msg = Message.fromWakuMessage(wakuMsg);
|
const msg = Message.fromWakuMessage(wakuMsg);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
@ -118,10 +129,26 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
waku.relay.addObserver(handleRelayMessage, [ChatContentTopic]);
|
let unsubscribe: undefined | (() => Promise<void>);
|
||||||
|
waku.filter.subscribe(handleIncomingMessage, [ChatContentTopic]).then(
|
||||||
|
(_unsubscribe) => {
|
||||||
|
console.log("subscribed to ", ChatContentTopic);
|
||||||
|
unsubscribe = _unsubscribe;
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
console.error("Failed to subscribe", e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return function cleanUp() {
|
return function cleanUp() {
|
||||||
waku?.relay.deleteObserver(handleRelayMessage, [ChatContentTopic]);
|
if (!waku) return;
|
||||||
|
if (typeof unsubscribe === "undefined") return;
|
||||||
|
unsubscribe().then(
|
||||||
|
() => {
|
||||||
|
console.log("unsubscribed to ", ChatContentTopic);
|
||||||
|
},
|
||||||
|
(e) => console.error("Failed to unsubscribe", e)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [waku, historicalMessagesRetrieved]);
|
}, [waku, historicalMessagesRetrieved]);
|
||||||
|
|
||||||
@ -130,7 +157,11 @@ export default function App() {
|
|||||||
if (historicalMessagesRetrieved) return;
|
if (historicalMessagesRetrieved) return;
|
||||||
|
|
||||||
const retrieveMessages = async () => {
|
const retrieveMessages = async () => {
|
||||||
await waku.waitForRemotePeer();
|
await waitForRemotePeer(waku, [
|
||||||
|
Protocols.Store,
|
||||||
|
Protocols.Filter,
|
||||||
|
Protocols.LightPush,
|
||||||
|
]);
|
||||||
console.log(`Retrieving archived messages`);
|
console.log(`Retrieving archived messages`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -175,19 +206,23 @@ export default function App() {
|
|||||||
|
|
||||||
async function initWaku(setter: (waku: Waku) => void) {
|
async function initWaku(setter: (waku: Waku) => void) {
|
||||||
try {
|
try {
|
||||||
const waku = await Waku.create({
|
// TODO: Remove this declaration once there are optional in js-waku
|
||||||
libp2p: {
|
const wakuRelay = new WakuRelay({ emitSelf: true });
|
||||||
config: {
|
|
||||||
pubsub: {
|
const libp2p = await defaultLibp2p(wakuRelay, {
|
||||||
enabled: true,
|
peerDiscovery: [
|
||||||
emitSelf: true,
|
new PeerDiscoveryStaticPeers(
|
||||||
},
|
getPredefinedBootstrapNodes(selectFleetEnv())
|
||||||
},
|
),
|
||||||
},
|
],
|
||||||
bootstrap: {
|
|
||||||
peers: getPredefinedBootstrapNodes(selectFleetEnv()),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const wakuStore = new WakuStore(libp2p);
|
||||||
|
|
||||||
|
const wakuLightPush = new WakuLightPush(libp2p);
|
||||||
|
const wakuFilter = new WakuFilter(libp2p);
|
||||||
|
|
||||||
|
const waku = new Waku({}, libp2p, wakuStore, wakuLightPush, wakuFilter);
|
||||||
|
await waku.start();
|
||||||
|
|
||||||
setter(waku);
|
setter(waku);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -197,10 +232,11 @@ async function initWaku(setter: (waku: Waku) => void) {
|
|||||||
|
|
||||||
function selectFleetEnv() {
|
function selectFleetEnv() {
|
||||||
// Works with react-scripts
|
// Works with react-scripts
|
||||||
if (process?.env?.NODE_ENV === "development") {
|
// TODO: Re-enable the switch once nwaku v0.12 is deployed
|
||||||
return discovery.predefined.Fleet.Test;
|
if (true || process?.env?.NODE_ENV === "development") {
|
||||||
|
return Fleet.Test;
|
||||||
} else {
|
} else {
|
||||||
return discovery.predefined.Fleet.Prod;
|
return Fleet.Prod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,12 +16,10 @@ export default function ChatList(props: Props) {
|
|||||||
const renderedMessages = props.messages.map((message) => (
|
const renderedMessages = props.messages.map((message) => (
|
||||||
<LiveMessage
|
<LiveMessage
|
||||||
key={
|
key={
|
||||||
message.sentTimestamp
|
message.nick +
|
||||||
? message.sentTimestamp.valueOf()
|
message.payloadAsUtf8 +
|
||||||
: "" +
|
message.timestamp.valueOf() +
|
||||||
message.timestamp.valueOf() +
|
message.sentTimestamp?.valueOf()
|
||||||
message.nick +
|
|
||||||
message.payloadAsUtf8
|
|
||||||
}
|
}
|
||||||
authorName={message.nick}
|
authorName={message.nick}
|
||||||
date={formatDisplayDate(message)}
|
date={formatDisplayDate(message)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeEvent, KeyboardEvent, useState } from "react";
|
import { ChangeEvent, KeyboardEvent, useEffect, useState } from "react";
|
||||||
import { useWaku } from "./WakuContext";
|
import { useWaku } from "./WakuContext";
|
||||||
import {
|
import {
|
||||||
TextInput,
|
TextInput,
|
||||||
@ -15,6 +15,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function MessageInput(props: Props) {
|
export default function MessageInput(props: Props) {
|
||||||
const [inputText, setInputText] = useState<string>("");
|
const [inputText, setInputText] = useState<string>("");
|
||||||
|
const [activeButton, setActiveButton] = useState<boolean>(false);
|
||||||
const { waku } = useWaku();
|
const { waku } = useWaku();
|
||||||
|
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
@ -39,9 +40,21 @@ export default function MessageInput(props: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable the button if there are relay peers available or the user is sending a command
|
// Enable the button if there are peers available or the user is sending a command
|
||||||
const activeButton =
|
useEffect(() => {
|
||||||
(waku && waku.relay.getPeers().size !== 0) || inputText.startsWith("/");
|
if (inputText.startsWith("/")) {
|
||||||
|
setActiveButton(true);
|
||||||
|
} else if (waku) {
|
||||||
|
(async () => {
|
||||||
|
const peers = await waku.lightPush.peers();
|
||||||
|
if (!!peers) {
|
||||||
|
setActiveButton(true);
|
||||||
|
} else {
|
||||||
|
setActiveButton(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}, [activeButton, inputText, waku]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextComposer
|
<TextComposer
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { WakuMessage } from "js-waku";
|
import { PushResponse, WakuMessage } from "js-waku";
|
||||||
import { ChatContentTopic } from "./App";
|
import { ChatContentTopic } from "./App";
|
||||||
import ChatList from "./ChatList";
|
import ChatList from "./ChatList";
|
||||||
import MessageInput from "./MessageInput";
|
import MessageInput from "./MessageInput";
|
||||||
@ -18,28 +18,22 @@ export default function Room(props: Props) {
|
|||||||
const { waku } = useWaku();
|
const { waku } = useWaku();
|
||||||
|
|
||||||
const [storePeers, setStorePeers] = useState(0);
|
const [storePeers, setStorePeers] = useState(0);
|
||||||
const [relayPeers, setRelayPeers] = useState(0);
|
const [filterPeers, setFilterPeers] = useState(0);
|
||||||
|
const [lightPushPeers, setLightPushPeers] = useState(0);
|
||||||
useEffect(() => {
|
|
||||||
if (!waku) return;
|
|
||||||
|
|
||||||
// Update relay peer count on heartbeat
|
|
||||||
waku.relay.on("gossipsub:heartbeat", () => {
|
|
||||||
setRelayPeers(waku.relay.getPeers().size);
|
|
||||||
});
|
|
||||||
}, [waku]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!waku) return;
|
if (!waku) return;
|
||||||
|
|
||||||
// Update store peer when new peer connected & identified
|
// Update store peer when new peer connected & identified
|
||||||
waku.libp2p.peerStore.on("change:protocols", async () => {
|
waku.libp2p.peerStore.addEventListener("change:protocols", async () => {
|
||||||
let counter = 0;
|
const storePeers = await waku.store.peers();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
setStorePeers(storePeers.length);
|
||||||
for await (const _peer of waku.store.peers) {
|
|
||||||
counter++;
|
const filterPeers = await waku.filter.peers();
|
||||||
}
|
setFilterPeers(filterPeers.length);
|
||||||
setStorePeers(counter);
|
|
||||||
|
const lightPushPeers = await waku.lightPush.peers();
|
||||||
|
setLightPushPeers(lightPushPeers.length);
|
||||||
});
|
});
|
||||||
}, [waku]);
|
}, [waku]);
|
||||||
|
|
||||||
@ -49,7 +43,9 @@ export default function Room(props: Props) {
|
|||||||
style={{ height: "98vh", display: "flex", flexDirection: "column" }}
|
style={{ height: "98vh", display: "flex", flexDirection: "column" }}
|
||||||
>
|
>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
leftIcons={[`Peers: ${relayPeers} relay ${storePeers} store.`]}
|
leftIcons={[
|
||||||
|
`Peers: ${lightPushPeers} light push, ${filterPeers} filter, ${storePeers} store.`,
|
||||||
|
]}
|
||||||
title="Waku v2 chat app"
|
title="Waku v2 chat app"
|
||||||
/>
|
/>
|
||||||
<ChatList messages={props.messages} />
|
<ChatList messages={props.messages} />
|
||||||
@ -61,7 +57,7 @@ export default function Room(props: Props) {
|
|||||||
messageToSend,
|
messageToSend,
|
||||||
props.nick,
|
props.nick,
|
||||||
props.commandHandler,
|
props.commandHandler,
|
||||||
waku.relay.send.bind(waku.relay)
|
waku.lightPush.push.bind(waku.lightPush)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
@ -75,7 +71,7 @@ async function handleMessage(
|
|||||||
message: string,
|
message: string,
|
||||||
nick: string,
|
nick: string,
|
||||||
commandHandler: (cmd: string) => void,
|
commandHandler: (cmd: string) => void,
|
||||||
messageSender: (msg: WakuMessage) => Promise<void>
|
messageSender: (msg: WakuMessage) => Promise<PushResponse | null>
|
||||||
) {
|
) {
|
||||||
if (message.startsWith("/")) {
|
if (message.startsWith("/")) {
|
||||||
commandHandler(message);
|
commandHandler(message);
|
||||||
@ -87,6 +83,6 @@ async function handleMessage(
|
|||||||
ChatContentTopic,
|
ChatContentTopic,
|
||||||
{ timestamp }
|
{ timestamp }
|
||||||
);
|
);
|
||||||
return messageSender(wakuMsg);
|
await messageSender(wakuMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import WakuMock, { Message } from "./WakuMock";
|
|
||||||
|
|
||||||
test("Messages are emitted", async () => {
|
|
||||||
const wakuMock = await WakuMock.create();
|
|
||||||
|
|
||||||
let message: Message;
|
|
||||||
wakuMock.on("message", (msg) => {
|
|
||||||
message = msg;
|
|
||||||
});
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
||||||
// @ts-ignore
|
|
||||||
expect(message.message).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Messages are sent", async () => {
|
|
||||||
const wakuMock = await WakuMock.create();
|
|
||||||
|
|
||||||
const text = "This is a message.";
|
|
||||||
|
|
||||||
let message: Message;
|
|
||||||
wakuMock.on("message", (msg) => {
|
|
||||||
message = msg;
|
|
||||||
});
|
|
||||||
|
|
||||||
await wakuMock.send(text);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(message.message).toEqual(text);
|
|
||||||
});
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
class EventEmitter<T> {
|
|
||||||
public callbacks: { [key: string]: Array<(data: T) => void> };
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.callbacks = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
on(event: string, cb: (data: T) => void) {
|
|
||||||
if (!this.callbacks[event]) this.callbacks[event] = [];
|
|
||||||
this.callbacks[event].push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: string, data: T) {
|
|
||||||
let cbs = this.callbacks[event];
|
|
||||||
if (cbs) {
|
|
||||||
cbs.forEach((cb) => cb(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
timestamp: Date;
|
|
||||||
handle: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class WakuMock extends EventEmitter<Message> {
|
|
||||||
index: number;
|
|
||||||
intervalId?: number | NodeJS.Timeout;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
super();
|
|
||||||
this.index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async create(): Promise<WakuMock> {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
const wakuMock = new WakuMock();
|
|
||||||
wakuMock.startInterval();
|
|
||||||
return wakuMock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async send(message: string): Promise<void> {
|
|
||||||
const timestamp = new Date();
|
|
||||||
const handle = "me";
|
|
||||||
this.emit("message", {
|
|
||||||
timestamp,
|
|
||||||
handle,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private startInterval() {
|
|
||||||
if (this.intervalId === undefined) {
|
|
||||||
this.intervalId = setInterval(this.emitMessage.bind(this), 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitMessage() {
|
|
||||||
const handle = "you";
|
|
||||||
const timestamp = new Date();
|
|
||||||
this.emit("message", {
|
|
||||||
timestamp,
|
|
||||||
handle,
|
|
||||||
message: `This is message #${this.index++}.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { Reader } from "protobufjs/minimal";
|
import { utils } from "js-waku";
|
||||||
|
|
||||||
import * as proto from "./proto/chat_message";
|
import * as proto from "./proto/chat_message";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,8 +19,8 @@ export class ChatMessage {
|
|||||||
nick: string,
|
nick: string,
|
||||||
text: string
|
text: string
|
||||||
): ChatMessage {
|
): ChatMessage {
|
||||||
const timestampNumber = Math.floor(timestamp.valueOf() / 1000);
|
const timestampNumber = BigInt(Math.floor(timestamp.valueOf() / 1000));
|
||||||
const payload = Buffer.from(text, "utf-8");
|
const payload = utils.utf8ToBytes(text);
|
||||||
|
|
||||||
return new ChatMessage({
|
return new ChatMessage({
|
||||||
timestamp: timestampNumber,
|
timestamp: timestampNumber,
|
||||||
@ -35,7 +34,7 @@ export class ChatMessage {
|
|||||||
* @param bytes The payload to decode.
|
* @param bytes The payload to decode.
|
||||||
*/
|
*/
|
||||||
static decode(bytes: Uint8Array): ChatMessage {
|
static decode(bytes: Uint8Array): ChatMessage {
|
||||||
const protoMsg = proto.ChatMessage.decode(Reader.create(bytes));
|
const protoMsg = proto.ChatMessage.decode(bytes);
|
||||||
return new ChatMessage(protoMsg);
|
return new ChatMessage(protoMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,11 +43,11 @@ export class ChatMessage {
|
|||||||
* @returns The encoded payload.
|
* @returns The encoded payload.
|
||||||
*/
|
*/
|
||||||
encode(): Uint8Array {
|
encode(): Uint8Array {
|
||||||
return proto.ChatMessage.encode(this.proto).finish();
|
return proto.ChatMessage.encode(this.proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
get timestamp(): Date {
|
get timestamp(): Date {
|
||||||
return new Date(this.proto.timestamp * 1000);
|
return new Date(Number(this.proto.timestamp * BigInt(1000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
get nick(): string {
|
get nick(): string {
|
||||||
@ -60,6 +59,6 @@ export class ChatMessage {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.from(this.proto.payload).toString("utf-8");
|
return utils.bytesToUtf8(this.proto.payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { multiaddr } from "multiaddr";
|
import { multiaddr } from "@multiformats/multiaddr";
|
||||||
import PeerId from "peer-id";
|
|
||||||
import { Waku } from "js-waku";
|
import { Waku } from "js-waku";
|
||||||
|
|
||||||
function help(): string[] {
|
function help(): string[] {
|
||||||
@ -26,7 +25,7 @@ function info(waku: Waku | undefined): string[] {
|
|||||||
if (!waku) {
|
if (!waku) {
|
||||||
return ["Waku node is starting"];
|
return ["Waku node is starting"];
|
||||||
}
|
}
|
||||||
return [`PeerId: ${waku.libp2p.peerId.toB58String()}`];
|
return [`PeerId: ${waku.libp2p.peerId.toString()}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
||||||
@ -42,9 +41,7 @@ function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
|||||||
if (!peerId) {
|
if (!peerId) {
|
||||||
return ["Peer Id needed to dial"];
|
return ["Peer Id needed to dial"];
|
||||||
}
|
}
|
||||||
waku.addPeerToAddressBook(PeerId.createFromB58String(peerId), [
|
waku.addPeerToAddressBook(peerId, [peerMultiaddr]);
|
||||||
peerMultiaddr,
|
|
||||||
]);
|
|
||||||
return [
|
return [
|
||||||
`${peerId}: ${peerMultiaddr.toString()} added to address book, autodial in progress`,
|
`${peerId}: ${peerMultiaddr.toString()} added to address book, autodial in progress`,
|
||||||
];
|
];
|
||||||
@ -58,13 +55,10 @@ async function peers(waku: Waku | undefined): Promise<string[]> {
|
|||||||
return ["Waku node is starting"];
|
return ["Waku node is starting"];
|
||||||
}
|
}
|
||||||
let response: string[] = [];
|
let response: string[] = [];
|
||||||
const peers = [];
|
const peers = await waku.libp2p.peerStore.all();
|
||||||
|
|
||||||
for await (const peer of waku.libp2p.peerStore.getPeers()) {
|
|
||||||
peers.push(peer);
|
|
||||||
}
|
|
||||||
Array.from(peers).forEach((peer) => {
|
Array.from(peers).forEach((peer) => {
|
||||||
response.push(peer.id.toB58String() + ":");
|
response.push(peer.id.toString() + ":");
|
||||||
let addresses = " addresses: [";
|
let addresses = " addresses: [";
|
||||||
peer.addresses.forEach(({ multiaddr }) => {
|
peer.addresses.forEach(({ multiaddr }) => {
|
||||||
addresses += " " + multiaddr.toString() + ",";
|
addresses += " " + multiaddr.toString() + ",";
|
||||||
@ -88,21 +82,14 @@ function connections(waku: Waku | undefined): string[] {
|
|||||||
return ["Waku node is starting"];
|
return ["Waku node is starting"];
|
||||||
}
|
}
|
||||||
let response: string[] = [];
|
let response: string[] = [];
|
||||||
waku.libp2p.connections.forEach(
|
let strConnections = " connections: \n";
|
||||||
(
|
waku.libp2p.connectionManager.getConnections().forEach((connection) => {
|
||||||
connections: import("libp2p-interfaces/src/connection/connection")[],
|
strConnections += connection.remotePeer.toString() + ", ";
|
||||||
peerId
|
strConnections += JSON.stringify(connection.stat);
|
||||||
) => {
|
strConnections += "; " + JSON.stringify(connection.streams);
|
||||||
response.push(peerId + ":");
|
strConnections += "\n";
|
||||||
let strConnections = " connections: [";
|
});
|
||||||
connections.forEach((connection) => {
|
response.push(strConnections);
|
||||||
strConnections += JSON.stringify(connection.stat);
|
|
||||||
strConnections += "; " + JSON.stringify(connection.streams);
|
|
||||||
});
|
|
||||||
strConnections += "]";
|
|
||||||
response.push(strConnections);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (response.length === 0) {
|
if (response.length === 0) {
|
||||||
response.push("Not connected to any peer.");
|
response.push("Not connected to any peer.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,169 +1,117 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable import/export */
|
||||||
import Long from "long";
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
import _m0 from "protobufjs/minimal";
|
|
||||||
|
|
||||||
export const protobufPackage = "";
|
import { encodeMessage, decodeMessage, message } from "protons-runtime";
|
||||||
|
import type { Uint8ArrayList } from "uint8arraylist";
|
||||||
|
import type { Codec } from "protons-runtime";
|
||||||
|
|
||||||
export interface ChatMessage {
|
export interface ChatMessage {
|
||||||
timestamp: number;
|
timestamp: bigint;
|
||||||
nick: string;
|
nick: string;
|
||||||
payload: Uint8Array;
|
payload: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBaseChatMessage(): ChatMessage {
|
export namespace ChatMessage {
|
||||||
return { timestamp: 0, nick: "", payload: new Uint8Array() };
|
let _codec: Codec<ChatMessage>;
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatMessage = {
|
export const codec = (): Codec<ChatMessage> => {
|
||||||
encode(
|
if (_codec == null) {
|
||||||
message: ChatMessage,
|
_codec = message<ChatMessage>(
|
||||||
writer: _m0.Writer = _m0.Writer.create()
|
(obj, writer, opts = {}) => {
|
||||||
): _m0.Writer {
|
if (opts.lengthDelimited !== false) {
|
||||||
if (message.timestamp !== 0) {
|
writer.fork();
|
||||||
writer.uint32(8).uint64(message.timestamp);
|
}
|
||||||
|
|
||||||
|
if (obj.timestamp != null) {
|
||||||
|
writer.uint32(8);
|
||||||
|
writer.uint64(obj.timestamp);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: required field "timestamp" was not found in object'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.nick != null) {
|
||||||
|
writer.uint32(18);
|
||||||
|
writer.string(obj.nick);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: required field "nick" was not found in object'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.payload != null) {
|
||||||
|
writer.uint32(26);
|
||||||
|
writer.bytes(obj.payload);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: required field "payload" was not found in object'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.lengthDelimited !== false) {
|
||||||
|
writer.ldelim();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(reader, length) => {
|
||||||
|
const obj: any = {
|
||||||
|
timestamp: 0n,
|
||||||
|
nick: "",
|
||||||
|
payload: new Uint8Array(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
const end = length == null ? reader.len : reader.pos + length;
|
||||||
|
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1:
|
||||||
|
obj.timestamp = reader.uint64();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
obj.nick = reader.string();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
obj.payload = reader.bytes();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reader.skipType(tag & 7);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.timestamp == null) {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: value for required field "timestamp" was not found in protobuf'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.nick == null) {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: value for required field "nick" was not found in protobuf'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.payload == null) {
|
||||||
|
throw new Error(
|
||||||
|
'Protocol error: value for required field "payload" was not found in protobuf'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (message.nick !== "") {
|
|
||||||
writer.uint32(18).string(message.nick);
|
|
||||||
}
|
|
||||||
if (message.payload.length !== 0) {
|
|
||||||
writer.uint32(26).bytes(message.payload);
|
|
||||||
}
|
|
||||||
return writer;
|
|
||||||
},
|
|
||||||
|
|
||||||
decode(input: _m0.Reader | Uint8Array, length?: number): ChatMessage {
|
return _codec;
|
||||||
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
|
};
|
||||||
let end = length === undefined ? reader.len : reader.pos + length;
|
|
||||||
const message = createBaseChatMessage();
|
|
||||||
while (reader.pos < end) {
|
|
||||||
const tag = reader.uint32();
|
|
||||||
switch (tag >>> 3) {
|
|
||||||
case 1:
|
|
||||||
message.timestamp = longToNumber(reader.uint64() as Long);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
message.nick = reader.string();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
message.payload = reader.bytes();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
reader.skipType(tag & 7);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
|
|
||||||
fromJSON(object: any): ChatMessage {
|
export const encode = (obj: ChatMessage): Uint8Array => {
|
||||||
const message = createBaseChatMessage();
|
return encodeMessage(obj, ChatMessage.codec());
|
||||||
message.timestamp =
|
};
|
||||||
object.timestamp !== undefined && object.timestamp !== null
|
|
||||||
? Number(object.timestamp)
|
|
||||||
: 0;
|
|
||||||
message.nick =
|
|
||||||
object.nick !== undefined && object.nick !== null
|
|
||||||
? String(object.nick)
|
|
||||||
: "";
|
|
||||||
message.payload =
|
|
||||||
object.payload !== undefined && object.payload !== null
|
|
||||||
? bytesFromBase64(object.payload)
|
|
||||||
: new Uint8Array();
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
|
|
||||||
toJSON(message: ChatMessage): unknown {
|
export const decode = (buf: Uint8Array | Uint8ArrayList): ChatMessage => {
|
||||||
const obj: any = {};
|
return decodeMessage(buf, ChatMessage.codec());
|
||||||
message.timestamp !== undefined &&
|
};
|
||||||
(obj.timestamp = Math.round(message.timestamp));
|
|
||||||
message.nick !== undefined && (obj.nick = message.nick);
|
|
||||||
message.payload !== undefined &&
|
|
||||||
(obj.payload = base64FromBytes(
|
|
||||||
message.payload !== undefined ? message.payload : new Uint8Array()
|
|
||||||
));
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
|
|
||||||
fromPartial<I extends Exact<DeepPartial<ChatMessage>, I>>(
|
|
||||||
object: I
|
|
||||||
): ChatMessage {
|
|
||||||
const message = createBaseChatMessage();
|
|
||||||
message.timestamp = object.timestamp ?? 0;
|
|
||||||
message.nick = object.nick ?? "";
|
|
||||||
message.payload = object.payload ?? new Uint8Array();
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
declare var self: any | undefined;
|
|
||||||
declare var window: any | undefined;
|
|
||||||
declare var global: any | undefined;
|
|
||||||
var globalThis: any = (() => {
|
|
||||||
if (typeof globalThis !== "undefined") return globalThis;
|
|
||||||
if (typeof self !== "undefined") return self;
|
|
||||||
if (typeof window !== "undefined") return window;
|
|
||||||
if (typeof global !== "undefined") return global;
|
|
||||||
throw "Unable to locate global object";
|
|
||||||
})();
|
|
||||||
|
|
||||||
const atob: (b64: string) => string =
|
|
||||||
globalThis.atob ||
|
|
||||||
((b64) => globalThis.Buffer.from(b64, "base64").toString("binary"));
|
|
||||||
function bytesFromBase64(b64: string): Uint8Array {
|
|
||||||
const bin = atob(b64);
|
|
||||||
const arr = new Uint8Array(bin.length);
|
|
||||||
for (let i = 0; i < bin.length; ++i) {
|
|
||||||
arr[i] = bin.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const btoa: (bin: string) => string =
|
|
||||||
globalThis.btoa ||
|
|
||||||
((bin) => globalThis.Buffer.from(bin, "binary").toString("base64"));
|
|
||||||
function base64FromBytes(arr: Uint8Array): string {
|
|
||||||
const bin: string[] = [];
|
|
||||||
for (const byte of arr) {
|
|
||||||
bin.push(String.fromCharCode(byte));
|
|
||||||
}
|
|
||||||
return btoa(bin.join(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builtin =
|
|
||||||
| Date
|
|
||||||
| Function
|
|
||||||
| Uint8Array
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends Builtin
|
|
||||||
? T
|
|
||||||
: T extends Array<infer U>
|
|
||||||
? Array<DeepPartial<U>>
|
|
||||||
: T extends ReadonlyArray<infer U>
|
|
||||||
? ReadonlyArray<DeepPartial<U>>
|
|
||||||
: T extends {}
|
|
||||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
||||||
: Partial<T>;
|
|
||||||
|
|
||||||
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
|
||||||
export type Exact<P, I extends P> = P extends Builtin
|
|
||||||
? P
|
|
||||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & Record<
|
|
||||||
Exclude<keyof I, KeysOfUnion<P>>,
|
|
||||||
never
|
|
||||||
>;
|
|
||||||
|
|
||||||
function longToNumber(long: Long): number {
|
|
||||||
if (long.gt(Number.MAX_SAFE_INTEGER)) {
|
|
||||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
|
||||||
}
|
|
||||||
return long.toNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_m0.util.Long !== Long) {
|
|
||||||
_m0.util.Long = Long as any;
|
|
||||||
_m0.configure();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user