git ssb

16+

cel / patchfoo



Tree: b66bcecec258b0a2631ec338501afa9409882fe8

Files: b66bcecec258b0a2631ec338501afa9409882fe8 / lib / app.js

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

Built with git-ssb-web