Commit 84289a1e105783bad299705f084b4a07b225fa38
Merge remote-tracking branch 'origin/master' into patch-router
mix irving committed on 7/16/2017, 6:36:37 AMParent: 4d1aa8528ed9342d5d663af75cb08beb82f0fbcc
Parent: af2008b48a9d597cb4374d5e2fb4523a29106aa1
Files changed
about/obs.js | changed |
feed/obs/recent.js | changed |
feed/obs/thread.js | changed |
feed/pull/rollup.js | changed |
index.js | changed |
lib/timeAgo.js | changed |
message/async/name.js | changed |
message/html/action/like.js | changed |
message/obs/backlinks.js | changed |
message/obs/likes.js | changed |
package.json | changed |
sbot.js | changed |
backlinks/obs.js | added |
about/obs.js | ||
---|---|---|
@@ -27,9 +27,10 @@ | ||
27 | 27 … | ] |
28 | 28 … | }) |
29 | 29 … | |
30 | 30 … | exports.create = function (api) { |
31 | - var sync = Value(false) | |
31 … | + var syncValue = Value(false) | |
32 … | + var sync = computed(syncValue, x => x) | |
32 | 33 … | var cache = null |
33 | 34 … | |
34 | 35 … | return nest({ |
35 | 36 … | 'about.obs': { |
@@ -51,30 +52,34 @@ | ||
51 | 52 … | groupedValues |
52 | 53 … | } |
53 | 54 … | }) |
54 | 55 … | |
55 | - | |
56 | 56 … | function valueFrom (id, key, author) { |
57 | 57 … | if (!ref.isLink(id)) throw new Error('About requires an ssb ref!') |
58 | - return computed([get(id), key, author], getValueFrom) | |
58 … | + return withSync(computed([get(id), key, author], getValueFrom)) | |
59 | 59 … | } |
60 | 60 … | |
61 | 61 … | function latestValue (id, key) { |
62 | 62 … | if (!ref.isLink(id)) throw new Error('About requires an ssb ref!') |
63 | - return computed([get(id), key], getLatestValue) | |
63 … | + return withSync(computed([get(id), key], getLatestValue)) | |
64 | 64 … | } |
65 | 65 … | |
66 | 66 … | function socialValue (id, key, defaultValue) { |
67 | 67 … | if (!ref.isLink(id)) throw new Error('About requires an ssb ref!') |
68 | 68 … | var yourId = api.keys.sync.id() |
69 | - return computed([get(id), key, id, yourId, defaultValue], getSocialValue) | |
69 … | + return withSync(computed([get(id), key, id, yourId, defaultValue], getSocialValue)) | |
70 | 70 … | } |
71 | 71 … | |
72 | 72 … | function groupedValues (id, key) { |
73 | 73 … | if (!ref.isLink(id)) throw new Error('About requires an ssb ref!') |
74 | - return computed([get(id), key], getGroupedValues) | |
74 … | + return withSync(computed([get(id), key], getGroupedValues)) | |
75 | 75 … | } |
76 | 76 … | |
77 … | + function withSync (obs) { | |
78 … | + obs.sync = sync | |
79 … | + return obs | |
80 … | + } | |
81 … | + | |
77 | 82 … | function get (id) { |
78 | 83 … | if (!ref.isLink(id)) throw new Error('About requires an ssb ref!') |
79 | 84 … | load() |
80 | 85 … | if (!cache[id]) { |
@@ -108,10 +113,10 @@ | ||
108 | 113 … | state.set(lastState) |
109 | 114 … | } |
110 | 115 … | } |
111 | 116 … | |
112 | - if (!sync()) { | |
113 | - sync.set(true) | |
117 … | + if (!syncValue()) { | |
118 … | + syncValue.set(true) | |
114 | 119 … | } |
115 | 120 … | }) |
116 | 121 … | ) |
117 | 122 … | } |
feed/obs/recent.js | ||
---|---|---|
@@ -15,9 +15,9 @@ | ||
15 | 15 … | exports.create = function (api) { |
16 | 16 … | return nest('feed.obs.recent', function (limit) { |
17 | 17 … | var stream = pull( |
18 | 18 … | pullCat([ |
19 | - api.sbot.pull.log({reverse: true, limit: limit || 500}), | |
19 … | + api.sbot.pull.log({reverse: true, limit: limit || 50}), | |
20 | 20 … | api.sbot.pull.log({old: false}) |
21 | 21 … | ]) |
22 | 22 … | ) |
23 | 23 … |
feed/obs/thread.js | ||
---|---|---|
@@ -1,16 +1,14 @@ | ||
1 | 1 … | var nest = require('depnest') |
2 | -var pull = require('pull-stream') | |
3 | -var pullCat = require('pull-cat') | |
4 | 2 … | var sort = require('ssb-sort') |
5 | 3 … | var ref = require('ssb-ref') |
6 | -var { map, computed } = require('mutant') | |
4 … | +var { Array: MutantArray, Value, map, concat, computed } = require('mutant') | |
7 | 5 … | |
8 | 6 … | exports.needs = nest({ |
9 | - 'sbot.pull.links': 'first', | |
7 … | + 'backlinks.obs.for': 'first', | |
10 | 8 … | 'sbot.async.get': 'first', |
11 | - 'lib.obs.pullLookup': 'first', | |
12 | - 'message.sync.unbox': 'first' | |
9 … | + 'message.sync.unbox': 'first', | |
10 … | + 'message.sync.root': 'first' | |
13 | 11 … | }) |
14 | 12 … | |
15 | 13 … | exports.gives = nest('feed.obs.thread') |
16 | 14 … | |
@@ -18,38 +16,42 @@ | ||
18 | 16 … | return nest('feed.obs.thread', thread) |
19 | 17 … | |
20 | 18 … | function thread (rootId, { branch } = {}) { |
21 | 19 … | if (!ref.isLink(rootId)) throw new Error('an id must be specified') |
20 … | + var sync = Value(false) | |
22 | 21 … | |
23 | - var rootMessageStream = pull( | |
24 | - pull.values([rootId]), | |
25 | - pull.asyncMap((key, cb) => { | |
26 | - return api.sbot.async.get(key, (err, value) => cb(err, {key, value})) | |
27 | - }) | |
28 | - ) | |
29 | - | |
30 | - var messageLookup = api.lib.obs.pullLookup(pull( | |
31 | - pullCat([ | |
32 | - rootMessageStream, | |
33 | - api.sbot.pull.links({ rel: branch ? 'branch' : 'root', dest: rootId, keys: true, values: true, live: true }) | |
34 | - ]), | |
35 | - unboxIfNeeded() | |
36 | - ), 'key') | |
37 | - | |
38 | - var orderedIds = computed(messageLookup, (lookup) => { | |
39 | - var msgs = Object.keys(lookup).map(k => lookup[k]) | |
40 | - return sort(msgs).map(getKey) | |
22 … | + var prepend = MutantArray() | |
23 … | + api.sbot.async.get(rootId, (err, value) => { | |
24 … | + sync.set(true) | |
25 … | + if (!err) { | |
26 … | + prepend.push( | |
27 … | + Value(unboxIfNeeded({key: rootId, value})) | |
28 … | + ) | |
29 … | + } | |
41 | 30 … | }) |
42 | 31 … | |
43 | - var messages = map(orderedIds, (id) => { | |
44 | - return messageLookup.get(id) | |
32 … | + var backlinks = api.backlinks.obs.for(rootId) | |
33 … | + var replies = map(computed(backlinks, (msgs) => { | |
34 … | + return msgs.filter(msg => { | |
35 … | + return msg.value.content.type !== 'vote' && ( | |
36 … | + api.message.sync.root(msg) === rootId || | |
37 … | + matchAny(msg.value.content.branch, rootId) | |
38 … | + ) | |
39 … | + }) | |
40 … | + }), x => Value(x), { | |
41 … | + // avoid refresh of entire list when items added | |
42 … | + comparer: (a, b) => a === b | |
45 | 43 … | }) |
46 | 44 … | |
45 … | + var messages = concat([prepend, replies]) | |
46 … | + | |
47 | 47 … | var result = { |
48 | 48 … | messages, |
49 | 49 … | lastId: computed(messages, (messages) => { |
50 | 50 … | var branches = sort.heads(messages) |
51 | - if(branches.length <= 1) branches = branches[0] | |
51 … | + if (branches.length <= 1) { | |
52 … | + branches = branches[0] | |
53 … | + } | |
52 | 54 … | return branches |
53 | 55 … | }), |
54 | 56 … | rootId: computed(messages, (messages) => { |
55 | 57 … | if (branch && messages.length) { |
@@ -80,28 +82,21 @@ | ||
80 | 82 … | return msgs[0].value.content.recps |
81 | 83 … | }) |
82 | 84 … | } |
83 | 85 … | |
84 | - result.sync = messageLookup.sync | |
85 | - | |
86 … | + result.sync = computed([backlinks.sync, sync], (a, b) => a && b, {idle: true}) | |
86 | 87 … | return result |
87 | 88 … | } |
88 | 89 … | |
89 | - function unboxIfNeeded () { | |
90 | - return pull.map(function (msg) { | |
91 | - if (msg.sync || (msg.value && typeof msg.value.content === 'object')) { | |
92 | - return msg | |
93 | - } else { | |
94 | - return api.message.sync.unbox(msg) | |
95 | - } | |
96 | - }) | |
90 … | + function unboxIfNeeded (msg) { | |
91 … | + if (msg.value && typeof msg.value.content === 'string') { | |
92 … | + return api.message.sync.unbox(msg) || msg | |
93 … | + } else { | |
94 … | + return msg | |
95 … | + } | |
97 | 96 … | } |
98 | 97 … | } |
99 | 98 … | |
100 | -function getKey (msg) { | |
101 | - return msg.key | |
102 | -} | |
103 | - | |
104 | 99 … | function PreviousKey (collection, item) { |
105 | 100 … | return computed(collection, (c) => { |
106 | 101 … | var index = collection.indexOf(item) |
107 | 102 … | if (~index) { |
@@ -111,4 +106,12 @@ | ||
111 | 106 … | } |
112 | 107 … | } |
113 | 108 … | }) |
114 | 109 … | } |
110 … | + | |
111 … | +function matchAny (valueOrArray, compare) { | |
112 … | + if (valueOrArray === compare) { | |
113 … | + return true | |
114 … | + } else if (Array.isArray(valueOrArray)) { | |
115 … | + return valueOrArray.includes(compare) | |
116 … | + } | |
117 … | +} |
feed/pull/rollup.js | ||
---|---|---|
@@ -4,53 +4,98 @@ | ||
4 | 4 … | |
5 | 5 … | var pull = require('pull-stream') |
6 | 6 … | var nest = require('depnest') |
7 | 7 … | var extend = require('xtend') |
8 … | +var HLRU = require('hashlru') | |
9 … | +var resolve = require('mutant/resolve') | |
10 … | +var onceTrue = require('mutant/once-true') | |
8 | 11 … | |
9 | 12 … | exports.needs = nest({ |
10 | - 'sbot.pull.backlinks': 'first', | |
13 … | + 'backlinks.obs.for': 'first', | |
11 | 14 … | 'sbot.async.get': 'first', |
12 | 15 … | 'message.sync.root': 'first', |
13 | 16 … | 'message.sync.unbox': 'first' |
14 | 17 … | }) |
15 | 18 … | |
16 | 19 … | exports.gives = nest('feed.pull.rollup', true) |
17 | 20 … | |
18 | 21 … | exports.create = function (api) { |
22 … | + // cache mostly just to avoid reading the same roots over and over again | |
23 … | + // not really big enough for multiple refresh cycles | |
24 … | + var cache = HLRU(100) | |
25 … | + | |
19 | 26 … | return nest('feed.pull.rollup', function (rootFilter) { |
27 … | + var seen = new Set() | |
20 | 28 … | return pull( |
21 | - pull.map(msg => api.message.sync.root(msg) || msg.key), | |
22 | - pull.unique(), | |
23 | - Lookup(), | |
29 … | + pull.map(msg => { | |
30 … | + if (msg.value) { | |
31 … | + var root = api.message.sync.root(msg) | |
32 … | + if (!root) { | |
33 … | + // already a root, pass thru! | |
34 … | + return msg | |
35 … | + } else { | |
36 … | + return root | |
37 … | + } | |
38 … | + } | |
39 … | + }), | |
40 … | + | |
41 … | + // UNIQUE | |
42 … | + pull.filter(idOrMsg => { | |
43 … | + if (idOrMsg) { | |
44 … | + if (idOrMsg.key) idOrMsg = idOrMsg.key | |
45 … | + if (typeof idOrMsg === 'string') { | |
46 … | + var key = idOrMsg | |
47 … | + if (!seen.has(key)) { | |
48 … | + seen.add(key) | |
49 … | + return true | |
50 … | + } | |
51 … | + } | |
52 … | + } | |
53 … | + }), | |
54 … | + | |
55 … | + // LOOKUP (if needed) | |
56 … | + pull.asyncMap((keyOrMsg, cb) => { | |
57 … | + if (keyOrMsg.value) { | |
58 … | + cb(null, keyOrMsg) | |
59 … | + } else { | |
60 … | + var key = keyOrMsg | |
61 … | + if (cache.has(key)) { | |
62 … | + cb(null, cache.get(key)) | |
63 … | + } else { | |
64 … | + api.sbot.async.get(key, (_, value) => { | |
65 … | + var msg = {key, value} | |
66 … | + if (msg.value) { | |
67 … | + cache.set(key, msg) | |
68 … | + } | |
69 … | + cb(null, msg) | |
70 … | + }) | |
71 … | + } | |
72 … | + } | |
73 … | + }), | |
74 … | + | |
75 … | + // UNBOX (if needed) | |
76 … | + pull.map(msg => { | |
77 … | + if (msg.value && typeof msg.value.content === 'string') { | |
78 … | + var unboxed = api.message.sync.unbox(msg) | |
79 … | + if (unboxed) return unboxed | |
80 … | + } | |
81 … | + return msg | |
82 … | + }), | |
83 … | + | |
84 … | + // FILTER | |
24 | 85 … | pull.filter(msg => msg && msg.value && !api.message.sync.root(msg)), |
25 | 86 … | pull.filter(rootFilter || (() => true)), |
26 | - AddReplies() | |
27 | - ) | |
28 | - }) | |
29 | 87 … | |
30 | - // scoped | |
31 | - function Lookup () { | |
32 | - return pull.asyncMap((key, cb) => { | |
33 | - api.sbot.async.get(key, (_, value) => { | |
34 | - if (value && typeof value.content === 'string') { | |
35 | - value = api.message.sync.unbox(value) | |
36 | - } | |
37 | - cb(null, {key, value}) | |
38 | - }) | |
39 | - }) | |
40 | - } | |
41 | - | |
42 | - function AddReplies () { | |
43 | - return pull.asyncMap((rootMessage, cb) => { | |
44 | - pull( | |
45 | - api.sbot.pull.backlinks({ | |
46 | - query: [{$filter: { dest: rootMessage.key }}] | |
47 | - }), | |
48 | - pull.filter(msg => (api.message.sync.root(msg) || rootMessage.key) === rootMessage.key), | |
49 | - pull.collect((err, replies) => { | |
50 | - if (err) return cb(err) | |
88 … | + // ADD REPLIES | |
89 … | + pull.asyncMap((rootMessage, cb) => { | |
90 … | + // use global backlinks cache | |
91 … | + var backlinks = api.backlinks.obs.for(rootMessage.key) | |
92 … | + onceTrue(backlinks.sync, () => { | |
93 … | + var replies = resolve(backlinks).filter((msg) => { | |
94 … | + return api.message.sync.root(msg) === rootMessage.key | |
95 … | + }) | |
51 | 96 … | cb(null, extend(rootMessage, { replies })) |
52 | 97 … | }) |
53 | - ) | |
54 | - }) | |
55 | - } | |
98 … | + }) | |
99 … | + ) | |
100 … | + }) | |
56 | 101 … | } |
index.js | ||
---|---|---|
@@ -2,7 +2,7 @@ | ||
2 | 2 … | |
3 | 3 … | module.exports = { |
4 | 4 … | patchcore: bulk(__dirname, [ |
5 | 5 … | './+(config|emoji|invite|keys|sbot).js', |
6 | - './+(about|blob|channel|contact|feed|lib|message|router)/**/*.js' | |
6 … | + './+(about|backlinks|blob|channel|contact|feed|lib|message|router)/**/*.js' | |
7 | 7 … | ]) |
8 | 8 … | } |
lib/timeAgo.js | ||
---|---|---|
@@ -12,14 +12,16 @@ | ||
12 | 12 … | var timer |
13 | 13 … | var value = Value(Time(timestamp)) |
14 | 14 … | return computed([value], (a) => a, { |
15 | 15 … | onListen: () => { |
16 | - timer = setInterval(refresh, 5e3) | |
16 … | + timer = setInterval(refresh, 30e3) | |
17 | 17 … | refresh() |
18 | 18 … | }, |
19 | 19 … | onUnlisten: () => { |
20 | 20 … | clearInterval(timer) |
21 | 21 … | } |
22 … | + }, { | |
23 … | + idle: true | |
22 | 24 … | }) |
23 | 25 … | |
24 | 26 … | function refresh () { |
25 | 27 … | value.set(Time(timestamp)) |
message/async/name.js | ||
---|---|---|
@@ -1,11 +1,13 @@ | ||
1 | 1 … | const nest = require('depnest') |
2 | -const getAvatar = require('ssb-avatar') | |
3 | 2 … | const ref = require('ssb-ref') |
3 … | +const {resolve, onceTrue} = require('mutant') | |
4 | 4 … | |
5 | 5 … | exports.needs = nest({ |
6 | 6 … | 'sbot.async.get': 'first', |
7 | 7 … | 'sbot.pull.links': 'first', |
8 … | + 'message.sync.unbox': 'first', | |
9 … | + 'about.obs.socialValue': 'first', | |
8 | 10 … | 'keys.sync.id': 'first' |
9 | 11 … | }) |
10 | 12 … | exports.gives = nest('message.async.name') |
11 | 13 … | |
@@ -15,8 +17,12 @@ | ||
15 | 17 … | return nest('message.async.name', function (id, cb) { |
16 | 18 … | if (!ref.isLink(id)) throw new Error('an id must be specified') |
17 | 19 … | var fallbackName = id.substring(0, 10) + '...' |
18 | 20 … | api.sbot.async.get(id, function (err, value) { |
21 … | + if (value && typeof value.content === 'string') { | |
22 … | + value = api.message.sync.unbox(value) | |
23 … | + } | |
24 … | + | |
19 | 25 … | if (err && err.name === 'NotFoundError') { |
20 | 26 … | return cb(null, fallbackName + '...(missing)') |
21 | 27 … | } else if (value.content.type === 'post' && typeof value.content.text === 'string') { |
22 | 28 … | if (value.content.text.trim()) { |
@@ -24,21 +30,21 @@ | ||
24 | 30 … | } |
25 | 31 … | } else if (typeof value.content.text === 'string') { |
26 | 32 … | return cb(null, value.content.type + ': ' + titleFromMarkdown(value.content.text, 30)) |
27 | 33 … | } else { |
28 | - getAboutName(id, cb) | |
34 … | + return getAboutName(id, cb) | |
29 | 35 … | } |
30 | 36 … | |
31 | 37 … | return cb(null, fallbackName) |
32 | 38 … | }) |
33 | 39 … | }) |
34 | 40 … | |
35 | 41 … | function getAboutName (id, cb) { |
36 | - getAvatar({ | |
37 | - links: api.sbot.pull.links, | |
38 | - get: api.sbot.async.get | |
39 | - }, api.keys.sync.id(), id, function (_, avatar) { | |
40 | - cb(null, avatar && avatar.name || id.substring(0, 10) + '...') | |
42 … | + var name = api.about.obs.socialValue(id, 'name') | |
43 … | + var title = api.about.obs.socialValue(id, 'title') | |
44 … | + | |
45 … | + onceTrue(name.sync, () => { | |
46 … | + cb(null, resolve(name) || resolve(title) || id.substring(0, 10) + '...') | |
41 | 47 … | }) |
42 | 48 … | } |
43 | 49 … | } |
44 | 50 … |
message/html/action/like.js | ||
---|---|---|
@@ -25,24 +25,24 @@ | ||
25 | 25 … | ) |
26 | 26 … | }) |
27 | 27 … | |
28 | 28 … | function publishLike (msg, status = true) { |
29 | - var dig = status ? { | |
29 … | + var like = status ? { | |
30 | 30 … | type: 'vote', |
31 | 31 … | channel: msg.value.content.channel, |
32 | - vote: { link: msg.key, value: 1, expression: 'Dig' } | |
32 … | + vote: { link: msg.key, value: 1, expression: 'Like' } | |
33 | 33 … | } : { |
34 | 34 … | type: 'vote', |
35 | 35 … | channel: msg.value.content.channel, |
36 | - vote: { link: msg.key, value: 0, expression: 'Undig' } | |
36 … | + vote: { link: msg.key, value: 0, expression: 'Unlike' } | |
37 | 37 … | } |
38 | 38 … | if (msg.value.content.recps) { |
39 | - dig.recps = msg.value.content.recps.map(function (e) { | |
39 … | + like.recps = msg.value.content.recps.map(function (e) { | |
40 | 40 … | return e && typeof e !== 'string' ? e.link : e |
41 | 41 … | }) |
42 | - dig.private = true | |
42 … | + like.private = true | |
43 | 43 … | } |
44 | - api.sbot.async.publish(dig) | |
44 … | + api.sbot.async.publish(like) | |
45 | 45 … | } |
46 | 46 … | } |
47 | 47 … | |
48 | 48 … | function doesLike (likes, userId) { |
message/obs/backlinks.js | ||
---|---|---|
@@ -1,40 +1,38 @@ | ||
1 | 1 … | var nest = require('depnest') |
2 | -var MutantPullReduce = require('mutant-pull-reduce') | |
2 … | +var computed = require('mutant/computed') | |
3 | 3 … | |
4 | 4 … | exports.needs = nest({ |
5 | - 'sbot.pull.backlinks': 'first' | |
5 … | + 'backlinks.obs.for': 'first' | |
6 | 6 … | }) |
7 | 7 … | |
8 | 8 … | exports.gives = nest('message.obs.backlinks', true) |
9 | 9 … | |
10 | 10 … | exports.create = function (api) { |
11 | 11 … | return nest({ |
12 … | + // DEPRECATED: should use backlinks.obs.for | |
12 | 13 … | 'message.obs.backlinks': (id) => backlinks(id) |
13 | 14 … | }) |
14 | 15 … | |
15 | 16 … | function backlinks (id) { |
16 | - return MutantPullReduce(api.sbot.pull.backlinks({ | |
17 | - query: [ | |
18 | - {$filter: { | |
19 | - dest: id | |
20 | - }}, | |
21 | - {$map: { | |
22 | - dest: 'dest', | |
23 | - id: 'key', | |
24 | - timestamp: 'timestamp', | |
25 | - type: ['value', 'content', 'type'], | |
26 | - root: ['value', 'content', 'root'], | |
27 | - branch: ['value', 'content', 'branch'], | |
28 | - author: ['value', 'author'] | |
29 | - }} | |
30 | - ] | |
31 | - }), (result, msg) => { | |
32 | - if (msg.type !== 'vote' && msg.type !== 'about') { | |
33 | - result.push(msg) | |
34 | - } | |
35 | - return result | |
17 … | + return computed([api.backlinks.obs.for(id)], (msgs) => { | |
18 … | + return msgs.map(map).filter((backlink) => { | |
19 … | + return backlink.type !== 'vote' && backlink.type !== 'about' | |
20 … | + }) | |
36 | 21 … | }, { |
37 | - startValue: [] | |
22 … | + // objects coming down this stream will be immutable | |
23 … | + comparer: (a, b) => a === b | |
38 | 24 … | }) |
39 | 25 … | } |
26 … | + | |
27 … | + function map (msg) { | |
28 … | + return { | |
29 … | + dest: msg.dest, | |
30 … | + id: msg.key, | |
31 … | + timestamp: msg.timestamp, | |
32 … | + type: msg.value.content.type, | |
33 … | + root: msg.value.content.root, | |
34 … | + branch: msg.value.content.branch, | |
35 … | + author: msg.value.author | |
36 … | + } | |
37 … | + } | |
40 | 38 … | } |
message/obs/likes.js | ||
---|---|---|
@@ -1,14 +1,15 @@ | ||
1 | 1 … | var nest = require('depnest') |
2 | 2 … | var ref = require('ssb-ref') |
3 | -var MutantPullReduce = require('mutant-pull-reduce') | |
4 | -var SortedArray = require('sorted-array-functions') | |
3 … | +var MutantArray = require('mutant/array') | |
4 … | +var concat = require('mutant/concat') | |
5 … | +var watch = require('mutant/watch') | |
5 | 6 … | |
6 | 7 … | var { computed } = require('mutant') |
7 | 8 … | |
8 | 9 … | exports.needs = nest({ |
9 | 10 … | 'message.sync.unbox': 'first', |
10 | - 'sbot.pull.backlinks': 'first' | |
11 … | + 'backlinks.obs.for': 'first' | |
11 | 12 … | }) |
12 | 13 … | |
13 | 14 … | exports.gives = nest({ |
14 | 15 … | 'sbot.hook.publish': true, |
@@ -30,74 +31,57 @@ | ||
30 | 31 … | if (!c.vote || !c.vote.link) return |
31 | 32 … | |
32 | 33 … | activeLikes.forEach((likes) => { |
33 | 34 … | if (likes.id === c.vote.link) { |
34 | - likes.push({ | |
35 | - dest: c.vote.link, | |
36 | - id: msg.key, | |
37 | - expression: c.vote.expression, | |
38 | - value: c.vote.value, | |
39 | - timestamp: msg.value.timestamp, | |
40 | - author: msg.value.author | |
41 | - }) | |
35 … | + likes.push(msg) | |
42 | 36 … | } |
43 | 37 … | }) |
44 | 38 … | }, |
45 | 39 … | 'message.obs.likes': (id) => { |
46 | 40 … | if (!ref.isLink(id)) throw new Error('an id must be specified') |
47 | 41 … | var obs = get(id) |
48 | 42 … | obs.id = id |
49 | - return computed(obs, getLikes, { | |
43 … | + var result = computed(obs, getLikes, { | |
50 | 44 … | // allow manual append for simulated realtime |
51 | 45 … | onListen: () => activeLikes.add(obs), |
52 | 46 … | onUnlisten: () => activeLikes.delete(obs) |
53 | 47 … | }) |
48 … | + result.sync = obs.sync | |
49 … | + return result | |
54 | 50 … | } |
55 | 51 … | }) |
56 | 52 … | |
57 | 53 … | function get (id) { |
58 | - var likes = MutantPullReduce(api.sbot.pull.backlinks({ | |
59 | - live: true, | |
60 | - query: [ | |
61 | - {$filter: { | |
62 | - dest: id, | |
63 | - value: { | |
64 | - content: { | |
65 | - type: 'vote', | |
66 | - vote: { link: id } | |
54 … | + var backlinks = api.backlinks.obs.for(id) | |
55 … | + var merge = MutantArray() | |
56 … | + | |
57 … | + var likes = computed([backlinks.sync, concat([backlinks, merge])], (sync, backlinks) => { | |
58 … | + if (sync) { | |
59 … | + return backlinks.reduce((result, msg) => { | |
60 … | + var c = msg.value.content | |
61 … | + if (c.type === 'vote' && c.vote && c.vote.link === id) { | |
62 … | + var value = result[msg.value.author] | |
63 … | + if (!value || value[0] < msg.value.timestamp) { | |
64 … | + result[msg.value.author] = [msg.value.timestamp, c.vote.value, c.vote.expression] | |
67 | 65 … | } |
68 | 66 … | } |
69 | - }}, | |
70 | - {$map: { | |
71 | - dest: 'dest', | |
72 | - id: 'key', | |
73 | - expression: ['value', 'content', 'vote', 'expression'], | |
74 | - value: ['value', 'content', 'vote', 'value'], | |
75 | - timestamp: 'timestamp', | |
76 | - author: ['value', 'author'] | |
77 | - }} | |
78 | - ] | |
79 | - }), (result, msg) => { | |
80 | - if (!result[msg.author]) { | |
81 | - result[msg.author] = [] | |
67 … | + return result | |
68 … | + }, {}) | |
69 … | + } else { | |
70 … | + return {} | |
82 | 71 … | } |
83 | - SortedArray.add(result[msg.author], msg, mostRecent) | |
84 | - return result | |
85 | - }, { | |
86 | - startValue: [] | |
87 | 72 … | }) |
73 … | + | |
74 … | + likes.push = merge.push | |
75 … | + likes.sync = backlinks.sync | |
88 | 76 … | return likes |
89 | 77 … | } |
90 | 78 … | } |
91 | 79 … | |
92 | 80 … | function getLikes (likes) { |
93 | 81 … | return Object.keys(likes).reduce((result, id) => { |
94 | - if (likes[id][0].value) { | |
82 … | + if (likes[id][1] > 0) { | |
95 | 83 … | result.push(id) |
96 | 84 … | } |
97 | 85 … | return result |
98 | 86 … | }, []) |
99 | 87 … | } |
100 | - | |
101 | -function mostRecent (a, b) { | |
102 | - return b.timestamp - a.timestamp | |
103 | -} |
package.json | ||
---|---|---|
@@ -1,7 +1,7 @@ | ||
1 | 1 … | { |
2 | 2 … | "name": "patchcore", |
3 | - "version": "1.5.4", | |
3 … | + "version": "1.8.1", | |
4 | 4 … | "description": "minimal core for ssb clients", |
5 | 5 … | "main": "index.js", |
6 | 6 … | "scripts": { |
7 | 7 … | "start": "electro example", |
@@ -36,11 +36,12 @@ | ||
36 | 36 … | "color-hash": "^1.0.3", |
37 | 37 … | "depnest": "^1.0.2", |
38 | 38 … | "emoji-named-characters": "^1.0.2", |
39 | 39 … | "es2040": "^1.2.4", |
40 … | + "hashlru": "^2.2.0", | |
40 | 41 … | "html-escape": "^2.0.0", |
41 | 42 … | "human-time": "0.0.1", |
42 | - "mutant": "^3.21.0", | |
43 … | + "mutant": "^3.21.2", | |
43 | 44 … | "mutant-pull-reduce": "^1.1.0", |
44 | 45 … | "pull-abortable": "^4.1.0", |
45 | 46 … | "pull-cat": "^1.1.11", |
46 | 47 … | "pull-reconnect": "0.0.3", |
@@ -48,9 +49,8 @@ | ||
48 | 49 … | "sheet-router": "^4.2.3", |
49 | 50 … | "simple-mime": "^0.1.0", |
50 | 51 … | "sorted-array-functions": "^1.0.0", |
51 | 52 … | "split-buffer": "^1.0.0", |
52 | - "ssb-avatar": "^0.2.0", | |
53 | 53 … | "ssb-client": "^4.4.0", |
54 | 54 … | "ssb-config": "^2.2.0", |
55 | 55 … | "ssb-feed": "^2.3.0", |
56 | 56 … | "ssb-keys": "^7.0.9", |
sbot.js | ||
---|---|---|
@@ -1,13 +1,12 @@ | ||
1 | 1 … | var pull = require('pull-stream') |
2 | 2 … | var defer = require('pull-defer') |
3 | -var { onceTrue } = require('mutant') | |
3 … | +var { Value, onceTrue, watch, Set: MutantSet } = require('mutant') | |
4 | 4 … | var ref = require('ssb-ref') |
5 | 5 … | var Reconnect = require('pull-reconnect') |
6 | 6 … | var createClient = require('ssb-client') |
7 | 7 … | var createFeed = require('ssb-feed') |
8 | 8 … | var nest = require('depnest') |
9 | -var Value = require('mutant/value') | |
10 | 9 … | var ssbKeys = require('ssb-keys') |
11 | 10 … | |
12 | 11 … | exports.needs = nest({ |
13 | 12 … | 'config.sync.load': 'first', |
@@ -52,13 +51,11 @@ | ||
52 | 51 … | |
53 | 52 … | var sbot = null |
54 | 53 … | var connection = Value() |
55 | 54 … | var connectionStatus = Value() |
56 | - var connectedPeers = Value([]) | |
57 | - var localPeers = Value([]) | |
55 … | + var connectedPeers = MutantSet() | |
56 … | + var localPeers = MutantSet() | |
58 | 57 … | |
59 | - setInterval(refreshPeers, 1e3) | |
60 | - | |
61 | 58 … | var rec = Reconnect(function (isConn) { |
62 | 59 … | function notify (value) { |
63 | 60 … | isConn(value); connectionStatus.set(value) |
64 | 61 … | } |
@@ -76,9 +73,8 @@ | ||
76 | 73 … | }) |
77 | 74 … | |
78 | 75 … | connection.set(sbot) |
79 | 76 … | notify() |
80 | - refreshPeers() | |
81 | 77 … | }) |
82 | 78 … | }) |
83 | 79 … | |
84 | 80 … | var internal = { |
@@ -89,8 +85,38 @@ | ||
89 | 85 … | sbot.add(msg, cb) |
90 | 86 … | }) |
91 | 87 … | } |
92 | 88 … | |
89 … | + watch(connection, (sbot) => { | |
90 … | + if (sbot) { | |
91 … | + sbot.gossip.peers((err, peers) => { | |
92 … | + if (err) return console.error(err) | |
93 … | + connectedPeers.set(peers.filter(x => x.state === 'connected').map(x => x.key)) | |
94 … | + localPeers.set(peers.filter(x => x.source === 'local').map(x => x.key)) | |
95 … | + }) | |
96 … | + pull( | |
97 … | + sbot.gossip.changes(), | |
98 … | + pull.drain(data => { | |
99 … | + if (data.peer) { | |
100 … | + if (data.type === 'remove') { | |
101 … | + connectedPeers.delete(data.peer.key) | |
102 … | + localPeers.delete(data.peer.key) | |
103 … | + } else { | |
104 … | + if (data.peer.source === 'local') { | |
105 … | + localPeers.add(data.peer.key) | |
106 … | + } | |
107 … | + if (data.peer.state === 'connected') { | |
108 … | + connectedPeers.add(data.peer.key) | |
109 … | + } else { | |
110 … | + connectedPeers.delete(data.peer.key) | |
111 … | + } | |
112 … | + } | |
113 … | + } | |
114 … | + }) | |
115 … | + ) | |
116 … | + } | |
117 … | + }) | |
118 … | + | |
93 | 119 … | var feed = createFeed(internal, keys, {remote: true}) |
94 | 120 … | |
95 | 121 … | return { |
96 | 122 … | sbot: { |
@@ -223,18 +249,8 @@ | ||
223 | 249 … | // cache[msg.key] = msg.value |
224 | 250 … | // api.sbot.hook.feed(msg) |
225 | 251 … | } |
226 | 252 … | } |
227 | - | |
228 | - function refreshPeers () { | |
229 | - if (sbot) { | |
230 | - sbot.gossip.peers((err, peers) => { | |
231 | - if (err) return console.error(err) | |
232 | - connectedPeers.set(peers.filter(x => x.state === 'connected').map(x => x.key)) | |
233 | - localPeers.set(peers.filter(x => x.source === 'local').map(x => x.key)) | |
234 | - }) | |
235 | - } | |
236 | - } | |
237 | 253 … | } |
238 | 254 … | |
239 | 255 … | function Hash (onHash) { |
240 | 256 … | var buffers = [] |
backlinks/obs.js | ||
---|---|---|
@@ -1,0 +1,92 @@ | ||
1 … | +var nest = require('depnest') | |
2 … | +var Value = require('mutant/value') | |
3 … | +var computed = require('mutant/computed') | |
4 … | +var Abortable = require('pull-abortable') | |
5 … | +var resolve = require('mutant/resolve') | |
6 … | +var pull = require('pull-stream') | |
7 … | +var onceIdle = require('mutant/once-idle') | |
8 … | + | |
9 … | +exports.needs = nest({ | |
10 … | + 'sbot.pull.backlinks': 'first' | |
11 … | +}) | |
12 … | + | |
13 … | +exports.gives = nest('backlinks.obs.for', true) | |
14 … | + | |
15 … | +exports.create = function (api) { | |
16 … | + var cache = {} | |
17 … | + | |
18 … | + // cycle remove sets for fast cleanup | |
19 … | + var newRemove = new Set() | |
20 … | + var oldRemove = new Set() | |
21 … | + | |
22 … | + // run cache cleanup every 5 seconds | |
23 … | + // an item will be removed from cache between 5 - 10 seconds after release | |
24 … | + // this ensures that the data is still available for a page reload | |
25 … | + var timer = setInterval(() => { | |
26 … | + oldRemove.forEach(id => { | |
27 … | + if (cache[id]) { | |
28 … | + cache[id].destroy() | |
29 … | + delete cache[id] | |
30 … | + } | |
31 … | + }) | |
32 … | + oldRemove.clear() | |
33 … | + | |
34 … | + // cycle | |
35 … | + var hold = oldRemove | |
36 … | + oldRemove = newRemove | |
37 … | + newRemove = hold | |
38 … | + }, 5e3) | |
39 … | + | |
40 … | + if (timer.unref) timer.unref() | |
41 … | + | |
42 … | + return nest({ | |
43 … | + 'backlinks.obs.for': (id) => backlinks(id) | |
44 … | + }) | |
45 … | + | |
46 … | + function backlinks (id) { | |
47 … | + if (!cache[id]) { | |
48 … | + var sync = Value(false) | |
49 … | + var aborter = Abortable() | |
50 … | + var collection = Value([]) | |
51 … | + | |
52 … | + // try not to saturate the thread | |
53 … | + onceIdle(() => { | |
54 … | + pull( | |
55 … | + api.sbot.pull.backlinks({ | |
56 … | + query: [ {$filter: { dest: id }} ], | |
57 … | + index: 'DTA', // use asserted timestamps | |
58 … | + live: true | |
59 … | + }), | |
60 … | + aborter, | |
61 … | + pull.drain((msg) => { | |
62 … | + if (msg.sync) { | |
63 … | + sync.set(true) | |
64 … | + } else { | |
65 … | + var value = resolve(collection) | |
66 … | + value.push(msg) | |
67 … | + collection.set(value) | |
68 … | + } | |
69 … | + }) | |
70 … | + ) | |
71 … | + }) | |
72 … | + | |
73 … | + cache[id] = computed([collection], x => x, { | |
74 … | + onListen: () => use(id), | |
75 … | + onUnlisten: () => release(id) | |
76 … | + }) | |
77 … | + | |
78 … | + cache[id].destroy = aborter.abort | |
79 … | + cache[id].sync = sync | |
80 … | + } | |
81 … | + return cache[id] | |
82 … | + } | |
83 … | + | |
84 … | + function use (id) { | |
85 … | + newRemove.delete(id) | |
86 … | + oldRemove.delete(id) | |
87 … | + } | |
88 … | + | |
89 … | + function release (id) { | |
90 … | + newRemove.add(id) | |
91 … | + } | |
92 … | +} |
Built with git-ssb-web