PoC of WebRTC signalling exchange through @waku/noise (#186)
* init PoC from noise example * reimplement webrtc logic, add sending of ice candidate to a peer * add RTC abstractions and pretify code * finalize rtc connection * fix sending of the message * add favicon and manifest, rename package, rename build folder * add README with details * fix styling * add new stage for the example
This commit is contained in:
parent
8d0f703593
commit
95d13a52db
|
@ -18,7 +18,8 @@ jobs:
|
|||
relay-reactjs-chat,
|
||||
store-reactjs-chat,
|
||||
web-chat,
|
||||
noise-js
|
||||
noise-js,
|
||||
noise-rtc
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
@ -40,6 +40,7 @@ pipeline {
|
|||
stage('store-reactjs-chat') { steps { script { buildExample() } } }
|
||||
stage('web-chat') { steps { script { buildExample() } } }
|
||||
stage('noise-js') { steps { script { buildExample() } } }
|
||||
stage('noise-rtc') { steps { script { buildExample() } } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
State of this example: **work in progress**
|
||||
|
||||
What's done:
|
||||
- By using `js-noise` users can establish secure communication channel.
|
||||
- This channel is used to exchange `offer/answer` to initiate direct WebRTC connection.
|
||||
|
||||
What should be done:
|
||||
- `STUN` server: in order not to loose benefits of peer-to-peer protocols used underneath we should find a way to be able to retrieve coordinates of a user to build `offer/answer` by not involving one `STUN` server for it;
|
||||
- `TURN` server: similarly to prev point we should be able to cover a need to create WebRTC connection for users who are behind secure `NAT` and are not able to communicated just as it is.
|
||||
|
||||
Additional reading that explains why `STUN/TURN` is not easily removable from the equation: https://github.com/libp2p/specs/pull/497/files#diff-2cb0b0dcc282bd123b21f5a0610e8a01b516fc453b95c655cf7e16734f2f7b11R48-R53
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,199 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>Waku NoiseRTC</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: 100%;
|
||||
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" class="progress">connecting...</span>
|
||||
</h3>
|
||||
<h3 id="handshake-span">
|
||||
<b>Handshake Status:</b>
|
||||
<span id="handshake-status" class="progress">waiting for waku</span>
|
||||
</h3>
|
||||
<h3>
|
||||
<b>RTC Status:</b>
|
||||
<span id="rtc-status" class="progress"
|
||||
>waiting for noise to be established</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: 100px">Open in new</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>
|
||||
<button id="connect-chat-btn" type="button">Connect to chat</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,551 @@
|
|||
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 ProtoMessage = new protobuf.Type("Message").add(
|
||||
new protobuf.Field("data", 3, "string")
|
||||
);
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const ui = initUI();
|
||||
ui.waku.connecting();
|
||||
|
||||
// Starting the node
|
||||
const node = await createLightNode({
|
||||
libp2p: {
|
||||
peerDiscovery: [
|
||||
new PeerDiscoveryStaticPeers(getPredefinedBootstrapNodes(Fleet.Test)),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
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 sendWakuMessage;
|
||||
let listenToWakuMessages;
|
||||
|
||||
try {
|
||||
ui.handshake.waiting();
|
||||
|
||||
if (!urlPairingInfo) {
|
||||
const pairingURL = buildPairingURLFromObj(pairingObj);
|
||||
ui.shareInfo.setURL(pairingURL);
|
||||
ui.shareInfo.renderQR(pairingURL);
|
||||
ui.shareInfo.show();
|
||||
}
|
||||
|
||||
[sendWakuMessage, listenToWakuMessages] = await buildWakuMessage(
|
||||
node,
|
||||
pExecute
|
||||
);
|
||||
|
||||
ui.handshake.connected();
|
||||
ui.shareInfo.hide();
|
||||
} catch (err) {
|
||||
ui.handshake.error(err.message);
|
||||
ui.hide();
|
||||
}
|
||||
|
||||
/*
|
||||
// The information needs to be backed up to decrypt messages sent with
|
||||
// codecs generated with the handshake. The `handshakeResult` variable
|
||||
// contains private information that needs to be stored safely
|
||||
const contentTopic = pairingObj.contentTopic;
|
||||
const handshakeResult = pairingObj.getHandshakeResult();
|
||||
// To restore the codecs for decrypting older messages, or continuing an existing
|
||||
// session, use this:
|
||||
[encoder, decoder] = WakuPairing.getSecureCodec(contentTopic, handshakeResult);
|
||||
*/
|
||||
ui.message.display();
|
||||
|
||||
const { peerConnection, sendMessage: sendRTCMessage } = initRTC({
|
||||
ui,
|
||||
onReceive: ui.message.onReceive.bind(ui.message),
|
||||
});
|
||||
|
||||
peerConnection.onicecandidate = async (event) => {
|
||||
if (event.candidate) {
|
||||
console.log("candidate sent");
|
||||
try {
|
||||
// if (!peerConnection.remoteDescription) return;
|
||||
ui.rtc.sendingCandidate();
|
||||
await sendWakuMessage({
|
||||
type: "candidate",
|
||||
candidate: event.candidate,
|
||||
});
|
||||
} catch (error) {
|
||||
ui.rtc.error(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sendOffer = async () => {
|
||||
console.log("offer sent");
|
||||
ui.rtc.sendingOffer();
|
||||
|
||||
try {
|
||||
const offer = await peerConnection.createOffer();
|
||||
await peerConnection.setLocalDescription(offer);
|
||||
|
||||
await sendWakuMessage({
|
||||
type: "offer",
|
||||
offer,
|
||||
});
|
||||
} catch (error) {
|
||||
ui.rtc.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const sendAnswer = async (data) => {
|
||||
console.log("answer sent");
|
||||
ui.rtc.sendingAnswer();
|
||||
try {
|
||||
await peerConnection.setRemoteDescription(
|
||||
new RTCSessionDescription(data.offer)
|
||||
);
|
||||
|
||||
const answer = await peerConnection.createAnswer();
|
||||
peerConnection.setLocalDescription(answer);
|
||||
|
||||
await sendWakuMessage({
|
||||
type: "answer",
|
||||
answer,
|
||||
});
|
||||
} catch (error) {
|
||||
ui.rtc.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const receiveAnswer = async (data) => {
|
||||
try {
|
||||
console.log("answer received");
|
||||
await peerConnection.setRemoteDescription(
|
||||
new RTCSessionDescription(data.answer)
|
||||
);
|
||||
console.log("answer saved");
|
||||
|
||||
await sendWakuMessage({
|
||||
type: "ready",
|
||||
text: "received answer",
|
||||
});
|
||||
} catch (error) {
|
||||
ui.rtc.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const receiveCandidate = async (data) => {
|
||||
try {
|
||||
// if (!peerConnection.pendingRemoteDescription) return;
|
||||
console.log("candidate saved");
|
||||
await peerConnection.addIceCandidate(
|
||||
new RTCIceCandidate(data.candidate)
|
||||
);
|
||||
} catch (error) {
|
||||
ui.rtc.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWakuMessages = async (data) => {
|
||||
if (data.type === "offer") {
|
||||
await sendAnswer(data);
|
||||
}
|
||||
|
||||
if (data.type === "answer") {
|
||||
await receiveAnswer(data);
|
||||
}
|
||||
|
||||
if (data.type === "ready") {
|
||||
console.log("RTC: partner is", data.text);
|
||||
}
|
||||
|
||||
if (data.type === "candidate") {
|
||||
await receiveCandidate(data);
|
||||
}
|
||||
};
|
||||
|
||||
await listenToWakuMessages(handleWakuMessages);
|
||||
ui.message.onSend(sendRTCMessage);
|
||||
|
||||
// if we are initiator of Noise handshake
|
||||
// let's initiate Web RTC as well
|
||||
if (!urlPairingInfo) {
|
||||
await sendOffer();
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
|
||||
async function buildWakuMessage(node, noiseExecute) {
|
||||
const [encoder, decoder] = await noiseExecute;
|
||||
|
||||
const sendMessage = async (message) => {
|
||||
let payload = ProtoMessage.create({
|
||||
// data: utils.utf8ToBytes(JSON.stringify(message)),
|
||||
data: JSON.stringify(message),
|
||||
});
|
||||
payload = ProtoMessage.encode(payload).finish();
|
||||
|
||||
return node.lightPush.push(encoder, { payload });
|
||||
};
|
||||
|
||||
const listenToMessages = async (fn) => {
|
||||
return node.filter.subscribe([decoder], ({ payload }) => {
|
||||
const { data } = ProtoMessage.decode(payload);
|
||||
// fn(JSON.parse(utils.bytesToUtf8(data)));
|
||||
fn(JSON.parse(data));
|
||||
});
|
||||
};
|
||||
|
||||
return [sendMessage, listenToMessages];
|
||||
}
|
||||
|
||||
function initRTC({ ui, onReceive }) {
|
||||
const configuration = {
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
};
|
||||
const peerConnection = new RTCPeerConnection(configuration);
|
||||
const sendChannel = peerConnection.createDataChannel("chat");
|
||||
|
||||
let receiveChannel;
|
||||
|
||||
sendChannel.onopen = (event) => {
|
||||
ui.rtc.ready();
|
||||
console.log("onopen send", event);
|
||||
};
|
||||
|
||||
peerConnection.ondatachannel = (event) => {
|
||||
receiveChannel = event.channel;
|
||||
|
||||
receiveChannel.onmessage = (event) => {
|
||||
onReceive(JSON.parse(event.data));
|
||||
};
|
||||
|
||||
receiveChannel.onopen = (event) => {
|
||||
ui.rtc.ready();
|
||||
console.log("onopen receive", event);
|
||||
};
|
||||
};
|
||||
|
||||
const sendMessage = (text, nick) => {
|
||||
sendChannel.send(JSON.stringify({ text, nick, timestamp: Date.now() }));
|
||||
};
|
||||
|
||||
return {
|
||||
peerConnection,
|
||||
sendChannel,
|
||||
receiveChannel,
|
||||
sendMessage,
|
||||
};
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
const rtcStatus = document.getElementById("rtc-status");
|
||||
const connectChat = document.getElementById("connect-chat-btn");
|
||||
|
||||
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(data) {
|
||||
const { timestamp, nick, text } = data;
|
||||
|
||||
this._render({
|
||||
nick,
|
||||
time: timestamp * 1000,
|
||||
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");
|
||||
},
|
||||
},
|
||||
rtc: {
|
||||
_val(msg) {
|
||||
rtcStatus.innerText = msg;
|
||||
},
|
||||
_class(name) {
|
||||
rtcStatus.className = name;
|
||||
},
|
||||
sendingOffer() {
|
||||
this._val("sending offer");
|
||||
this._class("progress");
|
||||
},
|
||||
sendingAnswer() {
|
||||
this._val("sending answer");
|
||||
this._class("progress");
|
||||
},
|
||||
sendingCandidate() {
|
||||
this._val("sending ice candidate");
|
||||
this._class("progress");
|
||||
},
|
||||
ready() {
|
||||
this._val("ready");
|
||||
this._class("success");
|
||||
},
|
||||
error(msg) {
|
||||
this._val(msg);
|
||||
this._class("error");
|
||||
},
|
||||
onConnect(cb) {
|
||||
connectChat.addEventListener("click", cb);
|
||||
},
|
||||
},
|
||||
hide() {
|
||||
this.shareInfo.hide();
|
||||
chatArea.style.display = "none";
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Waku NoiseRTC",
|
||||
"description": "Example showing WebRTC with Waku Noise.",
|
||||
"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,23 @@
|
|||
{
|
||||
"name": "@waku/noise-rtc",
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
entry: "./index.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build"),
|
||||
filename: "index.js",
|
||||
},
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
mode: "development",
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ["index.html"],
|
||||
}),
|
||||
],
|
||||
};
|
Loading…
Reference in New Issue