git ssb

0+

Dominic / ssb-peer-invites



Tree: c9b680a5f7988a98d99288af991d85a88c1f1398

Files: c9b680a5f7988a98d99288af991d85a88c1f1398 / index.js

5524 bytesRaw
1var I = require('./valid')
2var Reduce = require('flumeview-reduce')
3
4function code(err, c) {
5 err.code = 'user-invites:'+c
6 return err
7}
8var pull = require('pull-stream')
9function all (stream, cb) {
10 return pull(stream, pull.collect(cb))
11}
12
13/*
14uxer (someone who observes an invite, but not directly involved):
15
16if 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
38pub
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
47exports.name = 'invites'
48
49exports.version = '1.0.0'
50exports.manifest = {
51 getInvite: 'async',
52 accept: 'async',
53// create: 'async'
54}
55
56exports.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
68exports.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