Files: 3451510316992d414ec76ba5b29681fe359b7428 / lib / plugins / index.js
6572 bytesRaw
1 | const Heartbeat = require('./heartbeat') |
2 | const Subscriptions = require('./subscriptions') |
3 | const Progress = require('./progress') |
4 | const Search = require('./search') |
5 | const RecentFeeds = require('./recent-feeds') |
6 | const LiveBacklinks = require('./live-backlinks') |
7 | const pull = require('pull-stream') |
8 | const pCont = require('pull-cont/source') |
9 | |
10 | const plugins = { |
11 | likes: require('./likes'), |
12 | backlinks: require('./backlinks'), |
13 | profile: require('./profile'), |
14 | suggest: require('ssb-suggest'), |
15 | publicFeed: require('./public-feed'), |
16 | subscriptions2: require('./subscriptions2'), |
17 | thread: require('./thread'), |
18 | privateFeed: require('./private-feed'), |
19 | mentionsFeed: require('./mentions-feed'), |
20 | gatherings: require('./gatherings'), |
21 | networkFeed: require('./network-feed'), |
22 | channelFeed: require('./channel-feed'), |
23 | participatingFeed: require('./participating-feed'), |
24 | channels: require('./channels'), |
25 | contacts: require('./contacts') |
26 | } |
27 | |
28 | const ref = require('ssb-ref') |
29 | |
30 | exports.name = 'patchwork' |
31 | exports.version = require('../../package.json').version |
32 | exports.manifest = { |
33 | subscriptions: 'source', |
34 | linearSearch: 'source', |
35 | privateSearch: 'source', |
36 | |
37 | progress: 'source', |
38 | recentFeeds: 'source', |
39 | heartbeat: 'source', |
40 | |
41 | getSubscriptions: 'async', |
42 | |
43 | liveBacklinks: { |
44 | subscribe: 'sync', |
45 | unsubscribe: 'sync', |
46 | stream: 'source' |
47 | }, |
48 | |
49 | disconnect: 'async' |
50 | } |
51 | |
52 | for (const key in plugins) { |
53 | exports.manifest[key] = plugins[key].manifest |
54 | } |
55 | |
56 | exports.init = function (ssb, config) { |
57 | const progress = Progress(ssb) |
58 | const subscriptions = Subscriptions(ssb) |
59 | const search = Search(ssb) |
60 | const recentFeeds = RecentFeeds(ssb) |
61 | const replicating = new Set() |
62 | |
63 | const patchwork = { |
64 | heartbeat: Heartbeat(), |
65 | subscriptions: subscriptions.stream, |
66 | progress: progress.stream, |
67 | recentFeeds: recentFeeds.stream, |
68 | linearSearch: search.linear, |
69 | privateSearch: search.privateLinear, |
70 | getSubscriptions: subscriptions.get, |
71 | liveBacklinks: LiveBacklinks(ssb), |
72 | |
73 | disconnect: function (addr, cb) { |
74 | ssb.conn.disconnect(addr, cb) |
75 | return true |
76 | } |
77 | } |
78 | |
79 | for (const key in plugins) { |
80 | patchwork[key] = plugins[key].init(ssb, config) |
81 | } |
82 | |
83 | // CONNECTIONS |
84 | // refuse connections from blocked peers |
85 | ssb.auth.hook(function (fn, args) { |
86 | const self = this |
87 | patchwork.contacts.isBlocking({ source: ssb.id, dest: args[0] }, function (_, blocked) { |
88 | if (blocked) { |
89 | args[1](new Error('Client is blocked')) |
90 | } else { |
91 | fn.apply(self, args) |
92 | } |
93 | }) |
94 | }) |
95 | |
96 | // manually populate peer table from {type: 'pub'} messages |
97 | // exclude blocked pubs, only accept broadcasts from people within 2 hops |
98 | // wait 10 seconds after start before doing it to ease initial load |
99 | // (gossip.autoPopulate is disabled in config) |
100 | setTimeout(() => { |
101 | const discovered = new Set() |
102 | patchwork.contacts.raw.get((err, graph) => { |
103 | if (err) return |
104 | pull( |
105 | ssb.messagesByType({ type: 'pub', live: true, keys: false }), |
106 | pull.drain(function (value) { |
107 | if (value.sync && config.gossip && config.gossip.prune) { |
108 | // clean up pubs announced by peers more than 2 hops away if `--gossip.prune=true` |
109 | ssb.conn.query().peersConnectable('db').forEach(([addr, data]) => { |
110 | if (!discovered.has(data.key) && data.type === 'pub') { |
111 | ssb.conn.forget(addr) |
112 | } |
113 | }) |
114 | } |
115 | |
116 | if (!value.content) return |
117 | const address = value.content.address |
118 | if (replicating.has(value.author) && address && ref.isFeed(address.key)) { |
119 | // ignore blocked pubs |
120 | const blocking = graph && graph[ssb.id] && graph[ssb.id][address.key] === false |
121 | if (blocking) return |
122 | // make multiserver address as a string |
123 | let msAddr |
124 | try { |
125 | msAddr = ref.toMultiServerAddress(address) |
126 | } catch (err) { |
127 | return |
128 | } |
129 | // do not override room entries even if people declared them to be pubs |
130 | const oldEntry = ssb.conn.db().get(msAddr) |
131 | if (oldEntry && oldEntry.type === 'room') return |
132 | // add pub to the CONN database |
133 | discovered.add(address.key) |
134 | ssb.conn.remember(msAddr, { type: 'pub', key: address.key, autoconnect: true }) |
135 | } |
136 | }) |
137 | ) |
138 | }) |
139 | }, 10e3) |
140 | |
141 | // REPLICATION |
142 | // keep replicate up to date with replicateStream (replacement for ssb-friends) |
143 | pull( |
144 | patchwork.contacts.replicateStream({ live: true }), |
145 | pull.drain(state => { |
146 | for (const feedId in state) { |
147 | // track replicating for use by pub announcement filtering |
148 | if (state[feedId] === true) replicating.add(feedId) |
149 | else replicating.delete(feedId) |
150 | |
151 | // request (or unrequest) the feed |
152 | ssb.replicate.request(feedId, state[feedId] === true) |
153 | } |
154 | }) |
155 | ) |
156 | |
157 | // update ebt with latest block info |
158 | pull( |
159 | patchwork.contacts.raw.stream({ live: true }), |
160 | pull.drain((data) => { |
161 | if (!data) return |
162 | for (const from in data) { |
163 | for (const to in data[from]) { |
164 | const value = data[from][to] |
165 | ssb.ebt.block(from, to, value === false) |
166 | } |
167 | } |
168 | }) |
169 | ) |
170 | |
171 | // use blocks in legacy replication (adapted from ssb-friends for legacy compat) |
172 | ssb.createHistoryStream.hook(function (fn, args) { |
173 | const opts = args[0] |
174 | const peer = this |
175 | return pCont(cb => { |
176 | // wait till the index has loaded. |
177 | patchwork.contacts.raw.get((_, graph) => { |
178 | // don't allow the replication if the feed being requested blocks the requester |
179 | const requesterId = peer.id |
180 | const feedId = opts.id |
181 | if (graph && feedId !== requesterId && graph[feedId] && graph[feedId][requesterId] === false) { |
182 | cb(null, function (abort, cb) { |
183 | // just give them the cold shoulder |
184 | // `abort` and `cb` are passed to avoid a pull-cont error |
185 | }) |
186 | } else { |
187 | cb(null, pull( |
188 | fn.apply(peer, args), |
189 | // break off this feed if they suddenly block the recipient. |
190 | pull.take(function (msg) { |
191 | // handle when createHistoryStream is called with keys: true |
192 | if (!msg.content && msg.value.content) msg = msg.value |
193 | if (msg.content.type !== 'contact') return true |
194 | return !( |
195 | msg.content.blocking && msg.content.contact === peer.id |
196 | ) |
197 | }) |
198 | )) |
199 | } |
200 | }) |
201 | }) |
202 | }) |
203 | |
204 | return patchwork |
205 | } |
206 |
Built with git-ssb-web