mirror of https://github.com/waku-org/waku-lab.git
feat: add first version of dogfooding app (#68)
Co-authored-by: Sasha <oleksandr@status.im>
This commit is contained in:
parent
854ae02792
commit
e2ab5ae30f
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<title>Sent Received Message Ratio</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="wallet">Wallet Address:</label>
|
||||||
|
<span id="wallet"></span>
|
||||||
|
<br />
|
||||||
|
<label for="numSent">Messages Sent:</label>
|
||||||
|
<span id="numSent">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>
|
||||||
|
</div>
|
||||||
|
<script src="./index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "Sent Received Message Ratio",
|
||||||
|
"description": "Example meant to run across two browsers to test ratio of messages sent to messages received",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "waku-dogfooding",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"start": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@libp2p/peer-id": "^4.1.2",
|
||||||
|
"@waku/sdk": "0.0.26-16e9116.0",
|
||||||
|
"protobufjs": "^7.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.12.11",
|
||||||
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "13.5.6",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"webpack": "^5.74.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-dev-server": "^4.11.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
import {
|
||||||
|
createLightNode,
|
||||||
|
createEncoder,
|
||||||
|
createDecoder,
|
||||||
|
DecodedMessage,
|
||||||
|
waitForRemotePeer,
|
||||||
|
DefaultPubsubTopic,
|
||||||
|
LightNode,
|
||||||
|
} from "@waku/sdk";
|
||||||
|
|
||||||
|
import { Type, Field } from "protobufjs";
|
||||||
|
import {
|
||||||
|
TelemetryClient,
|
||||||
|
TelemetryPushFilter,
|
||||||
|
TelemetryType,
|
||||||
|
} from "./telemetry_client";
|
||||||
|
import { generateRandomNumber, hashNumber } from "./util";
|
||||||
|
|
||||||
|
const DEFAULT_CONTENT_TOPIC = "/js-waku-examples/1/message-ratio/utf8";
|
||||||
|
const TELEMETRY_URL = process.env.TELEMETRY_URL || "http://localhost:8080/waku-metrics";
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
|
const sequenceCompletedEvent = new CustomEvent("sequenceCompleted");
|
||||||
|
const messageSentEvent = new CustomEvent("messageSent");
|
||||||
|
|
||||||
|
const wakuNode = async (): Promise<LightNode> => {
|
||||||
|
return await createLightNode({
|
||||||
|
contentTopics: [DEFAULT_CONTENT_TOPIC],
|
||||||
|
defaultBootstrap: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function app(telemetryClient: TelemetryClient) {
|
||||||
|
const node = await wakuNode();
|
||||||
|
await node.start();
|
||||||
|
|
||||||
|
// TODO: https://github.com/waku-org/js-waku/issues/2079
|
||||||
|
// Dialing bootstrap peers right on start in order to have Filter subscription initiated properly
|
||||||
|
await node.dial("/dns4/node-01.do-ams3.waku.test.status.im/tcp/8000/wss");
|
||||||
|
await node.dial("/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/8000/wss");
|
||||||
|
await node.dial("/dns4/node-01.gc-us-central1-a.waku.test.status.im/tcp/8000/wss");
|
||||||
|
|
||||||
|
await waitForRemotePeer(node);
|
||||||
|
|
||||||
|
const peerId = node.libp2p.peerId.toString();
|
||||||
|
const encoder = createEncoder({
|
||||||
|
contentTopic: DEFAULT_CONTENT_TOPIC,
|
||||||
|
});
|
||||||
|
|
||||||
|
const startLightPushSequence = async (
|
||||||
|
numMessages: number,
|
||||||
|
period: number = 3000
|
||||||
|
) => {
|
||||||
|
const sequenceHash = await hashNumber(generateRandomNumber());
|
||||||
|
const sequenceTotal = numMessages;
|
||||||
|
let sequenceIndex = 0;
|
||||||
|
|
||||||
|
const sendMessage = async () => {
|
||||||
|
try {
|
||||||
|
const message = ProtoSequencedMessage.create({
|
||||||
|
hash: sequenceHash,
|
||||||
|
total: sequenceTotal,
|
||||||
|
index: sequenceIndex,
|
||||||
|
sender: peerId,
|
||||||
|
});
|
||||||
|
const payload = ProtoSequencedMessage.encode(message).finish();
|
||||||
|
const result = await node.lightPush.send(encoder, {
|
||||||
|
payload,
|
||||||
|
timestamp: new Date(),
|
||||||
|
});
|
||||||
|
console.log("light push successes: ", result.successes.length);
|
||||||
|
console.log("light push failures: ", result.failures.length);
|
||||||
|
if (result.successes.length > 0) {
|
||||||
|
// Push to telemetry client
|
||||||
|
telemetryClient.push<TelemetryPushFilter>([
|
||||||
|
{
|
||||||
|
messageType: TelemetryType.LIGHT_PUSH_FILTER,
|
||||||
|
timestamp: Math.floor(new Date().getTime() / 1000),
|
||||||
|
peerIdSender: peerId,
|
||||||
|
peerIdReporter: peerId,
|
||||||
|
sequenceHash: sequenceHash,
|
||||||
|
sequenceTotal: sequenceTotal,
|
||||||
|
sequenceIndex: sequenceIndex,
|
||||||
|
contentTopic: DEFAULT_CONTENT_TOPIC,
|
||||||
|
pubsubTopic: DefaultPubsubTopic,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update ui
|
||||||
|
const messageElement = document.createElement("div");
|
||||||
|
const messagesSent = document.getElementById("messagesSent");
|
||||||
|
messageElement.textContent = `Message: ${sequenceHash} ${sequenceIndex} of ${sequenceTotal}`;
|
||||||
|
messagesSent.insertBefore(messageElement, messagesSent.firstChild);
|
||||||
|
messagesSent.insertBefore(
|
||||||
|
document.createElement("br"),
|
||||||
|
messagesSent.firstChild
|
||||||
|
);
|
||||||
|
|
||||||
|
document.dispatchEvent(messageSentEvent);
|
||||||
|
|
||||||
|
// Increment sequence
|
||||||
|
sequenceIndex++;
|
||||||
|
}
|
||||||
|
if (result.failures.length > 0) {
|
||||||
|
console.error("Failed to send message", result.failures);
|
||||||
|
}
|
||||||
|
if (sequenceIndex < sequenceTotal) {
|
||||||
|
setTimeout(sendMessage, period); // Schedule the next send
|
||||||
|
} else {
|
||||||
|
document.dispatchEvent(sequenceCompletedEvent);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending message", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendMessage(); // Start the recursive sending
|
||||||
|
};
|
||||||
|
|
||||||
|
const startFilterSubscription = async () => {
|
||||||
|
const decoder = createDecoder(DEFAULT_CONTENT_TOPIC);
|
||||||
|
|
||||||
|
const messagesReceived = document.getElementById("messagesReceived");
|
||||||
|
const subscriptionCallback = (message: DecodedMessage) => {
|
||||||
|
const sequencedMessage: any = ProtoSequencedMessage.decode(
|
||||||
|
message.payload
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't bother reporting messages sent by this same node
|
||||||
|
if (sequencedMessage.sender === peerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
telemetryClient.push<TelemetryPushFilter>([
|
||||||
|
{
|
||||||
|
messageType: TelemetryType.LIGHT_PUSH_FILTER,
|
||||||
|
timestamp: Math.floor(new Date().getTime() / 1000),
|
||||||
|
peerIdSender: sequencedMessage.sender,
|
||||||
|
peerIdReporter: peerId,
|
||||||
|
sequenceHash: sequencedMessage.hash,
|
||||||
|
sequenceTotal: sequencedMessage.total,
|
||||||
|
sequenceIndex: sequencedMessage.index,
|
||||||
|
contentTopic: DEFAULT_CONTENT_TOPIC,
|
||||||
|
pubsubTopic: DefaultPubsubTopic,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const messageElement = document.createElement("div");
|
||||||
|
messageElement.textContent = `Message: ${sequencedMessage.hash} ${sequencedMessage.index} of ${sequencedMessage.total}`;
|
||||||
|
messagesReceived.appendChild(messageElement);
|
||||||
|
messagesReceived.appendChild(document.createElement("br"));
|
||||||
|
};
|
||||||
|
|
||||||
|
await node.filter.subscribe(decoder, subscriptionCallback);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
startLightPushSequence,
|
||||||
|
startFilterSubscription,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const telemetryClient = new TelemetryClient(
|
||||||
|
TELEMETRY_URL,
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
const { node, startLightPushSequence, startFilterSubscription } = await app(
|
||||||
|
telemetryClient
|
||||||
|
);
|
||||||
|
(window as any).waku = node;
|
||||||
|
|
||||||
|
const runningScreen = document.getElementById("runningScreen");
|
||||||
|
runningScreen.style.display = "block";
|
||||||
|
|
||||||
|
await telemetryClient.start();
|
||||||
|
startFilterSubscription();
|
||||||
|
|
||||||
|
let sentMessagesCount = 0;
|
||||||
|
const sentMessagesCounter = document.getElementById(
|
||||||
|
"numSent"
|
||||||
|
) as HTMLSpanElement;
|
||||||
|
document.addEventListener("messageSent", () => {
|
||||||
|
sentMessagesCount++;
|
||||||
|
sentMessagesCounter.textContent = sentMessagesCount.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
function startSequence() {
|
||||||
|
const numMessages = Math.floor(Math.random() * 16) + 5;
|
||||||
|
const messagePeriod = Math.floor(Math.random() * 2001) + 5000;
|
||||||
|
startLightPushSequence(numMessages, messagePeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener(sequenceCompletedEvent.type, () => startSequence());
|
||||||
|
startSequence();
|
||||||
|
})();
|
|
@ -0,0 +1,86 @@
|
||||||
|
export enum TelemetryType {
|
||||||
|
LIGHT_PUSH_FILTER = "LightPushFilter",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top level structure of a telemetry request
|
||||||
|
export interface TelemetryRequest {
|
||||||
|
id: number;
|
||||||
|
telemetryType: TelemetryType;
|
||||||
|
telemetryData: any; // Using 'any' to represent the raw JSON data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common to all telemetry messages
|
||||||
|
export interface TelemetryMessage {
|
||||||
|
timestamp: number;
|
||||||
|
messageType: TelemetryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TelemetryPushFilter extends TelemetryMessage {
|
||||||
|
peerIdSender: string;
|
||||||
|
peerIdReporter: string;
|
||||||
|
sequenceHash: string;
|
||||||
|
sequenceTotal: number;
|
||||||
|
sequenceIndex: number;
|
||||||
|
contentTopic: string;
|
||||||
|
pubsubTopic: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class TelemetryClient {
|
||||||
|
constructor(
|
||||||
|
private readonly url: string,
|
||||||
|
private intervalPeriod: number = 5000
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private queue: TelemetryMessage[] = [];
|
||||||
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
|
private requestId = 0;
|
||||||
|
|
||||||
|
public push<T extends TelemetryMessage>(messages: T[]) {
|
||||||
|
this.queue.push(...messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
if (!this.intervalId) {
|
||||||
|
this.intervalId = setInterval(async () => {
|
||||||
|
if (this.queue.length > 0) {
|
||||||
|
const success = await this.send(this.queue);
|
||||||
|
if (success) {
|
||||||
|
console.log("Sent ", this.queue.length, " telemetry logs");
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this.intervalPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {
|
||||||
|
if (this.intervalId) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async send<T extends TelemetryMessage>(messages: T[]) {
|
||||||
|
const telemetryRequests = messages.map((message) => ({
|
||||||
|
id: ++this.requestId,
|
||||||
|
telemetryType: message.messageType.toString(),
|
||||||
|
telemetryData: message
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(this.url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(telemetryRequests),
|
||||||
|
});
|
||||||
|
if (res.status !== 201) {
|
||||||
|
console.error("Error sending messages to telemetry service: ", res.status, res.statusText, res.json);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error sending messages to telemetry service: ", e);
|
||||||
|
console.error("Failed trying to send the following messages: ", telemetryRequests);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export const generateRandomNumber = (): number => {
|
||||||
|
return Math.floor(Math.random() * 1000000);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hashNumber = async (number: number): Promise<string> => {
|
||||||
|
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("");
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es5",
|
||||||
|
"jsx": "react",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
|
const path = require("path");
|
||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./src/index.ts", // Changed from index.js to index.ts
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "build"),
|
||||||
|
filename: "index.js",
|
||||||
|
},
|
||||||
|
experiments: {
|
||||||
|
asyncWebAssembly: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'], // Add .ts to the extensions
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts$/, // Add a rule for TypeScript files
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mode: "development",
|
||||||
|
plugins: [
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: ["index.html", "favicon.ico", "favicon.png", "manifest.json"],
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.TELEMETRY_URL': JSON.stringify(process.env.TELEMETRY_URL || "https://telemetry.status.im/waku-metrics")
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
Loading…
Reference in New Issue