Added InvitationManager

Change-Id: I0928eabfd138167301a0f2976cf427f8a9e047e9
diff --git a/src/group-manager.js b/src/group-manager.js
new file mode 100644
index 0000000..4b43e01
--- /dev/null
+++ b/src/group-manager.js
@@ -0,0 +1,49 @@
+// 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 defineClass = require('./util/define-class');
+
+var GroupManager = defineClass({
+  publics: {
+    createSyncGroup: function(name, prefixes) {
+      var self = this;
+
+      return this.prereq.then(function() {
+        var sg = self.syncbaseWrapper.syncGroup(self.sgAdmin, name);
+
+        var spec = sg.buildSpec(
+          prefixes,
+          [self.name('sgmt', name)]
+        );
+
+        /* 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);
+      });
+    }
+  },
+
+  init: function(vanadiumWrapper, syncbaseWrapper, mountNames) {
+    this.syncbaseWrapper = syncbaseWrapper;
+
+    this.name = function() {
+      var parts = [mountNames.app];
+      Array.prototype.push.apply(parts, arguments);
+      return vanadium.naming.join(parts);
+    };
+
+    this.sgAdmin = this.name('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);
+  }
+});
+
+module.exports = GroupManager;
\ No newline at end of file
diff --git a/src/invitation-manager.js b/src/invitation-manager.js
new file mode 100644
index 0000000..83488a9
--- /dev/null
+++ b/src/invitation-manager.js
@@ -0,0 +1,52 @@
+// 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 defineClass = require('./util/define-class');
+
+var InvitationManager = defineClass({
+  publics: {
+    invite: function(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);
+          });
+      });
+    }
+  },
+
+  events: {
+    onError: 'memory'
+  },
+
+  /**
+   * @param prereqs promise of { mountNames, vanadiumWrapper }
+   */
+  init: function(prereqs, groupManagerPromise) {
+    this.prereqs = prereqs;
+
+    this.groupManagerPromise = groupManagerPromise;
+
+    this.createInvitationsSg = groupManagerPromise.then(function(gm) {
+      return gm.createSyncGroup('invitations', ['invitations']);
+    });
+
+    this.advertise().catch(this.onError);
+  }
+});
+
+module.exports = InvitationManager;
\ No newline at end of file
diff --git a/src/strings.js b/src/strings.js
index 1a94323..d5d0362 100644
--- a/src/strings.js
+++ b/src/strings.js
@@ -29,6 +29,7 @@
     label: function(label, details) {
       return label + ': ' + details;
     },
+    'Not connected': 'Not connected',
     'Origin': 'Origin',
     'Search': 'Search',
     'Timeline': 'Timeline',
diff --git a/src/travel.js b/src/travel.js
index 78906ae..c3ff2df 100644
--- a/src/travel.js
+++ b/src/travel.js
@@ -72,12 +72,14 @@
 }
 
 function makeMountNames(id) {
-  // TODO: first-class app-wide rather than siloed by account
-  var parts = ['/ns.dev.v.io:8101', 'users', id.username, 'travel'];
+  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);
 
@@ -353,11 +355,11 @@
         wrapper.onCrash.add(error);
 
         var identity = new Identity(wrapper.getAccountName());
-        identity.mountNames = makeMountNames(identity);
+        var mountNames = makeMountNames(identity);
         messages.setUsername(identity.username);
 
         return {
-          identity: identity,
+          mountNames: mountNames,
           vanadiumWrapper: wrapper
         };
       });
@@ -476,7 +478,7 @@
     this.commands = {
       invite: {
         op: function(username) {
-          sync.invite(username);
+          this.sync.invitationManager.invite(username);
         }
       }
     };
diff --git a/src/travelsync.js b/src/travelsync.js
index 9591da0..36f73e1 100644
--- a/src/travelsync.js
+++ b/src/travelsync.js
@@ -13,6 +13,8 @@
 var defineClass = require('./util/define-class');
 
 var debug = require('./debug');
+var GroupManager = require('./group-manager');
+var InvitationManager = require('./invitation-manager');
 var Place = require('./place');
 
 var vdlTravel = require('../ifc');
@@ -39,10 +41,6 @@
       }
     },
 
