git ssb

7+

dinoworm ๐Ÿ› / patchcore



Tree: 21151e46b8d426b692c971ea2aa84a484ccc4b4d

Files: 21151e46b8d426b692c971ea2aa84a484ccc4b4d / contact / obs.js

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

Built with git-ssb-web