Files: 7bd9775b75c52ca02f3e47ccb20e59533accb156 / contact / obs.js
3222 bytesRaw
1 | var nest = require('depnest') |
2 | var {Value, computed} = require('mutant') |
3 | var pull = require('pull-stream') |
4 | var ref = require('ssb-ref') |
5 | |
6 | exports.needs = nest({ |
7 | 'sbot.pull.stream': 'first' |
8 | }) |
9 | |
10 | exports.gives = nest({ |
11 | 'contact.obs': ['following', 'followers', 'blocking', 'blockers'], |
12 | 'sbot.hook.publish': true |
13 | }) |
14 | |
15 | exports.create = function (api) { |
16 | var cacheLoading = false |
17 | var cache = {} |
18 | var sync = Value(false) |
19 | |
20 | return nest({ |
21 | 'contact.obs': { |
22 | following: (id) => values(get(id), 'following', true), |
23 | followers: (id) => values(get(id), 'followers', true), |
24 | blocking: (id) => values(get(id), 'blocking', true), |
25 | blockers: (id) => values(get(id), 'blockers', true), |
26 | }, |
27 | 'sbot.hook.publish': function (msg) { |
28 | if (isContact(msg)) { |
29 | // HACK: make interface more responsive when sbot is busy |
30 | var source = msg.value.author |
31 | var dest = msg.value.content.contact |
32 | if (typeof msg.value.content.following === 'boolean') { |
33 | update(source, { |
34 | following: { |
35 | [dest]: [msg.value.content] |
36 | } |
37 | }) |
38 | update(dest, { |
39 | followers: { |
40 | [source]: [msg.value.content] |
41 | } |
42 | }) |
43 | } |
44 | if (typeof msg.value.content.blocking === 'boolean') { |
45 | update(source, { |
46 | blocking: { |
47 | [dest]: [msg.value.content] |
48 | } |
49 | }) |
50 | update(dest, { |
51 | blockers: { |
52 | [source]: [msg.value.content] |
53 | } |
54 | }) |
55 | } |
56 | } |
57 | } |
58 | }) |
59 | |
60 | function values (state, key, compare) { |
61 | var obs = computed([state, key, compare], getIds) |
62 | obs.sync = sync |
63 | return obs |
64 | } |
65 | |
66 | function loadCache () { |
67 | pull( |
68 | api.sbot.pull.stream(sbot => sbot.contacts.stream({live: true})), |
69 | pull.drain(item => { |
70 | for (var target in item) { |
71 | if (ref.isFeed(target)) update(target, item[target]) |
72 | } |
73 | |
74 | if (!sync()) { |
75 | sync.set(true) |
76 | } |
77 | }) |
78 | ) |
79 | } |
80 | |
81 | function update (id, values) { |
82 | // values = { following, followers, blocking, blockedBy, ... } |
83 | var state = get(id) |
84 | var lastState = state() |
85 | var changed = false |
86 | for (var key in values) { |
87 | var valuesForKey = lastState[key] = lastState[key] || {} |
88 | for (var dest in values[key]) { |
89 | var value = values[key][dest] |
90 | if (!valuesForKey[dest] || value[1] > valuesForKey[dest][1] || !values[1] || !valuesForKey[dest[1]]) { |
91 | valuesForKey[dest] = value |
92 | changed = true |
93 | } |
94 | } |
95 | } |
96 | if (changed) { |
97 | state.set(lastState) |
98 | } |
99 | } |
100 | |
101 | function get (id) { |
102 | if (!ref.isFeed(id)) throw new Error('Contact state requires an id!') |
103 | if (!cacheLoading) { |
104 | cacheLoading = true |
105 | loadCache() |
106 | } |
107 | if (!cache[id]) { |
108 | cache[id] = Value({}) |
109 | } |
110 | return cache[id] |
111 | } |
112 | } |
113 | |
114 | function getIds (state, key, compare) { |
115 | var result = new Set() |
116 | if (state[key]) { |
117 | for (var dest in state[key]) { |
118 | if (state[key][dest][0] === compare) { |
119 | result.add(dest) |
120 | } |
121 | } |
122 | } |
123 | return result |
124 | } |
125 | |
126 | function isContact (msg) { |
127 | return msg.value && msg.value.content && msg.value.content.type === 'contact' |
128 | } |
129 |
Built with git-ssb-web