Files: c9b680a5f7988a98d99288af991d85a88c1f1398 / index.js
5524 bytesRaw
1 | var I = require('./valid') |
2 | var Reduce = require('flumeview-reduce') |
3 | |
4 | function code(err, c) { |
5 | err.code = 'user-invites:'+c |
6 | return err |
7 | } |
8 | var pull = require('pull-stream') |
9 | function all (stream, cb) { |
10 | return pull(stream, pull.collect(cb)) |
11 | } |
12 | |
13 | /* |
14 | uxer (someone who observes an invite, but not directly involved): |
15 | |
16 | if we see |
17 | someone post an invite I |
18 | someone else post a confirmation C that I has been accepted A |
19 | // OOO that A |
20 | emded that A inside a C(A) |
21 | match valid I->A's and interpret them like follows. |
22 | |
23 | we only care about confirmations if it's of an invite we follow, |
24 | and it hasn't been confirmed already. |
25 | |
26 | { |
27 | invited: { |
28 | <alice>: { <bob>: true, ...} |
29 | } |
30 | |
31 | invites: { <invites>, ...} |
32 | accepts: { <accepts>, ...} |
33 | |
34 | } |
35 | |
36 | --- |
37 | |
38 | pub |
39 | |
40 | someone connects, using key from an open invite I |
41 | they request that invite I (by it's id) |
42 | they send a message accepting A the invite. |
43 | the pub then posts confirmation (I,A) |
44 | |
45 | */ |
46 | |
47 | exports.name = 'invites' |
48 | |
49 | exports.version = '1.0.0' |
50 | exports.manifest = { |
51 | getInvite: 'async', |
52 | accept: 'async', |
53 | // create: 'async' |
54 | } |
55 | |
56 | exports.permissions = { |
57 | // master: {allow: ['create']} |
58 | } |
59 | |
60 | // KNOWN BUG: it's possible to accept an invite more than once, |
61 | // but peers will ignore subsequent acceptances. it's possible |
62 | // that this could create confusion in certain situations. |
63 | // (but you'd get a feed that some peers thought was invited by Alice |
64 | // other peers would think a different feed accepted that invite) |
65 | // I guess the answer is to let alice reject the second invite?) |
66 | // that would be easier to do if this was a levelreduce? (keys: reduce, instead of a single reduce?) |
67 | |
68 | exports.init = function (sbot, config) { |
69 | |
70 | var invites = sbot._flumeUse('invites', Reduce(1, function (acc, data) { |
71 | if(!acc) acc = {invited: {}, invites:{}, accepts: {}} |
72 | |
73 | var msg = data.value |
74 | var invite, accept |
75 | if(msg.content.type === 'invite') { |
76 | invite = msg |
77 | accept = acc.accepts[data.key] |
78 | } |
79 | else if(msg.content.type === 'invite/accept') { |
80 | accept = msg |
81 | invite = acc.invites[accept.content.receipt] |
82 | } |
83 | else if(msg.content.type === 'invite/confirm') { |
84 | accept = msg.content.embed |
85 | invite = acc.invites[accept.content.receipt] |
86 | } |
87 | if(invite && accept) { |
88 | if(invite === true) |
89 | return acc |
90 | var invite_id = accept.content.receipt |
91 | try { |
92 | I.verifyAccept(accept, invite) |
93 | //delete matched invites, but _only_ if they are valid. |
94 | delete acc.accepts[invite_id] |
95 | //but remember that this invite has been processed. |
96 | acc.invites[invite_id] = true |
97 | } catch (err) { |
98 | return acc //? or store something? |
99 | } |
100 | } |
101 | else if(invite) |
102 | acc.invites[data.key] = invite |
103 | else if(accept) |
104 | acc.accepts[accept.receipt] = accept |
105 | |
106 | return acc |
107 | |
108 | })) |
109 | |
110 | sbot.auth.hook(function (fn, args) { |
111 | var id = args[0], cb = args[1] |
112 | invites.get(function (err, v) { |
113 | if(err) return cb(err) |
114 | for(var k in v.invites) { |
115 | if(v.invites[k].content.invite === id) |
116 | return cb(null, { |
117 | allow: ['invites.getInvite', 'invites.accept'], |
118 | deny: null |
119 | }) |
120 | } |
121 | }) |
122 | }) |
123 | |
124 | invites.getInvite = function (invite_id, cb) { |
125 | var self = this |
126 | invites.get(function (err, v) { |
127 | var invite = v.invites[invite_id] |
128 | if(err) return cb(err) |
129 | if(!invite) |
130 | cb(code( |
131 | new Error('unknown invite:'+invite_id), |
132 | 'unknown-invite' |
133 | )) |
134 | else if(invite === true) |
135 | //TODO just retrive all confirmations we know about |
136 | //via links. |
137 | sbot.get(invite_id, cb) |
138 | //only allow the guest to request their own invite. |
139 | else if(self.id !== invite.content.invite) |
140 | cb(code( |
141 | new Error('invite did not match client id'), |
142 | 'invite-mismatch' |
143 | )) |
144 | else |
145 | cb(null, v.invites[invite_id]) |
146 | }) |
147 | } |
148 | |
149 | var accepted = {} |
150 | |
151 | invites.accept = function (accept, cb) { |
152 | //check if the invite in question hasn't already been accepted. |
153 | invites.get(function (err, v) { |
154 | var invite_id = accept.content.receipt |
155 | var invite = v.invites[invite_id] |
156 | |
157 | if(invite === true || accepted[invite_id]) |
158 | //TODO: this should return the confirmation, not an error. |
159 | return all( |
160 | sbot.links({dest: invite_id, values: true, keys: false, meta: false}), |
161 | function (err, confirms) { |
162 | if(err) cb(err) |
163 | else cb(null, |
164 | confirms.filter(function (e) { |
165 | try { |
166 | return ( |
167 | e.content.type === 'invite/confirm' && |
168 | e.content.embed.content.receipt === invite_id |
169 | ) |
170 | } catch (err) { |
171 | return false |
172 | } |
173 | })[0] |
174 | ) |
175 | } |
176 | ) |
177 | |
178 | try { |
179 | I.verifyAccept(accept, invite) |
180 | } catch (err) { |
181 | return cb(err) |
182 | } |
183 | //there is a little race condition here |
184 | accepted[invite_id] = true |
185 | sbot.publish({ |
186 | type: 'invite/confirm', |
187 | embed: accept, |
188 | //second pointer back to receipt, so that links can find it |
189 | //(since it unfortunately does not handle links nested deeper |
190 | //inside objects. when we look up the message, |
191 | //confirm that content.embed.content.receipt is the same) |
192 | receipt: accept.content.receipt |
193 | }, function (err, data) { |
194 | delete accepted[invite_id] |
195 | cb(err, data.value) |
196 | }) |
197 | }) |
198 | } |
199 | |
200 | return invites |
201 | |
202 | } |
203 | |
204 |
Built with git-ssb-web