Merge branch 'master' of github.com:waku-org/js-waku-examples into weboko/noise-rtc

This commit is contained in:
weboko 2023-01-24 22:34:25 +01:00
commit d6ff00724f
No known key found for this signature in database
17 changed files with 12843 additions and 49 deletions

View File

@ -17,7 +17,8 @@ jobs:
relay-angular-chat,
relay-reactjs-chat,
store-reactjs-chat,
web-chat
web-chat,
noise-js
]
runs-on: ubuntu-latest
steps:
@ -42,3 +43,34 @@ jobs:
- name: test
run: npm run test --if-present
working-directory: "examples/${{ matrix.example }}"
release_create_waku:
runs-on: ubuntu-latest
needs: [examples_build_and_test]
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2.3.3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_JS }}
- uses: bahmutov/npm-install@v1
- name: Build package
run: npm run build
working-directory: "create-waku-app"
- name: Append git hash to version
shell: bash
run: |
CURR_VERSION=$(cat package.json | jq .version | tr -d '"')
GIT_HASH=$(git rev-parse --short HEAD)
cat package.json| jq --arg version "$CURR_VERSION-$GIT_HASH" '.version |= $version' > _package.json
mv -f _package.json package.json
working-directory: "create-waku-app"
- name: Authenticate with registry
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.WAKU_CREATE_APP_PUBLISH }}" > ./.npmrc
working-directory: "create-waku-app"
- run: npm publish --tag next --access public
working-directory: "create-waku-app"

1
ci/Jenkinsfile vendored
View File

@ -39,6 +39,7 @@ pipeline {
stage('relay-reactjs-chat') { steps { script { buildExample() } } }
stage('store-reactjs-chat') { steps { script { buildExample() } } }
stage('web-chat') { steps { script { buildExample() } } }
stage('noise-js') { steps { script { buildExample() } } }
}
}

View File

@ -9,4 +9,4 @@ Usage:
For options you can specify template from which to initialize your app. Template correlates directly to the name of example you can see in this repository.
#### How to add support for new example:
Extend `wakuExamples` property defined in `package.json` in this package with the name of the example and relative path to it where folder of the example should be the same as it's name.
Just create an example in the `examples` folder in the root of the repository.

View File

@ -10,6 +10,7 @@
"license": "MIT OR Apache-2.0",
"dependencies": {
"commander": "^9.4.1",
"enquirer": "^2.3.6",
"fs-extra": "^11.1.0",
"semver": "^7.3.8",
"validate-npm-package-name": "^5.0.0"
@ -21,6 +22,14 @@
"node": ">=16"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"engines": {
"node": ">=6"
}
},
"node_modules/builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
@ -37,6 +46,17 @@
"node": "^12.20.0 || >=14"
}
},
"node_modules/enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
"dependencies": {
"ansi-colors": "^4.1.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/fs-extra": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
@ -117,6 +137,11 @@
}
},
"dependencies": {
"ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="
},
"builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
@ -130,6 +155,14 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz",
"integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw=="
},
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
"requires": {
"ansi-colors": "^4.1.1"
}
},
"fs-extra": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",

View File

@ -1,45 +1,45 @@
{
"name": "@waku/create-app",
"version": "0.1.0",
"description": "Helper package to bootstrap Waku app ",
"repository": {
"type": "git",
"url": "https://github.com/waku-org/js-waku-examples.git",
"directory": "packages/create-app"
},
"engines": {
"node": ">=16"
},
"bugs": {
"url": "https://github.com/waku-org/js-waku-examples/issues"
},
"files": [
"index.js",
"createApp.js",
"examples"
],
"main": "index.js",
"bin": {
"create-waku-app": "./index.js"
},
"scripts": {
"build": "node ./build.js",
"prepublishOnly": "npm run build"
},
"keywords": [
"waku",
"decentralised",
"communication",
"web3",
"ethereum",
"dapps"
],
"license": "MIT OR Apache-2.0",
"dependencies": {
"commander": "^9.4.1",
"enquirer": "^2.3.6",
"fs-extra": "^11.1.0",
"semver": "^7.3.8",
"validate-npm-package-name": "^5.0.0"
}
"name": "@waku/create-app",
"version": "0.1.1",
"description": "Helper package to bootstrap Waku app ",
"repository": {
"type": "git",
"url": "https://github.com/waku-org/js-waku-examples.git",
"directory": "packages/create-app"
},
"engines": {
"node": ">=16"
},
"bugs": {
"url": "https://github.com/waku-org/js-waku-examples/issues"
},
"files": [
"index.js",
"createApp.js",
"examples"
],
"main": "index.js",
"bin": {
"create-app": "./index.js"
},
"scripts": {
"build": "node ./build.js",
"prepublishOnly": "npm run build"
},
"keywords": [
"waku",
"decentralised",
"communication",
"web3",
"ethereum",
"dapps"
],
"license": "MIT OR Apache-2.0",
"dependencies": {
"commander": "^9.4.1",
"enquirer": "^2.3.6",
"fs-extra": "^11.1.0",
"semver": "^7.3.8",
"validate-npm-package-name": "^5.0.0"
}
}

