@libp2p/floodsub 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -0
- package/README.md +88 -0
- package/dist/src/cache.d.ts +21 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js +46 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +4 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +66 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +157 -0
- package/src/cache.ts +63 -0
- package/src/config.ts +5 -0
- package/src/index.ts +87 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# js-libp2p-floodsub <!-- omit in toc -->
|
|
2
|
+
|
|
3
|
+
[](http://protocol.ai)
|
|
4
|
+
[](http://libp2p.io/)
|
|
5
|
+
[](http://webchat.freenode.net/?channels=%23libp2p)
|
|
6
|
+
[](https://discuss.libp2p.io)
|
|
7
|
+
[](https://coveralls.io/github/libp2p/js-libp2p-floodsub?branch=master)
|
|
8
|
+
[](https://github.com/libp2p/js-libp2p-floodsub/actions/workflows/js-test-and-release.yml)
|
|
9
|
+
[](https://david-dm.org/libp2p/js-libp2p-floodsub) [](https://github.com/feross/standard)
|
|
10
|
+
[](https://waffle.io/libp2p/js-libp2p-floodsub)
|
|
11
|
+
|
|
12
|
+
> libp2p-floodsub, also known as pubsub-flood or just dumbsub, this implementation of pubsub focused on delivering an API for Publish/Subscribe, but with no CastTree Forming (it just floods the network).
|
|
13
|
+
|
|
14
|
+
## Table of Contents <!-- omit in toc -->
|
|
15
|
+
|
|
16
|
+
- [Install](#install)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [API](#api)
|
|
19
|
+
- [Create a floodsub implementation](#create-a-floodsub-implementation)
|
|
20
|
+
- [Events](#events)
|
|
21
|
+
- [Contribute](#contribute)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
> npm install @libp2p/floodsub
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```JavaScript
|
|
33
|
+
import { FloodSub } from '@libp2p/floodsub'
|
|
34
|
+
|
|
35
|
+
// registrar is provided by libp2p
|
|
36
|
+
const fsub = new FloodSub(peerId, registrar, options)
|
|
37
|
+
|
|
38
|
+
await fsub.start()
|
|
39
|
+
|
|
40
|
+
fsub.on('fruit', (data) => {
|
|
41
|
+
console.log(data)
|
|
42
|
+
})
|
|
43
|
+
fsub.subscribe('fruit')
|
|
44
|
+
|
|
45
|
+
fsub.publish('fruit', new TextEncoder().encode('banana'))
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### Create a floodsub implementation
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const options = {…}
|
|
54
|
+
const floodsub = new Floodsub(peerId, registrar, options)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Options is an optional object with the following key-value pairs:
|
|
58
|
+
|
|
59
|
+
* **`emitSelf`**: boolean identifying whether the node should emit to self on publish, in the event of the topic being subscribed (defaults to **false**).
|
|
60
|
+
|
|
61
|
+
For the remaining API, see https://github.com/libp2p/js-libp2p-pubsub
|
|
62
|
+
|
|
63
|
+
## Events
|
|
64
|
+
|
|
65
|
+
Floodsub emits two kinds of events:
|
|
66
|
+
1. `<topic>` when a message is received for a particular topic
|
|
67
|
+
```Javascript
|
|
68
|
+
fsub.on('fruit', (data) => { ... })
|
|
69
|
+
```
|
|
70
|
+
- `data`: a Uint8Array containing the data that was published to the topic
|
|
71
|
+
2. `floodsub:subscription-change` when the local peer receives an update to the subscriptions of a remote peer.
|
|
72
|
+
```Javascript
|
|
73
|
+
fsub.on('floodsub:subscription-change', (peerId, topics, changes) => { ... })
|
|
74
|
+
```
|
|
75
|
+
- `peerId`: a [PeerId](https://github.com/libp2p/js-peer-id) object
|
|
76
|
+
- `topics`: the topics that the peer is now subscribed to
|
|
77
|
+
- `changes`: an array of `{ topicID: <topic>, subscribe: <boolean> }`
|
|
78
|
+
eg `[ { topicID: 'fruit', subscribe: true }, { topicID: 'vegetables': false } ]`
|
|
79
|
+
|
|
80
|
+
## Contribute
|
|
81
|
+
|
|
82
|
+
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-pubsub/issues)!
|
|
83
|
+
|
|
84
|
+
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
[Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT) © Protocol Labs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface SimpleTimeCacheOpts {
|
|
2
|
+
validityMs: number;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
|
|
6
|
+
* for our own need, we don't use lodash throttle to improve performance.
|
|
7
|
+
* This gives 4x - 5x performance gain compared to npm TimeCache
|
|
8
|
+
*/
|
|
9
|
+
export declare class SimpleTimeCache<T> {
|
|
10
|
+
private entries;
|
|
11
|
+
private readonly validityMs;
|
|
12
|
+
private lastPruneTime;
|
|
13
|
+
constructor(options: SimpleTimeCacheOpts);
|
|
14
|
+
put(key: string, value: T): void;
|
|
15
|
+
prune(): void;
|
|
16
|
+
has(key: string): boolean;
|
|
17
|
+
get(key: string): T | undefined;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAA;CACnB;AAOD;;;;GAIG;AACH,qBAAa,eAAe,CAAC,CAAC;IAC5B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,aAAa,CAAI;gBAEZ,OAAO,EAAE,mBAAmB;IAQzC,GAAG,CAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAKjC,KAAK,IAAK,IAAI;IAiBd,GAAG,CAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAI1B,GAAG,CAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAKhC,KAAK,IAAK,IAAI;CAIf"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
|
|
3
|
+
* for our own need, we don't use lodash throttle to improve performance.
|
|
4
|
+
* This gives 4x - 5x performance gain compared to npm TimeCache
|
|
5
|
+
*/
|
|
6
|
+
export class SimpleTimeCache {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.lastPruneTime = 0;
|
|
9
|
+
this.entries = new Map();
|
|
10
|
+
this.validityMs = options.validityMs;
|
|
11
|
+
// allow negative validityMs so that this does not cache anything, spec test compliance.spec.js
|
|
12
|
+
// sends duplicate messages and expect peer to receive all. Application likely uses positive validityMs
|
|
13
|
+
}
|
|
14
|
+
put(key, value) {
|
|
15
|
+
this.entries.set(key, { value, validUntilMs: Date.now() + this.validityMs });
|
|
16
|
+
this.prune();
|
|
17
|
+
}
|
|
18
|
+
prune() {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
if (now - this.lastPruneTime < 200) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.lastPruneTime = now;
|
|
24
|
+
for (const [k, v] of this.entries.entries()) {
|
|
25
|
+
if (v.validUntilMs < now) {
|
|
26
|
+
this.entries.delete(k);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// sort by insertion order
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
has(key) {
|
|
35
|
+
return this.entries.has(key);
|
|
36
|
+
}
|
|
37
|
+
get(key) {
|
|
38
|
+
const value = this.entries.get(key);
|
|
39
|
+
return (value != null && value.validUntilMs >= Date.now()) ? value.value : undefined;
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.entries = new Map();
|
|
43
|
+
this.lastPruneTime = 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,OAAO,eAAe;IAK1B,YAAa,OAA4B;QAFjC,kBAAa,GAAG,CAAC,CAAA;QAGvB,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAA;QACxB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QAEpC,+FAA+F;QAC/F,uGAAuG;IACzG,CAAC;IAED,GAAG,CAAE,GAAW,EAAE,KAAQ;QACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,CAAA;QAC5E,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;IAED,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa,GAAG,GAAG,EAAE;YAClC,OAAM;SACP;QACD,IAAI,CAAC,aAAa,GAAG,GAAG,CAAA;QAExB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE;YAC3C,IAAI,CAAC,CAAC,YAAY,GAAG,GAAG,EAAE;gBACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;aACvB;iBAAM;gBACL,0BAA0B;gBAC1B,MAAK;aACN;SACF;IACH,CAAC;IAED,GAAG,CAAE,GAAW;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED,GAAG,CAAE,GAAW;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,OAAO,CAAC,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IACtF,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAA;QACxB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG,iCAA4B,CAAA;AAE5C,eAAO,MAAM,UAAU,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAE5C,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PubsubBaseProtocol } from '@libp2p/pubsub';
|
|
2
|
+
import { multicodec } from './config.js';
|
|
3
|
+
import { SimpleTimeCache } from './cache.js';
|
|
4
|
+
import type { PubSub, PubSubEvents, PubSubOptions, Message } from '@libp2p/interfaces/pubsub';
|
|
5
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id';
|
|
6
|
+
export { multicodec };
|
|
7
|
+
export interface FloodSubOptions extends PubSubOptions {
|
|
8
|
+
seenTTL?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* FloodSub (aka dumbsub is an implementation of pubsub focused on
|
|
12
|
+
* delivering an API for Publish/Subscribe, but with no CastTree Forming
|
|
13
|
+
* (it just floods the network).
|
|
14
|
+
*/
|
|
15
|
+
export declare class FloodSub<EventMap extends PubSubEvents = PubSubEvents> extends PubsubBaseProtocol<EventMap> implements PubSub<EventMap & PubSubEvents> {
|
|
16
|
+
seenCache: SimpleTimeCache<boolean>;
|
|
17
|
+
constructor(options: FloodSubOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Process incoming message
|
|
20
|
+
* Extends base implementation to check router cache.
|
|
21
|
+
*/
|
|
22
|
+
processMessage(from: PeerId, message: Message): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Publish message created. Forward it to the peers.
|
|
25
|
+
*/
|
|
26
|
+
publishMessage(from: PeerId, message: Message): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAIxD,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,qBAAa,QAAQ,CAAE,QAAQ,SAAS,YAAY,GAAG,YAAY,CAAE,SAAQ,kBAAkB,CAAC,QAAQ,CAAE,YAAW,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC;IAC3I,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;gBAE7B,OAAO,EAAE,eAAe;IAkBrC;;;OAGG;IACG,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAcpD;;OAEG;IACG,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;CAwBrD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { toString } from 'uint8arrays/to-string';
|
|
2
|
+
import { PubsubBaseProtocol } from '@libp2p/pubsub';
|
|
3
|
+
import { multicodec } from './config.js';
|
|
4
|
+
import { SimpleTimeCache } from './cache.js';
|
|
5
|
+
const debugName = 'libp2p:floodsub';
|
|
6
|
+
export { multicodec };
|
|
7
|
+
/**
|
|
8
|
+
* FloodSub (aka dumbsub is an implementation of pubsub focused on
|
|
9
|
+
* delivering an API for Publish/Subscribe, but with no CastTree Forming
|
|
10
|
+
* (it just floods the network).
|
|
11
|
+
*/
|
|
12
|
+
export class FloodSub extends PubsubBaseProtocol {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super({
|
|
15
|
+
...options,
|
|
16
|
+
debugName: debugName,
|
|
17
|
+
canRelayMessage: true,
|
|
18
|
+
multicodecs: [multicodec]
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Cache of seen messages
|
|
22
|
+
*
|
|
23
|
+
* @type {TimeCache}
|
|
24
|
+
*/
|
|
25
|
+
this.seenCache = new SimpleTimeCache({
|
|
26
|
+
validityMs: options.seenTTL ?? 30000
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Process incoming message
|
|
31
|
+
* Extends base implementation to check router cache.
|
|
32
|
+
*/
|
|
33
|
+
async processMessage(from, message) {
|
|
34
|
+
// Check if I've seen the message, if yes, ignore
|
|
35
|
+
const seqno = await super.getMsgId(message);
|
|
36
|
+
const msgIdStr = toString(seqno, 'base64');
|
|
37
|
+
if (this.seenCache.has(msgIdStr)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.seenCache.put(msgIdStr, true);
|
|
41
|
+
await super.processMessage(from, message);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Publish message created. Forward it to the peers.
|
|
45
|
+
*/
|
|
46
|
+
async publishMessage(from, message) {
|
|
47
|
+
const peers = this.getSubscribers(message.topic);
|
|
48
|
+
if (peers == null || peers.length === 0) {
|
|
49
|
+
this.log('no peers are subscribed to topic %s', message.topic);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
peers.forEach(id => {
|
|
53
|
+
if (this.peerId.equals(id)) {
|
|
54
|
+
this.log('not sending message on topic %s to myself', message.topic);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (id.equals(from)) {
|
|
58
|
+
this.log('not sending message on topic %s to sender %p', message.topic, id);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.log('publish msgs on topics %s %p', message.topic, id);
|
|
62
|
+
this.send(id, { messages: [message] });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5C,MAAM,SAAS,GAAG,iBAAiB,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,CAAA;AAMrB;;;;GAIG;AACH,MAAM,OAAO,QAAwD,SAAQ,kBAA4B;IAGvG,YAAa,OAAwB;QACnC,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,SAAS,EAAE,SAAS;YACpB,eAAe,EAAE,IAAI;YACrB,WAAW,EAAE,CAAC,UAAU,CAAC;SAC1B,CAAC,CAAA;QAEF;;;;WAIG;QACH,IAAI,CAAC,SAAS,GAAG,IAAI,eAAe,CAAU;YAC5C,UAAU,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;SACrC,CAAC,CAAA;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAE,IAAY,EAAE,OAAgB;QAClD,iDAAiD;QACjD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QAE1C,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;YAChC,OAAM;SACP;QAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAElC,MAAM,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAE,IAAY,EAAE,OAAgB;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAEhD,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;YACvC,IAAI,CAAC,GAAG,CAAC,qCAAqC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;YAC9D,OAAM;SACP;QAED,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;YACjB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;gBAC1B,IAAI,CAAC,GAAG,CAAC,2CAA2C,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;gBACpE,OAAM;aACP;YAED,IAAI,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;gBACnB,IAAI,CAAC,GAAG,CAAC,8CAA8C,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;gBAC3E,OAAM;aACP;YAED,IAAI,CAAC,GAAG,CAAC,8BAA8B,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YAE3D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACxC,CAAC,CAAC,CAAA;IACJ,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@libp2p/floodsub",
|
|
3
|
+
"version": "0.29.0",
|
|
4
|
+
"description": "libp2p-floodsub, also known as pubsub-flood or just dumbsub, this implementation of pubsub focused on delivering an API for Publish/Subscribe, but with no CastTree Forming (it just floods the network).",
|
|
5
|
+
"license": "Apache-2.0 OR MIT",
|
|
6
|
+
"homepage": "https://github.com/libp2p/js-libp2p-floodsub#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/libp2p/js-libp2p-floodsub.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/libp2p/js-libp2p-floodsub/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"IPFS",
|
|
16
|
+
"flood",
|
|
17
|
+
"flooding",
|
|
18
|
+
"gossip",
|
|
19
|
+
"libp2p",
|
|
20
|
+
"pubsub"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.0.0",
|
|
24
|
+
"npm": ">=7.0.0"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"types": "./dist/src/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"dist/src",
|
|
31
|
+
"!dist/test",
|
|
32
|
+
"!**/*.tsbuildinfo"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": "./dist/src/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"eslintConfig": {
|
|
40
|
+
"extends": "ipfs",
|
|
41
|
+
"parserOptions": {
|
|
42
|
+
"sourceType": "module"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"release": {
|
|
46
|
+
"branches": [
|
|
47
|
+
"master"
|
|
48
|
+
],
|
|
49
|
+
"plugins": [
|
|
50
|
+
[
|
|
51
|
+
"@semantic-release/commit-analyzer",
|
|
52
|
+
{
|
|
53
|
+
"preset": "conventionalcommits",
|
|
54
|
+
"releaseRules": [
|
|
55
|
+
{
|
|
56
|
+
"breaking": true,
|
|
57
|
+
"release": "major"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"revert": true,
|
|
61
|
+
"release": "patch"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "feat",
|
|
65
|
+
"release": "minor"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"type": "fix",
|
|
69
|
+
"release": "patch"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "chore",
|
|
73
|
+
"release": "patch"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"type": "docs",
|
|
77
|
+
"release": "patch"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"type": "test",
|
|
81
|
+
"release": "patch"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"scope": "no-release",
|
|
85
|
+
"release": false
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"@semantic-release/release-notes-generator",
|
|
92
|
+
{
|
|
93
|
+
"preset": "conventionalcommits",
|
|
94
|
+
"presetConfig": {
|
|
95
|
+
"types": [
|
|
96
|
+
{
|
|
97
|
+
"type": "feat",
|
|
98
|
+
"section": "Features"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "fix",
|
|
102
|
+
"section": "Bug Fixes"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "chore",
|
|
106
|
+
"section": "Trivial Changes"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"type": "docs",
|
|
110
|
+
"section": "Trivial Changes"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"type": "test",
|
|
114
|
+
"section": "Tests"
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"@semantic-release/changelog",
|
|
121
|
+
"@semantic-release/npm",
|
|
122
|
+
"@semantic-release/github",
|
|
123
|
+
"@semantic-release/git"
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
"scripts": {
|
|
127
|
+
"lint": "aegir lint",
|
|
128
|
+
"dep-check": "aegir dep-check",
|
|
129
|
+
"build": "tsc",
|
|
130
|
+
"pretest": "npm run build",
|
|
131
|
+
"test": "aegir test -f dist/test",
|
|
132
|
+
"test:chrome": "npm run test -- -t browser --cov",
|
|
133
|
+
"test:chrome-webworker": "npm run test -- -t webworker",
|
|
134
|
+
"test:firefox": "npm run test -- -t browser -- --browser firefox",
|
|
135
|
+
"test:firefox-webworker": "npm run test -- -t webworker -- --browser firefox",
|
|
136
|
+
"test:node": "npm run test -- -t node --cov",
|
|
137
|
+
"test:electron-main": "npm run test -- -t electron-main",
|
|
138
|
+
"release": "semantic-release"
|
|
139
|
+
},
|
|
140
|
+
"dependencies": {
|
|
141
|
+
"@libp2p/interfaces": "^1.3.6",
|
|
142
|
+
"@libp2p/logger": "^1.0.3",
|
|
143
|
+
"@libp2p/pubsub": "^1.2.4",
|
|
144
|
+
"uint8arrays": "^3.0.0"
|
|
145
|
+
},
|
|
146
|
+
"devDependencies": {
|
|
147
|
+
"@libp2p/interface-compliance-tests": "^1.0.8",
|
|
148
|
+
"@libp2p/peer-id": "^1.1.3",
|
|
149
|
+
"@libp2p/peer-id-factory": "^1.0.5",
|
|
150
|
+
"@multiformats/multiaddr": "^10.1.5",
|
|
151
|
+
"aegir": "^36.1.1",
|
|
152
|
+
"multiformats": "^9.4.5",
|
|
153
|
+
"p-wait-for": "^4.1.0",
|
|
154
|
+
"sinon": "^13.0.1",
|
|
155
|
+
"wherearewe": "^1.0.0"
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
interface SimpleTimeCacheOpts {
|
|
2
|
+
validityMs: number
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface CacheValue<T> {
|
|
6
|
+
value: T
|
|
7
|
+
validUntilMs: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
|
|
12
|
+
* for our own need, we don't use lodash throttle to improve performance.
|
|
13
|
+
* This gives 4x - 5x performance gain compared to npm TimeCache
|
|
14
|
+
*/
|
|
15
|
+
export class SimpleTimeCache<T> {
|
|
16
|
+
private entries: Map<string, CacheValue<T>>
|
|
17
|
+
private readonly validityMs: number
|
|
18
|
+
private lastPruneTime = 0
|
|
19
|
+
|
|
20
|
+
constructor (options: SimpleTimeCacheOpts) {
|
|
21
|
+
this.entries = new Map()
|
|
22
|
+
this.validityMs = options.validityMs
|
|
23
|
+
|
|
24
|
+
// allow negative validityMs so that this does not cache anything, spec test compliance.spec.js
|
|
25
|
+
// sends duplicate messages and expect peer to receive all. Application likely uses positive validityMs
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
put (key: string, value: T): void {
|
|
29
|
+
this.entries.set(key, { value, validUntilMs: Date.now() + this.validityMs })
|
|
30
|
+
this.prune()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
prune (): void {
|
|
34
|
+
const now = Date.now()
|
|
35
|
+
if (now - this.lastPruneTime < 200) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
this.lastPruneTime = now
|
|
39
|
+
|
|
40
|
+
for (const [k, v] of this.entries.entries()) {
|
|
41
|
+
if (v.validUntilMs < now) {
|
|
42
|
+
this.entries.delete(k)
|
|
43
|
+
} else {
|
|
44
|
+
// sort by insertion order
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
has (key: string): boolean {
|
|
51
|
+
return this.entries.has(key)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get (key: string): T | undefined {
|
|
55
|
+
const value = this.entries.get(key)
|
|
56
|
+
return (value != null && value.validUntilMs >= Date.now()) ? value.value : undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
clear (): void {
|
|
60
|
+
this.entries = new Map()
|
|
61
|
+
this.lastPruneTime = 0
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/config.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { toString } from 'uint8arrays/to-string'
|
|
2
|
+
import { PubsubBaseProtocol } from '@libp2p/pubsub'
|
|
3
|
+
import { multicodec } from './config.js'
|
|
4
|
+
import { SimpleTimeCache } from './cache.js'
|
|
5
|
+
import type { PubSub, PubSubEvents, PubSubOptions, Message } from '@libp2p/interfaces/pubsub'
|
|
6
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
7
|
+
|
|
8
|
+
const debugName = 'libp2p:floodsub'
|
|
9
|
+
|
|
10
|
+
export { multicodec }
|
|
11
|
+
|
|
12
|
+
export interface FloodSubOptions extends PubSubOptions {
|
|
13
|
+
seenTTL?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FloodSub (aka dumbsub is an implementation of pubsub focused on
|
|
18
|
+
* delivering an API for Publish/Subscribe, but with no CastTree Forming
|
|
19
|
+
* (it just floods the network).
|
|
20
|
+
*/
|
|
21
|
+
export class FloodSub <EventMap extends PubSubEvents = PubSubEvents> extends PubsubBaseProtocol<EventMap> implements PubSub<EventMap & PubSubEvents> {
|
|
22
|
+
public seenCache: SimpleTimeCache<boolean>
|
|
23
|
+
|
|
24
|
+
constructor (options: FloodSubOptions) {
|
|
25
|
+
super({
|
|
26
|
+
...options,
|
|
27
|
+
debugName: debugName,
|
|
28
|
+
canRelayMessage: true,
|
|
29
|
+
multicodecs: [multicodec]
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Cache of seen messages
|
|
34
|
+
*
|
|
35
|
+
* @type {TimeCache}
|
|
36
|
+
*/
|
|
37
|
+
this.seenCache = new SimpleTimeCache<boolean>({
|
|
38
|
+
validityMs: options.seenTTL ?? 30000
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Process incoming message
|
|
44
|
+
* Extends base implementation to check router cache.
|
|
45
|
+
*/
|
|
46
|
+
async processMessage (from: PeerId, message: Message) {
|
|
47
|
+
// Check if I've seen the message, if yes, ignore
|
|
48
|
+
const seqno = await super.getMsgId(message)
|
|
49
|
+
const msgIdStr = toString(seqno, 'base64')
|
|
50
|
+
|
|
51
|
+
if (this.seenCache.has(msgIdStr)) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.seenCache.put(msgIdStr, true)
|
|
56
|
+
|
|
57
|
+
await super.processMessage(from, message)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Publish message created. Forward it to the peers.
|
|
62
|
+
*/
|
|
63
|
+
async publishMessage (from: PeerId, message: Message) {
|
|
64
|
+
const peers = this.getSubscribers(message.topic)
|
|
65
|
+
|
|
66
|
+
if (peers == null || peers.length === 0) {
|
|
67
|
+
this.log('no peers are subscribed to topic %s', message.topic)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
peers.forEach(id => {
|
|
72
|
+
if (this.peerId.equals(id)) {
|
|
73
|
+
this.log('not sending message on topic %s to myself', message.topic)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (id.equals(from)) {
|
|
78
|
+
this.log('not sending message on topic %s to sender %p', message.topic, id)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.log('publish msgs on topics %s %p', message.topic, id)
|
|
83
|
+
|
|
84
|
+
this.send(id, { messages: [message] })
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|