First pass of working invitations
Change-Id: I62e37bfa0d7d6156b622df5723acb3ae38cd3348
diff --git a/src/invitation-manager.js b/src/invitation-manager.js
index e1a26a4..1323a53 100644
--- a/src/invitation-manager.js
+++ b/src/invitation-manager.js
@@ -6,6 +6,7 @@
var $ = require('./util/jquery');
var defineClass = require('./util/define-class');
+var debug = require('./debug');
// TODO(rosswang): generalize this
var ESC = {
@@ -31,42 +32,24 @@
});
}
-function invitationKey(owner, recipient) {
- return ['invitations', escapeUsername(owner), escapeUsername(recipient)];
+function invitationKey(recipient, owner, tripId) {
+ return [
+ 'invitations',
+ escapeUsername(recipient),
+ escapeUsername(owner),
+ tripId
+ ];
}
var InvitationManager = defineClass({
publics: {
- accept: function(owner) {
- },
-
- decline: function(owner) {
- return Promise.all([
- this.syncbasePromise,
- this.prereqs
- ]).then(function(args) {
- var syncbase = args[0];
- var username = args[1].identity.username;
-
- return syncbase.delete(invitationKey(owner, username));
- });
- },
-
- getActiveInvite: function() {
- return this.activeInvite;
- },
-
- invite: function(username) {
+ invite: function(recipient, owner, tripId) {
var self = this;
return this.groupManagerPromise.then(function(gm) {
- return gm.joinSyncGroup(username, 'invitations').then(function() {
- return self.prereqs;
- }).then(function(prereqs) {
- var owner = self.activeInvite || prereqs.identity.username;
-
- return gm.syncbaseWrapper.put(invitationKey(owner, username),
- prereqs.identity.username);
+ return gm.joinSyncGroup(recipient, 'invitations').then(function() {
+ return gm.syncbaseWrapper.put(invitationKey(recipient, owner, tripId),
+ self.username);
});
});
},
@@ -77,55 +60,103 @@
},
privates: {
+ invitation: defineClass.innerClass({
+ publics: {
+ accept: function() {
+ var self = this;
+
+ return this.outer.groupManagerPromise.then(function(gm) {
+ return gm.joinSyncGroup(self.owner, 'trip').then(function() {
+ return self.decline();
+ });
+ });
+ },
+
+ decline: 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;
+ }
+ }),
+
processUpdates: function(data) {
var self = this;
- if (data.invitations) {
- $.each(data.invitations, function(owner, record) {
+ var toMe;
+ if (data.invitations &&
+ (toMe = data.invitations[escapeUsername(this.username)])) {
+ $.each(toMe, function(owner, ownerRecords) {
var ownerInvites = self.invitations[owner];
if (!ownerInvites) {
ownerInvites = self.invitations[owner] = {};
}
- $.each(record, function(recipient, sender) {
- if (ownerInvites[recipient]) {
- delete ownerInvites[recipient];
+ var uOwner;
+
+ $.each(ownerRecords, function(tripId, sender) {
+ var record = ownerInvites[tripId];
+ if (record) {
+ record.seen = true;
} else {
- self.onInvite(unescapeUsername(owner),
- unescapeUsername(recipient), sender);
+ 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, record) {
- $.each(record, function(recipient, sender) {
- self.onDismiss(unescapeUsername(owner),
- unescapeUsername(recipient), sender);
+ $.each(this.invitations, function(owner, ownerRecords) {
+ $.each(ownerRecords, function(tripId, record) {
+ if (record.seen) {
+ delete record.seen;
+ } else {
+ delete ownerRecords[tripId];
+ record.dismiss();
+ }
});
});
}
-
- this.invitations = data.invitations || {};
}
},
events: {
/**
- * @param owner the user who owns the trip.
- * @param recipient the user invited to the trip.
- * @param sender the user who sent the invitation.
+ * @param invitation
*/
onInvite: '',
- /**
- * @param owner the user who owns the trip.
- * @param recipient the user invited to the trip.
- * @param sender the user who sent the invitation.
- */
- onDismiss: '',
-
onError: 'memory'
},
@@ -145,11 +176,13 @@
this.invitations = {};
prereqs.then(function(args) {
+ //this will have been set prior to groupManagerPromise completing
self.username = args.identity.username;
});
groupManagerPromise.then(function(gm) {
- gm.createSyncGroup('invitations', ['invitations'])
+ gm.createSyncGroup('invitations',
+ [['invitations', escapeUsername(self.username)]])
.catch(self.onError);
});
}
diff --git a/src/travel.js b/src/travel.js
index e595521..3e86d43 100644
--- a/src/travel.js
+++ b/src/travel.js
@@ -86,6 +86,27 @@
text: info,
promise: promise
}));
+ },
+
+ invite: function(recipient) {
+ var self = this;
+
+ this.info(strings.sendingInvite(recipient),
+ this.sync.invitationManager.invite(recipient,
+ this.sync.getActiveTripOwner(), this.sync.getActiveTripId())
+ .then(function() {
+ var me = self.sync.invitationManager.getUsername();
+ self.sync.message({
+ type: Message.INFO,
+ text: strings.invitationSent(recipient, me)
+ });
+ }, function(err) {
+ if (err.id === 'v.io/v23/verror.NoServers') {
+ throw strings.notReachable(recipient);
+ } else {
+ throw err;
+ }
+ }));
}
},
@@ -279,55 +300,40 @@
}
},
- dismissInvite: function(owner, sender) {
- var invite = this.invites[owner];
- if (invite) {
- invite.resolve(strings.invitationDismissed(sender, owner));
- delete this.invites[owner];
- }
- },
-
- handleInvite: function(owner, recipient, sender) {
+ handleInvite: function(invitation) {
var self = this;
var invitationManager = this.sync.invitationManager;
- var me = invitationManager.getUsername();
- if (recipient === me) {
- var async = new Deferred();
+ var sender = invitation.sender;
+ var owner = invitation.owner;
- this.dismissInvite(owner, sender);
- this.invites[owner] = async;
+ var async = new Deferred();
- var message = new Message({
- type: Message.INFO,
- html: strings.invitationReceived(sender, owner),
- promise: async.promise
- });
+ var message = new Message({
+ type: Message.INFO,
+ html: strings.invitationReceived(sender, owner),
+ promise: async.promise
+ });
- message.$.find('a[name=accept]').click(function() {
- invitationManager.accept(owner).then(function() {
- delete self.invites[owner];
- return strings.invitationAccepted(sender, owner);
- }).then(async.resolve, async.reject);
- return false;
- });
- message.$.find('a[name=decline]').click(function() {
- invitationManager.decline(owner).then(function() {
- delete self.invites[owner];
- return strings.invitationDeclined(sender, owner);
- }).then(async.resolve, async.reject);
- return false;
- });
+ message.$.find('a[name=accept]').click(function() {
+ invitation.accept().then(function() {
+ self.sync.watchForTrip(invitation.tripId);
+ return strings.invitationAccepted(sender, owner);
+ }).then(async.resolve, async.reject);
+ return false;
+ });
+ message.$.find('a[name=decline]').click(function() {
+ invitationManager.decline().then(function() {
+ return strings.invitationDeclined(sender, owner);
+ }).then(async.resolve, async.reject);
+ return false;
+ });
- self.messages.push(message);
- }
- },
+ invitation.onDismiss.add(function() {
+ async.resolve(strings.invitationDismissed(sender, owner));
+ });
- handleInviteDismiss: function(owner, recipient, sender) {
- var me = this.sync.invitationManager.getUsername();
- if (recipient === me) {
- this.dismissInvite(owner, sender);
- }
+ this.messages.push(message);
},
handleUserMessage: function(message, raw) {
@@ -376,8 +382,6 @@
opts = opts || {};
var vanadiumWrapper = opts.vanadiumWrapper || vanadiumWrapperDefault;
- this.invites = {};
-
var destinations = this.destinations = new Destinations();
destinations.onAdd.add(this.handleDestinationAdd);
destinations.onRemove.add(this.handleDestinationRemove);
@@ -437,7 +441,6 @@
});
sync.invitationManager.onInvite.add(this.handleInvite);
- sync.invitationManager.onDismiss.add(this.handleInviteDismiss);
messages.onMessage.add(this.handleUserMessage);
@@ -522,23 +525,7 @@
this.commands = {
invite: {
- op: function(username) {
- this.info(strings.sendingInvite(username),
- this.sync.invitationManager.invite(username)
- .then(function() {
- var me = self.sync.invitationManager.getUsername();
- self.sync.message({
- type: Message.INFO,
- text: strings.invitationSent(username, me)
- });
- }, function(err) {
- if (err.id === 'v.io/v23/verror.NoServers') {
- throw strings.notReachable(username);
- } else {
- throw err;
- }
- }));
- }
+ op: this.invite
},
status: {
diff --git a/src/travelsync.js b/src/travelsync.js
index 7b217dc..e3406ac 100644
--- a/src/travelsync.js
+++ b/src/travelsync.js
@@ -61,7 +61,15 @@
pushStatus: function() {
},
- setActiveTrip: function(tripId) {
+ getActiveTripId: function() {
+ return this.activeTripId;
+ },
+
+ getActiveTripOwner: function() {
+ return this.activeTripOwner;
+ },
+
+ setActiveTripId: function(tripId, pull) {
var self = this;
this.activeTripId = tripId;
@@ -71,8 +79,15 @@
syncbase.put(['user', 'tripMetadata', tripId, 'latestSwitch'],
Date.now()).catch(self.onError);
- return syncbase.refresh();
+ return pull? syncbase.refresh() : Promise.resolve();
});
+ },
+
+ /**
+ * Sets the active trip to the given trip ID after it is available.
+ */
+ watchForTrip: function(tripId) {
+ this.awaitedTripId = tripId;
}
},
@@ -485,8 +500,21 @@
},
processTrips: function(userTripMetadata, trips) {
+ var self = this;
var trip;
+ if (this.awaitedTripId) {
+ this.setActiveTripId(this.awaitedTripId, false);
+ delete this.awaitedTripId;
+
+ /* Override isNascent check this frame. (Subsequently syncbase will be
+ * up to date.) */
+ if (!userTripMetadata) {
+ userTripMetadata = {};
+ }
+ userTripMetadata[this.activeTripId].latestSwitch = Date.now();
+ }
+
if (this.activeTripId) {
trip = trips && trips[this.activeTripId];
if (!trip) {
@@ -514,10 +542,17 @@
} else {
this.activeTripId = uuid.v4();
debug.log('Creating new trip ' + this.activeTripId);
- trip = {};
+ trip = {
+ owner: this.invitationManager.getUsername()
+ };
+ this.startSyncbase.then(function(syncbase) {
+ syncbase.put(['trips', self.activeTripId, 'owner'], trip.owner)
+ .catch(self.onError);
+ });
}
}
+ this.activeTripOwner = trip.owner;
this.processMessages(trip.messages);
this.processDestinations(trip.destinations);
},
@@ -586,7 +621,7 @@
var self = this;
this.status.tripSyncGroup = 'creating';
- return groupManager.createSyncGroup('trip', [''])
+ return groupManager.createSyncGroup('trip', [[]])
.then(function(sg) {
self.status.tripSyncGroup = 'created';
return sg;
diff --git a/src/vanadium-wrapper/syncbase-wrapper.js b/src/vanadium-wrapper/syncbase-wrapper.js
index c8b7fb6..b6f63ae 100644
--- a/src/vanadium-wrapper/syncbase-wrapper.js
+++ b/src/vanadium-wrapper/syncbase-wrapper.js
@@ -213,7 +213,7 @@
['Resolve', {in: ['...']}],
['Debug', {in: ['...']}]
]),
- prefixes: prefixes.map(function(p) { return 't:' + p; }),
+ prefixes: prefixes.map(function(p) { return 't:' + joinKey(p); }),
mountTables: mountTables
});
},