git ssb

7+

dinoworm ๐Ÿ› / patchcore



Tree: 5a28f5df1543b0e5813e5fe7dbc74f2ec907ff5a

Files: 5a28f5df1543b0e5813e5fe7dbc74f2ec907ff5a / contact / obs.js

3641 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', 'raw'],
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 raw: (key) => get(key, cache)
37 },
38 'sbot.hook.publish': function (msg) {
39 if (!isContact(msg)) return
40
41 // HACK: make interface more responsive when sbot is busy
42 var source = msg.value.author
43 var dest = msg.value.content.contact
44 var tristate = ( // from ssb-friends
45 msg.value.content.following ? true
46 : msg.value.content.flagged || msg.value.content.blocking ? false
47 : null
48 )
49
50 update(source, { [dest]: tristate }, cache)
51 update(dest, { [source]: tristate }, reverseCache)
52 }
53 })
54
55 function matchingValueKeys (state, value) {
56 var obs = computed(state, state => {
57 return Object.keys(state).filter(key => {
58 return state[key] === value
59 })
60 })
61
62 obs.sync = sync
63 return obs
64 }
65
66 function loadCache () {
67 pull(
68 api.sbot.pull.stream(sbot => sbot.friends.stream({live: true})),
69 pull.drain(item => {
70 if (!sync()) {
71 // populate observable cache
72 var reverse = {}
73 for (var source in item) {
74 if (ref.isFeed(source)) {
75 update(source, item[source], cache)
76
77 // generate reverse lookup
78 for (let dest in item[source]) {
79 reverse[dest] = reverse[dest] || {}
80 reverse[dest][source] = item[source][dest]
81 }
82 }
83 }
84
85 // populate reverse observable cache
86 for (let dest in reverse) {
87 update(dest, reverse[dest], reverseCache)
88 }
89
90 sync.set(true)
91 } else if (item && ref.isFeed(item.from) && ref.isFeed(item.to)) {
92 // handle realtime updates
93 update(item.from, {[item.to]: item.value}, cache)
94 update(item.to, {[item.from]: item.value}, reverseCache)
95 }
96 })
97 )
98 }
99
100 function update (sourceId, values, lookup) {
101 // ssb-friends: values = {
102 // keyA: true|null|false (friend, neutral, block)
103 // keyB: true|null|false (friend, neutral, block)
104 // }
105 var state = get(sourceId, lookup)
106 var lastState = state()
107 var changed = false
108
109 for (var targetId in values) {
110 if (values[targetId] !== lastState[targetId]) {
111 lastState[targetId] = values[targetId]
112 changed = true
113 }
114 }
115
116 if (changed) {
117 state.set(lastState)
118 }
119 }
120
121 function get (id, lookup) {
122 if (!ref.isFeed(id)) throw new Error('Contact state requires an id!')
123 if (!cacheLoading) {
124 cacheLoading = true
125 loadCache()
126 }
127 if (!lookup[id]) {
128 lookup[id] = Value({})
129 }
130 return lookup[id]
131 }
132}
133
134function isContact (msg) {
135 return msg.value && msg.value.content && msg.value.content.type === 'contact'
136}
137

Built with git-ssb-web