git ssb

16+

cel / patchfoo



Tree: b90b36f54546b4bad7618a57e7f5484892e23b52

Files: b90b36f54546b4bad7618a57e7f5484892e23b52 / lib / app.js

8575 bytesRaw
1var http = require('http')
2var memo = require('asyncmemo')
3var lru = require('hashlru')
4var pkg = require('../package')
5var u = require('./util')
6var pull = require('pull-stream')
7var hasher = require('pull-hash/ext/ssb')
8var multicb = require('multicb')
9var paramap = require('pull-paramap')
10var Contacts = require('ssb-contact')
11var About = require('./about')
12var Serve = require('./serve')
13var Render = require('./render')
14
15module.exports = App
16
17function App(sbot, config) {
18 this.sbot = sbot
19 this.config = config
20
21 var conf = config.patchfoo || {}
22 this.port = conf.port || 8027
23 this.host = conf.host || '::1'
24
25 var base = conf.base || '/'
26 this.opts = {
27 base: base,
28 blob_base: conf.blob_base || conf.img_base || base,
29 img_base: conf.img_base || base,
30 emoji_base: conf.emoji_base || (base + 'emoji/'),
31 }
32
33 sbot.get = memo({cache: lru(100)}, sbot.get)
34 this.about = new About(this, sbot.id)
35 this.getMsg = memo({cache: lru(100)}, getMsgWithValue, sbot)
36 this.getAbout = memo({cache: this.aboutCache = lru(500)},
37 this._getAbout.bind(this))
38 this.unboxContent = memo({cache: lru(100)}, sbot.private.unbox)
39 this.reverseNameCache = lru(100)
40
41 this.unboxMsg = this.unboxMsg.bind(this)
42
43 this.render = new Render(this, this.opts)
44}
45
46App.prototype.go = function () {
47 var self = this
48 http.createServer(function (req, res) {
49 new Serve(self, req, res).go()
50 }).listen(self.port, self.host, function () {
51 var host = /:/.test(self.host) ? '[' + self.host + ']' : self.host
52 self.log('Listening on http://' + host + ':' + self.port)
53 })
54
55 // invalidate cached About info when new About messages come in
56 pull(
57 self.sbot.links({rel: 'about', old: false, values: true}),
58 pull.drain(function (link) {
59 self.aboutCache.remove(link.dest)
60 }, function (err) {
61 if (err) self.error('about:', err)
62 })
63 )
64}
65
66var logPrefix = '[' + pkg.name + ']'
67App.prototype.log = console.log.bind(console, logPrefix)
68App.prototype.error = console.error.bind(console, logPrefix)
69
70App.prototype.unboxMsg = function (msg, cb) {
71 var self = this
72 var c = msg.value && msg.value.content
73 if (typeof c !== 'string') cb(null, msg)
74 else self.unboxContent(c, function (err, content) {
75 if (err) {
76 self.error('unbox:', err)
77 return cb(null, msg)
78 } else if (!content) {
79 return cb(null, msg)
80 }
81 var m = {}
82 for (var k in msg) m[k] = msg[k]
83 m.value = {}
84 for (var k in msg.value) m.value[k] = msg.value[k]
85 m.value.content = content
86 m.value.private = true
87 cb(null, m)
88 })
89}
90
91App.prototype.search = function (opts) {
92 var search = this.sbot.fulltext && this.sbot.fulltext.search
93 if (!search) return pull.error(new Error('Missing fulltext search plugin'))
94 return search(opts)
95}
96
97App.prototype.advancedSearch = function (opts) {
98 return pull(
99 opts.dest ?
100 this.sbot.links({
101 values: true,
102 dest: opts.dest,
103 source: opts.source || undefined,
104 reverse: true,
105 })
106 : opts.source ?
107 this.sbot.createUserStream({
108 reverse: true,
109 id: opts.source
110 })
111 :
112 this.sbot.createFeedStream({
113 reverse: true,
114 }),
115 opts.text && pull.filter(filterByText(opts.text))
116 )
117}
118
119function forSome(each) {
120 return function some(obj) {
121 if (obj == null) return false
122 if (typeof obj === 'string') return each(obj)
123 if (Array.isArray(obj)) return obj.some(some)
124 if (typeof obj === 'object')
125 for (var k in obj) if (some(obj[k])) return true
126 return false
127 }
128}
129
130function filterByText(str) {
131 if (!str) return function () { return true }
132 var search = new RegExp(str, 'i')
133 var matches = forSome(search.test.bind(search))
134 return function (msg) {
135 var c = msg.value.content
136 return c && matches(c)
137 }
138}
139
140App.prototype.getMsgDecrypted = function (key, cb) {
141 var self = this
142 this.getMsg(key, function (err, msg) {
143 if (err) return cb(err)
144 self.unboxMsg(msg, cb)
145 })
146}
147
148App.prototype.publish = function (content, cb) {
149 var self = this
150 function tryPublish(triesLeft) {
151 if (Array.isArray(content.recps)) {
152 recps = content.recps.map(u.linkDest)
153 self.sbot.private.publish(content, recps, next)
154 } else {
155 self.sbot.publish(content, next)
156 }
157 function next(err, msg) {
158 if (err) {
159 if (triesLeft > 0) {
160 if (/^expected previous:/.test(err.message)) {
161 return tryPublish(triesLeft-1)
162 }
163 }
164 }
165 return cb(err, msg)
166 }
167 }
168 tryPublish(2)
169}
170
171App.prototype.addBlob = function (cb) {
172 var done = multicb({pluck: 1, spread: true})
173 var hashCb = done()
174 var addCb = done()
175 done(function (err, hash, add) {
176 cb(err, hash)
177 })
178 return pull(
179 hasher(hashCb),
180 this.sbot.blobs.add(addCb)
181 )
182}
183
184App.prototype.pushBlob = function (id, cb) {
185 console.error('pushing blob', id)
186 this.sbot.blobs.push(id, cb)
187}
188
189App.prototype.getReverseNameSync = function (name) {
190 var id = this.reverseNameCache.get(name)
191 return id
192}
193
194App.prototype.getNameSync = function (name) {
195 var about = this.aboutCache.get(name)
196 return about && about.name
197}
198
199function getMsgWithValue(sbot, id, cb) {
200 if (!id) return cb()
201 sbot.get(id, function (err, value) {
202 if (err) return cb(err)
203 cb(null, {key: id, value: value})
204 })
205}
206
207App.prototype._getAbout = function (id, cb) {
208 var self = this
209 if (!u.isRef(id)) return cb(null, {})
210 self.about.get(id, function (err, about) {
211 if (err) return cb(err)
212 var sigil = id[0] || '@'
213 if (about.name && about.name[0] !== sigil) {
214 about.name = sigil + about.name
215 }
216 self.reverseNameCache.set(about.name, id)
217 cb(null, about)
218 })
219}
220
221App.prototype.pullGetMsg = function (id) {
222 return pull.asyncMap(this.getMsg)(pull.once(id))
223}
224
225App.prototype.createLogStream = function (opts) {
226 opts = opts || {}
227 return opts.sortByTimestamp
228 ? this.sbot.createFeedStream(opts)
229 : this.sbot.createLogStream(opts)
230}
231
232var stateVals = {
233 connected: 3,
234 connecting: 2,
235 disconnecting: 1,
236}
237
238function comparePeers(a, b) {
239 var aState = stateVals[a.state] || 0
240 var bState = stateVals[b.state] || 0
241 return (bState - aState)
242 || (b.stateChange|0 - a.stateChange|0)
243}
244
245App.prototype.streamPeers = function (opts) {
246 var gossip = this.sbot.gossip
247 return u.readNext(function (cb) {
248 gossip.peers(function (err, peers) {
249 if (err) return cb(err)
250 if (opts) peers = peers.filter(function (peer) {
251 for (var k in opts) if (opts[k] !== peer[k]) return false
252 return true
253 })
254 peers.sort(comparePeers)
255 cb(null, pull.values(peers))
256 })
257 })
258}
259
260App.prototype.getFollow = function (source, dest, cb) {
261 var self = this
262 pull(
263 self.sbot.links({source: source, dest: dest, rel: 'contact', reverse: true,
264 values: true, meta: false, keys: false}),
265 pull.filter(function (value) {
266 var c = value && value.content
267 return c && c.type === 'contact'
268 }),
269 pull.take(1),
270 pull.collect(function (err, msgs) {
271 if (err) return cb(err)
272 cb(null, msgs[0] && !!msgs[0].content.following)
273 })
274 )
275}
276
277App.prototype.unboxMessages = function () {
278 return paramap(this.unboxMsg, 16)
279}
280
281App.prototype.streamChannels = function (opts) {
282 return pull(
283 this.sbot.messagesByType({type: 'channel', reverse: true}),
284 this.unboxMessages(),
285 pull.filter(function (msg) {
286 return msg.value.content.subscribed
287 }),
288 pull.map(function (msg) {
289 return msg.value.content.channel
290 }),
291 pull.unique()
292 )
293}
294
295App.prototype.streamMyChannels = function (id, opts) {
296 // use ssb-query plugin if it is available, since it has an index for
297 // author + type
298 if (this.sbot.query) return pull(
299 this.sbot.query.read({
300 reverse: true,
301 query: [
302 {$filter: {
303 value: {
304 author: id,
305 content: {type: 'channel', subscribed: true}
306 }
307 }},
308 {$map: ['value', 'content', 'channel']}
309 ]
310 }),
311 pull.unique()
312 )
313
314 return pull(
315 this.sbot.createUserStream({id: id, reverse: true}),
316 this.unboxMessages(),
317 pull.filter(function (msg) {
318 if (msg.value.content.type == 'channel') {
319 return msg.value.content.subscribed
320 }
321 }),
322 pull.map(function (msg) {
323 return msg.value.content.channel
324 }),
325 pull.unique()
326 )
327}
328
329App.prototype.createContactStreams = function (id) {
330 return new Contacts(this.sbot).createContactStreams(id)
331}
332
333App.prototype.createAboutStreams = function (id) {
334 return this.about.createAboutStreams(id)
335}
336

Built with git-ssb-web