-    invite: function(username) {
-
-    },
-
     message: function(messageContent) {
       var id = uuid.v4();
       var payload = $.extend({
@@ -390,7 +388,7 @@
       var self = this;
 
       var vanadiumWrapper = args.vanadiumWrapper;
-      var identity = args.identity;
+      var mountNames = args.mountNames;
 
       var sbName = queryString.parse(location.search).syncbase || 4000;
       if ($.isNumeric(sbName)) {
@@ -402,46 +400,34 @@
         .then(function(syncbase) {
           syncbase.onError.add(self.onError);
           syncbase.onUpdate.add(self.processUpdates);
-
-          /* TODO(rosswang): Once Vanadium supports global sync-group admin
-           * creation, remove this. For now, use the first local SyncBase
-           * instance to administrate. */
-          var sgAdmin = vanadium.naming.join(
-            identity.mountNames.user, 'sgadmin');
-          return vanadiumWrapper.mount(sgAdmin, sbName,
-              vanadiumWrapper.multiMount.FAIL)
-            .then(function() {
-              var sg = syncbase.syncGroup(sgAdmin, 'trip');
-
-              var spec = sg.buildSpec(
-                [''],
-                [vanadium.naming.join(identity.mountNames.user, 'sgmt')]
-              );
-
-              /* 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);
-            })
-            .then(function() {
-              return syncbase;
-            });
+          return syncbase;
         });
 
+      var gmp = startSyncbase.then(function(syncbase) {
+        return new GroupManager(vanadiumWrapper, syncbase, mountNames);
+      });
+
+      var createPrimarySyncGroup = gmp.then(function(gm) {
+          return gm.createSyncGroup('trip', ['']);
+      });
+
       return Promise.all([
         vanadiumWrapper.server(
-          vanadium.naming.join(identity.mountNames.device, 'rpc'), this.server),
-        startSyncbase
+          vanadium.naming.join(mountNames.device, 'rpc'), this.server),
+        startSyncbase,
+        gmp,
+        createPrimarySyncGroup
       ]).then(function(values) {
         return {
           server: values[0],
-          syncbase: values[1]
+          syncbase: values[1],
+          groupManager: values[2]
         };
       });
     }
   },
 
-  constants: [ 'startup' ],
+  constants: [ 'startup', 'invitationManager' ],
   events: {
     /**
      * @param newSize
@@ -465,12 +451,12 @@
   },
 
   /**
-   * @param promise a promise that produces { mountName, vanadiumWrapper }.
+   * @param prereqs a promise that produces { mountNames, vanadiumWrapper }.
    * @mapsDependencies an object with the following keys:
    *  maps
    *  placesService
    */
-  init: function(promise, mapsDependencies) {
+  init: function(prereqs, mapsDependencies) {
     var self = this;
 
     this.mapsDeps = mapsDependencies;
@@ -480,7 +466,15 @@
     this.destRecords = [];
 
     this.server = new vdlTravel.TravelSync();
-    this.startup = promise.then(this.start);
+    this.startup = prereqs.then(this.start);
+
+    this.invitationManager = new InvitationManager(
+      prereqs,
+      this.startup.then(function(services) {
+        return services.groupManager;
+      }));
+
+    this.invitationManager.onError.add(this.onError);
 
     this.handleDestinationPlaceChange = function() {
       self.updateDestinationPlace(this);
diff --git a/src/vanadium-wrapper/index.js b/src/vanadium-wrapper/index.js
index ba2a3a0..e136879 100644
--- a/src/vanadium-wrapper/index.js
+++ b/src/vanadium-wrapper/index.js
@@ -77,6 +77,16 @@
       return refreshName();
     },
 
+    getPermissions: function(name) {
+      return this.runtime.namespace().getPermissions(
+        this.runtime.getContext(), name);
+    },
+
+    setPermissions: function(name, perms) {
+      return this.runtime.namespace().setPermissions(
+        this.runtime.getContext(), name, perms);
+    },
+
     /**
      * @param endpoint Vanadium name
      * @returns a promise resolving to a client or rejecting with an error.
diff --git a/src/vanadium-wrapper/syncbase-wrapper.js b/src/vanadium-wrapper/syncbase-wrapper.js
index dcb1080..d4f510e 100644
--- a/src/vanadium-wrapper/syncbase-wrapper.js
+++ b/src/vanadium-wrapper/syncbase-wrapper.js
@@ -84,7 +84,7 @@
       var db = app.noSqlDatabase('db');
 
       return setUp(context, app, db).then(function() {
-        return new SyncbaseWrapper(context, db);
+        return new SyncbaseWrapper(context, db, mountName);
       });
     }
   },
@@ -340,16 +340,21 @@
     }
   },
 
+  constants: [ 'mountName' ],
+
   events: {
     onError: 'memory',
     onUpdate: 'memory'
   },
 
-  init: function(context, db) {
+  init: function(context, db, mountName) {
+    // TODO(rosswang): mountName probably won't be necessary after mount tables
+    // can spin up SyncBase instances (see group-manager).
     var self = this;
     this.context = context;
     this.db = db;
     this.t = db.table('t');
+    this.mountName = mountName;
 
     this.writes = new Set();