@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 ADDED
@@ -0,0 +1,4 @@
1
+ This project is dual licensed under MIT and Apache-2.0.
2
+
3
+ MIT: https://www.opensource.org/licenses/mit
4
+ Apache-2.0: https://www.apache.org/licenses/license-2.0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # js-libp2p-floodsub <!-- omit in toc -->
2
+
3
+ [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
4
+ [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
5
+ [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
6
+ [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
7
+ [![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-floodsub/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-floodsub?branch=master)
8
+ [![Build Status](https://github.com/libp2p/js-libp2p-floodsub/actions/workflows/js-test-and-release.yml/badge.svg?branch=main)](https://github.com/libp2p/js-libp2p-floodsub/actions/workflows/js-test-and-release.yml)
9
+ [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
10
+ [![](https://img.shields.io/badge/pm-waffle-yellow.svg?style=flat-square)](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,3 @@
1
+ export declare const log: import("@libp2p/logger").Logger;
2
+ export declare const multicodec = "/floodsub/1.0.0";
3
+ //# sourceMappingURL=config.d.ts.map
@@ -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,4 @@
1
+ import { logger } from '@libp2p/logger';
2
+ export const log = logger('libp2p:floodsub');
3
+ export const multicodec = '/floodsub/1.0.0';
4
+ //# sourceMappingURL=config.js.map
@@ -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
@@ -0,0 +1,5 @@
1
+ import { logger } from '@libp2p/logger'
2
+
3
+ export const log = logger('libp2p:floodsub')
4
+
5
+ export const multicodec = '/floodsub/1.0.0'
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
+ }