From 46c41dc50fcc1175096ecf066768a0c77ca3b7f1 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 23 Mar 2021 11:14:51 +1100 Subject: [PATCH 01/16] Test connection triggered from js and add API --- src/lib/waku.ts | 8 ++++ src/lib/waku_relay.spec.ts | 77 +++++++++++++++++++++++++++++++++++++- src/test_utils/nim_waku.ts | 24 +++++++++--- 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/lib/waku.ts b/src/lib/waku.ts index f9c3e0c9f7..be6a734ceb 100644 --- a/src/lib/waku.ts +++ b/src/lib/waku.ts @@ -37,6 +37,14 @@ export default class Waku { 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[]) { this.libp2p.peerStore.addressBook.set(peerId, multiaddr); await this.libp2p.dialProtocol(peerId, CODEC); diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index ba1d02a86d..4f6250d783 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import Pubsub from 'libp2p-interfaces/src/pubsub'; 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 { NimWaku } from '../test_utils/nim_waku'; @@ -187,12 +188,84 @@ describe('Waku Relay', () => { this.timeout(10_000); waku = await Waku.create(NOISE_KEY_1); - nimWaku = new NimWaku(makeLogFileName(this)); + 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) + ); + }); + + 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 = Message.fromUtf8String('This is a message'); + // TODO: nim-waku does not really follow the `StrictNoSign` policy hence we need to change + // it for nim-waku to process our messages. Can be removed once + // https://github.com/status-im/nim-waku/issues/422 is fixed + waku.libp2p.pubsub.globalSignaturePolicy = 'StrictSign'; + + 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 = Message.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); + + 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(NOISE_KEY_1); + + nimWaku = new NimWaku(makeLogFileName(this)); + await nimWaku.start(); - await waku.dialWithMultiAddr(nimPeerId, [nimWaku.multiaddr]); + await waku.dial(await nimWaku.getMultiaddrWithId()); await new Promise((resolve) => waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve) diff --git a/src/test_utils/nim_waku.ts b/src/test_utils/nim_waku.ts index 34e7e6e9f1..e80f1aed9a 100644 --- a/src/test_utils/nim_waku.ts +++ b/src/test_utils/nim_waku.ts @@ -36,6 +36,7 @@ export class NimWaku { private pid?: number; private portsShift: number; private peerId?: PeerId; + private multiaddrWithId?: Multiaddr; private logPath: string; constructor(logName: string) { @@ -160,14 +161,25 @@ export class NimWaku { } async getPeerId(): Promise { - if (this.peerId) { - return this.peerId; - } + return await this.setPeerId().then((res) => res.peerId); + } - const res = await this.info(); - const strPeerId = multiaddr(res.listenStr).getPeerId(); + async getMultiaddrWithId(): Promise { + return await this.setPeerId().then((res) => res.multiaddrWithId); + } - return PeerId.createFromB58String(strPeerId); + 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(); + this.multiaddrWithId = multiaddr(res.listenStr); + const peerIdStr = this.multiaddrWithId.getPeerId(); + this.peerId = PeerId.createFromB58String(peerIdStr); + return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId }; } get multiaddr(): Multiaddr { From e167f4fba4a86628d8a0e67ead52c566d51a588b Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 24 Mar 2021 16:31:54 +1100 Subject: [PATCH 02/16] Decode payload to utf8 string --- src/lib/waku_message.spec.ts | 11 +++++++++++ src/lib/waku_message.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/lib/waku_message.spec.ts b/src/lib/waku_message.spec.ts index 5d82f8db8c..5865f7fe18 100644 --- a/src/lib/waku_message.spec.ts +++ b/src/lib/waku_message.spec.ts @@ -14,4 +14,15 @@ describe('Waku Message', function () { }) ); }); + + it('Payload to utf-8', function () { + fc.assert( + fc.property(fc.string(), (s) => { + const msg = Message.fromUtf8String(s); + const utf8 = msg.utf8Payload(); + + return utf8 === s; + }) + ); + }); }); diff --git a/src/lib/waku_message.ts b/src/lib/waku_message.ts index f7734d4ba1..1b46978fb0 100644 --- a/src/lib/waku_message.ts +++ b/src/lib/waku_message.ts @@ -37,6 +37,18 @@ export class Message { }).finish(); } + utf8Payload(): string { + if (!this.payload) { + return ''; + } + + return Array.from(this.payload) + .map((char) => { + return String.fromCharCode(char); + }) + .join(''); + } + // Purely for tests purposes. // We do consider protobuf field when checking equality // As the content is held by the other fields. From f2c1c9235383d1526f5824604bf738c8ee87bbef Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 24 Mar 2021 17:08:15 +1100 Subject: [PATCH 03/16] Chat app example --- chat/index.ts | 38 +++++++++++++++++++++++++++++++ package.json | 2 ++ src/lib/waku_relay.ts | 2 +- tsconfig.chat.json | 53 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 chat/index.ts create mode 100644 tsconfig.chat.json diff --git a/chat/index.ts b/chat/index.ts new file mode 100644 index 0000000000..632a71fc7c --- /dev/null +++ b/chat/index.ts @@ -0,0 +1,38 @@ +import Waku from '../build/main/lib/waku'; +import { TOPIC } from '../build/main/lib/waku_relay'; +import { Message } from '../build/main/lib/waku_message'; + +import readline from 'readline'; + +;(async function() { + + const waku = await Waku.create(); + console.log('Waku started'); + await waku.dial('/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'); + // await waku.dial('/ip4/134.209.113.86/tcp/9000/p2p/16Uiu2HAmVVi6Q4j7MAKVibquW8aA27UNrA4Q8Wkz9EetGViu8ZF1'); + console.log('Static node has been dialed'); + + // TODO: Automatically subscribe + await waku.relay.subscribe(); + console.log('Subscribed to waku relay'); + + // TODO: Bubble event to waku, infere topic, decode msg + waku.libp2p.pubsub.on(TOPIC, event => { + const msg = Message.fromBinary(event.data); + console.log(msg.utf8Payload()); + }); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + console.log('Ready to chat!'); + rl.prompt(); + rl.on('line', async (line) => { + rl.prompt(); + const msg = Message.fromUtf8String(line); + await waku.relay.publish(msg); + }); + +})(); diff --git a/package.json b/package.json index de887e04dd..8829b8b5e6 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "build:1-proto": "buf generate", "build:main": "tsc -p tsconfig.json", "build:module": "tsc -p tsconfig.module.json", + "build:chat": "tsc -p tsconfig.chat.json", "fix": "run-s fix:*", "fix:prettier": "prettier \"src/**/*.ts\" --write", "fix:lint": "eslint src --ext .ts --fix", "pretest": "run-s pretest:*", "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", + "start-chat": "ts-node chat/index.ts", "test": "run-s build test:*", "test:lint": "eslint src --ext .ts", "test:prettier": "prettier \"src/**/*.ts\" --list-different", diff --git a/src/lib/waku_relay.ts b/src/lib/waku_relay.ts index 76a03caec3..f8e91350f6 100644 --- a/src/lib/waku_relay.ts +++ b/src/lib/waku_relay.ts @@ -20,7 +20,7 @@ export class WakuRelayPubsub extends Gossipsub { */ constructor(libp2p: Libp2p) { super(libp2p, { - emitSelf: true, + emitSelf: false, // Ensure that no signature is expected in the messages. globalSignaturePolicy: SignaturePolicy.StrictNoSign, }); diff --git a/tsconfig.chat.json b/tsconfig.chat.json new file mode 100644 index 0000000000..7a9b8796f6 --- /dev/null +++ b/tsconfig.chat.json @@ -0,0 +1,53 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "es2017", + "outDir": "build/chat", + "rootDir": "chat", + "moduleResolution": "node", + "module": "commonjs", + "declaration": true, + "inlineSourceMap": true, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + "resolveJsonModule": true /* Include modules imported with .json extension. */, + + "strict": true /* Enable all strict type-checking options. */, + + /* Strict Type-Checking Options */ + // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + // "strictNullChecks": true /* Enable strict null checks. */, + // "strictFunctionTypes": true /* Enable strict checking of function types. */, + // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, + // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, + // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + + /* Additional Checks */ + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, + "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, + + /* Debugging Options */ + "traceResolution": false /* Report module resolution log messages. */, + "listEmittedFiles": false /* Print names of generated files part of the compilation. */, + "listFiles": false /* Print names of files part of the compilation. */, + "pretty": true /* Stylize errors and messages using color and context. */, + + // Due to broken types in indirect dependencies + "skipLibCheck": true, + + /* Experimental Options */ + // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, + // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, + + "lib": ["es2017"], + "types": ["node", "mocha"], + "typeRoots": ["node_modules/@types"] + }, + "include": ["chat/**/*.ts"], + "exclude": ["node_modules/**"], + "compileOnSave": false, + "ts-node": { + "files": true + } +} From 086623243e38ca3b42c3ba36685ace1cda4194db Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 25 Mar 2021 16:13:14 +1100 Subject: [PATCH 04/16] Remove `StrictSign` hack --- src/lib/waku_relay.spec.ts | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index 4f6250d783..6a829d0e18 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -160,12 +160,6 @@ describe('Waku Relay', () => { this.timeout(5000); const message = Message.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); await nimWaku.sendMessage(message); @@ -197,6 +191,12 @@ describe('Waku Relay', () => { 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 () { @@ -213,10 +213,6 @@ describe('Waku Relay', () => { it('Js publishes to nim', async function () { const message = Message.fromUtf8String('This is a message'); - // TODO: nim-waku does not really follow the `StrictNoSign` policy hence we need to change - // it for nim-waku to process our messages. Can be removed once - // https://github.com/status-im/nim-waku/issues/422 is fixed - waku.libp2p.pubsub.globalSignaturePolicy = 'StrictSign'; await waku.relay.publish(message); @@ -234,12 +230,6 @@ describe('Waku Relay', () => { it('Nim publishes to js', async function () { const message = Message.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); await nimWaku.sendMessage(message); From 2b450cf2ddbe8a0079e3214ba9e2b8ccfa62ceaa Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 25 Mar 2021 16:39:21 +1100 Subject: [PATCH 05/16] Include js>nim>js test --- src/lib/waku_relay.spec.ts | 73 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index 6a829d0e18..3d905c7217 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -312,6 +312,79 @@ describe('Waku Relay', () => { 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(NOISE_KEY_1), + Waku.create(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 = Message.fromUtf8String(msgStr); + + const waku2ReceivedPromise = waitForNextData(waku2.libp2p.pubsub); + + await waku1.relay.publish(message); + + const waku2ReceivedMsg = await waku2ReceivedPromise; + + expect(waku2ReceivedMsg.utf8Payload()).to.eq(msgStr); + }); + }); }); }); From d2f1995f3e55ddd2f9020341e1a24a081895e1f5 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 26 Mar 2021 12:10:26 +1100 Subject: [PATCH 06/16] Can receive message over internet --- chat/index.ts | 32 +++++++++++++++++++++++++------- src/lib/waku.ts | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/chat/index.ts b/chat/index.ts index 632a71fc7c..d865a9d3f8 100644 --- a/chat/index.ts +++ b/chat/index.ts @@ -3,24 +3,42 @@ import { TOPIC } from '../build/main/lib/waku_relay'; import { Message } from '../build/main/lib/waku_message'; import readline from 'readline'; +import { delay } from '../build/main/test_utils/delay'; ;(async function() { const waku = await Waku.create(); + + // TODO: Bubble event to waku, infere topic, decode msg + waku.libp2p.pubsub.on(TOPIC, event => { + const msg = Message.fromBinary(event.data); + console.log(msg.utf8Payload()); + }); + console.log('Waku started'); - await waku.dial('/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'); + // Status static node + await waku.dial('/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'); + + // Richard's node // await waku.dial('/ip4/134.209.113.86/tcp/9000/p2p/16Uiu2HAmVVi6Q4j7MAKVibquW8aA27UNrA4Q8Wkz9EetGViu8ZF1'); + + // await waku.dial('/ip4/0.0.0.0/tcp/60000/p2p/16Uiu2HAmDVYacyxN4t1SYBhRSTDr6nmYwuY6qWWTgagZm558rFA6') + + await delay(100); + console.log('Static node has been dialed'); + await new Promise((resolve) => + waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve) + ); + // TODO: Automatically subscribe await waku.relay.subscribe(); console.log('Subscribed to waku relay'); - // TODO: Bubble event to waku, infere topic, decode msg - waku.libp2p.pubsub.on(TOPIC, event => { - const msg = Message.fromBinary(event.data); - console.log(msg.utf8Payload()); - }); + await new Promise((resolve) => + waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve) + ); const rl = readline.createInterface({ input: process.stdin, @@ -31,7 +49,7 @@ import readline from 'readline'; rl.prompt(); rl.on('line', async (line) => { rl.prompt(); - const msg = Message.fromUtf8String(line); + const msg = Message.fromUtf8String('(js-chat) ' + line); await waku.relay.publish(msg); }); diff --git a/src/lib/waku.ts b/src/lib/waku.ts index be6a734ceb..7148dfb1d3 100644 --- a/src/lib/waku.ts +++ b/src/lib/waku.ts @@ -20,7 +20,7 @@ export default class Waku { static async create(staticNoiseKey?: bytes): Promise { const libp2p = await Libp2p.create({ addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'], + listen: ['/ip4/0.0.0.0/tcp/55123'], }, modules: { transport: [TCP], From c3cf6462cc7e3fe7f7f67c7185e65d2c9ca47b5e Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Mon, 29 Mar 2021 13:56:17 +1100 Subject: [PATCH 07/16] Pass listening addresses as argument --- chat/index.ts | 2 +- src/lib/waku.spec.ts | 2 +- src/lib/waku.ts | 20 +++++++++++++++++--- src/lib/waku_relay.spec.ts | 14 +++++++------- src/lib/waku_relay.ts | 1 - 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/chat/index.ts b/chat/index.ts index d865a9d3f8..a584d33e6c 100644 --- a/chat/index.ts +++ b/chat/index.ts @@ -7,7 +7,7 @@ import { delay } from '../build/main/test_utils/delay'; ;(async function() { - const waku = await Waku.create(); + const waku = await Waku.create({listenAddresses: ['/ip4/0.0.0.0/tcp/55123']}); // TODO: Bubble event to waku, infere topic, decode msg waku.libp2p.pubsub.on(TOPIC, event => { diff --git a/src/lib/waku.spec.ts b/src/lib/waku.spec.ts index 89c9421a10..690feccb86 100644 --- a/src/lib/waku.spec.ts +++ b/src/lib/waku.spec.ts @@ -11,7 +11,7 @@ describe('Waku', function () { describe('Interop: Nim', function () { it('nim connects to js', async function () { 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(); diff --git a/src/lib/waku.ts b/src/lib/waku.ts index 7148dfb1d3..129313047c 100644 --- a/src/lib/waku.ts +++ b/src/lib/waku.ts @@ -8,24 +8,38 @@ import PeerId from 'peer-id'; import { CODEC, WakuRelay, WakuRelayPubsub } from './waku_relay'; +export interface CreateOptions { + listenAddresses: string[]; + staticNoiseKey: bytes | undefined; +} + export default class Waku { private constructor(public libp2p: Libp2p, public relay: WakuRelay) {} /** * 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, * mainly used for test to reduce entropy usage. * @returns {Promise} */ - static async create(staticNoiseKey?: bytes): Promise { + static async create(options: Partial): Promise { + const opts = Object.assign( + { + listenAddresses: ['/ip4/0.0.0.0/tcp/0'], + staticNoiseKey: undefined, + }, + options + ); + const libp2p = await Libp2p.create({ addresses: { - listen: ['/ip4/0.0.0.0/tcp/55123'], + listen: opts.listenAddresses, }, modules: { transport: [TCP], streamMuxer: [Mplex], - connEncryption: [new Noise(staticNoiseKey)], + connEncryption: [new Noise(opts.staticNoiseKey)], // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: Type needs update pubsub: WakuRelayPubsub, diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index 3d905c7217..24b9d465fa 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -21,8 +21,8 @@ describe('Waku Relay', () => { let waku2: Waku; beforeEach(async function () { [waku1, waku2] = await Promise.all([ - Waku.create(NOISE_KEY_1), - Waku.create(NOISE_KEY_2), + Waku.create({ staticNoiseKey: NOISE_KEY_1 }), + Waku.create({ staticNoiseKey: NOISE_KEY_2 }), ]); await waku1.dialWithMultiAddr(waku2.libp2p.peerId, waku2.libp2p.multiaddrs); @@ -109,7 +109,7 @@ describe('Waku Relay', () => { beforeEach(async function () { 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 localMultiaddr = waku.libp2p.multiaddrs.find((addr) => @@ -180,7 +180,7 @@ describe('Waku Relay', () => { beforeEach(async function () { 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(); @@ -250,7 +250,7 @@ describe('Waku Relay', () => { beforeEach(async function () { this.timeout(10_000); - waku = await Waku.create(NOISE_KEY_1); + waku = await Waku.create({ staticNoiseKey: NOISE_KEY_1 }); nimWaku = new NimWaku(makeLogFileName(this)); await nimWaku.start(); @@ -321,8 +321,8 @@ describe('Waku Relay', () => { beforeEach(async function () { this.timeout(10_000); [waku1, waku2] = await Promise.all([ - Waku.create(NOISE_KEY_1), - Waku.create(NOISE_KEY_2), + Waku.create({ staticNoiseKey: NOISE_KEY_1 }), + Waku.create({ staticNoiseKey: NOISE_KEY_2 }), ]); nimWaku = new NimWaku(this.test!.ctx!.currentTest!.title); diff --git a/src/lib/waku_relay.ts b/src/lib/waku_relay.ts index f8e91350f6..8cedd5e189 100644 --- a/src/lib/waku_relay.ts +++ b/src/lib/waku_relay.ts @@ -16,7 +16,6 @@ export class WakuRelayPubsub extends Gossipsub { /** * * @param libp2p: Libp2p - * @param options: Partial */ constructor(libp2p: Libp2p) { super(libp2p, { From b4e5d6d93c218ab106aabe8167cc5d2a97eee4eb Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Mon, 29 Mar 2021 16:46:32 +1100 Subject: [PATCH 08/16] Get peer connection details from command line --- chat/index.ts | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/chat/index.ts b/chat/index.ts index a584d33e6c..4b074b77c4 100644 --- a/chat/index.ts +++ b/chat/index.ts @@ -6,8 +6,9 @@ import readline from 'readline'; import { delay } from '../build/main/test_utils/delay'; ;(async function() { + const opts = processArguments(); - const waku = await Waku.create({listenAddresses: ['/ip4/0.0.0.0/tcp/55123']}); + const waku = await Waku.create({ listenAddresses: ['/ip4/0.0.0.0/tcp/55123'] }); // TODO: Bubble event to waku, infere topic, decode msg waku.libp2p.pubsub.on(TOPIC, event => { @@ -16,17 +17,12 @@ import { delay } from '../build/main/test_utils/delay'; }); console.log('Waku started'); - // Status static node - await waku.dial('/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'); - // Richard's node - // await waku.dial('/ip4/134.209.113.86/tcp/9000/p2p/16Uiu2HAmVVi6Q4j7MAKVibquW8aA27UNrA4Q8Wkz9EetGViu8ZF1'); - - // await waku.dial('/ip4/0.0.0.0/tcp/60000/p2p/16Uiu2HAmDVYacyxN4t1SYBhRSTDr6nmYwuY6qWWTgagZm558rFA6') - - await delay(100); - - console.log('Static node has been dialed'); + if (opts.staticNode) { + console.log(`dialing ${opts.staticNode}`); + await waku.dial(opts.staticNode); + await delay(100); + } await new Promise((resolve) => waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve) @@ -48,9 +44,32 @@ import { delay } from '../build/main/test_utils/delay'; console.log('Ready to chat!'); rl.prompt(); rl.on('line', async (line) => { - rl.prompt(); - const msg = Message.fromUtf8String('(js-chat) ' + line); - await waku.relay.publish(msg); + rl.prompt(); + const msg = Message.fromUtf8String('(js-chat) ' + line); + await waku.relay.publish(msg); }); })(); + +interface Options { + staticNode?: string; +} + +function processArguments(): Options { + let passedArgs = process.argv.slice(2); + + let opts: Options = {}; + + while (passedArgs.length) { + const arg = passedArgs.shift(); + switch (arg) { + case '--staticNode': + opts = Object.assign(opts, { staticNode: passedArgs.shift() }); + break; + default: + console.log(`Argument ignored: ${arg}`); + } + } + + return opts; +} From d9decabae903b5050dd5cf733a29930c1e58cbc1 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Mon, 29 Mar 2021 17:00:24 +1100 Subject: [PATCH 09/16] Seem to fix receiving messages Looking at runs where receiving messages work vs doesn't, in the case it does not work, js-chat is still receiving some messages as part of the identify process after subscribing. Whereas, when it works, the subscribe is done after those messages. With a 2s sleep to delay the subscribe, it seems that it always works. --- chat/index.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/chat/index.ts b/chat/index.ts index 4b074b77c4..3b21e418bf 100644 --- a/chat/index.ts +++ b/chat/index.ts @@ -8,7 +8,7 @@ import { delay } from '../build/main/test_utils/delay'; ;(async function() { const opts = processArguments(); - const waku = await Waku.create({ listenAddresses: ['/ip4/0.0.0.0/tcp/55123'] }); + const waku = await Waku.create({ listenAddresses: [opts.listenAddr] }); // TODO: Bubble event to waku, infere topic, decode msg waku.libp2p.pubsub.on(TOPIC, event => { @@ -21,13 +21,15 @@ import { delay } from '../build/main/test_utils/delay'; if (opts.staticNode) { console.log(`dialing ${opts.staticNode}`); await waku.dial(opts.staticNode); - await delay(100); } 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. + await delay(2000); // TODO: Automatically subscribe await waku.relay.subscribe(); console.log('Subscribed to waku relay'); @@ -53,12 +55,13 @@ import { delay } from '../build/main/test_utils/delay'; interface Options { staticNode?: string; + listenAddr: string; } function processArguments(): Options { let passedArgs = process.argv.slice(2); - let opts: Options = {}; + let opts: Options = {listenAddr: '/ip4/0.0.0.0/tcp/0'}; while (passedArgs.length) { const arg = passedArgs.shift(); @@ -66,8 +69,12 @@ function processArguments(): Options { case '--staticNode': opts = Object.assign(opts, { staticNode: passedArgs.shift() }); break; + case '--listenAddr': + opts = Object.assign(opts, { listenAddr: passedArgs.shift() }); + break; default: - console.log(`Argument ignored: ${arg}`); + console.log(`Unsupported argument: ${arg}`); + process.exit(1) } } From 0b282be882b5b891f85e94da99c9822bf19ff29b Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 30 Mar 2021 09:24:29 +1100 Subject: [PATCH 10/16] Move chat folder inside src --- package.json | 3 +-- {chat => src/chat}/index.ts | 25 +++++++++-------- tsconfig.chat.json | 53 ------------------------------------- 3 files changed, 13 insertions(+), 68 deletions(-) rename {chat => src/chat}/index.ts (77%) delete mode 100644 tsconfig.chat.json diff --git a/package.json b/package.json index 8829b8b5e6..5fe8faf01c 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,13 @@ "build:1-proto": "buf generate", "build:main": "tsc -p tsconfig.json", "build:module": "tsc -p tsconfig.module.json", - "build:chat": "tsc -p tsconfig.chat.json", "fix": "run-s fix:*", "fix:prettier": "prettier \"src/**/*.ts\" --write", "fix:lint": "eslint src --ext .ts --fix", "pretest": "run-s pretest:*", "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", - "start-chat": "ts-node chat/index.ts", + "chat:start": "ts-node src/chat/index.ts", "test": "run-s build test:*", "test:lint": "eslint src --ext .ts", "test:prettier": "prettier \"src/**/*.ts\" --list-different", diff --git a/chat/index.ts b/src/chat/index.ts similarity index 77% rename from chat/index.ts rename to src/chat/index.ts index 3b21e418bf..6e26a75f56 100644 --- a/chat/index.ts +++ b/src/chat/index.ts @@ -1,17 +1,17 @@ -import Waku from '../build/main/lib/waku'; -import { TOPIC } from '../build/main/lib/waku_relay'; -import { Message } from '../build/main/lib/waku_message'; - import readline from 'readline'; -import { delay } from '../build/main/test_utils/delay'; -;(async function() { +import Waku from '../lib/waku'; +import { Message } from '../lib/waku_message'; +import { TOPIC } from '../lib/waku_relay'; +import { delay } from '../test_utils/delay'; + +(async function () { const opts = processArguments(); const waku = await Waku.create({ listenAddresses: [opts.listenAddr] }); - // TODO: Bubble event to waku, infere topic, decode msg - waku.libp2p.pubsub.on(TOPIC, event => { + // TODO: Bubble event to waku, infer topic, decode msg + waku.libp2p.pubsub.on(TOPIC, (event) => { const msg = Message.fromBinary(event.data); console.log(msg.utf8Payload()); }); @@ -40,7 +40,7 @@ import { delay } from '../build/main/test_utils/delay'; const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); console.log('Ready to chat!'); @@ -50,7 +50,6 @@ import { delay } from '../build/main/test_utils/delay'; const msg = Message.fromUtf8String('(js-chat) ' + line); await waku.relay.publish(msg); }); - })(); interface Options { @@ -59,9 +58,9 @@ interface Options { } function processArguments(): Options { - let passedArgs = process.argv.slice(2); + const passedArgs = process.argv.slice(2); - let opts: Options = {listenAddr: '/ip4/0.0.0.0/tcp/0'}; + let opts: Options = { listenAddr: '/ip4/0.0.0.0/tcp/0' }; while (passedArgs.length) { const arg = passedArgs.shift(); @@ -74,7 +73,7 @@ function processArguments(): Options { break; default: console.log(`Unsupported argument: ${arg}`); - process.exit(1) + process.exit(1); } } diff --git a/tsconfig.chat.json b/tsconfig.chat.json deleted file mode 100644 index 7a9b8796f6..0000000000 --- a/tsconfig.chat.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "compilerOptions": { - "incremental": true, - "target": "es2017", - "outDir": "build/chat", - "rootDir": "chat", - "moduleResolution": "node", - "module": "commonjs", - "declaration": true, - "inlineSourceMap": true, - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - "resolveJsonModule": true /* Include modules imported with .json extension. */, - - "strict": true /* Enable all strict type-checking options. */, - - /* Strict Type-Checking Options */ - // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, - // "strictNullChecks": true /* Enable strict null checks. */, - // "strictFunctionTypes": true /* Enable strict checking of function types. */, - // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, - // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, - // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, - - /* Additional Checks */ - "noUnusedLocals": true /* Report errors on unused locals. */, - "noUnusedParameters": true /* Report errors on unused parameters. */, - "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, - "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, - - /* Debugging Options */ - "traceResolution": false /* Report module resolution log messages. */, - "listEmittedFiles": false /* Print names of generated files part of the compilation. */, - "listFiles": false /* Print names of files part of the compilation. */, - "pretty": true /* Stylize errors and messages using color and context. */, - - // Due to broken types in indirect dependencies - "skipLibCheck": true, - - /* Experimental Options */ - // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, - // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, - - "lib": ["es2017"], - "types": ["node", "mocha"], - "typeRoots": ["node_modules/@types"] - }, - "include": ["chat/**/*.ts"], - "exclude": ["node_modules/**"], - "compileOnSave": false, - "ts-node": { - "files": true - } -} From 0f694cf8e185dcbbdc5f4ef53a96036e1d22e150 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 30 Mar 2021 12:00:09 +1100 Subject: [PATCH 11/16] Correct package name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fe8faf01c..4970d432d1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "waku-js-chat", + "name": "js-waku", "version": "1.0.0", "description": "A chat application running on node and waku", "main": "build/main/index.js", From cca1d685dc04c1f098c2cb2b0fb230f2d1de361f Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 31 Mar 2021 10:43:29 +1100 Subject: [PATCH 12/16] Implement chat message protobuf to support nick and time handles --- buf.gen.yaml | 2 +- proto/chat/v2/chat_message.proto | 9 ++++++++ src/chat/chat_message.spec.ts | 26 ++++++++++++++++++++++++ src/chat/chat_message.ts | 35 ++++++++++++++++++++++++++++++++ src/chat/index.ts | 20 +++++++++++++++--- src/lib/waku_message.spec.ts | 2 +- src/lib/waku_message.ts | 19 ++++++++++++----- src/lib/waku_relay.spec.ts | 2 +- 8 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 proto/chat/v2/chat_message.proto create mode 100644 src/chat/chat_message.spec.ts create mode 100644 src/chat/chat_message.ts diff --git a/buf.gen.yaml b/buf.gen.yaml index d04c651fb3..40edf9635d 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -3,4 +3,4 @@ version: v1beta1 plugins: - name: ts_proto out: ./src/proto - opt: grpc_js + opt: grpc_js,esModuleInterop=true diff --git a/proto/chat/v2/chat_message.proto b/proto/chat/v2/chat_message.proto new file mode 100644 index 0000000000..9d33bec91f --- /dev/null +++ b/proto/chat/v2/chat_message.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package chat.v2; + +message ChatMessageProto { + uint64 timestamp = 1; + string nick = 2; + bytes payload = 3; +} diff --git a/src/chat/chat_message.spec.ts b/src/chat/chat_message.spec.ts new file mode 100644 index 0000000000..a33d08c22c --- /dev/null +++ b/src/chat/chat_message.spec.ts @@ -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); + } + ) + ); + }); +}); diff --git a/src/chat/chat_message.ts b/src/chat/chat_message.ts new file mode 100644 index 0000000000..855245aca1 --- /dev/null +++ b/src/chat/chat_message.ts @@ -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(); + } +} diff --git a/src/chat/index.ts b/src/chat/index.ts index 6e26a75f56..f9f83058d1 100644 --- a/src/chat/index.ts +++ b/src/chat/index.ts @@ -5,6 +5,8 @@ import { Message } 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(); @@ -12,8 +14,18 @@ import { delay } from '../test_utils/delay'; // TODO: Bubble event to waku, infer topic, decode msg waku.libp2p.pubsub.on(TOPIC, (event) => { - const msg = Message.fromBinary(event.data); - console.log(msg.utf8Payload()); + const wakuMsg = Message.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'); @@ -47,7 +59,9 @@ import { delay } from '../test_utils/delay'; rl.prompt(); rl.on('line', async (line) => { rl.prompt(); - const msg = Message.fromUtf8String('(js-chat) ' + line); + const chatMessage = new ChatMessage(new Date(), 'js-chat', line); + + const msg = Message.fromBytes(chatMessage.encode()); await waku.relay.publish(msg); }); })(); diff --git a/src/lib/waku_message.spec.ts b/src/lib/waku_message.spec.ts index 5865f7fe18..53bbeed709 100644 --- a/src/lib/waku_message.spec.ts +++ b/src/lib/waku_message.spec.ts @@ -8,7 +8,7 @@ describe('Waku Message', function () { fc.property(fc.string(), (s) => { const msg = Message.fromUtf8String(s); const binary = msg.toBinary(); - const actual = Message.fromBinary(binary); + const actual = Message.decode(binary); return actual.isEqualTo(msg); }) diff --git a/src/lib/waku_message.ts b/src/lib/waku_message.ts index 1b46978fb0..07bf32221a 100644 --- a/src/lib/waku_message.ts +++ b/src/lib/waku_message.ts @@ -15,16 +15,25 @@ export class Message { ) {} /** - * Create Message from utf-8 string - * @param message + * Create Message with a utf-8 string as payload + * @param payload * @returns {Message} */ - static fromUtf8String(message: string): Message { - const payload = Buffer.from(message, 'utf-8'); + static fromUtf8String(payload: string): Message { + const buf = Buffer.from(payload, 'utf-8'); + return new Message(buf, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); + } + + /** + * Create Message with a byte array as payload + * @param payload + * @returns {Message} + */ + static fromBytes(payload: Uint8Array): Message { return new Message(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); } - static fromBinary(bytes: Uint8Array): Message { + static decode(bytes: Uint8Array): Message { const wakuMsg = WakuMessage.decode(Reader.create(bytes)); return new Message(wakuMsg.payload, wakuMsg.contentTopic, wakuMsg.version); } diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index 24b9d465fa..cfbf011c72 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -392,6 +392,6 @@ function waitForNextData(pubsub: Pubsub): Promise { return new Promise((resolve) => { pubsub.once(TOPIC, resolve); }).then((msg: any) => { - return Message.fromBinary(msg.data); + return Message.decode(msg.data); }); } From 58838ec898efa92e5f858fd35acd5f298deb28c6 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 1 Apr 2021 10:37:16 +1100 Subject: [PATCH 13/16] Custom nick --- src/chat/index.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/chat/index.ts b/src/chat/index.ts index f9f83058d1..19eb4f5d7b 100644 --- a/src/chat/index.ts +++ b/src/chat/index.ts @@ -1,4 +1,5 @@ import readline from 'readline'; +import util from 'util'; import Waku from '../lib/waku'; import { Message } from '../lib/waku_message'; @@ -10,6 +11,20 @@ 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 @@ -50,20 +65,15 @@ import { ChatMessage } from './chat_message'; waku.libp2p.pubsub.once('gossipsub:heartbeat', resolve) ); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - console.log('Ready to chat!'); rl.prompt(); - rl.on('line', async (line) => { + for await (const line of rl) { rl.prompt(); - const chatMessage = new ChatMessage(new Date(), 'js-chat', line); + const chatMessage = new ChatMessage(new Date(), nick, line); const msg = Message.fromBytes(chatMessage.encode()); await waku.relay.publish(msg); - }); + } })(); interface Options { From f15f4ff7f2ee4555312a908301c2394cca73a000 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 1 Apr 2021 11:04:48 +1100 Subject: [PATCH 14/16] Reference GitHub issues --- src/chat/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/chat/index.ts b/src/chat/index.ts index 19eb4f5d7b..b48ea0f532 100644 --- a/src/chat/index.ts +++ b/src/chat/index.ts @@ -28,6 +28,7 @@ import { ChatMessage } from './chat_message'; 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 = Message.decode(event.data); if (wakuMsg.payload) { @@ -55,9 +56,11 @@ import { ChatMessage } from './chat_message'; ); // TODO: identify if it is possible to listen to an event to confirm dial - // finished instead of an arbitrary delay. + // finished instead of an arbitrary delay. Tracked with + // https://github.com/status-im/js-waku/issues/18 await delay(2000); - // TODO: Automatically subscribe + // TODO: Automatically subscribe, tracked with + // https://github.com/status-im/js-waku/issues/17 await waku.relay.subscribe(); console.log('Subscribed to waku relay'); From 861bc2d0b467d312cabadcab2652690cb8441688 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 1 Apr 2021 11:09:51 +1100 Subject: [PATCH 15/16] Update readme to include chat app --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 902df87e56..d215c9f6c3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,24 @@ 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 From 5a967ecbcc6bab9d7f6289e2ad414d09162c2ca0 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 1 Apr 2021 11:18:35 +1100 Subject: [PATCH 16/16] Avoid possible type name clash between js-waku and consuming apps `Message` is a very generic name and JS does not offer strong namespace boundaries. Using `WakuMessage` avoid name clashing with classes of the consumer app. --- proto/waku/v2/waku.proto | 2 +- src/chat/index.ts | 6 +++--- src/lib/waku_message.spec.ts | 8 ++++---- src/lib/waku_message.ts | 30 +++++++++++++++++------------- src/lib/waku_relay.spec.ts | 22 +++++++++++----------- src/lib/waku_relay.ts | 4 ++-- src/test_utils/nim_waku.ts | 4 ++-- 7 files changed, 40 insertions(+), 36 deletions(-) diff --git a/proto/waku/v2/waku.proto b/proto/waku/v2/waku.proto index 1ce9976156..6082c8efea 100644 --- a/proto/waku/v2/waku.proto +++ b/proto/waku/v2/waku.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package waku.v2; -message WakuMessage { +message WakuMessageProto { optional bytes payload = 1; optional uint32 content_topic = 2; optional uint32 version = 3; diff --git a/src/chat/index.ts b/src/chat/index.ts index b48ea0f532..9be295e75b 100644 --- a/src/chat/index.ts +++ b/src/chat/index.ts @@ -2,7 +2,7 @@ import readline from 'readline'; import util from 'util'; import Waku from '../lib/waku'; -import { Message } from '../lib/waku_message'; +import { WakuMessage } from '../lib/waku_message'; import { TOPIC } from '../lib/waku_relay'; import { delay } from '../test_utils/delay'; @@ -30,7 +30,7 @@ import { ChatMessage } from './chat_message'; // 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 = Message.decode(event.data); + const wakuMsg = WakuMessage.decode(event.data); if (wakuMsg.payload) { const chatMsg = ChatMessage.decode(wakuMsg.payload); const timestamp = chatMsg.timestamp.toLocaleString([], { @@ -74,7 +74,7 @@ import { ChatMessage } from './chat_message'; rl.prompt(); const chatMessage = new ChatMessage(new Date(), nick, line); - const msg = Message.fromBytes(chatMessage.encode()); + const msg = WakuMessage.fromBytes(chatMessage.encode()); await waku.relay.publish(msg); } })(); diff --git a/src/lib/waku_message.spec.ts b/src/lib/waku_message.spec.ts index 53bbeed709..8b1f386697 100644 --- a/src/lib/waku_message.spec.ts +++ b/src/lib/waku_message.spec.ts @@ -1,14 +1,14 @@ import fc from 'fast-check'; -import { Message } from './waku_message'; +import { WakuMessage } from './waku_message'; describe('Waku Message', function () { it('Waku message round trip binary serialization', function () { fc.assert( fc.property(fc.string(), (s) => { - const msg = Message.fromUtf8String(s); + const msg = WakuMessage.fromUtf8String(s); const binary = msg.toBinary(); - const actual = Message.decode(binary); + const actual = WakuMessage.decode(binary); return actual.isEqualTo(msg); }) @@ -18,7 +18,7 @@ describe('Waku Message', function () { it('Payload to utf-8', function () { fc.assert( fc.property(fc.string(), (s) => { - const msg = Message.fromUtf8String(s); + const msg = WakuMessage.fromUtf8String(s); const utf8 = msg.utf8Payload(); return utf8 === s; diff --git a/src/lib/waku_message.ts b/src/lib/waku_message.ts index 07bf32221a..0a37e9296c 100644 --- a/src/lib/waku_message.ts +++ b/src/lib/waku_message.ts @@ -2,12 +2,12 @@ import { Reader } from 'protobufjs/minimal'; // 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_VERSION = 0; -export class Message { +export class WakuMessage { private constructor( public payload?: Uint8Array, public contentTopic?: number, @@ -17,29 +17,33 @@ export class Message { /** * Create Message with a utf-8 string as payload * @param payload - * @returns {Message} + * @returns {WakuMessage} */ - static fromUtf8String(payload: string): Message { + static fromUtf8String(payload: string): WakuMessage { const buf = Buffer.from(payload, 'utf-8'); - return new Message(buf, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); + return new WakuMessage(buf, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); } /** * Create Message with a byte array as payload * @param payload - * @returns {Message} + * @returns {WakuMessage} */ - static fromBytes(payload: Uint8Array): Message { - return new Message(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); + static fromBytes(payload: Uint8Array): WakuMessage { + return new WakuMessage(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); } - static decode(bytes: Uint8Array): Message { - const wakuMsg = WakuMessage.decode(Reader.create(bytes)); - return new Message(wakuMsg.payload, wakuMsg.contentTopic, wakuMsg.version); + static decode(bytes: Uint8Array): WakuMessage { + const wakuMsg = WakuMessageProto.decode(Reader.create(bytes)); + return new WakuMessage( + wakuMsg.payload, + wakuMsg.contentTopic, + wakuMsg.version + ); } toBinary(): Uint8Array { - return WakuMessage.encode({ + return WakuMessageProto.encode({ payload: this.payload, version: this.version, contentTopic: this.contentTopic, @@ -61,7 +65,7 @@ export class Message { // Purely for tests purposes. // We do consider protobuf field when checking equality // As the content is held by the other fields. - isEqualTo(other: Message) { + isEqualTo(other: WakuMessage) { const payloadsAreEqual = this.payload && other.payload ? Buffer.compare(this.payload, other.payload) === 0 diff --git a/src/lib/waku_relay.spec.ts b/src/lib/waku_relay.spec.ts index cfbf011c72..bedcacd766 100644 --- a/src/lib/waku_relay.spec.ts +++ b/src/lib/waku_relay.spec.ts @@ -7,7 +7,7 @@ import { makeLogFileName } from '../test_utils/log_file'; import { NimWaku } from '../test_utils/nim_waku'; import Waku from './waku'; -import { Message } from './waku_message'; +import { WakuMessage } from './waku_message'; import { CODEC, TOPIC } from './waku_relay'; describe('Waku Relay', () => { @@ -77,7 +77,7 @@ describe('Waku Relay', () => { it.skip('Publish', async function () { 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'; const receivedPromise = waitForNextData(waku2.libp2p.pubsub); @@ -141,7 +141,7 @@ describe('Waku Relay', () => { it('Js publishes to nim', async function () { this.timeout(5000); - const message = Message.fromUtf8String('This is a message'); + const message = WakuMessage.fromUtf8String('This is a message'); await waku.relay.publish(message); @@ -158,7 +158,7 @@ describe('Waku Relay', () => { it('Nim publishes to js', async function () { this.timeout(5000); - const message = Message.fromUtf8String('Here is another message.'); + const message = WakuMessage.fromUtf8String('Here is another message.'); const receivedPromise = waitForNextData(waku.libp2p.pubsub); @@ -212,7 +212,7 @@ describe('Waku Relay', () => { }); 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); @@ -228,7 +228,7 @@ describe('Waku Relay', () => { }); 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); @@ -281,7 +281,7 @@ describe('Waku Relay', () => { }); 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); @@ -297,7 +297,7 @@ describe('Waku Relay', () => { }); 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); @@ -374,7 +374,7 @@ describe('Waku Relay', () => { ).to.be.false; const msgStr = 'Hello there!'; - const message = Message.fromUtf8String(msgStr); + const message = WakuMessage.fromUtf8String(msgStr); const waku2ReceivedPromise = waitForNextData(waku2.libp2p.pubsub); @@ -388,10 +388,10 @@ describe('Waku Relay', () => { }); }); -function waitForNextData(pubsub: Pubsub): Promise { +function waitForNextData(pubsub: Pubsub): Promise { return new Promise((resolve) => { pubsub.once(TOPIC, resolve); }).then((msg: any) => { - return Message.decode(msg.data); + return WakuMessage.decode(msg.data); }); } diff --git a/src/lib/waku_relay.ts b/src/lib/waku_relay.ts index 8cedd5e189..1069e1f159 100644 --- a/src/lib/waku_relay.ts +++ b/src/lib/waku_relay.ts @@ -4,7 +4,7 @@ import Pubsub from 'libp2p-interfaces/src/pubsub'; import { SignaturePolicy } from 'libp2p-interfaces/src/pubsub/signature-policy'; 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'; @@ -99,7 +99,7 @@ export class WakuRelay { await this.pubsub.subscribe(TOPIC); } - async publish(message: Message) { + async publish(message: WakuMessage) { const msg = message.toBinary(); await this.pubsub.publish(TOPIC, msg); } diff --git a/src/test_utils/nim_waku.ts b/src/test_utils/nim_waku.ts index e80f1aed9a..11bd4030fc 100644 --- a/src/test_utils/nim_waku.ts +++ b/src/test_utils/nim_waku.ts @@ -7,7 +7,7 @@ import Multiaddr from 'multiaddr'; import multiaddr from 'multiaddr'; import PeerId from 'peer-id'; -import { Message } from '../lib/waku_message'; +import { WakuMessage } from '../lib/waku_message'; import { TOPIC } from '../lib/waku_relay'; import { existsAsync, mkdirAsync, openAsync } from './async_fs'; @@ -132,7 +132,7 @@ export class NimWaku { return res.result; } - async sendMessage(message: Message) { + async sendMessage(message: WakuMessage) { this.checkProcess(); if (!message.payload) {