| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| require('es6-shim'); |
| |
| var verror = require('vanadium').verror; |
| |
| var $ = require('./util/jquery'); |
| var defineClass = require('./util/define-class'); |
| var debug = require('./debug'); |
| |
| // TODO(rosswang): generalize this |
| var ESC = { |
| '_': '_', |
| '.': 'd', |
| '@': 'a' |
| }; |
| |
| var INV = {}; |
| $.each(ESC, function(k, v) { |
| INV[v] = k; |
| }); |
| |
| function escapeUsername(str) { |
| return str.replace(/_|\.|@/g, function(m) { |
| return '_' + ESC[m]; |
| }); |
| } |
| |
| function unescapeUsername(str) { |
| return str.replace(/_(.)/g, function(m, p1) { |
| return INV[p1]; |
| }); |
| } |
| |
| function invitationKey(recipient, owner, tripId) { |
| return [ |
| 'invitations', |
| escapeUsername(recipient), |
| escapeUsername(owner), |
| tripId |
| ]; |
| } |
| |
| function tripSgName(tripId) { |
| return 'trip-' + tripId; |
| } |
| |
| var InvitationManager = defineClass({ |
| publics: { |
| invite: function(recipient, owner, tripId) { |
| var self = this; |
| |
| return this.sgmPromise.then(function(sgm) { |
| return Promise.all([ |
| self.addTripCollaborator(owner, tripId, recipient), |
| sgm.joinSyncGroup(recipient, 'invitations') |
| ]).then(function() { |
| return sgm.syncbaseWrapper.put( |
| invitationKey(recipient, owner, tripId), self.username); |
| }); |
| }); |
| }, |
| |
| getUsername: function() { |
| return this.username; |
| } |
| }, |
| |
| privates: { |
| invitation: defineClass.innerClass({ |
| publics: { |
| accept: function() { |
| return this.outer.joinTripSyncGroup(this.owner, this.tripId) |
| .then(this.delete); |
| }, |
| |
| decline: function() { |
| return this.delete(); |
| } |
| }, |
| |
| privates: { |
| delete: function() { |
| var self = this; |
| |
| var username = this.outer.username; |
| return this.outer.syncbasePromise.then(function(syncbase) { |
| return syncbase.delete(invitationKey( |
| username, self.owner, self.tripId)); |
| }); |
| } |
| }, |
| |
| constants: [ 'owner', 'tripId', 'sender' ], |
| |
| events: { |
| onDismiss: 'memory once' |
| }, |
| |
| init: function(owner, tripId, sender, callbacks) { |
| this.owner = owner; |
| this.tripId = tripId; |
| this.sender = sender; |
| callbacks.dismiss = this.onDismiss; |
| } |
| }), |
| |
| createTripSyncGroup: function(tripId, initialCollaborators) { |
| return this.sgmPromise.then(function(sgm) { |
| return sgm.createSyncGroup(tripSgName(tripId), [['trips', tripId]], |
| [sgm.identity.username].concat(initialCollaborators)); |
| }); |
| }, |
| |
| joinTripSyncGroup: function(owner, tripId) { |
| return this.sgmPromise.then(function(sgm) { |
| return sgm.joinSyncGroup(owner, tripSgName(tripId)); |
| }); |
| }, |
| |
| addTripCollaborator: function(owner, tripId, collaborator) { |
| var self = this; |
| |
| return this.sgmPromise.then(function(sgm) { |
| return sgm.addCollaborator(owner, tripSgName(tripId), collaborator) |
| .catch(function(err) { |
| if (err instanceof verror.NoExistError && |
| owner === self.username) { |
| return self.createTripSyncGroup(tripId, collaborator); |
| } else { |
| throw err; |
| } |
| }); |
| }); |
| }, |
| |
| manageTripSyncGroups: function(trips) { |
| var self = this; |
| |
| //TODO(rosswang): maybe make this more intelligent, and handle ejection |
| if (trips) { |
| $.each(trips, function(tripId, trip) { |
| if (trip.owner) { |
| self.joinTripSyncGroup(trip.owner, tripId) |
| .catch(function(err) { |
| if (!(err instanceof verror.NoExistError)) { |
| throw err; |
| } |
| }).catch(self.onError); |
| } |
| }); |
| } |
| }, |
| |
| processUpdates: function(data) { |
| var self = this; |
| |
| this.manageTripSyncGroups(data.trips); |
| |
| var toMe = data.invitations && |
| data.invitations[escapeUsername(this.username)]; |
| if (toMe) { |
| $.each(toMe, function(owner, ownerRecords) { |
| var ownerInvites = self.invitations[owner]; |
| if (!ownerInvites) { |
| ownerInvites = self.invitations[owner] = {}; |
| } |
| |
| var uOwner; |
| |
| $.each(ownerRecords, function(tripId, sender) { |
| var record = ownerInvites[tripId]; |
| if (record) { |
| record.seen = true; |
| } else { |
| if (!uOwner) { |
| uOwner = unescapeUsername(owner); |
| } |
| |
| debug.log('Received invite from ' + sender + ' to ' + uOwner + |
| ':' + tripId); |
| |
| var callbacks = {}; |
| var invite = self.invitation(uOwner, tripId, sender, callbacks); |
| ownerInvites[tripId] = { |
| invite: invite, |
| dismiss: callbacks.dismiss, |
| seen: true |
| }; |
| self.onInvite(invite); |
| } |
| }); |
| }); |
| } |
| |
| if (this.invitations) { |
| $.each(this.invitations, function(owner, ownerRecords) { |
| $.each(ownerRecords, function(tripId, record) { |
| if (record.seen) { |
| delete record.seen; |
| } else { |
| delete ownerRecords[tripId]; |
| record.dismiss(); |
| } |
| }); |
| }); |
| } |
| } |
| }, |
| |
| events: { |
| /** |
| * @param invitation |
| */ |
| onInvite: '', |
| |
| onError: 'memory' |
| }, |
| |
| init: function(sgmPromise) { |
| var self = this; |
| |
| this.syncbasePromise = sgmPromise.then(function(sgm) { |
| self.username = sgm.identity.username; |
| sgm.syncbaseWrapper.onUpdate.add(self.processUpdates); |
| return sgm.syncbaseWrapper; |
| }); |
| this.sgmPromise = sgmPromise; |
| |
| this.invitations = {}; |
| |
| sgmPromise.then(function(sgm) { |
| sgm.createSyncGroup('invitations', |
| [['invitations', escapeUsername(self.username)]], ['...']) |
| .catch(self.onError); |
| }); |
| } |
| }); |
| |
| module.exports = InvitationManager; |