View File

@ -49,7 +49,9 @@ export default function BroadcastPublicKey({
const publicKeyMessageEncoder = createEncoder(
PublicKeyContentTopic,
PublicKeyMessageEncryptionKey
PublicKeyMessageEncryptionKey,
undefined,
true
);
await waku.relay.send(publicKeyMessageEncoder, { payload });

View File

@ -118,7 +118,12 @@ async function sendMessage(
});
const payload = privateMessage.encode();
const encoder = createEncoder(PrivateMessageContentTopic, recipientPublicKey);
const encoder = createEncoder(
PrivateMessageContentTopic,
recipientPublicKey,
undefined,
true
);
console.log("pushing");
const res = await waku.relay.send(encoder, { payload });

View File

@ -0,0 +1,7 @@
# Waku Noise Pairing Example App
```
npm install
npm start
```
Browse to http://localhost:8080

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Waku Noise</title>
<link rel="apple-touch-icon" href="./favicon.png" />
<link rel="manifest" href="./manifest.json" />
<link rel="icon" href="./favicon.ico" />
<style>
* {
margin: 0;
padding: 0;
word-wrap: break-word;
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
html {
font-size: 16px;
overflow: hidden;
}
body {
display: flex;
align-items: center;
padding: 10px;
flex-direction: column;
justify-content: center;
}
.container {
width: 800px;
min-width: 300px;
max-width: 800px;
height: 100%;
display: flex;
flex-direction: column;
align-content: space-between;
}
h2 {
text-align: center;
margin-bottom: 5px;
}
h3 {
margin-bottom: 10px;
}
h3:last-of-type {
margin-bottom: 20px;
}
h2 span,
h3 span {
font-weight: normal;
}
.progress {
color: #9ea13b;
}
.success {
color: #3ba183;
}
.error {
color: #c84740;
}
button.progress {
color: white;
background-color: #9ea13b;
}
button.success {
color: white;
background-color: #3ba183;
}
button.error {
color: white;
background-color: #c84740;
}
.pairingInfo {
display: flex;
flex-direction: column;
align-items: center;
}
.pairingInfo input {
display: block;
min-width: 250px;
width: 100%;
max-width: 600px;
font-size: 1.1rem;
line-height: 1.5rem;
padding: 5px;
margin-bottom: 10px;
}
.pairingInfo button {
flex-grow: 1;
cursor: pointer;
padding: 10px;
}
.pairingInfo button + button {
margin-left: 5px;
}
.chatArea {
}
.chatArea ul {
margin-bottom: 30px;
list-style: none;
}
.chatArea ul li + li {
margin-top: 5px;
}
.chatArea div {
display: flex;
flex-direction: column;
}
.chatArea div > * {
font-size: 1.1rem;
line-height: 1.5rem;
padding: 5px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="status">
<h3>
<b>Waku Node Status:</b> <span id="waku-status">connecting...</span>
</h3>
<h3 id="handshake-span">
<b>Handshake Status:</b>
<span id="handshake-status" class="progress">waiting for waku</span>
</h3>
</div>
<div class="pairingInfo" id="qr-url-container" style="display: none">
<h2>Pairing information</h2>
<input
type="text"
id="qr-url"
readonly
placeholder="generating URL..."
/>
<div>
<button id="copy-url" style="width: 100px">Copy URL</button>
<button id="open-tab" style="width: 150px">Open in new tab</button>
</div>
<canvas id="qr-canvas"></canvas>
</div>
<div class="chatArea" id="chat-area" style="display: none">
<h2>Chat</h2>
<ul id="messages"></ul>
<div>
<input id="nick-input" placeholder="Choose a nickname" type="text" />
<textarea
id="text-input"
placeholder="Type your message here"
type="text"
></textarea>
<button id="send-btn" type="button" disabled>Send message</button>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>

341
examples/noise-js/index.js Normal file
View File

@ -0,0 +1,341 @@
import { createLightNode } from "js-waku/lib/create_waku";
import { utils } from "js-waku";
import { waitForRemotePeer } from "js-waku/lib/wait_for_remote_peer";
import {
Fleet,
getPredefinedBootstrapNodes,
} from "js-waku/lib/predefined_bootstrap_nodes";
import { PeerDiscoveryStaticPeers } from "js-waku/lib/peer_discovery_static_list";
import { Protocols } from "js-waku";
import * as noise from "@waku/noise";
import protobuf from "protobufjs";
import QRCode from "qrcode";
// Protobuf
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"));
main();
async function main() {
const ui = initUI();
ui.waku.connecting();
// Starting the node
const node = await createLightNode({
defaultBootstrap: true,
});
try {
await node.start();
await waitForRemotePeer(node, [Protocols.Filter, Protocols.LightPush]);
ui.waku.connected();
const [sender, responder] = getSenderAndResponder(node);
const myStaticKey = noise.generateX25519KeyPair();
const urlPairingInfo = getPairingInfoFromURL();
const pairingObj = new noise.WakuPairing(
sender,
responder,
myStaticKey,
urlPairingInfo || new noise.ResponderParameters()
);
const pExecute = pairingObj.execute(120000); // timeout after 2m
scheduleHandshakeAuthConfirmation(pairingObj, ui);
let encoder;
let decoder;
try {
ui.handshake.waiting();
if (!urlPairingInfo) {
const pairingURL = buildPairingURLFromObj(pairingObj);
ui.shareInfo.setURL(pairingURL);
ui.shareInfo.renderQR(pairingURL);
ui.shareInfo.show();
}
[encoder, decoder] = await pExecute;
ui.handshake.connected();
ui.shareInfo.hide();
} catch (err) {
ui.handshake.error(err.message);
ui.hide();
}
ui.message.display();
await node.filter.subscribe(
[decoder],
ui.message.onReceive.bind(ui.message)
);
ui.message.onSend(async (text, nick) => {
const timestamp = Math.floor(Date.now() / 1000);
const message = ProtoChatMessage.create({
nick,
timestamp,
text: utils.utf8ToBytes(text),
});
const payload = ProtoChatMessage.encode(message).finish();
await node.lightPush.push(encoder, { payload, timestamp });
});
} catch (err) {
ui.waku.error(err.message);
ui.hide();
}
}
function buildPairingURLFromObj(pairingObj) {
const pInfo = pairingObj.getPairingInfo();
// Data to encode in the QR code. The qrMessageNametag too to the QR string (separated by )
const messageNameTagParam = `messageNameTag=${utils.bytesToHex(
pInfo.qrMessageNameTag
)}`;
const qrCodeParam = `qrCode=${encodeURIComponent(pInfo.qrCode)}`;
return `${window.location.href}?${messageNameTagParam}&${qrCodeParam}`;
}
function getPairingInfoFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const messageNameTag = urlParams.get("messageNameTag");
const qrCodeString = urlParams.get("qrCode");
if (!(messageNameTag && qrCodeString)) {
return undefined;
}
return new noise.InitiatorParameters(
decodeURIComponent(qrCodeString),
utils.hexToBytes(messageNameTag)
);
}
function getSenderAndResponder(node) {
const sender = {
async publish(encoder, msg) {
await node.lightPush.push(encoder, msg);
},
};
const msgQueue = new Array();
const subscriptions = new Map();
const intervals = new Map();
const responder = {
async subscribe(decoder) {
const subscription = await node.filter.subscribe(
[decoder],
(wakuMessage) => {
msgQueue.push(wakuMessage);
}
);
subscriptions.set(decoder.contentTopic, subscription);
},
async nextMessage(contentTopic) {
if (msgQueue.length != 0) {
const oldestMsg = msgQueue.shift();
if (oldestMsg.contentTopic === contentTopic) {
return oldestMsg;
}
}
return new Promise((resolve) => {
const interval = setInterval(() => {
if (msgQueue.length != 0) {
clearInterval(interval);
const oldestMsg = msgQueue.shift();
if (oldestMsg.contentTopic === contentTopic) {
resolve(oldestMsg);
}
}
}, 100);
intervals.set(contentTopic, interval);
});
},
async stop(contentTopic) {
if (intervals.has(contentTopic)) {
clearInterval(intervals.get(contentTopic));
intervals.delete(contentTopic);
}
if (subscriptions.has(contentTopic)) {
await subscriptions.get(contentTopic)();
subscriptions.delete(contentTopic);
} else {
console.log("Subscriptipon doesnt exist");
}
},
};
return [sender, responder];
}
async function scheduleHandshakeAuthConfirmation(pairingObj, ui) {
const authCode = await pairingObj.getAuthCode();
ui.handshake.connecting();
pairingObj.validateAuthCode(confirm("Confirm that authcode is: " + authCode));
}
function initUI() {
const messagesList = document.getElementById("messages");
const nicknameInput = document.getElementById("nick-input");
const textInput = document.getElementById("text-input");
const sendButton = document.getElementById("send-btn");
const chatArea = document.getElementById("chat-area");
const wakuStatusSpan = document.getElementById("waku-status");
const handshakeStatusSpan = document.getElementById("handshake-status");
const qrCanvas = document.getElementById("qr-canvas");
const qrUrlContainer = document.getElementById("qr-url-container");
const qrUrl = document.getElementById("qr-url");
const copyURLButton = document.getElementById("copy-url");
const openTabButton = document.getElementById("open-tab");
copyURLButton.onclick = () => {
const copyText = document.getElementById("qr-url"); // need to get it each time otherwise copying does not work
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
};
openTabButton.onclick = () => {
window.open(qrUrl.value, "_blank");
};
const disableChatUIStateIfNeeded = () => {
const readyToSend = nicknameInput.value !== "";
textInput.disabled = !readyToSend;
sendButton.disabled = !readyToSend;
};
nicknameInput.onchange = disableChatUIStateIfNeeded;
nicknameInput.onblur = disableChatUIStateIfNeeded;
return {
shareInfo: {
setURL(url) {
qrUrl.value = url;
},
hide() {
qrUrlContainer.style.display = "none";
},
show() {
qrUrlContainer.style.display = "flex";
},
renderQR(url) {
QRCode.toCanvas(qrCanvas, url, (err) => {
if (err) {
throw err;
}
});
},
},
waku: {
_val(msg) {
wakuStatusSpan.innerText = msg;
},
_class(name) {
wakuStatusSpan.className = name;
},
connecting() {
this._val("connecting...");
this._class("progress");
},
connected() {
this._val("connected");
this._class("success");
},
error(msg) {
this._val(msg);
this._class("error");
},
},
handshake: {
_val(val) {
handshakeStatusSpan.innerText = val;
},
_class(name) {
handshakeStatusSpan.className = name;
},
error(msg) {
this._val(msg);
this._class("error");
},
waiting() {
this._val("waiting for handshake...");
this._class("progress");
},
generating() {
this._val("generating QR code...");
this._class("progress");
},
connecting() {
this._val("executing handshake...");
this._class("progress");
},
connected() {
this._val("handshake completed!");
this._class("success");
},
},
message: {
_render({ time, text, nick }) {
messagesList.innerHTML += `
<li>
(${nick})
<strong>${text}</strong>
<i>[${new Date(time).toISOString()}]</i>
</li>
`;
},
_status(text, className) {
sendButton.className = className;
},
onReceive({ payload }) {
const { timestamp, nick, text } = ProtoChatMessage.decode(payload);
this._render({
nick,
time: timestamp * 1000,
text: utils.bytesToUtf8(text),
});
},
onSend(cb) {
sendButton.addEventListener("click", async () => {
try {
this._status("sending...", "progress");
await cb(textInput.value, nicknameInput.value);
this._status("sent", "success");
this._render({
time: Date.now(), // a bit different from what receiver will see but for the matter of example is good enough
text: textInput.value,
nick: nicknameInput.value,
});
textInput.value = "";
} catch (e) {
this._status(`error: ${e.message}`, "error");
}
});
},
display() {
chatArea.style.display = "block";
this._status("waiting for input", "progress");
},
},
hide() {
this.shareInfo.hide();
chatArea.style.display = "none";
},
};
}

View File

@ -0,0 +1,19 @@
{
"name": "Waku Noise",
"description": "Example showing Waku noise capabilities.",
"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"
}

12121
examples/noise-js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"name": "@waku/noise-example",
"private": true,
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
"dependencies": {
"@waku/noise": "https://github.com/waku-org/js-noise.git",
"js-waku": "^0.29.0-29436ea",
"protobufjs": "^7.1.2",
"qrcode": "^1.5.1"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
}

View File

@ -0,0 +1,19 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
experiments: {
asyncWebAssembly: true,
},
mode: "development",
plugins: [
new CopyWebpackPlugin({
patterns: ["index.html", "favicon.ico", "favicon.png", "manifest.json"],
}),
],
};

View File

@ -205,7 +205,7 @@
let retrievedRLNEvents = false;
const rlnInstancePromise = create();
const DEFAULT_SIGNATURE_MESSAGE =
const SIGNATURE_MESSAGE =
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
// Load zero-kit WASM blob.
@ -283,7 +283,7 @@
importFromWalletButton.onclick = async () => {
const signer = provider.getSigner();
const signature = await signer.signMessage(DEFAULT_SIGNATURE_MESSAGE);
const signature = await signer.signMessage(SIGNATURE_MESSAGE);
membershipKey = await rlnInstance.generateSeededMembershipKey(
signature