git ssb

7+

dinoworm ๐Ÿ› / patchcore



Tree: 8e5d3ee4715c946c3cd8fb59f953ff358997b396

Files: 8e5d3ee4715c946c3cd8fb59f953ff358997b396 / contact / obs.js

3584 bytesRaw
1var nest = require('depnest')
2var { Value, computed } = require('mutant')
3var pull = require('pull-stream')
4var ref = require('ssb-ref')
5
6exports.needs = nest({
7 'sbot.pull.stream': 'first',
8})
9
10exports.gives = nest({
11 'contact.obs': ['following', 'followers', 'blocking', 'blockers'],
12 'sbot.hook.publish': true
13})
14
15exports.create = function (api) {
16 var cacheLoading = false
17 var cache = {}
18 var reverseCache = {}
19
20 var sync = Value(false)
21
22 return nest({
23 'contact.obs': {
24
25 // states:
26 // true = following,
27 // null = neutral (may have unfollowed),
28 // false = blocking
29
30 following: (key) => matchingValueKeys(get(key, cache), true),
31 followers: (key) => matchingValueKeys(get(key, reverseCache), true),
32 blocking: (key) => matchingValueKeys(get(key, cache), false),
33 blockers: (key) => matchingValueKeys(get(key, reverseCache), false)
34 },
35 'sbot.hook.publish': function (msg) {
36 if (!isContact(msg)) return
37
38 // HACK: make interface more responsive when sbot is busy
39 var source = msg.value.author
40 var dest = msg.value.content.contact
41 var tristate = ( // from ssb-friends
42 msg.value.content.following ? true
43 : msg.value.content.flagged || msg.value.content.blocking ? false
44 : null
45 )
46
47 update(source, { [dest]: tristate }, cache)
48 update(dest, { [source]: tristate }, reverseCache)
49 }
50 })
51
52 function matchingValueKeys (state, value) {
53 var obs = computed(state, state => {
54 return Object.keys(state).filter(key => {
55 return state[key] === value
56 })
57 })
58
59 obs.sync = sync
60 return obs
61 }
62
63 function loadCache () {
64 pull(
65 api.sbot.pull.stream(sbot => sbot.friends.stream({live: true})),
66 pull.drain(item => {
67 if (!sync()) {
68 // populate observable cache
69 var reverse = {}
70 for (var source in item) {
71 if (ref.isFeed(source)) {
72 update(source, item[source], cache)
73
74 // generate reverse lookup
75 for (var dest in item[source]) {
76 reverse[dest] = reverse[dest] || {}
77 reverse[dest][source] = item[source][dest]
78 }
79 }
80 }
81
82 // populate reverse observable cache
83 for (var dest in reverse) {
84 update(dest, reverse[dest], reverseCache)
85 }
86
87 sync.set(true)
88 } else if (item && ref.isFeed(item.from) && ref.isFeed(item.to)) {
89 // handle realtime updates
90 update(item.from, {[item.to]: item.value}, cache)
91 update(item.to, {[item.from]: item.value}, reverseCache)
92 }
93 })
94 )
95 }
96
97 function update (sourceId, values, lookup) {
98 // ssb-friends: values = {
99 // keyA: true|null|false (friend, neutral, block)
100 // keyB: true|null|false (friend, neutral, block)
101 // }
102 var state = get(sourceId, lookup)
103 var lastState = state()
104 var changed = false
105
106 for (var targetId in values) {
107 if (values[targetId] !== lastState[targetId]) {
108 lastState[targetId] = values[targetId]
109 changed = true
110 }
111 }
112
113 if (changed) {
114 state.set(lastState)
115 }
116 }
117
118 function get (id, lookup) {
119 if (!ref.isFeed(id)) throw new Error('Contact state requires an id!')
120 if (!cacheLoading) {
121 cacheLoading = true
122 loadCache()
123 }
124 if (!lookup[id]) {
125 lookup[id] = Value({})
126 }
127 return lookup[id]
128 }
129}
130
131function isContact (msg) {
132 return msg.value && msg.value.content && msg.value.content.type === 'contact'
133}
134

Built with git-ssb-web