git ssb

16+

cel / patchfoo



Tree: 80d6f2d0c52876f05da71c887266523c37645bf1

Files: 80d6f2d0c52876f05da71c887266523c37645bf1 / lib / app.js

7242 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 ssbAvatar = require('ssb-avatar')
8var hasher = require('pull-hash/ext/ssb')
9var multicb = require('multicb')
10var paramap = require('pull-paramap')
11var Contacts = require('ssb-contact')
12
13var Serve = require('./serve')
14var Render = require('./render')
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.getMsg = memo({cache: lru(100)}, getMsgWithValue, sbot)
36 this.getAbout = memo({cache: this.aboutCache = lru(500)},
37 getAbout.bind(this), sbot, sbot.id)
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
194function getMsgWithValue(sbot, id, cb) {
195 if (!id) return cb()
196 sbot.get(id, function (err, value) {
197 if (err) return cb(err)
198 cb(null, {key: id, value: value})
199 })
200}
201
202function getAbout(sbot, src, id, cb) {
203 var self = this
204 ssbAvatar(sbot, src, id, function (err, about) {
205 if (err) return cb(err)
206 var sigil = id && id[0] || '@'
207 if (about.name && about.name[0] !== sigil) {
208 about.name = sigil + about.name
209 }
210 self.reverseNameCache.set(about.name, id)
211 cb(null, about)
212 })
213}
214
215App.prototype.createLogStream = function (opts) {
216 opts = opts || {}
217 return opts.sortByTimestamp
218 ? this.sbot.createFeedStream(opts)
219 : this.sbot.createLogStream(opts)
220}
221
222var stateVals = {
223 connected: 3,
224 connecting: 2,
225 disconnecting: 1,
226}
227
228function comparePeers(a, b) {
229 var aState = stateVals[a.state] || 0
230 var bState = stateVals[b.state] || 0
231 return (bState - aState)
232 || (b.stateChange|0 - a.stateChange|0)
233}
234
235App.prototype.streamPeers = function (opts) {
236 var gossip = this.sbot.gossip
237 return u.readNext(function (cb) {
238 gossip.peers(function (err, peers) {
239 if (err) return cb(err)
240 if (opts) peers = peers.filter(function (peer) {
241 for (var k in opts) if (opts[k] !== peer[k]) return false
242 return true
243 })
244 peers.sort(comparePeers)
245 cb(null, pull.values(peers))
246 })
247 })
248}
249
250App.prototype.getFollow = function (source, dest, cb) {
251 var self = this
252 pull(
253 self.sbot.links({source: source, dest: dest, rel: 'contact', values: true}),
254 pull.filter(function (msg) {
255 var c = msg && msg.value && msg.value.content
256 return c && c.type === 'contact'
257 }),
258 pull.reduce(function (doesFollow, msg) {
259 var c = msg.value.content
260 return !!c.following
261 }, false, cb)
262 )
263}
264
265App.prototype.unboxMessages = function () {
266 return paramap(this.unboxMsg, 16)
267}
268
269App.prototype.streamChannels = function (opts) {
270 return pull(
271 this.sbot.messagesByType({type: 'channel', reverse: true}),
272 this.unboxMessages(),
273 pull.map(function (msg) {
274 return msg.value.content.channel
275 }),
276 pull.unique()
277 )
278}
279
280App.prototype.createContactStreams = function (id) {
281 return new Contacts(this.sbot).createContactStreams(id)
282}
283

Built with git-ssb-web