Files: bcadbcc3325b20b2d11e736b46940303044e756c / lib / friends-with-sync.js
4913 bytesRaw
1 | var Graphmitter = require('graphmitter') |
2 | var pull = require('pull-stream') |
3 | var mlib = require('ssb-msgs') |
4 | var memview = require('level-memview') |
5 | var pushable = require('pull-pushable') |
6 | var mdm = require('mdmanifest') |
7 | var valid = require('scuttlebot/lib/validators') |
8 | var apidoc = require('scuttlebot/lib/apidocs').friends |
9 | |
10 | // friends plugin |
11 | // methods to analyze the social graph |
12 | // maintains a 'follow' and 'flag' graph |
13 | |
14 | function isFunction (f) { |
15 | return 'function' === typeof f |
16 | } |
17 | |
18 | function isString (s) { |
19 | return 'string' === typeof s |
20 | } |
21 | |
22 | function isFriend (friends, a, b) { |
23 | return friends[a] && friends[b] && friends[a][b] && friends[b][a] |
24 | } |
25 | |
26 | exports.name = 'friends' |
27 | exports.version = '1.0.0' |
28 | exports.manifest = mdm.manifest(apidoc) |
29 | |
30 | exports.init = function (sbot, config) { |
31 | |
32 | var graphs = { |
33 | follow: new Graphmitter(), |
34 | flag: new Graphmitter() |
35 | } |
36 | |
37 | // view processor |
38 | var syncCbs = [] |
39 | function awaitSync (cb) { |
40 | if (syncCbs) syncCbs.push(cb) |
41 | else cb() |
42 | } |
43 | |
44 | // read/watch the log for changes to the social graph |
45 | pull(sbot.createLogStream({ live: true }), pull.drain(function (msg) { |
46 | |
47 | if (msg.sync) { |
48 | syncCbs.forEach(function (cb) { cb() }) |
49 | syncCbs = null |
50 | |
51 | if (sbot.gossip) { |
52 | // prioritize friends |
53 | var friends = graphs['follow'].toJSON() |
54 | sbot.gossip.peers().forEach(function(peer) { |
55 | if (isFriend(friends, sbot.id, peer.key)) { |
56 | sbot.gossip.add(peer, 'friends') |
57 | } |
58 | }) |
59 | } |
60 | |
61 | return |
62 | } |
63 | |
64 | var c = msg.value.content |
65 | if (c.type == 'contact') { |
66 | mlib.asLinks(c.contact, 'feed').forEach(function (link) { |
67 | if ('following' in c) { |
68 | if (c.following) |
69 | graphs.follow.edge(msg.value.author, link.link, true) |
70 | else |
71 | graphs.follow.del(msg.value.author, link.link) |
72 | |
73 | } |
74 | if ('flagged' in c) { |
75 | if (c.flagged) |
76 | graphs.flag.edge(msg.value.author, link.link, c.flagged) |
77 | else |
78 | graphs.flag.del(msg.value.author, link.link) |
79 | } |
80 | }) |
81 | } |
82 | })) |
83 | |
84 | return { |
85 | |
86 | get: valid.sync(function (opts) { |
87 | var g = graphs[opts.graph || 'follow'] |
88 | if(!g) throw new Error('opts.graph must be provided') |
89 | return g.get(opts.source, opts.dest) |
90 | }, 'object?'), |
91 | |
92 | all: valid.async(function (graph, cb) { |
93 | if (typeof graph == 'function') { |
94 | cb = graph |
95 | graph = null |
96 | } |
97 | if (!graph) |
98 | graph = 'follow' |
99 | awaitSync(function () { |
100 | cb(null, graphs[graph] ? graphs[graph].toJSON() : null) |
101 | }) |
102 | }, 'string?'), |
103 | |
104 | path: valid.sync(function (opts) { |
105 | if(isString(opts)) |
106 | opts = {source: sbot.id, dest: opts} |
107 | return graphs.follow.path(opts) |
108 | |
109 | }, 'string|object'), |
110 | |
111 | createFriendStream: valid.source(function (opts) { |
112 | opts = opts || {} |
113 | var live = opts.live === true |
114 | var meta = opts.meta === true |
115 | var start = opts.start || sbot.id |
116 | var graph = graphs[opts.graph || 'follow'] |
117 | if(!graph) |
118 | return pull.error(new Error('unknown graph:' + opts.graph)) |
119 | var cancel, ps = pushable(function () { |
120 | cancel && cancel() |
121 | }) |
122 | |
123 | function push (to, hops) { |
124 | return ps.push(meta ? {id: to, hops: hops} : to) |
125 | } |
126 | |
127 | if (live) { |
128 | awaitSync(function () { |
129 | ps.push({sync: true}) |
130 | }) |
131 | } |
132 | |
133 | //by default, also emit your own key. |
134 | if(opts.self !== false) |
135 | push(start, 0) |
136 | |
137 | var conf = config.friends || {} |
138 | cancel = graph.traverse({ |
139 | start: start, |
140 | hops: opts.hops || conf.hops || 3, |
141 | max: opts.dunbar || conf.dunbar || 150, |
142 | each: function (_, to, hops) { |
143 | if(to !== start) push(to, hops) |
144 | } |
145 | }) |
146 | |
147 | if(!live) { cancel(); ps.end() } |
148 | |
149 | return ps |
150 | }, 'createFriendStreamOpts?'), |
151 | |
152 | hops: valid.async(function (start, graph, opts, cb) { |
153 | if (typeof opts == 'function') { // (start|opts, graph, cb) |
154 | cb = opts |
155 | opts = null |
156 | } else if (typeof graph == 'function') { // (start|opts, cb) |
157 | cb = graph |
158 | opts = graph = null |
159 | } |
160 | opts = opts || {} |
161 | if(isString(start)) { // (start, ...) |
162 | // first arg is id string |
163 | opts.start = start |
164 | } else if (start && typeof start == 'object') { // (opts, ...) |
165 | // first arg is opts |
166 | for (var k in start) |
167 | opts[k] = start[k] |
168 | } |
169 | |
170 | var conf = config.friends || {} |
171 | opts.start = opts.start || sbot.id |
172 | opts.dunbar = opts.dunbar || conf.dunbar || 150 |
173 | opts.hops = opts.hops || conf.hops || 3 |
174 | |
175 | var g = graphs[graph || 'follow'] |
176 | if (!g) |
177 | return cb(new Error('Invalid graph type: '+graph)) |
178 | |
179 | awaitSync(function () { |
180 | cb(null, g.traverse(opts)) |
181 | }) |
182 | }, ['feedId', 'string?', 'object?'], ['createFriendStreamOpts']) |
183 | } |
184 | } |
185 |
Built with git-ssb-web