mirror of https://github.com/waku-org/js-waku.git
Merge pull request #234 from status-im/fix-doc
This commit is contained in:
commit
228cdab89b
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Examples (web-chat): New `/fleet` command to switch connection between Status prod and test fleets.
|
||||||
|
- Export `generatePrivateKey` and `getPublicKey` directly from the root.
|
||||||
|
- Usage of the encryption and signature APIs to the readme.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Breaking**: Renamed `WakuRelay.(add|delete)PrivateDecryptionKey` to `WakuRelay.(add|delete)DecryptionKey` to make it clearer that it accepts both symmetric keys and asymmetric private keys.
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
- Align `WakuMessage` readme example with actual code behaviour.
|
||||||
|
|
||||||
## [0.8.0] - 2021-07-15
|
## [0.8.0] - 2021-07-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
214
README.md
214
README.md
|
@ -10,20 +10,17 @@ Install `js-waku` package:
|
||||||
npm install js-waku
|
npm install js-waku
|
||||||
```
|
```
|
||||||
|
|
||||||
Start a waku node:
|
### Start a waku node
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
import { Waku } from 'js-waku';
|
import { Waku } from 'js-waku';
|
||||||
|
|
||||||
const waku = await Waku.create();
|
const waku = await Waku.create();
|
||||||
```
|
```
|
||||||
|
|
||||||
Connect to a new peer:
|
### Connect to a new peer
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { multiaddr } from 'multiaddr';
|
|
||||||
import PeerId from 'peer-id';
|
|
||||||
|
|
||||||
|
```ts
|
||||||
// Directly dial a new peer
|
// Directly dial a new peer
|
||||||
await waku.dial('/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ');
|
await waku.dial('/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ');
|
||||||
|
|
||||||
|
@ -36,17 +33,18 @@ waku.addPeerToAddressBook(
|
||||||
|
|
||||||
You can also use `getStatusFleetNodes` to connect to nodes run by Status:
|
You can also use `getStatusFleetNodes` to connect to nodes run by Status:
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
import { getStatusFleetNodes } from 'js-waku';
|
import { getStatusFleetNodes } from 'js-waku';
|
||||||
|
|
||||||
const nodes = await getStatusFleetNodes();
|
getStatusFleetNodes().then((nodes) => {
|
||||||
await Promise.all(
|
nodes.forEach((addr) => {
|
||||||
nodes.map((addr) => {
|
waku.dial(addr);
|
||||||
return waku.dial(addr);
|
});
|
||||||
})
|
});
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Listen for messages
|
||||||
|
|
||||||
The `contentTopic` is a metadata `string` that allows categorization of messages on the waku network.
|
The `contentTopic` is a metadata `string` that allows categorization of messages on the waku network.
|
||||||
Depending on your use case, you can either create one (or several) new `contentTopic`(s) or look at the [RFCs](https://rfc.vac.dev/) and use an existing `contentTopic`.
|
Depending on your use case, you can either create one (or several) new `contentTopic`(s) or look at the [RFCs](https://rfc.vac.dev/) and use an existing `contentTopic`.
|
||||||
See the [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for more details.
|
See the [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for more details.
|
||||||
|
@ -54,7 +52,7 @@ See the [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for
|
||||||
For example, if you were to use a new `contentTopic` such as `/my-cool-app/1/my-use-case/proto`,
|
For example, if you were to use a new `contentTopic` such as `/my-cool-app/1/my-use-case/proto`,
|
||||||
here is how to listen to new messages received via [Waku v2 Relay](https://rfc.vac.dev/spec/11/):
|
here is how to listen to new messages received via [Waku v2 Relay](https://rfc.vac.dev/spec/11/):
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
waku.relay.addObserver((msg) => {
|
waku.relay.addObserver((msg) => {
|
||||||
console.log("Message received:", msg.payloadAsUtf8)
|
console.log("Message received:", msg.payloadAsUtf8)
|
||||||
}, ["/my-cool-app/1/my-use-case/proto"]);
|
}, ["/my-cool-app/1/my-use-case/proto"]);
|
||||||
|
@ -62,37 +60,199 @@ waku.relay.addObserver((msg) => {
|
||||||
|
|
||||||
The examples chat apps currently use content topic `"/toy-chat/2/huilong/proto"`.
|
The examples chat apps currently use content topic `"/toy-chat/2/huilong/proto"`.
|
||||||
|
|
||||||
Send a message on the waku relay network:
|
### Send messages
|
||||||
|
|
||||||
```javascript
|
There are two ways to send messages:
|
||||||
|
|
||||||
|
#### Waku Relay
|
||||||
|
|
||||||
|
[Waku Relay](https://rfc.vac.dev/spec/11/) is the most decentralized option,
|
||||||
|
peer receiving your messages are unlikely to know whether you are the originator or simply forwarding them.
|
||||||
|
However, it does not give you any delivery information.
|
||||||
|
|
||||||
|
```ts
|
||||||
import { WakuMessage } from 'js-waku';
|
import { WakuMessage } from 'js-waku';
|
||||||
|
|
||||||
const msg = WakuMessage.fromUtf8String("Here is a message!", "/my-cool-app/1/my-use-case/proto")
|
const msg = await WakuMessage.fromUtf8String("Here is a message!", { contentTopic: "/my-cool-app/1/my-use-case/proto" })
|
||||||
await waku.relay.send(msg);
|
await waku.relay.send(msg);
|
||||||
```
|
```
|
||||||
|
|
||||||
The [Waku v2 Store protocol](https://rfc.vac.dev/spec/13/) enables full nodes to store messages received via relay
|
#### Waku Light Push
|
||||||
and clients to retrieve them (e.g. after resuming connectivity).
|
|
||||||
|
[Waku Light Push](https://rfc.vac.dev/spec/19/) gives you confirmation that the light push server node has
|
||||||
|
received your message.
|
||||||
|
However, it means that said node knows you are the originator of the message.
|
||||||
|
It cannot guarantee that the node will forward the message.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const ack = await waku.lightPush.push(message);
|
||||||
|
if (!ack?.isSuccess) {
|
||||||
|
// Message was not sent
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retrieve archived messages
|
||||||
|
|
||||||
|
The [Waku v2 Store protocol](https://rfc.vac.dev/spec/13/) enables more permanent nodes to store messages received via relay
|
||||||
|
and ephemeral clients to retrieve them (e.g. mobile phone resuming connectivity).
|
||||||
The protocol implements pagination meaning that it may take several queries to retrieve all messages.
|
The protocol implements pagination meaning that it may take several queries to retrieve all messages.
|
||||||
|
|
||||||
Query a waku store peer to check historical messages:
|
Query a waku store peer to check historical messages:
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
// Process messages once they are all retrieved:
|
// Process messages once they are all retrieved
|
||||||
const messages = await waku.store.queryHistory(storePeerId, ["my-cool-app"]);
|
const messages = await waku.store.queryHistory({ contentTopics: ["/my-cool-app/1/my-use-case/proto"] });
|
||||||
messages.forEach((msg) => {
|
messages.forEach((msg) => {
|
||||||
console.log("Message retrieved:", msg.payloadAsUtf8)
|
console.log("Message retrieved:", msg.payloadAsUtf8)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Or, pass a callback function to be executed as pages are received:
|
// Or, pass a callback function to be executed as pages are received:
|
||||||
waku.store.queryHistory(storePeerId, ["my-cool-app"],
|
waku.store.queryHistory({
|
||||||
(messages) => {
|
contentTopics: ["/my-cool-app/1/my-use-case/proto"],
|
||||||
|
callback: (messages) => {
|
||||||
messages.forEach((msg) => {
|
messages.forEach((msg) => {
|
||||||
console.log("Message retrieved:", msg.payloadAsUtf8)
|
console.log("Message retrieved:", msg.payloadAsUtf8);
|
||||||
})
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Encryption & Signature
|
||||||
|
|
||||||
|
With js-waku, you can:
|
||||||
|
|
||||||
|
- Encrypt messages over the wire using public/private key pair (asymmetric encryption),
|
||||||
|
- Encrypt messages over the wire using a unique key to both encrypt and decrypt (symmetric encryption),
|
||||||
|
- Sign and verify your waku messages (must use encryption, compatible with both symmetric and asymmetric).
|
||||||
|
|
||||||
|
### Cryptographic Libraries
|
||||||
|
|
||||||
|
A quick note on the cryptographic libraries used as it is a not a straightforward affair:
|
||||||
|
- Asymmetric encryption:
|
||||||
|
Uses [ecies-geth](https://github.com/cyrildever/ecies-geth/)
|
||||||
|
which in turns uses [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) Web API (browser),
|
||||||
|
[secp256k1](https://www.npmjs.com/package/secp256k1) (native binding for node)
|
||||||
|
or [elliptic](https://www.npmjs.com/package/elliptic) (pure JS if none of the other libraries are available).
|
||||||
|
- Symmetric encryption:
|
||||||
|
Uses [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) Web API (browser)
|
||||||
|
or [NodeJS' crypto](https://nodejs.org/api/crypto.html) module.
|
||||||
|
|
||||||
|
### Create new keys
|
||||||
|
|
||||||
|
Asymmetric private keys and symmetric keys are expected to be 32 bytes arrays.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { generatePrivateKey, getPublicKey } from 'js-waku';
|
||||||
|
|
||||||
|
// Asymmetric
|
||||||
|
const privateKey = generatePrivateKey();
|
||||||
|
const publicKey = getPublicKey(privateKey);
|
||||||
|
|
||||||
|
// Symmetric
|
||||||
|
const symKey = generatePrivateKey();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Encrypt Waku Messages
|
||||||
|
|
||||||
|
To encrypt your waku messages, simply pass the encryption key when creating it:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { WakuMessage } from 'js-waku';
|
||||||
|
|
||||||
|
// Asymmetric
|
||||||
|
const message = await WakuMessage.fromBytes(payload, {
|
||||||
|
contentTopic: myAppContentTopic,
|
||||||
|
encPublicKey: publicKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Symmetric
|
||||||
|
const message = await WakuMessage.fromBytes(payload, {
|
||||||
|
contentTopic: myAppContentTopic,
|
||||||
|
symKey: symKey,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decrypt Waku Messages
|
||||||
|
|
||||||
|
#### Waku Relay
|
||||||
|
|
||||||
|
If you expect to receive encrypted messages then simply add private decryption key(s) to `WakuRelay`.
|
||||||
|
Waku Relay will attempt to decrypt incoming messages with each keys, both for symmetric and asymmetric encryption.
|
||||||
|
Messages that are successfully decrypted (or received in clear) will be passed to the observers, other messages will be omitted.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Asymmetric
|
||||||
|
waku.relay.addDecryptionKey(privateKey);
|
||||||
|
|
||||||
|
// Symmetric
|
||||||
|
waku.relay.addDecryptionKey(symKey);
|
||||||
|
|
||||||
|
// Then add the observer
|
||||||
|
waku.relay.addObserver(callback, [contentTopic]);
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys can be removed using `WakuMessage.deleteDecryptionKey`.
|
||||||
|
|
||||||
|
#### Waku Store
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const messages = await waku.store.queryHistory({
|
||||||
|
contentTopics: [],
|
||||||
|
decryptionKeys: [privateKey, symKey],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly to relay, only decrypted or clear messages will be returned.
|
||||||
|
|
||||||
|
### Sign Waku Messages
|
||||||
|
|
||||||
|
As per version 1`s [specs](https://rfc.vac.dev/spec/26/), signatures are only included in encrypted messages.
|
||||||
|
In the case where your app does not need encryption then you could use symmetric encryption with a trivial key, I intend to dig [more on the subject](https://github.com/status-im/js-waku/issues/74#issuecomment-880440186) and come back with recommendation and examples.
|
||||||
|
|
||||||
|
Signature keys can be generated the same way asymmetric keys for encryption are:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { generatePrivateKey, getPublicKey, WakuMessage } from 'js-waku';
|
||||||
|
|
||||||
|
const signPrivateKey = generatePrivateKey();
|
||||||
|
|
||||||
|
// Asymmetric Encryption
|
||||||
|
const message = await WakuMessage.fromBytes(payload, {
|
||||||
|
contentTopic: myAppContentTopic,
|
||||||
|
encPublicKey: recipientPublicKey,
|
||||||
|
sigPrivKey: signPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
// Symmetric Encryption
|
||||||
|
const message = await WakuMessage.fromBytes(payload, {
|
||||||
|
contentTopic: myAppContentTopic,
|
||||||
|
encPublicKey: symKey,
|
||||||
|
sigPrivKey: signPrivateKey
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Waku Message signatures
|
||||||
|
|
||||||
|
Two fields are available on `WakuMessage` regarding signatures:
|
||||||
|
|
||||||
|
- `signaturePublicKey`: If the message is signed, it holds the public key of the signature,
|
||||||
|
- `signature`: If the message is signed, it holds the actual signature.
|
||||||
|
|
||||||
|
Thus, if you expect messages to be signed by Alice,
|
||||||
|
you can simply compare `WakuMessage.signaturePublicKey` with Alice's public key.
|
||||||
|
As comparing hex string can lead to issues (is the `0x` prefix present?),
|
||||||
|
simply use helper function `equalByteArrays`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { equalByteArrays } from 'js-waku/lib/utils';
|
||||||
|
|
||||||
|
const sigPubKey = wakuMessage.signaturePublicKey;
|
||||||
|
|
||||||
|
const isSignedByAlice = sigPubKey && equalByteArrays(sigPubKey, alicePublicKey);
|
||||||
|
```
|
||||||
|
|
||||||
|
## More documentation
|
||||||
|
|
||||||
Find more [examples](#examples) below
|
Find more [examples](#examples) below
|
||||||
or checkout the latest `main` branch documentation at [https://status-im.github.io/js-waku/docs/](https://status-im.github.io/js-waku/docs/).
|
or checkout the latest `main` branch documentation at [https://status-im.github.io/js-waku/docs/](https://status-im.github.io/js-waku/docs/).
|
||||||
|
|
||||||
|
|
|
@ -128,13 +128,13 @@ function App() {
|
||||||
if (!waku) return;
|
if (!waku) return;
|
||||||
if (!ethDmKeyPair) return;
|
if (!ethDmKeyPair) return;
|
||||||
|
|
||||||
waku.relay.addDecryptionPrivateKey(ethDmKeyPair.privateKey);
|
waku.relay.addDecryptionKey(ethDmKeyPair.privateKey);
|
||||||
|
|
||||||
return function cleanUp() {
|
return function cleanUp() {
|
||||||
if (!waku) return;
|
if (!waku) return;
|
||||||
if (!ethDmKeyPair) return;
|
if (!ethDmKeyPair) return;
|
||||||
|
|
||||||
waku.relay.deleteDecryptionPrivateKey(ethDmKeyPair.privateKey);
|
waku.relay.deleteDecryptionKey(ethDmKeyPair.privateKey);
|
||||||
};
|
};
|
||||||
}, [waku, ethDmKeyPair]);
|
}, [waku, ethDmKeyPair]);
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,7 @@ import { ethers } from 'ethers';
|
||||||
import { Signer } from '@ethersproject/abstract-signer';
|
import { Signer } from '@ethersproject/abstract-signer';
|
||||||
import { PublicKeyMessage } from './messaging/wire';
|
import { PublicKeyMessage } from './messaging/wire';
|
||||||
import { hexToBuf, equalByteArrays, bufToHex } from 'js-waku/lib/utils';
|
import { hexToBuf, equalByteArrays, bufToHex } from 'js-waku/lib/utils';
|
||||||
import {
|
import { generatePrivateKey, getPublicKey } from 'js-waku';
|
||||||
generatePrivateKey,
|
|
||||||
getPublicKey,
|
|
||||||
} from 'js-waku/lib/waku_message/version_1';
|
|
||||||
|
|
||||||
export interface KeyPair {
|
export interface KeyPair {
|
||||||
privateKey: Uint8Array;
|
privateKey: Uint8Array;
|
||||||
|
|
|
@ -74,28 +74,27 @@ async function retrieveStoreMessages(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
let [newMessages, setNewMessages] = useState<Message[]>([]);
|
const [newMessages, setNewMessages] = useState<Message[]>([]);
|
||||||
let [archivedMessages, setArchivedMessages] = useState<Message[]>([]);
|
const [archivedMessages, setArchivedMessages] = useState<Message[]>([]);
|
||||||
let [stateWaku, setWaku] = useState<Waku | undefined>(undefined);
|
const [waku, setWaku] = useState<Waku | undefined>(undefined);
|
||||||
let [nick, setNick] = useState<string>(() => {
|
const [nick, setNick] = useState<string>(() => {
|
||||||
const persistedNick = window.localStorage.getItem('nick');
|
const persistedNick = window.localStorage.getItem('nick');
|
||||||
return persistedNick !== null ? persistedNick : generate();
|
return persistedNick !== null ? persistedNick : generate();
|
||||||
});
|
});
|
||||||
|
const [fleetEnv, setFleetEnv] = useState<Environment>(defaultFleetEnv);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('nick', nick);
|
localStorage.setItem('nick', nick);
|
||||||
}, [nick]);
|
}, [nick]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stateWaku) return;
|
initWaku(fleetEnv, setWaku)
|
||||||
|
|
||||||
initWaku(setWaku)
|
|
||||||
.then(() => console.log('Waku init done'))
|
.then(() => console.log('Waku init done'))
|
||||||
.catch((e) => console.log('Waku init failed ', e));
|
.catch((e) => console.log('Waku init failed ', e));
|
||||||
}, [stateWaku]);
|
}, [fleetEnv]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!stateWaku) return;
|
if (!waku) return;
|
||||||
|
|
||||||
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
||||||
console.log('Message received: ', wakuMsg);
|
console.log('Message received: ', wakuMsg);
|
||||||
|
@ -105,23 +104,25 @@ export default function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stateWaku.relay.addObserver(handleRelayMessage, [ChatContentTopic]);
|
waku.relay.addObserver(handleRelayMessage, [ChatContentTopic]);
|
||||||
|
|
||||||
return;
|
return function cleanUp() {
|
||||||
}, [stateWaku]);
|
waku?.relay.deleteObserver(handleRelayMessage, [ChatContentTopic]);
|
||||||
|
};
|
||||||
|
}, [waku]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!stateWaku) return;
|
if (!waku) return;
|
||||||
|
|
||||||
const handleProtocolChange = async (
|
const handleProtocolChange = async (
|
||||||
waku: Waku,
|
_waku: Waku,
|
||||||
{ peerId, protocols }: { peerId: PeerId; protocols: string[] }
|
{ peerId, protocols }: { peerId: PeerId; protocols: string[] }
|
||||||
) => {
|
) => {
|
||||||
if (protocols.includes(StoreCodec)) {
|
if (protocols.includes(StoreCodec)) {
|
||||||
console.log(`${peerId.toB58String()}: retrieving archived messages}`);
|
console.log(`${peerId.toB58String()}: retrieving archived messages}`);
|
||||||
try {
|
try {
|
||||||
const length = await retrieveStoreMessages(
|
const length = await retrieveStoreMessages(
|
||||||
waku,
|
_waku,
|
||||||
peerId,
|
peerId,
|
||||||
setArchivedMessages
|
setArchivedMessages
|
||||||
);
|
);
|
||||||
|
@ -135,36 +136,38 @@ export default function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stateWaku.libp2p.peerStore.on(
|
waku.libp2p.peerStore.on(
|
||||||
'change:protocols',
|
'change:protocols',
|
||||||
handleProtocolChange.bind({}, stateWaku)
|
handleProtocolChange.bind({}, waku)
|
||||||
);
|
);
|
||||||
|
|
||||||
// To clean up listener when component unmounts
|
return function cleanUp() {
|
||||||
return () => {
|
waku?.libp2p.peerStore.removeListener(
|
||||||
stateWaku?.libp2p.peerStore.removeListener(
|
|
||||||
'change:protocols',
|
'change:protocols',
|
||||||
handleProtocolChange.bind({}, stateWaku)
|
handleProtocolChange.bind({}, waku)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, [stateWaku]);
|
}, [waku]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="chat-app"
|
className="chat-app"
|
||||||
style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}
|
style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}
|
||||||
>
|
>
|
||||||
<WakuContext.Provider value={{ waku: stateWaku }}>
|
<WakuContext.Provider value={{ waku: waku }}>
|
||||||
<ThemeProvider theme={themes}>
|
<ThemeProvider theme={themes}>
|
||||||
<Room
|
<Room
|
||||||
nick={nick}
|
nick={nick}
|
||||||
newMessages={newMessages}
|
newMessages={newMessages}
|
||||||
archivedMessages={archivedMessages}
|
archivedMessages={archivedMessages}
|
||||||
|
fleetEnv={fleetEnv}
|
||||||
commandHandler={(input: string) => {
|
commandHandler={(input: string) => {
|
||||||
const { command, response } = handleCommand(
|
const { command, response } = handleCommand(
|
||||||
input,
|
input,
|
||||||
stateWaku,
|
waku,
|
||||||
setNick
|
setNick,
|
||||||
|
fleetEnv,
|
||||||
|
setFleetEnv
|
||||||
);
|
);
|
||||||
const commandMessages = response.map((msg) => {
|
const commandMessages = response.map((msg) => {
|
||||||
return Message.fromUtf8String(command, msg);
|
return Message.fromUtf8String(command, msg);
|
||||||
|
@ -178,7 +181,7 @@ export default function App() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initWaku(setter: (waku: Waku) => void) {
|
async function initWaku(fleetEnv: Environment, setter: (waku: Waku) => void) {
|
||||||
try {
|
try {
|
||||||
const waku = await Waku.create({
|
const waku = await Waku.create({
|
||||||
libp2p: {
|
libp2p: {
|
||||||
|
@ -193,7 +196,7 @@ async function initWaku(setter: (waku: Waku) => void) {
|
||||||
|
|
||||||
setter(waku);
|
setter(waku);
|
||||||
|
|
||||||
const nodes = await getNodes();
|
const nodes = await getStatusFleetNodes(fleetEnv);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
nodes.map((addr) => {
|
nodes.map((addr) => {
|
||||||
return waku.dial(addr);
|
return waku.dial(addr);
|
||||||
|
@ -204,11 +207,11 @@ async function initWaku(setter: (waku: Waku) => void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodes() {
|
function defaultFleetEnv() {
|
||||||
// Works with react-scripts
|
// Works with react-scripts
|
||||||
if (process?.env?.NODE_ENV === 'development') {
|
if (process?.env?.NODE_ENV === 'development') {
|
||||||
return getStatusFleetNodes(Environment.Test);
|
return Environment.Test;
|
||||||
} else {
|
} else {
|
||||||
return getStatusFleetNodes(Environment.Prod);
|
return Environment.Prod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChatMessage, WakuMessage } from 'js-waku';
|
import { ChatMessage, Environment, 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';
|
||||||
|
@ -11,17 +11,28 @@ interface Props {
|
||||||
archivedMessages: Message[];
|
archivedMessages: Message[];
|
||||||
commandHandler: (cmd: string) => void;
|
commandHandler: (cmd: string) => void;
|
||||||
nick: string;
|
nick: string;
|
||||||
|
fleetEnv: Environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Room(props: Props) {
|
export default function Room(props: Props) {
|
||||||
const { waku } = useWaku();
|
const { waku } = useWaku();
|
||||||
|
|
||||||
|
let relayPeers = 0;
|
||||||
|
let storePeers = 0;
|
||||||
|
if (waku) {
|
||||||
|
relayPeers = waku.relay.getPeers().size;
|
||||||
|
storePeers = waku.store.peers.length;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="chat-container"
|
className="chat-container"
|
||||||
style={{ height: '98vh', display: 'flex', flexDirection: 'column' }}
|
style={{ height: '98vh', display: 'flex', flexDirection: 'column' }}
|
||||||
>
|
>
|
||||||
<TitleBar title="Waku v2 chat app" />
|
<TitleBar
|
||||||
|
leftIcons={`Peers: ${relayPeers} relay, ${storePeers} store. Fleet: ${props.fleetEnv}`}
|
||||||
|
title="Waku v2 chat app"
|
||||||
|
/>
|
||||||
<ChatList
|
<ChatList
|
||||||
newMessages={props.newMessages}
|
newMessages={props.newMessages}
|
||||||
archivedMessages={props.archivedMessages}
|
archivedMessages={props.archivedMessages}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { multiaddr } from 'multiaddr';
|
import { multiaddr } from 'multiaddr';
|
||||||
import PeerId from 'peer-id';
|
import PeerId from 'peer-id';
|
||||||
import { Waku } from 'js-waku';
|
import { Environment, Waku } from 'js-waku';
|
||||||
|
|
||||||
function help(): string[] {
|
function help(): string[] {
|
||||||
return [
|
return [
|
||||||
'/nick <nickname>: set a new nickname',
|
'/nick <nickname>: set a new nickname',
|
||||||
'/info: some information about the node',
|
'/info: some information about the node',
|
||||||
'/connect <Multiaddr>: connect to the given peer',
|
'/connect <Multiaddr>: connect to the given peer',
|
||||||
|
'/fleet <prod|test>: connect to this fleet; beware it restarts waku node.',
|
||||||
'/help: Display this help',
|
'/help: Display this help',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -22,11 +23,14 @@ function nick(
|
||||||
return [`New nick: ${nick}`];
|
return [`New nick: ${nick}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
function info(waku: Waku | undefined): string[] {
|
function info(waku: Waku | undefined, fleetEnv: Environment): 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.toB58String()}`,
|
||||||
|
`Fleet environment: ${fleetEnv}`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
function connect(peer: string | undefined, waku: Waku | undefined): string[] {
|
||||||
|
@ -78,6 +82,28 @@ function peers(waku: Waku | undefined): string[] {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fleet(
|
||||||
|
newFleetEnv: string | undefined,
|
||||||
|
currFleetEnv: Environment,
|
||||||
|
setFleetEnv: (fleetEnv: Environment) => void
|
||||||
|
): string[] {
|
||||||
|
switch (newFleetEnv) {
|
||||||
|
case Environment.Test:
|
||||||
|
setFleetEnv(newFleetEnv);
|
||||||
|
break;
|
||||||
|
case Environment.Prod:
|
||||||
|
setFleetEnv(newFleetEnv);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
`Incorrect values, acceptable values are ${Environment.Test}, ${Environment.Prod}`,
|
||||||
|
`Current fleet environment is ${currFleetEnv}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [`New fleet Environment: ${newFleetEnv}`];
|
||||||
|
}
|
||||||
|
|
||||||
function connections(waku: Waku | undefined): string[] {
|
function connections(waku: Waku | undefined): string[] {
|
||||||
if (!waku) {
|
if (!waku) {
|
||||||
return ['Waku node is starting'];
|
return ['Waku node is starting'];
|
||||||
|
@ -107,7 +133,9 @@ function connections(waku: Waku | undefined): string[] {
|
||||||
export default function handleCommand(
|
export default function handleCommand(
|
||||||
input: string,
|
input: string,
|
||||||
waku: Waku | undefined,
|
waku: Waku | undefined,
|
||||||
setNick: (nick: string) => void
|
setNick: (nick: string) => void,
|
||||||
|
currFleetEnv: Environment,
|
||||||
|
setFleetEnv: (fleetEnv: Environment) => void
|
||||||
): { command: string; response: string[] } {
|
): { command: string; response: string[] } {
|
||||||
let response: string[] = [];
|
let response: string[] = [];
|
||||||
const args = parseInput(input);
|
const args = parseInput(input);
|
||||||
|
@ -120,7 +148,7 @@ export default function handleCommand(
|
||||||
nick(args.shift(), setNick).map((str) => response.push(str));
|
nick(args.shift(), setNick).map((str) => response.push(str));
|
||||||
break;
|
break;
|
||||||
case '/info':
|
case '/info':
|
||||||
info(waku).map((str) => response.push(str));
|
info(waku, currFleetEnv).map((str) => response.push(str));
|
||||||
break;
|
break;
|
||||||
case '/connect':
|
case '/connect':
|
||||||
connect(args.shift(), waku).map((str) => response.push(str));
|
connect(args.shift(), waku).map((str) => response.push(str));
|
||||||
|
@ -131,6 +159,11 @@ export default function handleCommand(
|
||||||
case '/connections':
|
case '/connections':
|
||||||
connections(waku).map((str) => response.push(str));
|
connections(waku).map((str) => response.push(str));
|
||||||
break;
|
break;
|
||||||
|
case '/fleet':
|
||||||
|
fleet(args.shift(), currFleetEnv, setFleetEnv).map((str) =>
|
||||||
|
response.push(str)
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
response.push(`Unknown Command '${command}'`);
|
response.push(`Unknown Command '${command}'`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@ export { getStatusFleetNodes, Environment, Protocol } from './lib/discover';
|
||||||
export * as utils from './lib/utils';
|
export * as utils from './lib/utils';
|
||||||
|
|
||||||
export { Waku } from './lib/waku';
|
export { Waku } from './lib/waku';
|
||||||
|
|
||||||
export { WakuMessage } from './lib/waku_message';
|
export { WakuMessage } from './lib/waku_message';
|
||||||
|
|
||||||
|
export { generatePrivateKey, getPublicKey } from './lib/waku_message/version_1';
|
||||||
|
|
||||||
export { ChatMessage } from './lib/chat_message';
|
export { ChatMessage } from './lib/chat_message';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -40,7 +40,7 @@ export interface CreateOptions {
|
||||||
* Set keep alive frequency in seconds: Waku will send a ping request to each peer
|
* Set keep alive frequency in seconds: Waku will send a ping request to each peer
|
||||||
* after the set number of seconds. Set to 0 to disable the keep alive feature
|
* after the set number of seconds. Set to 0 to disable the keep alive feature
|
||||||
*
|
*
|
||||||
* @default 10
|
* @default 0
|
||||||
*/
|
*/
|
||||||
keepAlive?: number;
|
keepAlive?: number;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -62,7 +62,7 @@ describe('Waku Message: Node only', function () {
|
||||||
|
|
||||||
const privateKey = generatePrivateKey();
|
const privateKey = generatePrivateKey();
|
||||||
|
|
||||||
waku.relay.addDecryptionPrivateKey(privateKey);
|
waku.relay.addDecryptionKey(privateKey);
|
||||||
|
|
||||||
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
||||||
(resolve) => {
|
(resolve) => {
|
||||||
|
@ -118,7 +118,7 @@ describe('Waku Message: Node only', function () {
|
||||||
|
|
||||||
const symKey = generatePrivateKey();
|
const symKey = generatePrivateKey();
|
||||||
|
|
||||||
waku.relay.addDecryptionPrivateKey(symKey);
|
waku.relay.addDecryptionKey(symKey);
|
||||||
|
|
||||||
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
||||||
(resolve) => {
|
(resolve) => {
|
||||||
|
|
|
@ -67,7 +67,7 @@ export class WakuRelay extends Gossipsub {
|
||||||
/**
|
/**
|
||||||
* Decryption private keys to use to attempt decryption of incoming messages.
|
* Decryption private keys to use to attempt decryption of incoming messages.
|
||||||
*/
|
*/
|
||||||
public decPrivateKeys: Set<Uint8Array>;
|
public decryptionKeys: Set<Uint8Array>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* observers called when receiving new message.
|
* observers called when receiving new message.
|
||||||
|
@ -91,7 +91,7 @@ export class WakuRelay extends Gossipsub {
|
||||||
|
|
||||||
this.heartbeat = new RelayHeartbeat(this);
|
this.heartbeat = new RelayHeartbeat(this);
|
||||||
this.observers = {};
|
this.observers = {};
|
||||||
this.decPrivateKeys = new Set();
|
this.decryptionKeys = new Set();
|
||||||
|
|
||||||
const multicodecs = [constants.RelayCodec];
|
const multicodecs = [constants.RelayCodec];
|
||||||
|
|
||||||
|
@ -124,21 +124,21 @@ export class WakuRelay extends Gossipsub {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a decryption private key to attempt decryption of messages of
|
* Register a decryption private key or symmetric key to attempt decryption
|
||||||
* the given content topic. This can either be a private key for asymmetric
|
* of messages received on the given content topic. This can either be a
|
||||||
* encryption or a symmetric key. Waku relay will attempt to decrypt messages
|
* private key for asymmetric encryption or a symmetric key. Waku relay will
|
||||||
* using both methods.
|
* attempt to decrypt messages using both methods.
|
||||||
*/
|
*/
|
||||||
addDecryptionPrivateKey(privateKey: Uint8Array): void {
|
addDecryptionKey(privateKey: Uint8Array): void {
|
||||||
this.decPrivateKeys.add(privateKey);
|
this.decryptionKeys.add(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a decryption private key to attempt decryption of messages of
|
* Delete a decryption key to attempt decryption of messages received on the
|
||||||
* the given content topic.
|
* given content topic.
|
||||||
*/
|
*/
|
||||||
deleteDecryptionPrivateKey(privateKey: Uint8Array): void {
|
deleteDecryptionKey(privateKey: Uint8Array): void {
|
||||||
this.decPrivateKeys.delete(privateKey);
|
this.decryptionKeys.delete(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,7 +210,7 @@ export class WakuRelay extends Gossipsub {
|
||||||
subscribe(pubsubTopic: string): void {
|
subscribe(pubsubTopic: string): void {
|
||||||
this.on(pubsubTopic, (event) => {
|
this.on(pubsubTopic, (event) => {
|
||||||
dbg(`Message received on ${pubsubTopic}`);
|
dbg(`Message received on ${pubsubTopic}`);
|
||||||
WakuMessage.decode(event.data, Array.from(this.decPrivateKeys))
|
WakuMessage.decode(event.data, Array.from(this.decryptionKeys))
|
||||||
.then((wakuMsg) => {
|
.then((wakuMsg) => {
|
||||||
if (!wakuMsg) {
|
if (!wakuMsg) {
|
||||||
dbg('Failed to decode Waku Message');
|
dbg('Failed to decode Waku Message');
|
||||||
|
|
Loading…
Reference in New Issue