Skip to content

Commit 0136720

Browse files
authored
Merge pull request #201 from status-im/174-custom-pubsub-topic
2 parents e2e1416 + 5ce0717 commit 0136720

File tree

13 files changed

+354
-104
lines changed

13 files changed

+354
-104
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
- **Breaking**: Options passed to `Waku.create` used to be passed to `Libp2p.create`;
12+
Now, only the `libp2p` property is passed to `Libp2p.create`, allowing for a cleaner interface.
13+
1014
### Added
1115
- Enable access to `WakuMessage.timestamp`.
1216
- Examples (web chat): Use `WakuMessage.timestamp` as unique key for list items.
1317
- Doc: Link to new [topic guidelines](https://rfc.vac.dev/spec/23/) in README.
1418
- Doc: Link to [Waku v2 Toy Chat specs](https://rfc.vac.dev/spec/22/) in README.
1519
- Examples (web chat): Persist nick.
20+
- Support for custom PubSub Topics to `Waku`, `WakuRelay`, `WakuStore` and `WakuLightPush`;
21+
Passing a PubSub Topic is optional and still defaults to `/waku/2/default-waku/proto`;
22+
JS-Waku currently supports one, and only, PubSub topic per instance.
1623

1724
## [0.5.0] - 2021-05-21
1825

examples/cli-chat/src/chat.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ export default async function startChat(): Promise<void> {
2727
}
2828

2929
const waku = await Waku.create({
30-
listenAddresses: [opts.listenAddr],
31-
modules: { transport: [TCP] },
30+
libp2p: {
31+
addresses: { listen: [opts.listenAddr] },
32+
modules: { transport: [TCP] },
33+
},
3234
});
3335
console.log('PeerId: ', waku.libp2p.peerId.toB58String());
3436
console.log('Listening on ');

examples/web-chat/src/App.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,12 @@ export default function App() {
181181
async function initWaku(setter: (waku: Waku) => void) {
182182
try {
183183
const waku = await Waku.create({
184-
config: {
185-
pubsub: {
186-
enabled: true,
187-
emitSelf: true,
184+
libp2p: {
185+
config: {
186+
pubsub: {
187+
enabled: true,
188+
emitSelf: true,
189+
},
188190
},
189191
},
190192
});

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
"cov:send": "run-s cov:lcov && codecov",
4545
"cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
4646
"doc": "run-s doc:html && open-cli build/docs/index.html",
47-
"doc:html": "typedoc --exclude **/*.spec.ts --out build/docs src/",
48-
"doc:json": "typedoc src/ --exclude **/*.spec.ts --json build/docs/typedoc.json",
47+
"doc:html": "typedoc --excludeInternal --listInvalidSymbolLinks --exclude **/*.spec.ts --out build/docs src/",
48+
"doc:json": "typedoc src/ --excludeInternal --listInvalidSymbolLinks --exclude **/*.spec.ts --json build/docs/typedoc.json",
4949
"doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs",
5050
"version": "standard-version",
5151
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build && for d in examples/*; do (cd $d; npm i); done",

src/lib/waku.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('Waku Dial', function () {
1717
const [waku1, waku2] = await Promise.all([
1818
Waku.create({
1919
staticNoiseKey: NOISE_KEY_1,
20-
listenAddresses: ['/ip4/0.0.0.0/tcp/0/wss'],
20+
libp2p: { addresses: { listen: ['/ip4/0.0.0.0/tcp/0/wss'] } },
2121
}),
2222
Waku.create({ staticNoiseKey: NOISE_KEY_2 }),
2323
]);
@@ -39,8 +39,10 @@ describe('Waku Dial', function () {
3939
this.timeout(10_000);
4040
const waku = await Waku.create({
4141
staticNoiseKey: NOISE_KEY_1,
42-
listenAddresses: ['/ip4/0.0.0.0/tcp/0'],
43-
modules: { transport: [TCP] },
42+
libp2p: {
43+
addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] },
44+
modules: { transport: [TCP] },
45+
},
4446
});
4547

4648
const multiAddrWithId = waku.getLocalMultiaddrWithID();

src/lib/waku.ts

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Libp2p, { Libp2pConfig, Libp2pModules, Libp2pOptions } from 'libp2p';
1+
import Libp2p, { Libp2pModules, Libp2pOptions } from 'libp2p';
22
import Mplex from 'libp2p-mplex';
33
import { bytes } from 'libp2p-noise/dist/src/@types/basic';
44
import { Noise } from 'libp2p-noise/dist/src/noise';
@@ -11,16 +11,40 @@ import { WakuLightPush } from './waku_light_push';
1111
import { RelayCodec, WakuRelay } from './waku_relay';
1212
import { StoreCodec, WakuStore } from './waku_store';
1313

14-
const transportKey = Websockets.prototype[Symbol.toStringTag];
14+
const websocketsTransportKey = Websockets.prototype[Symbol.toStringTag];
1515

16-
export type CreateOptions =
17-
| {
18-
listenAddresses: string[] | undefined;
19-
staticNoiseKey: bytes | undefined;
20-
modules: Partial<Libp2pModules>;
21-
config: Partial<Libp2pConfig>;
22-
}
23-
| (Libp2pOptions & import('libp2p').CreateOptions);
16+
export interface CreateOptions {
17+
/**
18+
* The PubSub Topic to use. Defaults to {@link DefaultPubsubTopic}.
19+
*
20+
* One and only one pubsub topic is used by Waku. This is used by:
21+
* - WakuRelay to receive, route and send messages,
22+
* - WakuLightPush to send messages,
23+
* - WakuStore to retrieve messages.
24+
*
25+
* The usage of the default pubsub topic is recommended.
26+
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
27+
*
28+
* @default {@link DefaultPubsubTopic}
29+
*/
30+
pubsubTopic?: string;
31+
/**
32+
* You can pass options to the `Libp2p` instance used by {@link Waku} using the {@link CreateOptions.libp2p} property.
33+
* This property is the same type than the one passed to [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create)
34+
* apart that we made the `modules` property optional and partial,
35+
* allowing its omission and letting Waku set good defaults.
36+
* Notes that some values are overridden by {@link Waku} to ensure it implements the Waku protocol.
37+
*/
38+
libp2p?: Omit<Libp2pOptions & import('libp2p').CreateOptions, 'modules'> & {
39+
modules?: Partial<Libp2pModules>;
40+
};
41+
/**
42+
* Byte array used as key for the noise protocol used for connection encryption
43+
* by [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create)
44+
* This is only used for test purposes to not run out of entropy during CI runs.
45+
*/
46+
staticNoiseKey?: bytes;
47+
}
2448

2549
export class Waku {
2650
public libp2p: Libp2p;
@@ -44,53 +68,56 @@ export class Waku {
4468
*
4569
* @param options Takes the same options than `Libp2p`.
4670
*/
47-
static async create(options: Partial<CreateOptions>): Promise<Waku> {
48-
const opts = Object.assign(
49-
{
50-
listenAddresses: [],
51-
staticNoiseKey: undefined,
52-
},
53-
options
54-
);
71+
static async create(options?: CreateOptions): Promise<Waku> {
72+
// Get an object in case options or libp2p are undefined
73+
const libp2pOpts = Object.assign({}, options?.libp2p);
5574

56-
opts.config = Object.assign(
75+
// Default for Websocket filter is `all`:
76+
// Returns all TCP and DNS based addresses, both with ws or wss.
77+
libp2pOpts.config = Object.assign(
5778
{
5879
transport: {
59-
[transportKey]: {
80+
[websocketsTransportKey]: {
6081
filter: filters.all,
6182
},
6283
},
6384
},
64-
options.config
85+
options?.libp2p?.config
6586
);
6687

67-
opts.modules = Object.assign({}, options.modules);
68-
69-
let transport = [Websockets];
70-
if (opts.modules?.transport) {
71-
transport = transport.concat(opts.modules?.transport);
88+
// Pass pubsub topic to relay
89+
if (options?.pubsubTopic) {
90+
libp2pOpts.config.pubsub = Object.assign(
91+
{ pubsubTopic: options.pubsubTopic },
92+
libp2pOpts.config.pubsub
93+
);
7294
}
7395

74-
// FIXME: By controlling the creation of libp2p we have to think about what
75-
// needs to be exposed and what does not. Ideally, we should be able to let
76-
// the user create the WakuStore, WakuRelay instances and pass them when
77-
// creating the libp2p instance.
78-
const libp2p = await Libp2p.create({
79-
addresses: {
80-
listen: opts.listenAddresses,
81-
},
82-
modules: {
83-
transport,
84-
streamMuxer: [Mplex],
85-
connEncryption: [new Noise(opts.staticNoiseKey)],
86-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
87-
// @ts-ignore: Type needs update
88-
pubsub: WakuRelay,
96+
libp2pOpts.modules = Object.assign({}, options?.libp2p?.modules);
97+
98+
// Default transport for libp2p is Websockets
99+
libp2pOpts.modules = Object.assign(
100+
{
101+
transport: [Websockets],
89102
},
90-
config: opts.config,
103+
options?.libp2p?.modules
104+
);
105+
106+
// streamMuxer, connection encryption and pubsub are overridden
107+
// as those are the only ones currently supported by Waku nodes.
108+
libp2pOpts.modules = Object.assign(libp2pOpts.modules, {
109+
streamMuxer: [Mplex],
110+
connEncryption: [new Noise(options?.staticNoiseKey)],
111+
pubsub: WakuRelay,
91112
});
92113

93-
const wakuStore = new WakuStore(libp2p);
114+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
115+
// @ts-ignore: modules property is correctly set thanks to voodoo
116+
const libp2p = await Libp2p.create(libp2pOpts);
117+
118+
const wakuStore = new WakuStore(libp2p, {
119+
pubsubTopic: options?.pubsubTopic,
120+
});
94121
const wakuLightPush = new WakuLightPush(libp2p);
95122

96123
await libp2p.start();

src/lib/waku_light_push/index.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,47 @@ describe('Waku Light Push', () => {
2323

2424
waku = await Waku.create({
2525
staticNoiseKey: NOISE_KEY_1,
26-
modules: { transport: [TCP] },
26+
libp2p: { modules: { transport: [TCP] } },
27+
});
28+
await waku.dial(await nimWaku.getMultiaddrWithId());
29+
30+
// Wait for identify protocol to finish
31+
await new Promise((resolve) => {
32+
waku.libp2p.peerStore.once('change:protocols', resolve);
33+
});
34+
35+
const nimPeerId = await nimWaku.getPeerId();
36+
37+
const messageText = 'Light Push works!';
38+
const message = WakuMessage.fromUtf8String(messageText);
39+
40+
const pushResponse = await waku.lightPush.push(nimPeerId, message);
41+
expect(pushResponse?.isSuccess).to.be.true;
42+
43+
let msgs: WakuMessage[] = [];
44+
45+
while (msgs.length === 0) {
46+
await delay(200);
47+
msgs = await nimWaku.messages();
48+
}
49+
50+
expect(msgs[0].contentTopic).to.equal(message.contentTopic);
51+
expect(msgs[0].version).to.equal(message.version);
52+
expect(msgs[0].payloadAsUtf8).to.equal(messageText);
53+
});
54+
55+
it('Push on custom pubsub topic', async function () {
56+
this.timeout(5_000);
57+
58+
const customPubSubTopic = '/waku/2/custom-dapp/proto';
59+
60+
nimWaku = new NimWaku(makeLogFileName(this));
61+
await nimWaku.start({ lightpush: true, topics: customPubSubTopic });
62+
63+
waku = await Waku.create({
64+
pubsubTopic: customPubSubTopic,
65+
staticNoiseKey: NOISE_KEY_1,
66+
libp2p: { modules: { transport: [TCP] } },
2767
});
2868
await waku.dial(await nimWaku.getMultiaddrWithId());
2969

src/lib/waku_light_push/index.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,36 @@ import { PushRPC } from './push_rpc';
1313
export const LightPushCodec = '/vac/waku/lightpush/2.0.0-alpha1';
1414
export { PushResponse };
1515

16+
export interface CreateOptions {
17+
/**
18+
* The PubSub Topic to use. Defaults to {@link DefaultPubsubTopic}.
19+
*
20+
* The usage of the default pubsub topic is recommended.
21+
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
22+
*
23+
* @default {@link DefaultPubsubTopic}
24+
*/
25+
pubsubTopic?: string;
26+
}
27+
1628
/**
1729
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
1830
*/
1931
export class WakuLightPush {
20-
constructor(public libp2p: Libp2p) {}
32+
pubsubTopic: string;
33+
34+
constructor(public libp2p: Libp2p, options?: CreateOptions) {
35+
if (options?.pubsubTopic) {
36+
this.pubsubTopic = options.pubsubTopic;
37+
} else {
38+
this.pubsubTopic = DefaultPubsubTopic;
39+
}
40+
}
2141

2242
async push(
2343
peerId: PeerId,
2444
message: WakuMessage,
25-
pubsubTopic: string = DefaultPubsubTopic
45+
pubsubTopic: string = this.pubsubTopic
2646
): Promise<PushResponse | null> {
2747
const peer = this.libp2p.peerStore.get(peerId);
2848
if (!peer) throw 'Peer is unknown';

0 commit comments

Comments
 (0)