mirror of
https://github.com/waku-org/js-waku.git
synced 2025-01-11 21:15:01 +00:00
Merge #9
9: chat example r=D4nte a=D4nte Resolves #15 Co-authored-by: Franck Royer <franck@royer.one>
This commit is contained in:
commit
62b27fddd6
19
README.md
19
README.md
@ -2,7 +2,24 @@
|
|||||||
|
|
||||||
A JavaScript implementation of the [Waku v2 protocol](https://specs.vac.dev/specs/waku/v2/waku-v2).
|
A JavaScript implementation of the [Waku v2 protocol](https://specs.vac.dev/specs/waku/v2/waku-v2).
|
||||||
|
|
||||||
**This repo is a Work In Progress**
|
## This is a Work In Progress
|
||||||
|
|
||||||
|
You can track progress on the [project board](https://github.com/status-im/js-waku/projects/1).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
## Chat app
|
||||||
|
|
||||||
|
A node chat app is provided as a working example of the library.
|
||||||
|
It is interoperable with the [nim-waku chat app example](https://github.com/status-im/nim-waku/blob/master/examples/v2/chat2.nim).
|
||||||
|
To run the chat app:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install
|
||||||
|
npm run chat:app -- --staticNode /ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ --listenAddr /ip4/0.0.0.0/tcp/55123
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--listenAddr` parameter is optional, however [NAT passthrough](https://github.com/status-im/js-waku/issues/12) is not yet supported, so you'll need the listening port to be open to receive messages from the fleet.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -3,4 +3,4 @@ version: v1beta1
|
|||||||
plugins:
|
plugins:
|
||||||
- name: ts_proto
|
- name: ts_proto
|
||||||
out: ./src/proto
|
out: ./src/proto
|
||||||
opt: grpc_js
|
opt: grpc_js,esModuleInterop=true
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "waku-js-chat",
|
"name": "js-waku",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A chat application running on node and waku",
|
"description": "A chat application running on node and waku",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
@ -19,6 +19,7 @@
|
|||||||
"pretest": "run-s pretest:*",
|
"pretest": "run-s pretest:*",
|
||||||
"pretest:1-init-git-submodules": "[ -f './nim-waku/build/wakunode2' ] || git submodule update --init --recursive",
|
"pretest:1-init-git-submodules": "[ -f './nim-waku/build/wakunode2' ] || git submodule update --init --recursive",
|
||||||
"pretest:2-build-nim-waku": "cd nim-waku; [ -f './build/wakunode2' ] || make -j$(nproc --all 2>/dev/null || echo 2) wakunode2",
|
"pretest:2-build-nim-waku": "cd nim-waku; [ -f './build/wakunode2' ] || make -j$(nproc --all 2>/dev/null || echo 2) wakunode2",
|
||||||
|
"chat:start": "ts-node src/chat/index.ts",
|
||||||
"test": "run-s build test:*",
|
"test": "run-s build test:*",
|
||||||
"test:lint": "eslint src --ext .ts",
|
"test:lint": "eslint src --ext .ts",
|
||||||
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
|
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
|
||||||
|
9
proto/chat/v2/chat_message.proto
Normal file
9
proto/chat/v2/chat_message.proto
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package chat.v2;
|
||||||
|
|
||||||
|
message ChatMessageProto {
|
||||||
|
uint64 timestamp = 1;
|
||||||
|
string nick = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
}
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package waku.v2;
|
package waku.v2;
|
||||||
|
|
||||||
message WakuMessage {
|
message WakuMessageProto {
|
||||||
optional bytes payload = 1;
|
optional bytes payload = 1;
|
||||||
optional uint32 content_topic = 2;
|
optional uint32 content_topic = 2;
|
||||||
optional uint32 version = 3;
|
optional uint32 version = 3;
|
||||||
|
26
src/chat/chat_message.spec.ts
Normal file
26
src/chat/chat_message.spec.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import fc from 'fast-check';
|
||||||
|
|
||||||
|
import { ChatMessage } from './chat_message';
|
||||||
|
|
||||||
|
describe('Chat Message', function () {
|
||||||
|
it('Chat message round trip binary serialization', function () {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(
|
||||||
|
fc.date({ min: new Date(0) }),
|
||||||
|
fc.string(),
|
||||||
|
fc.string(),
|
||||||
|
(timestamp, nick, message) => {
|
||||||
|
const msg = new ChatMessage(timestamp, nick, message);
|
||||||
|
const buf = msg.encode();
|
||||||
|
const actual = ChatMessage.decode(buf);
|
||||||
|
|
||||||
|
// Date.toString does not include ms, as we loose this precision by design
|
||||||
|
expect(actual.timestamp.toString()).to.eq(timestamp.toString());
|
||||||
|
expect(actual.nick).to.eq(nick);
|
||||||
|
expect(actual.message).to.eq(message);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
35
src/chat/chat_message.ts
Normal file
35
src/chat/chat_message.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Reader } from 'protobufjs/minimal';
|
||||||
|
|
||||||
|
import { ChatMessageProto } from '../proto/chat/v2/chat_message';
|
||||||
|
|
||||||
|
export class ChatMessage {
|
||||||
|
public constructor(
|
||||||
|
public timestamp: Date,
|
||||||
|
public nick: string,
|
||||||
|
public message: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static decode(bytes: Uint8Array): ChatMessage {
|
||||||
|
const protoMsg = ChatMessageProto.decode(Reader.create(bytes));
|
||||||
|
const timestamp = new Date(protoMsg.timestamp * 1000);
|
||||||
|
const message = protoMsg.payload
|
||||||
|
? Array.from(protoMsg.payload)
|
||||||
|
.map((char) => {
|
||||||
|
return String.fromCharCode(char);
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
: '';
|
||||||
|
return new ChatMessage(timestamp, protoMsg.nick, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(): Uint8Array {
|
||||||
|
const timestamp = Math.floor(this.timestamp.valueOf() / 1000);
|
||||||
|
const payload = Buffer.from(this.message, 'utf-8');
|
||||||
|
|
||||||
|
return ChatMessageProto.encode({
|
||||||
|
timestamp,
|
||||||
|
nick: this.nick,
|
||||||
|
payload,
|
||||||
|
}).finish();
|
||||||
|
}
|
||||||
|
}
|
108
src/chat/index.ts
Normal file
108
src/chat/index.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import readline from 'readline';
|
||||||
|
import util from 'util';
|
||||||
|
|
||||||
|
import Waku from '../lib/waku';
|
||||||
|
import { WakuMessage } from '../lib/waku_message';
|
||||||
|
import { TOPIC } from '../lib/waku_relay';
|
||||||
|
import { delay } from '../test_utils/delay';
|
||||||
|
|
||||||
|
import { ChatMessage } from './chat_message';
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
const opts = processArguments();
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const question = util.promisify(rl.question).bind(rl);
|
||||||
|
|
||||||
|
// Looks like wrong type definition of promisify is picked.
|
||||||
|
// May be related to https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20497
|
||||||
|
const nick = ((await question(
|
||||||
|
'Please choose a nickname: '
|
||||||
|
)) as unknown) as string;
|
||||||
|
console.log(`Hi ${nick}!`);
|
||||||
|
|
||||||
|
const waku = await Waku.create({ listenAddresses: [opts.listenAddr] });
|
||||||
|
|
||||||
|
// TODO: Bubble event to waku, infer topic, decode msg
|
||||||
|
// Tracked with https://github.com/status-im/js-waku/issues/19
|
||||||
|
waku.libp2p.pubsub.on(TOPIC, (event) => {
|
||||||
|
const wakuMsg = WakuMessage.decode(event.data);
|
||||||
|
if (wakuMsg.payload) {
|
||||||
|
const chatMsg = ChatMessage.decode(wakuMsg.payload);
|
||||||
|
const timestamp = chatMsg.timestamp.toLocaleString([], {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
console.log(`<${timestamp}> ${chatMsg.nick}: ${chatMsg.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Waku started');
|
||||||
|
|
||||||
|
if (opts.staticNode) {
|
||||||
|
console.log(`dialing ${opts.staticNode}`);
|
||||||
|
await waku.dial(opts.staticNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: identify if it is possible to listen to an event to confirm dial
|
||||||
|
// finished instead of an arbitrary delay. Tracked with
|
||||||
|
// https://github.com/status-im/js-waku/issues/18
|
||||||
|
await delay(2000);
|
||||||
|
// TODO: Automatically subscribe, tracked with
|
||||||
|
// https://github.com/status-im/js-waku/issues/17
|
||||||
|
await waku.relay.subscribe();
|
||||||
|
console.log('Subscribed to waku relay');
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Ready to chat!');
|
||||||
|
rl.prompt();
|
||||||
|
for await (const line of rl) {
|
||||||
|
rl.prompt();
|
||||||
|
const chatMessage = new ChatMessage(new Date(), nick, line);
|
||||||
|
|
||||||
|
const msg = WakuMessage.fromBytes(chatMessage.encode());
|
||||||
|
await waku.relay.publish(msg);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
staticNode?: string;
|
||||||
|
listenAddr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processArguments(): Options {
|
||||||
|
const passedArgs = process.argv.slice(2);
|
||||||
|
|
||||||
|
let opts: Options = { listenAddr: '/ip4/0.0.0.0/tcp/0' };
|
||||||
|
|
||||||
|
while (passedArgs.length) {
|
||||||
|
const arg = passedArgs.shift();
|
||||||
|
switch (arg) {
|
||||||
|
case '--staticNode':
|
||||||
|
opts = Object.assign(opts, { staticNode: passedArgs.shift() });
|
||||||
|
break;
|
||||||
|
case '--listenAddr':
|
||||||
|
opts = Object.assign(opts, { listenAddr: passedArgs.shift() });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(`Unsupported argument: ${arg}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
@ -11,7 +11,7 @@ describe('Waku', function () {
|
|||||||
describe('Interop: Nim', function () {
|
describe('Interop: Nim', function () {
|
||||||
it('nim connects to js', async function () {
|
it('nim connects to js', async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
const waku = await Waku.create(NOISE_KEY_1);
|
const waku = await Waku.create({ staticNoiseKey: NOISE_KEY_1 });
|
||||||
|
|
||||||
const peerId = waku.libp2p.peerId.toB58String();
|
const peerId = waku.libp2p.peerId.toB58String();
|
||||||
|
|
||||||
|
@ -8,24 +8,38 @@ import PeerId from 'peer-id';
|
|||||||
|
|
||||||
import { CODEC, WakuRelay, WakuRelayPubsub } from './waku_relay';
|
import { CODEC, WakuRelay, WakuRelayPubsub } from './waku_relay';
|
||||||
|
|
||||||
|
export interface CreateOptions {
|
||||||
|
listenAddresses: string[];
|
||||||
|
staticNoiseKey: bytes | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Waku {
|
export default class Waku {
|
||||||
private constructor(public libp2p: Libp2p, public relay: WakuRelay) {}
|
private constructor(public libp2p: Libp2p, public relay: WakuRelay) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new waku node
|
* Create new waku node
|
||||||
|
* @param listenAddresses: Array of Multiaddrs on which the node should listen. If not present, defaults to ['/ip4/0.0.0.0/tcp/0'].
|
||||||
* @param staticNoiseKey: A static key to use for noise,
|
* @param staticNoiseKey: A static key to use for noise,
|
||||||
* mainly used for test to reduce entropy usage.
|
* mainly used for test to reduce entropy usage.
|
||||||
* @returns {Promise<Waku>}
|
* @returns {Promise<Waku>}
|
||||||
*/
|
*/
|
||||||
static async create(staticNoiseKey?: bytes): Promise<Waku> {
|
static async create(options: Partial<CreateOptions>): Promise<Waku> {
|
||||||
|
const opts = Object.assign(
|
||||||
|
{
|
||||||
|
listenAddresses: ['/ip4/0.0.0.0/tcp/0'],
|
||||||
|
staticNoiseKey: undefined,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
const libp2p = await Libp2p.create({
|
const libp2p = await Libp2p.create({
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: ['/ip4/0.0.0.0/tcp/0'],
|
listen: opts.listenAddresses,
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
transport: [TCP],
|
transport: [TCP],
|
||||||
streamMuxer: [Mplex],
|
streamMuxer: [Mplex],
|
||||||
connEncryption: [new Noise(staticNoiseKey)],
|
connEncryption: [new Noise(opts.staticNoiseKey)],
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Type needs update
|
// @ts-ignore: Type needs update
|
||||||
pubsub: WakuRelayPubsub,
|
pubsub: WakuRelayPubsub,
|
||||||
@ -37,6 +51,14 @@ export default class Waku {
|
|||||||
return new Waku(libp2p, new WakuRelay(libp2p.pubsub));
|
return new Waku(libp2p, new WakuRelay(libp2p.pubsub));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dials to the provided peer. If successful, the known metadata of the peer will be added to the nodes peerStore, and the Connection will be returned
|
||||||
|
* @param peer The peer to dial
|
||||||
|
*/
|
||||||
|
async dial(peer: PeerId | Multiaddr | string) {
|
||||||
|
return this.libp2p.dialProtocol(peer, CODEC);
|
||||||
|
}
|
||||||
|
|
||||||
async dialWithMultiAddr(peerId: PeerId, multiaddr: Multiaddr[]) {
|
async dialWithMultiAddr(peerId: PeerId, multiaddr: Multiaddr[]) {
|
||||||
this.libp2p.peerStore.addressBook.set(peerId, multiaddr);
|
this.libp2p.peerStore.addressBook.set(peerId, multiaddr);
|
||||||
await this.libp2p.dialProtocol(peerId, CODEC);
|
await this.libp2p.dialProtocol(peerId, CODEC);
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
import fc from 'fast-check';
|
import fc from 'fast-check';
|
||||||
|
|
||||||
import { Message } from './waku_message';
|
import { WakuMessage } from './waku_message';
|
||||||
|
|
||||||
describe('Waku Message', function () {
|
describe('Waku Message', function () {
|
||||||
it('Waku message round trip binary serialization', function () {
|
it('Waku message round trip binary serialization', function () {
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.property(fc.string(), (s) => {
|
fc.property(fc.string(), (s) => {
|
||||||
const msg = Message.fromUtf8String(s);
|
const msg = WakuMessage.fromUtf8String(s);
|
||||||
const binary = msg.toBinary();
|
const binary = msg.toBinary();
|
||||||
const actual = Message.fromBinary(binary);
|
const actual = WakuMessage.decode(binary);
|
||||||
|
|
||||||
return actual.isEqualTo(msg);
|
return actual.isEqualTo(msg);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Payload to utf-8', function () {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.string(), (s) => {
|
||||||
|
const msg = WakuMessage.fromUtf8String(s);
|
||||||
|
const utf8 = msg.utf8Payload();
|
||||||
|
|
||||||
|
return utf8 === s;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
import { Reader } from 'protobufjs/minimal';
|
import { Reader } from 'protobufjs/minimal';
|
||||||
|
|
||||||
// Protecting the user from protobuf oddities
|
// Protecting the user from protobuf oddities
|
||||||
import { WakuMessage } from '../proto/waku/v2/waku';
|
import { WakuMessageProto } from '../proto/waku/v2/waku';
|
||||||
|
|
||||||
const DEFAULT_CONTENT_TOPIC = 1;
|
const DEFAULT_CONTENT_TOPIC = 1;
|
||||||
const DEFAULT_VERSION = 0;
|
const DEFAULT_VERSION = 0;
|
||||||
|
|
||||||
export class Message {
|
export class WakuMessage {
|
||||||
private constructor(
|
private constructor(
|
||||||
public payload?: Uint8Array,
|
public payload?: Uint8Array,
|
||||||
public contentTopic?: number,
|
public contentTopic?: number,
|
||||||
@ -15,32 +15,57 @@ export class Message {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Message from utf-8 string
|
* Create Message with a utf-8 string as payload
|
||||||
* @param message
|
* @param payload
|
||||||
* @returns {Message}
|
* @returns {WakuMessage}
|
||||||
*/
|
*/
|
||||||
static fromUtf8String(message: string): Message {
|
static fromUtf8String(payload: string): WakuMessage {
|
||||||
const payload = Buffer.from(message, 'utf-8');
|
const buf = Buffer.from(payload, 'utf-8');
|
||||||
return new Message(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION);
|
return new WakuMessage(buf, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromBinary(bytes: Uint8Array): Message {
|
/**
|
||||||
const wakuMsg = WakuMessage.decode(Reader.create(bytes));
|
* Create Message with a byte array as payload
|
||||||
return new Message(wakuMsg.payload, wakuMsg.contentTopic, wakuMsg.version);
|
* @param payload
|
||||||
|
* @returns {WakuMessage}
|
||||||
|
*/
|
||||||
|
static fromBytes(payload: Uint8Array): WakuMessage {
|
||||||
|
return new WakuMessage(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
static decode(bytes: Uint8Array): WakuMessage {
|
||||||
|
const wakuMsg = WakuMessageProto.decode(Reader.create(bytes));
|
||||||
|
return new WakuMessage(
|
||||||
|
wakuMsg.payload,
|
||||||
|
wakuMsg.contentTopic,
|
||||||
|
wakuMsg.version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toBinary(): Uint8Array {
|
toBinary(): Uint8Array {
|
||||||
return WakuMessage.encode({
|
return WakuMessageProto.encode({
|
||||||
payload: this.payload,
|
payload: this.payload,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
contentTopic: this.contentTopic,
|
contentTopic: this.contentTopic,
|
||||||
}).finish();
|
}).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utf8Payload(): string {
|
||||||
|
if (!this.payload) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(this.payload)
|
||||||
|
.map((char) => {
|
||||||
|
return String.fromCharCode(char);
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
// Purely for tests purposes.
|
// Purely for tests purposes.
|
||||||
// We do consider protobuf field when checking equality
|
// We do consider protobuf field when checking equality
|
||||||
// As the content is held by the other fields.
|
// As the content is held by the other fields.
|
||||||
isEqualTo(other: Message) {
|
isEqualTo(other: WakuMessage) {
|
||||||
const payloadsAreEqual =
|
const payloadsAreEqual =
|
||||||
this.payload && other.payload
|
this.payload && other.payload
|
||||||
? Buffer.compare(this.payload, other.payload) === 0
|
? Buffer.compare(this.payload, other.payload) === 0
|
||||||
|
@ -2,11 +2,12 @@ import { expect } from 'chai';
|
|||||||
import Pubsub from 'libp2p-interfaces/src/pubsub';
|
import Pubsub from 'libp2p-interfaces/src/pubsub';
|
||||||
|
|
||||||
import { NOISE_KEY_1, NOISE_KEY_2 } from '../test_utils/constants';
|
import { NOISE_KEY_1, NOISE_KEY_2 } from '../test_utils/constants';
|
||||||
|
import { delay } from '../test_utils/delay';
|
||||||
import { makeLogFileName } from '../test_utils/log_file';
|
import { makeLogFileName } from '../test_utils/log_file';
|
||||||
import { NimWaku } from '../test_utils/nim_waku';
|
import { NimWaku } from '../test_utils/nim_waku';
|
||||||
|
|
||||||
import Waku from './waku';
|
import Waku from './waku';
|
||||||
import { Message } from './waku_message';
|
import { WakuMessage } from './waku_message';
|
||||||
import { CODEC, TOPIC } from './waku_relay';
|
import { CODEC, TOPIC } from './waku_relay';
|
||||||
|
|
||||||
describe('Waku Relay', () => {
|
describe('Waku Relay', () => {
|
||||||
@ -20,8 +21,8 @@ describe('Waku Relay', () => {
|
|||||||
let waku2: Waku;
|
let waku2: Waku;
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
[waku1, waku2] = await Promise.all([
|
[waku1, waku2] = await Promise.all([
|
||||||
Waku.create(NOISE_KEY_1),
|
Waku.create({ staticNoiseKey: NOISE_KEY_1 }),
|
||||||
Waku.create(NOISE_KEY_2),
|
Waku.create({ staticNoiseKey: NOISE_KEY_2 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await waku1.dialWithMultiAddr(waku2.libp2p.peerId, waku2.libp2p.multiaddrs);
|
await waku1.dialWithMultiAddr(waku2.libp2p.peerId, waku2.libp2p.multiaddrs);
|
||||||
@ -76,7 +77,7 @@ describe('Waku Relay', () => {
|
|||||||
it.skip('Publish', async function () {
|
it.skip('Publish', async function () {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
|
|
||||||
const message = Message.fromUtf8String('JS to JS communication works');
|
const message = WakuMessage.fromUtf8String('JS to JS communication works');
|
||||||
// waku.libp2p.pubsub.globalSignaturePolicy = 'StrictSign';
|
// waku.libp2p.pubsub.globalSignaturePolicy = 'StrictSign';
|
||||||
|
|
||||||
const receivedPromise = waitForNextData(waku2.libp2p.pubsub);
|
const receivedPromise = waitForNextData(waku2.libp2p.pubsub);
|
||||||
@ -108,7 +109,7 @@ describe('Waku Relay', () => {
|
|||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
waku = await Waku.create(NOISE_KEY_1);
|
waku = await Waku.create({ staticNoiseKey: NOISE_KEY_1 });
|
||||||
|
|
||||||
const peerId = waku.libp2p.peerId.toB58String();
|
const peerId = waku.libp2p.peerId.toB58String();
|
||||||
const localMultiaddr = waku.libp2p.multiaddrs.find((addr) =>
|
const localMultiaddr = waku.libp2p.multiaddrs.find((addr) =>
|
||||||
@ -140,7 +141,7 @@ describe('Waku Relay', () => {
|
|||||||
it('Js publishes to nim', async function () {
|
it('Js publishes to nim', async function () {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
|
|
||||||
const message = Message.fromUtf8String('This is a message');
|
const message = WakuMessage.fromUtf8String('This is a message');
|
||||||
|
|
||||||
await waku.relay.publish(message);
|
await waku.relay.publish(message);
|
||||||
|
|
||||||
@ -157,13 +158,7 @@ describe('Waku Relay', () => {
|
|||||||
|
|
||||||
it('Nim publishes to js', async function () {
|
it('Nim publishes to js', async function () {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
const message = Message.fromUtf8String('Here is another message.');
|
const message = WakuMessage.fromUtf8String('Here is another message.');
|
||||||
|
|
||||||
await waku.relay.subscribe();
|
|
||||||
|
|
||||||
await new Promise((resolve) =>
|
|
||||||
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
|
||||||
);
|
|
||||||
|
|
||||||
const receivedPromise = waitForNextData(waku.libp2p.pubsub);
|
const receivedPromise = waitForNextData(waku.libp2p.pubsub);
|
||||||
|
|
||||||
@ -185,14 +180,82 @@ describe('Waku Relay', () => {
|
|||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
waku = await Waku.create(NOISE_KEY_1);
|
waku = await Waku.create({ staticNoiseKey: NOISE_KEY_1 });
|
||||||
|
|
||||||
|
nimWaku = new NimWaku(this.test!.ctx!.currentTest!.title);
|
||||||
|
await nimWaku.start();
|
||||||
|
|
||||||
|
await waku.dial(await nimWaku.getMultiaddrWithId());
|
||||||
|
|
||||||
|
await delay(100);
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
);
|
||||||
|
|
||||||
|
await waku.relay.subscribe();
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
nimWaku ? nimWaku.stop() : null;
|
||||||
|
waku ? await waku.stop() : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nim subscribes to js', async function () {
|
||||||
|
const subscribers = waku.libp2p.pubsub.getSubscribers(TOPIC);
|
||||||
|
|
||||||
|
const nimPeerId = await nimWaku.getPeerId();
|
||||||
|
expect(subscribers).to.contain(nimPeerId.toB58String());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Js publishes to nim', async function () {
|
||||||
|
const message = WakuMessage.fromUtf8String('This is a message');
|
||||||
|
|
||||||
|
await waku.relay.publish(message);
|
||||||
|
|
||||||
|
await nimWaku.waitForLog('WakuMessage received');
|
||||||
|
|
||||||
|
const msgs = await nimWaku.messages();
|
||||||
|
|
||||||
|
expect(msgs[0].contentTopic).to.equal(message.contentTopic);
|
||||||
|
expect(msgs[0].version).to.equal(message.version);
|
||||||
|
|
||||||
|
const payload = Buffer.from(msgs[0].payload);
|
||||||
|
expect(Buffer.compare(payload, message.payload!)).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Nim publishes to js', async function () {
|
||||||
|
const message = WakuMessage.fromUtf8String('Here is another message.');
|
||||||
|
|
||||||
|
const receivedPromise = waitForNextData(waku.libp2p.pubsub);
|
||||||
|
|
||||||
|
await nimWaku.sendMessage(message);
|
||||||
|
|
||||||
|
const receivedMsg = await receivedPromise;
|
||||||
|
|
||||||
|
expect(receivedMsg.contentTopic).to.eq(message.contentTopic);
|
||||||
|
expect(receivedMsg.version).to.eq(message.version);
|
||||||
|
|
||||||
|
const payload = Buffer.from(receivedMsg.payload!);
|
||||||
|
expect(Buffer.compare(payload, message.payload!)).to.eq(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Js connects to nim', function () {
|
||||||
|
let waku: Waku;
|
||||||
|
let nimWaku: NimWaku;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.timeout(10_000);
|
||||||
|
waku = await Waku.create({ staticNoiseKey: NOISE_KEY_1 });
|
||||||
|
|
||||||
nimWaku = new NimWaku(makeLogFileName(this));
|
nimWaku = new NimWaku(makeLogFileName(this));
|
||||||
await nimWaku.start();
|
await nimWaku.start();
|
||||||
|
|
||||||
const nimPeerId = await nimWaku.getPeerId();
|
await waku.dial(await nimWaku.getMultiaddrWithId());
|
||||||
|
|
||||||
await waku.dialWithMultiAddr(nimPeerId, [nimWaku.multiaddr]);
|
|
||||||
|
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
@ -218,7 +281,7 @@ describe('Waku Relay', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Js publishes to nim', async function () {
|
it('Js publishes to nim', async function () {
|
||||||
const message = Message.fromUtf8String('This is a message');
|
const message = WakuMessage.fromUtf8String('This is a message');
|
||||||
|
|
||||||
await waku.relay.publish(message);
|
await waku.relay.publish(message);
|
||||||
|
|
||||||
@ -234,7 +297,7 @@ describe('Waku Relay', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Nim publishes to js', async function () {
|
it('Nim publishes to js', async function () {
|
||||||
const message = Message.fromUtf8String('Here is another message.');
|
const message = WakuMessage.fromUtf8String('Here is another message.');
|
||||||
|
|
||||||
const receivedPromise = waitForNextData(waku.libp2p.pubsub);
|
const receivedPromise = waitForNextData(waku.libp2p.pubsub);
|
||||||
|
|
||||||
@ -249,13 +312,86 @@ describe('Waku Relay', () => {
|
|||||||
expect(Buffer.compare(payload, message.payload!)).to.eq(0);
|
expect(Buffer.compare(payload, message.payload!)).to.eq(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('js to nim to js', function () {
|
||||||
|
let waku1: Waku;
|
||||||
|
let waku2: Waku;
|
||||||
|
let nimWaku: NimWaku;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.timeout(10_000);
|
||||||
|
[waku1, waku2] = await Promise.all([
|
||||||
|
Waku.create({ staticNoiseKey: NOISE_KEY_1 }),
|
||||||
|
Waku.create({ staticNoiseKey: NOISE_KEY_2 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
nimWaku = new NimWaku(this.test!.ctx!.currentTest!.title);
|
||||||
|
await nimWaku.start();
|
||||||
|
|
||||||
|
const nimWakuMultiaddr = await nimWaku.getMultiaddrWithId();
|
||||||
|
await Promise.all([
|
||||||
|
waku1.dial(nimWakuMultiaddr),
|
||||||
|
waku2.dial(nimWakuMultiaddr),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await delay(100);
|
||||||
|
await Promise.all([
|
||||||
|
new Promise((resolve) =>
|
||||||
|
waku1.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
),
|
||||||
|
new Promise((resolve) =>
|
||||||
|
waku2.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await Promise.all([waku1.relay.subscribe(), waku2.relay.subscribe()]);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
new Promise((resolve) =>
|
||||||
|
waku1.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
),
|
||||||
|
new Promise((resolve) =>
|
||||||
|
waku2.libp2p.pubsub.once('gossipsub:heartbeat', resolve)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
nimWaku ? nimWaku.stop() : null;
|
||||||
|
await Promise.all([
|
||||||
|
waku1 ? await waku1.stop() : null,
|
||||||
|
waku2 ? await waku2.stop() : null,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Js publishes, other Js receives', async function () {
|
||||||
|
// Check that the two JS peers are NOT directly connected
|
||||||
|
expect(
|
||||||
|
waku1.libp2p.peerStore.peers.has(waku2.libp2p.peerId.toB58String())
|
||||||
|
).to.be.false;
|
||||||
|
expect(
|
||||||
|
waku2.libp2p.peerStore.peers.has(waku1.libp2p.peerId.toB58String())
|
||||||
|
).to.be.false;
|
||||||
|
|
||||||
|
const msgStr = 'Hello there!';
|
||||||
|
const message = WakuMessage.fromUtf8String(msgStr);
|
||||||
|
|
||||||
|
const waku2ReceivedPromise = waitForNextData(waku2.libp2p.pubsub);
|
||||||
|
|
||||||
|
await waku1.relay.publish(message);
|
||||||
|
|
||||||
|
const waku2ReceivedMsg = await waku2ReceivedPromise;
|
||||||
|
|
||||||
|
expect(waku2ReceivedMsg.utf8Payload()).to.eq(msgStr);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function waitForNextData(pubsub: Pubsub): Promise<Message> {
|
function waitForNextData(pubsub: Pubsub): Promise<WakuMessage> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
pubsub.once(TOPIC, resolve);
|
pubsub.once(TOPIC, resolve);
|
||||||
}).then((msg: any) => {
|
}).then((msg: any) => {
|
||||||
return Message.fromBinary(msg.data);
|
return WakuMessage.decode(msg.data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import Pubsub from 'libp2p-interfaces/src/pubsub';
|
|||||||
import { SignaturePolicy } from 'libp2p-interfaces/src/pubsub/signature-policy';
|
import { SignaturePolicy } from 'libp2p-interfaces/src/pubsub/signature-policy';
|
||||||
|
|
||||||
import { getWakuPeers } from './get_waku_peers';
|
import { getWakuPeers } from './get_waku_peers';
|
||||||
import { Message } from './waku_message';
|
import { WakuMessage } from './waku_message';
|
||||||
|
|
||||||
export const CODEC = '/vac/waku/relay/2.0.0-beta2';
|
export const CODEC = '/vac/waku/relay/2.0.0-beta2';
|
||||||
|
|
||||||
@ -16,11 +16,10 @@ export class WakuRelayPubsub extends Gossipsub {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param libp2p: Libp2p
|
* @param libp2p: Libp2p
|
||||||
* @param options: Partial<GossipInputOptions>
|
|
||||||
*/
|
*/
|
||||||
constructor(libp2p: Libp2p) {
|
constructor(libp2p: Libp2p) {
|
||||||
super(libp2p, {
|
super(libp2p, {
|
||||||
emitSelf: true,
|
emitSelf: false,
|
||||||
// Ensure that no signature is expected in the messages.
|
// Ensure that no signature is expected in the messages.
|
||||||
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
||||||
});
|
});
|
||||||
@ -100,7 +99,7 @@ export class WakuRelay {
|
|||||||
await this.pubsub.subscribe(TOPIC);
|
await this.pubsub.subscribe(TOPIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(message: Message) {
|
async publish(message: WakuMessage) {
|
||||||
const msg = message.toBinary();
|
const msg = message.toBinary();
|
||||||
await this.pubsub.publish(TOPIC, msg);
|
await this.pubsub.publish(TOPIC, msg);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import Multiaddr from 'multiaddr';
|
|||||||
import multiaddr from 'multiaddr';
|
import multiaddr from 'multiaddr';
|
||||||
import PeerId from 'peer-id';
|
import PeerId from 'peer-id';
|
||||||
|
|
||||||
import { Message } from '../lib/waku_message';
|
import { WakuMessage } from '../lib/waku_message';
|
||||||
import { TOPIC } from '../lib/waku_relay';
|
import { TOPIC } from '../lib/waku_relay';
|
||||||
|
|
||||||
import { existsAsync, mkdirAsync, openAsync } from './async_fs';
|
import { existsAsync, mkdirAsync, openAsync } from './async_fs';
|
||||||
@ -36,6 +36,7 @@ export class NimWaku {
|
|||||||
private pid?: number;
|
private pid?: number;
|
||||||
private portsShift: number;
|
private portsShift: number;
|
||||||
private peerId?: PeerId;
|
private peerId?: PeerId;
|
||||||
|
private multiaddrWithId?: Multiaddr;
|
||||||
private logPath: string;
|
private logPath: string;
|
||||||
|
|
||||||
constructor(logName: string) {
|
constructor(logName: string) {
|
||||||
@ -131,7 +132,7 @@ export class NimWaku {
|
|||||||
return res.result;
|
return res.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage(message: Message) {
|
async sendMessage(message: WakuMessage) {
|
||||||
this.checkProcess();
|
this.checkProcess();
|
||||||
|
|
||||||
if (!message.payload) {
|
if (!message.payload) {
|
||||||
@ -160,14 +161,25 @@ export class NimWaku {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getPeerId(): Promise<PeerId> {
|
async getPeerId(): Promise<PeerId> {
|
||||||
if (this.peerId) {
|
return await this.setPeerId().then((res) => res.peerId);
|
||||||
return this.peerId;
|
}
|
||||||
|
|
||||||
|
async getMultiaddrWithId(): Promise<Multiaddr> {
|
||||||
|
return await this.setPeerId().then((res) => res.multiaddrWithId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setPeerId(): Promise<{
|
||||||
|
peerId: PeerId;
|
||||||
|
multiaddrWithId: Multiaddr;
|
||||||
|
}> {
|
||||||
|
if (this.peerId && this.multiaddrWithId) {
|
||||||
|
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.info();
|
const res = await this.info();
|
||||||
const strPeerId = multiaddr(res.listenStr).getPeerId();
|
this.multiaddrWithId = multiaddr(res.listenStr);
|
||||||
|
const peerIdStr = this.multiaddrWithId.getPeerId();
|
||||||
return PeerId.createFromB58String(strPeerId);
|
this.peerId = PeerId.createFromB58String(peerIdStr);
|
||||||
|
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
||||||
}
|
}
|
||||||
|
|
||||||
get multiaddr(): Multiaddr {
|
get multiaddr(): Multiaddr {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user