Adding basic invitation operations sans accept
Change-Id: I3ffdc0a460f47f02af299cdc7ac14c4a3f1c5bcc
diff --git a/package.json b/package.json
index 65b6895..4166d93 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"es6-promisify": "^2.0.0",
"es6-shim": "^0.33.0",
"global": "^4.3.0",
+ "htmlencode": "^0.0.4",
"jquery": "^2.1.4",
"lodash": "^3.10.1",
"query-string": "^2.4.0",
diff --git a/src/components/destination-marker.js b/src/components/destination-marker.js
index af4d6ec..f496c3e 100644
--- a/src/components/destination-marker.js
+++ b/src/components/destination-marker.js
@@ -47,7 +47,7 @@
color: color,
listeners: []
}));
- if (update) {
+ if (update !== false) {
this.updateIcon();
this.updateTitle();
}
diff --git a/src/components/map-widget.js b/src/components/map-widget.js
index 68632b0..40b643e 100644
--- a/src/components/map-widget.js
+++ b/src/components/map-widget.js
@@ -136,7 +136,7 @@
var place = new Place(result);
var marker = self.getOrCreateMarker(place, SEARCH_CLIENT,
- DestinationMarker.color.RED);
+ DestinationMarker.color.RED, null, false);
self.searchMarkers.push(marker);
marker.onClick.add(marker.restrictListenerToClient(function() {
@@ -196,7 +196,7 @@
this.destMeta.delete(destination);
},
- getOrCreateMarker: function(place, client, color, mergePredicate) {
+ getOrCreateMarker: function(place, client, color, mergePredicate, update) {
var self = this;
var key = place.toKey();
@@ -204,7 +204,7 @@
var marker = this.markers[key];
if (marker) {
if (!mergePredicate || mergePredicate(marker)) {
- marker.pushClient(client, color);
+ marker.pushClient(client, color, update);
} else {
marker = null;
}
diff --git a/src/components/message.js b/src/components/message.js
index e68ce89..3cb1918 100644
--- a/src/components/message.js
+++ b/src/components/message.js
@@ -53,7 +53,13 @@
this.$text.text(text);
},
+ setHtml: function(html) {
+ this.$text.html(html);
+ },
+
setTimestamp: function(timestamp) {
+ this.timestamp = timestamp;
+
var fmt;
if (timestamp === null || timestamp === undefined) {
fmt = '';
@@ -68,6 +74,10 @@
}
},
+ getTimestamp: function() {
+ return this.timestamp;
+ },
+
setSender: function(sender) {
this.$sender.text(sender);
if (sender) {
@@ -77,32 +87,42 @@
}
},
- set: function(message) {
- if (!message) {
- this.onLowerPriority();
- return;
- }
-
- if (typeof message === 'string') {
- message = Message.info(message);
- }
-
+ setPromise: function(promise) {
var self = this;
-
- this.setType(message.type);
- this.setSender(message.sender);
- this.setTimestamp(message.timestamp);
- this.setText(message.text);
-
- if (message.promise) {
- message.promise.then(function(message) {
- self.set(message);
+ if (promise) {
+ promise.then(function(obj) {
+ self.set(obj);
}, function(err) {
self.set(Message.error(err));
});
} else {
this.onLowerPriority();
}
+ },
+
+ set: function(obj) {
+ if (!obj) {
+ this.onLowerPriority();
+ return;
+ }
+
+ if (typeof obj === 'string') {
+ obj = {
+ type: Message.INFO,
+ text: obj
+ };
+ }
+
+ this.setType(obj.type);
+ this.setSender(obj.sender);
+ this.setTimestamp(obj.timestamp);
+ if (obj.text) {
+ this.setText(obj.text);
+ } else {
+ this.setHtml(obj.html);
+ }
+
+ this.setPromise(obj.promise);
}
},
diff --git a/src/components/messages.js b/src/components/messages.js
index 3a25184..65d3c87 100644
--- a/src/components/messages.js
+++ b/src/components/messages.js
@@ -91,7 +91,7 @@
this.$content.focus();
},
- push: function(messageData) {
+ push: function(message) {
var self = this;
$.each(arguments, function() {
self.pushOne(this);
@@ -133,22 +133,25 @@
}
},
- pushOne: function(messageData) {
+ pushOne: function(message) {
var self = this;
- var messageObject = new Message(messageData);
- this.$messages.append(messageObject.$);
+ if ($.isPlainObject(message)) {
+ message = new Message(message);
+ }
- var isOld = messageData.timestamp !== undefined &&
- messageData.timestamp !== null &&
- Date.now() - messageData.timestamp >= Messages.OLD;
+ this.$messages.append(message.$);
+
+ var timestamp = message.getTimestamp();
+ var isOld = timestamp !== undefined && timestamp !== null &&
+ Date.now() - timestamp >= Messages.OLD;
if (this.isOpen()) {
this.$messages.scrollTop(this.$messages.prop('scrollHeight'));
}
if (isOld) {
- messageObject.$.addClass('history');
+ message.$.addClass('history');
} else {
if (!this.isOpen()) {
/*
@@ -167,23 +170,23 @@
* It would be best to use CSS animations, but at this time that would
* mean sacrificing either auto-height or flow-affecting sliding.
*/
- messageObject.$
+ message.$
.addClass('animating')
.hide()
.slideDown(this.SLIDE_DOWN);
}
- messageObject.onLowerPriority.add(function() {
- messageObject.$.addClass('history');
+ message.onLowerPriority.add(function() {
+ message.$.addClass('history');
if (self.isClosed()) {
- messageObject.$
+ message.$
.addClass('animating')
.show()
.delay(Messages.TTL)
.animate({ opacity: 0 }, Messages.FADE)
.slideUp(Messages.SLIDE_UP, function() {
- messageObject.$
+ message.$
.removeClass('animating')
.attr('style', null);
});
diff --git a/src/group-manager.js b/src/group-manager.js
index 4b43e01..60925ac 100644
--- a/src/group-manager.js
+++ b/src/group-manager.js
@@ -5,6 +5,7 @@
var vanadium = require('vanadium');
var defineClass = require('./util/define-class');
+var naming = require('./naming');
var GroupManager = defineClass({
publics: {
@@ -14,35 +15,75 @@
return this.prereq.then(function() {
var sg = self.syncbaseWrapper.syncGroup(self.sgAdmin, name);
- var spec = sg.buildSpec(
- prefixes,
- [self.name('sgmt', name)]
- );
+ var mgmt = vanadium.naming.join(self.mountNames.app, 'sgmt', name);
+ var spec = sg.buildSpec(prefixes, [mgmt]);
/* TODO(rosswang): Right now, duplicate SyncBase creates on
* different SyncBase instances results in siloed SyncGroups.
* Revisit this logic once it merges properly. */
- return sg.joinOrCreate(spec);
+ return sg.joinOrCreate(spec).then(function() {
+ // TODO(rosswang): this is a hack to make the syncgroup joinable
+ return self.vanadiumWrapper.setPermissions(mgmt, new Map([
+ ['Admin', {in: ['...']}],
+ ['Read', {in: ['...']}],
+ ['Resolve', {in: ['...']}]
+ ]));
+ });
});
+ },
+
+ joinSyncGroup: function(owner, name) {
+ var sg = this.syncbaseWrapper.syncGroup(
+ vanadium.naming.join(naming.appMount(owner), 'sgadmin'), name);
+ return sg.join();
}
},
- init: function(vanadiumWrapper, syncbaseWrapper, mountNames) {
+ privates: {
+ advertise: function() {
+ var self = this;
+
+ var basicPerms = new Map([
+ ['Admin', {in: [this.identity.account]}],
+ ['Read', {in: ['...']}],
+ ['Resolve', {in: ['...']}]
+ ]);
+
+ return Promise.all([
+ /* TODO(rosswang): this is a very short term hack just because user
+ * mount names on ns.dev.v.io don't yet default to Resolve in [...].
+ */
+ this.vanadiumWrapper.setPermissions(this.mountNames.user, basicPerms),
+ this.vanadiumWrapper.setPermissions(this.mountNames.app, basicPerms),
+ this.prereq.then(function() {
+ // TODO(rosswang): This seems wrong too.
+ return self.vanadiumWrapper.setPermissions(self.sgAdmin, basicPerms);
+ })
+ ]);
+ }
+ },
+
+ constants: [ 'sgAdmin', 'syncbaseWrapper' ],
+
+ events: {
+ onError: 'memory'
+ },
+
+ init: function(identity, vanadiumWrapper, syncbaseWrapper, mountNames) {
+ this.identity = identity;
+ this.vanadiumWrapper = vanadiumWrapper;
this.syncbaseWrapper = syncbaseWrapper;
+ this.mountNames = mountNames;
- this.name = function() {
- var parts = [mountNames.app];
- Array.prototype.push.apply(parts, arguments);
- return vanadium.naming.join(parts);
- };
-
- this.sgAdmin = this.name('sgadmin');
+ this.sgAdmin = vanadium.naming.join(mountNames.app, 'sgadmin');
/* TODO(rosswang): Once Vanadium supports global sync-group admin
* creation, remove this. For now, use the first local SyncBase
* instance to administrate. */
this.prereq = vanadiumWrapper.mount(this.sgAdmin, syncbaseWrapper.mountName,
vanadiumWrapper.multiMount.FAIL);
+
+ this.advertise().catch(this.onError);
}
});
diff --git a/src/identity.js b/src/identity.js
index d9211cf..b13e25e 100644
--- a/src/identity.js
+++ b/src/identity.js
@@ -7,7 +7,10 @@
module.exports = Identity;
function Identity(accountName) {
- this.username = extractUsername(accountName);
+ var account = processAccountName(accountName);
+
+ this.account = account.name;
+ this.username = account.username;
this.deviceType = 'desktop';
this.deviceId = uuid.v4();
@@ -19,15 +22,22 @@
return uuid.v4();
}
-function extractUsername(accountName) {
+var ACCOUNT_REGEX = /(dev\.v\.io\/u\/([^\/]+)).*/;
+
+function processAccountName(accountName) {
if (!accountName || accountName === 'unknown') {
- return autoUsername();
+ return {
+ name: '...',
+ username: autoUsername()
+ };
}
- var parts = accountName.split('/');
- if (parts[0] !== 'dev.v.io' || parts[1] !== 'u') {
- return accountName;
- }
-
- return parts[2];
+ var match = ACCOUNT_REGEX.exec(accountName);
+ return match? {
+ name: match[1],
+ username: match[2]
+ } : {
+ name: accountName,
+ username: accountName
+ };
}
diff --git a/src/invitation-manager.js b/src/invitation-manager.js
index 83488a9..9b0d878 100644
--- a/src/invitation-manager.js
+++ b/src/invitation-manager.js
@@ -2,50 +2,154 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var $ = require('./util/jquery');
var defineClass = require('./util/define-class');
+// 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(owner, recipient) {
+ return ['invitations', escapeUsername(owner), escapeUsername(recipient)];
+}
+
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) {
+ 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);
+ });
+ });
+ },
+
+ getUsername: function() {
+ return this.username;
}
},
privates: {
- /**
- * TODO(rosswang): this is a very short term hack just because user mount
- * names on ns.dev.v.io don't yet default to Resolve in [...].
- */
- advertise: function() {
- return this.prereqs.then(function(prereqs) {
- var mountNames = prereqs.mountNames;
- var vanadiumWrapper = prereqs.vanadiumWrapper;
- return vanadiumWrapper.getPermissions(mountNames.user)
- .then(function(results) {
- var perms = results[0];
- perms.set('Resolve', {in: ['...']});
- return vanadiumWrapper.setPermissions(mountNames.user, perms);
+ processUpdates: function(data) {
+ var self = this;
+
+ if (data.invitations) {
+ $.each(data.invitations, function(owner, record) {
+ var ownerInvites = self.invitations[owner];
+ if (!ownerInvites) {
+ ownerInvites = self.invitations[owner] = {};
+ }
+
+ $.each(record, function(recipient, sender) {
+ if (ownerInvites[recipient]) {
+ delete ownerInvites[recipient];
+ } else {
+ self.onInvite(unescapeUsername(owner),
+ unescapeUsername(recipient), sender);
+ }
});
- });
+ });
+ }
+
+ if (this.invitations) {
+ $.each(this.invitations, function(owner, record) {
+ $.each(record, function(recipient, sender) {
+ self.onDismiss(unescapeUsername(owner),
+ unescapeUsername(recipient), sender);
+ });
+ });
+ }
+
+ 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.
+ */
+ 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'
},
/**
- * @param prereqs promise of { mountNames, vanadiumWrapper }
+ * @param prereqs promise of { identity, mountNames, vanadiumWrapper }
*/
init: function(prereqs, groupManagerPromise) {
- this.prereqs = prereqs;
+ var self = this;
+ this.prereqs = prereqs;
+ this.syncbasePromise = groupManagerPromise.then(function(gm) {
+ gm.syncbaseWrapper.onUpdate.add(self.processUpdates);
+ return gm.syncbaseWrapper;
+ });
this.groupManagerPromise = groupManagerPromise;
- this.createInvitationsSg = groupManagerPromise.then(function(gm) {
- return gm.createSyncGroup('invitations', ['invitations']);
+ this.invitations = {};
+
+ prereqs.then(function(args) {
+ self.username = args.identity.username;
});
- this.advertise().catch(this.onError);
+ groupManagerPromise.then(function(gm) {
+ gm.createSyncGroup('invitations', ['invitations'])
+ .catch(self.onError);
+ });
}
});
diff --git a/src/naming.js b/src/naming.js
new file mode 100644
index 0000000..cc371c6
--- /dev/null
+++ b/src/naming.js
@@ -0,0 +1,34 @@
+// 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.
+
+var vanadium = require('vanadium');
+
+var ROOT = '/ns.dev.v.io:8101';
+
+function userMount(username) {
+ return vanadium.naming.join(ROOT, 'users', username);
+}
+
+function appMount(username) {
+ return vanadium.naming.join(userMount(username), 'travel');
+}
+
+function deviceMount(username, deviceName) {
+ return vanadium.naming.join(appMount(username), deviceName);
+}
+
+function mountNames(id) {
+ return {
+ user: userMount(id.username),
+ app: appMount(id.username),
+ device: deviceMount(id.username, id.deviceName)
+ };
+}
+
+module.exports = {
+ userMount: userMount,
+ appMount: appMount,
+ deviceMount: deviceMount,
+ mountNames: mountNames
+};
diff --git a/src/static/index.css b/src/static/index.css
index db4d8ef..a0e7517 100644
--- a/src/static/index.css
+++ b/src/static/index.css
@@ -151,6 +151,12 @@
margin-top: 3px;
}
+.messages.headlines a {
+ background-color: rgba(255, 255, 255, .8);
+ border-radius: 2px;
+ padding: 1px 3px;
+}
+
.messages.headlines li.history {
display: none;
}
diff --git a/src/strings.js b/src/strings.js
index d5d0362..110e555 100644
--- a/src/strings.js
+++ b/src/strings.js
@@ -2,6 +2,24 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var htmlEncode = require('htmlencode').htmlEncode;
+
+function text(plainText) {
+ return htmlEncode(plainText);
+}
+
+function link(name, linkText) {
+ return '<a name="' + name + '" href="#">' + text(linkText) + '</a>';
+}
+
+function pre(preText) {
+ return '<pre>' + preText + '</pre>';
+}
+
+function ownerOfTrip(sender, owner) {
+ return sender === owner? 'a' : owner + '\'s';
+}
+
function getStrings(locale) {
return {
'Add destination': 'Add destination',
@@ -29,9 +47,36 @@
label: function(label, details) {
return label + ': ' + details;
},
+ invitationAccepted: function(sender, owner) {
+ return 'Accepted invite from ' + sender + ' to join ' +
+ ownerOfTrip(sender, owner) + ' trip.';
+ },
+ invitationDeclined: function(sender, owner) {
+ return 'Declined invite from ' + sender + ' to join ' +
+ ownerOfTrip(sender, owner) + ' trip.';
+ },
+ invitationReceived: function(sender, owner) {
+ return text(sender + ' has invited you to join ' +
+ ownerOfTrip(sender, owner) + ' trip. ') +
+ link('accept', 'Accept') + text(' / ') + link('decline', 'Decline');
+ },
+ invitationSent: function(recipient, sender) {
+ return sender?
+ sender + ' invited ' + recipient + ' to join the trip.' :
+ 'Invited ' + recipient + ' to join the trip.';
+ },
'Not connected': 'Not connected',
+ notReachable: function(username) {
+ return username + ' is not reachable or is not a Travel Planner user.';
+ },
'Origin': 'Origin',
'Search': 'Search',
+ sendingInvite: function(username) {
+ return 'Inviting ' + username + ' to join the trip...';
+ },
+ status: function(status) {
+ return text('Status: ') + pre(status);
+ },
'Timeline': 'Timeline',
'Travel Planner': 'Travel Planner',
'Unknown error': 'Unknown error'
diff --git a/src/travel.js b/src/travel.js
index c3ff2df..9707d59 100644
--- a/src/travel.js
+++ b/src/travel.js
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var Deferred = require('vanadium/src/lib/deferred');
var raf = require('raf');
-var vanadium = require('vanadium');
var $ = require('./util/jquery');
var defineClass = require('./util/define-class');
@@ -21,8 +21,9 @@
var vanadiumWrapperDefault = require('./vanadium-wrapper');
-var strings = require('./strings').currentLocale;
var describeDestination = require('./describe-destination');
+var naming = require('./naming');
+var strings = require('./strings').currentLocale;
function bindControlToDestination(control, destination) {
function updateOrdinal() {
@@ -71,21 +72,6 @@
control.setPlaceholder(describeDestination.descriptionOpenEnded(destination));
}
-function makeMountNames(id) {
- var parts = ['/ns.dev.v.io:8101', 'users', id.username];
- var names = {
- user: vanadium.naming.join(parts)
- };
-
- parts.push('travel');
- names.app = vanadium.naming.join(parts);
-
- parts.push(id.deviceName);
- names.device = vanadium.naming.join(parts);
-
- return names;
-}
-
var CMD_REGEX = /\/(\S*)(?:\s+(.*))?/;
var Travel = defineClass({
@@ -95,9 +81,11 @@
},
info: function (info, promise) {
- var messageData = Message.info(info);
- messageData.promise = promise;
- this.messages.push(messageData);
+ this.messages.push(new Message({
+ type: Message.INFO,
+ text: info,
+ promise: promise
+ }));
}
},
@@ -291,6 +279,37 @@
}
},
+ handleInvite: function(owner, recipient, sender) {
+ var self = this;
+ var invitationManager = this.sync.invitationManager;
+ var me = invitationManager.getUsername();
+
+ if (recipient === me) {
+ var async = new Deferred();
+
+ 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() {
+ return strings.invitationAccepted(sender, owner);
+ }).then(async.resolve, async.reject);
+ return false;
+ });
+ message.$.find('a[name=decline]').click(function() {
+ invitationManager.decline(owner).then(function() {
+ return strings.invitationDeclined(sender, owner);
+ }).then(async.resolve, async.reject);
+ return false;
+ });
+
+ self.messages.push(message);
+ }
+ },
+
handleUserMessage: function(message, raw) {
var match = CMD_REGEX.exec(raw);
if (match) {
@@ -355,10 +374,11 @@
wrapper.onCrash.add(error);
var identity = new Identity(wrapper.getAccountName());
- var mountNames = makeMountNames(identity);
+ var mountNames = naming.mountNames(identity);
messages.setUsername(identity.username);
return {
+ identity: identity,
mountNames: mountNames,
vanadiumWrapper: wrapper
};
@@ -394,6 +414,8 @@
self.messages.push.apply(self.messages, messages);
});
+ sync.invitationManager.onInvite.add(this.handleInvite);
+
messages.onMessage.add(this.handleUserMessage);
timeline.onAddClick.add(this.handleTimelineDestinationAdd);
@@ -478,7 +500,30 @@
this.commands = {
invite: {
op: function(username) {
- this.sync.invitationManager.invite(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;
+ }
+ }));
+ }
+ },
+
+ status: {
+ op: function() {
+ this.messages.push(new Message({
+ type: Message.INFO,
+ html: strings.status(JSON.stringify(this.sync.status, null, 2))
+ }));
}
}
};
diff --git a/src/travelsync.js b/src/travelsync.js
index 36f73e1..16be682 100644
--- a/src/travelsync.js
+++ b/src/travelsync.js
@@ -384,50 +384,70 @@
this.processDestinations(data.destinations);
},
- start: function(args) {
+ serve: function(args) {
var self = this;
-
- var vanadiumWrapper = args.vanadiumWrapper;
var mountNames = args.mountNames;
+ var vanadiumWrapper = args.vanadiumWrapper;
+
+ this.status.rpc = 'starting';
+ return vanadiumWrapper.server(
+ vanadium.naming.join(mountNames.device, 'rpc'), this.server)
+ .then(function(server) {
+ self.status.rpc = 'ready';
+ return server;
+ }, function(err) {
+ self.status.rpc = 'failed';
+ throw err;
+ });
+ },
+
+ connectSyncbase: function(args) {
+ var self = this;
+ var vanadiumWrapper = args.vanadiumWrapper;
var sbName = queryString.parse(location.search).syncbase || 4000;
if ($.isNumeric(sbName)) {
sbName = '/localhost:' + sbName;
}
- var startSyncbase = vanadiumWrapper
+ this.status.syncbase = 'starting';
+ return vanadiumWrapper
.syncbase(sbName)
.then(function(syncbase) {
syncbase.onError.add(self.onError);
syncbase.onUpdate.add(self.processUpdates);
+ self.status.syncbase = 'ready';
return syncbase;
+ }, function(err) {
+ self.status.syncbase = 'failed';
+ throw err;
});
+ },
- var gmp = startSyncbase.then(function(syncbase) {
- return new GroupManager(vanadiumWrapper, syncbase, mountNames);
- });
+ createGroupManager: function(args, syncbase) {
+ var gm = new GroupManager(args.identity, args.vanadiumWrapper, syncbase,
+ args.mountNames);
+ gm.onError.add(this.onError);
- var createPrimarySyncGroup = gmp.then(function(gm) {
- return gm.createSyncGroup('trip', ['']);
- });
+ return gm;
+ },
- return Promise.all([
- vanadiumWrapper.server(
- vanadium.naming.join(mountNames.device, 'rpc'), this.server),
- startSyncbase,
- gmp,
- createPrimarySyncGroup
- ]).then(function(values) {
- return {
- server: values[0],
- syncbase: values[1],
- groupManager: values[2]
- };
- });
+ createPrimarySyncGroup: function(groupManager) {
+ var self = this;
+
+ this.status.tripSyncGroup = 'creating';
+ return groupManager.createSyncGroup('trip', [''])
+ .then(function(sg) {
+ self.status.tripSyncGroup = 'created';
+ return sg;
+ }, function(err) {
+ self.status.tripSyncGroup = 'failed';
+ throw err;
+ });
}
},
- constants: [ 'startup', 'invitationManager' ],
+ constants: [ 'invitationManager', 'startup', 'status' ],
events: {
/**
* @param newSize
@@ -451,7 +471,8 @@
},
/**
- * @param prereqs a promise that produces { mountNames, vanadiumWrapper }.
+ * @param prereqs a promise that produces { identity, mountNames,
+ * vanadiumWrapper }.
* @mapsDependencies an object with the following keys:
* maps
* placesService
@@ -464,16 +485,33 @@
this.tripStatus = {};
this.messages = {};
this.destRecords = [];
+ this.status = {};
this.server = new vdlTravel.TravelSync();
- this.startup = prereqs.then(this.start);
+ var startRpc = prereqs.then(this.serve);
+ var startSyncbase = prereqs.then(this.connectSyncbase);
+ var startGroupManager = Promise
+ .all([prereqs, startSyncbase])
+ .then(function(args) {
+ return self.createGroupManager(args[0], args[1]);
+ });
+ var createPrimarySyncGroup = startGroupManager
+ .then(this.createPrimarySyncGroup);
- this.invitationManager = new InvitationManager(
- prereqs,
- this.startup.then(function(services) {
- return services.groupManager;
- }));
+ this.startup = Promise.all([
+ startRpc,
+ startSyncbase,
+ startGroupManager,
+ createPrimarySyncGroup
+ ]).then(function(values) {
+ return {
+ server: values[0],
+ syncbase: values[1],
+ groupManager: values[2]
+ };
+ });
+ this.invitationManager = new InvitationManager(prereqs, startGroupManager);
this.invitationManager.onError.add(this.onError);
this.handleDestinationPlaceChange = function() {
diff --git a/test/identity.js b/test/identity.js
index 98c2988..42d7d2e 100644
--- a/test/identity.js
+++ b/test/identity.js
@@ -6,23 +6,24 @@
var Identity = require('../src/identity');
-function verifyAutoAccountName(t, n) {
- t.assert(n.length > 1, 'auto-generated username is nontrivial');
+function verifyAutoAccount(t, i) {
+ t.equals(i.account, '...', 'unknown account defaults to open');
+ t.assert(i.username.length > 1, 'auto-generated username is nontrivial');
}
test('auto-generated username from unknown', function(t) {
- var a = new Identity('unknown').username,
- b = new Identity('unknown').username;
- verifyAutoAccountName(t, a);
- verifyAutoAccountName(t, b);
- t.notEqual(b, a, 'auto-generated username is unique');
+ var a = new Identity('unknown'),
+ b = new Identity('unknown');
+ verifyAutoAccount(t, a);
+ verifyAutoAccount(t, b);
+ t.notEqual(b.username, a.username, 'auto-generated username is unique');
t.end();
});
function testAutoExtract(t, r) {
- var n = new Identity(r).username;
- verifyAutoAccountName(t, n);
- t.not(n, r);
+ var i = new Identity(r);
+ verifyAutoAccount(t, i);
+ t.not(i.username, r);
t.end();
}
@@ -44,6 +45,8 @@
test('init', function(t) {
var i = new Identity(testAccountName);
+ t.equals(i.account, 'dev.v.io/u/joeuser@google.com',
+ 'should generalize a dev.v.io account name');
t.equals(i.username, 'joeuser@google.com',
'should extract a username from a dev.v.io account name');
var expectedPrefix = 'joeuser@google.com/desktop_';